第一部分:核心概念与工作机制
1. pinctrl 子系统概述
pinctrl(pin control)子系统是linux内核中用于管理和配置soc引脚的标准框架。
它解决了传统直接操作寄存器配置引脚复用和电气特性所带来的繁琐、易错、功能冲突等问题。
1.1 主要功能
- 引脚复用(pin muxing):将同一物理引脚切换为不同的功能(如gpio、i2c、uart、spi等)。
- 引脚配置(pin configuration):设置引脚的电气特性,包括上拉/下拉、驱动能力、开漏、压摆率等。
- 引脚分组(pin groups):将一组引脚定义为逻辑组,方便整体配置。
- 状态管理(state management):为设备定义多个引脚状态(如
default、sleep、idle),并在运行时动态切换。
1.2 架构分层
pinctrl子系统采用服务端(provider)— 客户端(consumer) 模型:
- 服务端(provider):由芯片厂商实现,负责描述soc中所有引脚、引脚组、可复用功能以及硬件操作函数(
struct pinctrl_desc+ ops)。 - 客户端(consumer):由设备驱动编写者使用,通过在设备树中声明所需引脚状态,让pinctrl子系统自动完成配置。
下图展示了子系统内部模块间的协作关系:

2. 核心数据结构
pinctrl子系统通过一系列精心设计的数据结构,将服务端能力、客户端需求以及状态配置连接起来。
2.1 服务端核心结构
struct pinctrl_desc—— 引脚控制器的描述符
每个pinctrl控制器驱动都必须提供一个pinctrl_desc实例,用于向内核描述该控制器的资源与操作能力。
struct pinctrl_desc {
const char *name; // 控制器名称
const struct pinctrl_pin_desc *pins; // 引脚数组
unsigned int npins; // 引脚数量
const struct pinctrl_ops *pctlops; // 全局控制操作
const struct pinmux_ops *pmxops; // 复用操作
const struct pinconf_ops *confops; // 配置操作
struct module *owner;
// ... 其他参数
};
三大操作集(ops)是驱动必须实现的核心函数表:
pinctrl_ops:提供引脚组信息、设备树节点到映射的转换(dt_node_to_map)等。pinmux_ops:提供复用功能数量、名称、组以及set_mux回调(最终写寄存器)。pinconf_ops:提供引脚或引脚组的电气特性读取/设置(如pin_config_set)。
struct pinctrl_dev—— 控制器实例
内核通过pinctrl_register()将pinctrl_desc注册为一个pinctrl_dev对象,并挂载到全局链表中。
2.2 客户端核心结构
struct dev_pin_info—— 挂载在struct device中的引脚信息
每个设备(struct device)如果启用了config_pinctrl,都会包含一个dev_pin_info指针,记录该设备所使用的pinctrl句柄及各种状态。
struct dev_pin_info {
struct pinctrl *p; // 该设备的pinctrl句柄
struct pinctrl_state *default_state; // 默认状态(如“default”)
struct pinctrl_state *init_state; // 初始化状态(可选)
#ifdef config_pm
struct pinctrl_state *sleep_state; // 睡眠状态
struct pinctrl_state *idle_state; // 空闲状态
#endif
};
struct pinctrl—— 客户端句柄
代表一个设备获取到的完整pinctrl资源,内部包含了该设备所有可能的状态(states链表)以及从设备树解析出的映射表(dt_maps)。
struct pinctrl {
struct list_head node;
struct device *dev;
struct list_head states; // 该设备的所有状态
struct pinctrl_state *state; // 当前激活的状态
struct list_head dt_maps;
struct kref users;
};
struct pinctrl_state与struct pinctrl_setting
- 每个状态(如“default”)对应一个
pinctrl_state对象,内部settings链表包含多个pinctrl_setting。 pinctrl_setting表示一个具体的配置项:要么是复用设置(哪个group切换到哪个function),要么是配置设置(某个引脚的上下拉/驱动能力)。
struct pinctrl_setting {
struct list_head node;
enum pinctrl_map_type type; // mux_group 或 configs_pin/configs_group
struct pinctrl_dev *pctldev;
union {
struct pinctrl_setting_mux mux; // 复用信息:group + func
struct pinctrl_setting_configs configs; // 配置信息:引脚/组 + 配置值数组
} data;
};
3. 设备树描述与解析
3.1 服务端设备树节点
芯片厂商会在.dtsi中定义pinctrl控制器节点,例如rk3568.dtsi:
pinctrl: pinctrl {
compatible = "rockchip,rk3568-pinctrl";
rockchip,grf = <&grf>;
rockchip,pmu = <&pmugrf>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfdd60000 0x0 0x100>;
interrupts = <gic_spi 33 irq_type_level_high>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
};
// ... 其他 gpio 组
};
同时,在rk3568-pinctrl.dtsi中定义了所有可用的引脚功能组(groups)和配置(pinconf),例如i2c3的两种引脚组:
i2c3m0_xfer: i2c3m0-xfer {
rockchip,pins =
<1 rk_pa1 1 &pcfg_pull_none_smt>, // i2c3_sclm0
<1 rk_pa0 1 &pcfg_pull_none_smt>; // i2c3_sdam0
};
i2c3m1_xfer: i2c3m1-xfer {
rockchip,pins =
<3 rk_pb5 4 &pcfg_pull_none_smt>,
<3 rk_pb6 4 &pcfg_pull_none_smt>;
};
每个rockchip,pins属性包含四个字段:
- pin_bank:gpio组(0~4)
- pin_bank_idx:组内引脚索引(如rk_pa0=0,rk_pc0=16)
- mux:复用值(0表示gpio,1表示功能1,依芯片手册)
- phandle:指向引脚配置节点(如
&pcfg_pull_none_smt),定义上下拉、驱动能力等。
3.2 客户端设备树节点
外设驱动在它的节点中通过pinctrl-*属性声明所需引脚状态。例如一个i2c控制器:
&i2c3 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&i2c3m0_xfer>;
pinctrl-1 = <&i2c3m1_xfer>;
status = "okay";
};
pinctrl-names:定义状态名称列表,内核预定义了pinctrl_state_default(“default”)、pinctrl_state_sleep(“sleep”)等,也可以自定义。pinctrl-0:对应第一个状态(“default”)的引脚组引用,可以是单个或多个phandle。pinctrl-1:对应第二个状态(“sleep”)的引脚组。
3.3 从设备树到内部映射
当内核解析客户端设备树时,会为设备创建pinctrl句柄,并调用服务端驱动的dt_node_to_map回调,将设备树描述转换为pinctrl_map数组。
以rockchip驱动为例,rockchip_dt_node_to_map会做两件事:
根据设备树节点名找到对应的rockchip_pin_group。
创建两个映射条目:
- 第一个是
pin_map_type_mux_group,记录function名称(取自父节点名)和group名称(取自节点名)。 - 随后为组内每个引脚创建
pin_map_type_configs_pin条目,记录引脚名和从pcfg_xxx解析出的配置值。
static int rockchip_dt_node_to_map(...)
{
// 找到 group
grp = pinctrl_name_to_group(info, np->name);
// 分配 new_map,数量为 1(复用) + npins(配置)
new_map[0].type = pin_map_type_mux_group;
new_map[0].data.mux.function = parent->name;
new_map[0].data.mux.group = np->name;
// 填充每个引脚的配置 map
for (i = 0; i < grp->npins; i++) {
new_map[i+1].type = pin_map_type_configs_pin;
new_map[i+1].data.configs.configs = grp->data[i].configs;
...
}
}
这些pinctrl_map随后会被add_setting转换为pinctrl_setting,并挂载到对应状态的settings链表中,供后续激活使用。
4. 控制器驱动初始化流程
以rockchip pinctrl驱动(pinctrl-rockchip.c)为例,展示一个pinctrl服务端是如何初始化的。
4.1 驱动入口
static struct platform_driver rockchip_pinctrl_driver = {
.probe = rockchip_pinctrl_probe,
.driver = {
.name = "rockchip-pinctrl",
.of_match_table = rockchip_pinctrl_dt_match,
},
};
postcore_initcall(rockchip_pinctrl_drv_register);
驱动使用postcore_initcall,在系统初始化早期完成注册,确保其他设备probe时pinctrl已经可用。
4.2 probe函数核心步骤
rockchip_pinctrl_probe完成以下关键工作:
分配私有数据结构:struct rockchip_pinctrl *info
获取硬件资源:regmap(通过syscon_node_to_regmap或devm_ioremap_resource)
解析soc数据:rockchip_pinctrl_get_soc_data,填充rockchip_pin_ctrl
注册pinctrl控制器:调用rockchip_pinctrl_register,该函数内部:
- 填充
pinctrl_desc的各个ops(rockchip_pctrl_ops,rockchip_pmx_ops,rockchip_pinconf_ops) - 调用
devm_pinctrl_register->pinctrl_init_controller-> 创建pinctrl_dev
注册gpio子设备:of_platform_populate,让每个gpio bank作为一个独立平台设备被probe。
static int rockchip_pinctrl_probe(struct platform_device *pdev)
{
struct rockchip_pinctrl *info;
// ... 分配内存、获取regmap
ctrl = rockchip_pinctrl_get_soc_data(info, pdev);
info->ctrl = ctrl;
ret = rockchip_pinctrl_register(pdev, info);
ret = of_platform_populate(np, rockchip_bank_match, null, null);
return 0;
}
4.3pinctrl_init_controller内部初始化
该函数分配struct pinctrl_dev,初始化radix tree(用于快速查找引脚描述符、组、函数),并调用pinctrl_check_ops验证驱动是否提供了必要的回调函数。
5. 客户端引脚自动绑定流程(核心机制)
这是pinctrl最核心的自动化机制:在设备驱动自身的probe函数被调用之前,内核已经根据设备树完成了该设备所有引脚的复用和配置。
5.1 调用链
当设备和驱动匹配成功后,内核的drivers/base/dd.c中的really_probe函数会执行:
static int really_probe(struct device *dev, struct device_driver *drv)
{
// ...
ret = pinctrl_bind_pins(dev); // 关键!绑定并配置引脚
if (ret)
goto pinctrl_bind_failed;
// ... 然后才调用驱动的probe
ret = drv->probe(dev);
}
5.2pinctrl_bind_pins实现
pinctrl_bind_pins位于drivers/base/pinctrl.c,它执行以下步骤:
- 调用
devm_pinctrl_get(dev)获取设备的pinctrl句柄(如果尚未获取)。 - 查找
pinctrl_state_default和pinctrl_state_init状态。 - 优先选择init状态(若存在),否则选择default状态,然后调用
pinctrl_select_state激活该状态。
int pinctrl_bind_pins(struct device *dev)
{
dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), gfp_kernel);
dev->pins->p = devm_pinctrl_get(dev);
dev->pins->default_state = pinctrl_lookup_state(dev->pins->p, pinctrl_state_default);
dev->pins->init_state = pinctrl_lookup_state(dev->pins->p, pinctrl_state_init);
if (!is_err(dev->pins->init_state))
ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state);
else
ret = pinctrl_select_state(dev->pins->p, dev->pins->default_state);
// ...
}
5.3pinctrl_select_state激活硬件配置
pinctrl_select_state会遍历目标状态下的所有pinctrl_setting,对每个setting:
- 若是mux_group类型,调用
pinmux_enable_setting,最终通过驱动提供的set_mux回调写入复用寄存器。 - 若是configs_group或configs_pin类型,调用
pinconf_apply_setting,通过pin_config_set回调写入配置寄存器。
下图展示了从设备树到硬件配置完成的完整流程:

6. 动态api使用(在驱动中主动切换状态)
除了自动绑定,驱动还可以在运行时主动切换引脚状态,常用于电源管理(suspend/resume)。
主要api函数如下:
| 函数 | 作用 |
|---|---|
| devm_pinctrl_get(dev) / pinctrl_get(dev) | 获取设备的pinctrl句柄 |
| pinctrl_put(p) | 释放句柄(通常用devm_版本自动管理) |
| pinctrl_lookup_state(p, name) | 根据名称(如“sleep”)查找状态对象 |
| pinctrl_select_state(p, state) | 将指定状态应用到硬件 |
典型用法示例(摄像头驱动suspend时切换为sleep状态):
static int ov5670_suspend(struct device *dev)
{
struct ov5670 *ov5670 = dev_get_drvdata(dev);
if (!is_err_or_null(ov5670->pins_sleep)) {
pinctrl_select_state(ov5670->pinctrl, ov5670->pins_sleep);
}
return 0;
}
static int ov5670_resume(struct device *dev)
{
struct ov5670 *ov5670 = dev_get_drvdata(dev);
if (!is_err_or_null(ov5670->pins_default)) {
pinctrl_select_state(ov5670->pinctrl, ov5670->pins_default);
}
return 0;
}
驱动通常在probe中获取句柄和状态:
ov5670->pinctrl = devm_pinctrl_get(dev); ov5670->pins_default = pinctrl_lookup_state(ov5670->pinctrl, "default"); ov5670->pins_sleep = pinctrl_lookup_state(ov5670->pinctrl, "sleep");
7. 调试方法(debugfs)
pinctrl子系统提供了丰富的调试接口,位于/sys/kernel/debug/pinctrl/下,对排查引脚冲突、配置错误非常有用。
| 文件 | 内容说明 |
|---|---|
| pinctrl-devices | 列出所有已注册的pinctrl控制器,显示是否支持pinmux/pinconf |
| pinctrl-handles | 最重要:显示每个客户端设备当前请求的引脚状态、每个状态下的复用组和配置详情。可以清楚看到某个引脚被哪个设备占用。 |
| <controller>/pins | 该控制器下所有引脚的列表(如pin 0 (gpio_0)) |
| <controller>/pingroups | 该控制器定义的引脚组及其包含的引脚 |
| <controller>/pinmux-functions | 每个功能(function)对应哪些引脚组(groups) |
| <controller>/pinconf-groups | 每个引脚组的电气配置参数(上下拉、驱动能力等) |
示例输出片段(pinctrl-handles):
device: 78b6000.i2c
state: i2c_active
type: mux_group controller 1000000.pinctrl group: gpio6 (6) function: blsp_i2c2 (12)
type: configs_group controller 1000000.pinctrl group gpio6 (6) 00000000 00020009
state: i2c_sleep
type: mux_group controller 1000000.pinctrl group: gpio6 (6) function: gpio (1)
type: configs_group controller 1000000.pinctrl group gpio6 (6) 00010004 00020009
通过分析pinctrl-handles可以快速定位:
- 哪个设备占用了哪个引脚组
- 该引脚组当前处于active还是sleep状态
- 复用功能是否正确(例如期望i2c,实际却配置为gpio)
总结:
linux pinctrl子系统通过标准化的服务端/客户端模型,将引脚配置从硬件细节中抽象出来。其核心工作流程可概括为:
- 芯片厂商实现
pinctrl_desc+ ops,并注册控制器。 - 设备树编写者在客户端节点中声明
pinctrl-names和pinctrl-0/1/...。 - 内核pinctrl core在设备probe前自动解析设备树,调用驱动回调完成硬件配置。
- 驱动开发者也可以在运行时调用
pinctrl_select_state动态切换状态(如电源管理)。
这种设计极大简化了驱动开发,避免了引脚配置冲突,是linux soc支持中的关键基础设施。理解pinctrl,对于阅读和编写嵌入式linux板级支持包(bsp)至关重要。
第二部分:rockchip pinctrl 驱动
1. 引言
在第一部分中,我们介绍了 pinctrl 子系统的通用框架、核心数据结构以及客户端自动绑定的流程。第二部分将以 rockchip rk3568 平台的 pinctrl 驱动(pinctrl-rockchip.c)为例,深入分析 服务端(provider) 驱动的具体实现细节,包括:
- 驱动数据结构设计与 soc 专属描述
- 如何从设备树构建引脚组(groups)和功能(functions)
- 复用(mux)与配置(conf)硬件寄存器的底层操作
- 设备树节点到映射(
dt_node_to_map)的完整转换过程
通过学习这部分,你将掌握如何为新的 soc 编写或理解 pinctrl 驱动。
2. rockchip pinctrl 驱动概览
rockchip 的 pinctrl 驱动位于 drivers/pinctrl/pinctrl-rockchip.c 及其头文件 pinctrl-rockchip.h。它实现了:
- 支持多个 soc 系列(rk3188, rk3288, rk3399, rk3568 等)
- 通过
rockchip_pin_ctrl数据结构区分不同 soc 的引脚布局、寄存器偏移和功能映射 - 利用 regmap 统一访问 grf(通用寄存器文件)和 pmugrf(电源管理单元寄存器文件)
- 提供
pinctrl_ops,pinmux_ops,pinconf_ops三个操作集,供 pinctrl core 调用
下图展示了 rockchip pinctrl 驱动与内核其他模块的关系:

3. 关键数据结构
3.1rockchip_pinctrl—— 驱动私有数据
该结构体保存了驱动运行时的所有上下文信息。
struct rockchip_pinctrl {
struct regmap *regmap_base; // grf 寄存器映射
int reg_size; // 寄存器区域大小
struct regmap *regmap_pull; // 拉取寄存器映射(旧绑定)
struct regmap *regmap_pmu; // pmugrf 寄存器映射
struct device *dev;
struct rockchip_pin_ctrl *ctrl; // soc 专属引脚控制描述
struct pinctrl_desc pctl; // 通用 pinctrl 描述符
struct pinctrl_dev *pctl_dev;
struct rockchip_pin_group *groups; // 该 soc 的所有引脚组
unsigned int ngroups;
struct rockchip_pmx_func *functions; // 该 soc 的所有复用功能
unsigned int nfunctions;
};
其中 rockchip_pin_ctrl 是 soc 相关的配置核心。
3.2rockchip_pin_ctrl—— soc 引脚控制器描述
该结构体包含了不同 soc 的差异化信息,例如引脚总数、寄存器布局、驱动强度计算方式等。
struct rockchip_pin_ctrl {
enum rockchip_pinctrl_type type; // 芯片类型(rk3568等)
struct pinctrl_pin_desc *pins; // 引脚描述符数组
unsigned int npins; // 引脚数量
unsigned int grf_mux_offset; // 复用寄存器偏移
unsigned int pmu_mux_offset; // pmu 复用寄存器偏移
int (*pull_calc_reg)(...); // 计算上下拉寄存器的函数
int (*drv_calc_reg)(...); // 计算驱动能力寄存器的函数
const struct rockchip_mux_recalced_data *recalced_data;
unsigned int num_recalced;
// ... 更多 soc 特有回调
};
以 rk3568 为例,驱动中定义了 rk3568_pin_ctrl 静态实例,其中包含引脚数组、寄存器偏移、以及专用的计算函数。
3.3rockchip_pin_group与rockchip_pmx_func
rockchip_pin_group:描述一个引脚组(group),包含组内引脚编号列表、每个引脚的配置数据数组(上下拉、驱动能力等)。rockchip_pmx_func:描述一个复用功能(function),包含功能名称以及属于该功能的组名列表。
这两者都是从设备树中解析出来的。
4. 驱动初始化深入
4.1rockchip_pinctrl_probe步骤详解
static int rockchip_pinctrl_probe(struct platform_device *pdev)
{
struct rockchip_pinctrl *info;
struct rockchip_pin_ctrl *ctrl;
struct device_node *np = pdev->dev.of_node;
// 1. 分配私有数据
info = devm_kzalloc(dev, sizeof(*info), gfp_kernel);
// 2. 获取 soc 专属配置(根据 compatible 匹配)
ctrl = rockchip_pinctrl_get_soc_data(info, pdev);
info->ctrl = ctrl;
// 3. 获取 regmap:首先尝试 "rockchip,grf" phandle,否则 ioremap
node = of_parse_phandle(np, "rockchip,grf", 0);
if (node)
info->regmap_base = syscon_node_to_regmap(node);
else
// 回退:直接映射 ioresource_mem
info->regmap_base = devm_regmap_init_mmio(...);
// 同样处理 pmugrf
node = of_parse_phandle(np, "rockchip,pmu", 0);
if (node)
info->regmap_pmu = syscon_node_to_regmap(node);
// 4. 注册 pinctrl 控制器
ret = rockchip_pinctrl_register(pdev, info);
// 5. 创建 gpio bank 子设备(每个 gpio 控制器作为一个 platform_device)
ret = of_platform_populate(np, rockchip_bank_match, null, null);
return 0;
}
关键点:
rockchip_pinctrl_get_soc_data通过of_match_device获取匹配的rockchip_pin_ctrl指针(存储在platform_driver的data字段)。- 寄存器访问使用
regmap,可以统一处理位宽、缓存等,并兼容多个寄存器区域。 of_platform_populate会为每个gpio子节点(如gpio0)创建 platform 设备,其驱动rockchip_gpio_probe会进一步注册 gpio 控制器。
4.2rockchip_pinctrl_register—— 填充并注册 pinctrl_desc
static int rockchip_pinctrl_register(struct platform_device *pdev,
struct rockchip_pinctrl *info)
{
struct pinctrl_desc *ctrldesc = &info->pctl;
struct rockchip_pin_ctrl *ctrl = info->ctrl;
ctrldesc->name = "rockchip-pinctrl";
ctrldesc->pins = ctrl->pins;
ctrldesc->npins = ctrl->npins;
ctrldesc->pctlops = &rockchip_pctrl_ops;
ctrldesc->pmxops = &rockchip_pmx_ops;
ctrldesc->confops = &rockchip_pinconf_ops;
ctrldesc->owner = this_module;
// 解析设备树,构建 groups 和 functions
ret = rockchip_pinctrl_parse_dt(pdev, info);
// 注册到 pinctrl core
info->pctl_dev = devm_pinctrl_register(&pdev->dev, ctrldesc, info);
return ptr_err_or_zero(info->pctl_dev);
}
4.3rockchip_pinctrl_parse_dt—— 构建设备树映射
该函数遍历 pinctrl 控制器节点下的所有子节点(每个子节点代表一个 function,如 i2c3),然后再遍历每个 function 下的引脚组子节点(如 i2c3m0_xfer),最终填充 info->groups 和 info->functions。
static int rockchip_pinctrl_parse_dt(struct platform_device *pdev,
struct rockchip_pinctrl *info)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *child;
int ret;
// 第一遍:统计 groups 和 functions 的数量
for_each_child_of_node(np, child) {
// 每个 child 代表一个 function 节点(如 i2c3, uart0)
info->nfunctions++;
for_each_child_of_node(child, grp) {
info->ngroups++;
}
}
// 分配内存
info->groups = devm_kcalloc(dev, info->ngroups, sizeof(*info->groups), ...);
info->functions = devm_kcalloc(dev, info->nfunctions, sizeof(*info->functions), ...);
// 第二遍:填充实际内容,解析每个 group 的 rockchip,pins 属性
// 将引脚编号、复用值、配置值存入 group 结构
}
在解析 rockchip,pins 属性时,会调用 rockchip_pinctrl_parse_pin 将设备树中的 bank、pin、mux 和 config phandle 转换为:
- 全局引脚编号(如
(bank-1)*32 + pin_index) - 复用值(mux)
- 配置参数(从
&pcfg_pull_none等节点读取 bias、drive-strength 等)
这些配置参数会最终存储在 rockchip_pin_group 的 data[i].configs 数组中。
5. 复用操作(pinmux_ops)的实现
rockchip pinctrl 驱动实现了 rockchip_pmx_ops 结构体:
static const struct pinmux_ops rockchip_pmx_ops = {
.get_functions_count = rockchip_pmx_get_funcs_count,
.get_function_name = rockchip_pmx_get_func_name,
.get_function_groups = rockchip_pmx_get_groups,
.set_mux = rockchip_pmx_set,
};
其中最核心的是 rockchip_pmx_set,它负责将某个引脚组切换到指定的复用功能。
5.1rockchip_pmx_set实现分析
static int rockchip_pmx_set(struct pinctrl_dev *pctldev,
unsigned int func_selector,
unsigned int group_selector)
{
struct rockchip_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
struct rockchip_pin_group *group = &info->groups[group_selector];
struct rockchip_pmx_func *func = &info->functions[func_selector];
int i;
for (i = 0; i < group->npins; i++) {
unsigned int pin = group->pins[i];
unsigned int mux = group->data[i].mux; // 该引脚在该 group 中的复用值
int ret = rockchip_pinctrl_set_mux(info, pin, mux);
if (ret)
return ret;
}
return 0;
}
实际写寄存器的函数是 rockchip_pinctrl_set_mux,它根据引脚所在的 bank 和 pin 索引计算出 iomux 寄存器的地址和位域,然后通过 regmap 更新对应位。
static int rockchip_pinctrl_set_mux(struct rockchip_pinctrl *info,
unsigned int pin, int mux)
{
struct rockchip_pin_bank *bank = pin_to_bank(info, pin);
int pin_offset = pin - bank->pin_base;
int reg, bit, ret;
// 根据 soc 类型计算复用寄存器偏移(grf 或 pmu)
rockchip_get_mux(bank, pin_offset, ®, &bit, &bank->pin_sel_type[pin_offset]);
// 写寄存器:清除原来的 2 位(或 3 位)复用值,再写入新值
ret = regmap_update_bits(info->regmap_base, reg, 0x3 << bit, mux << bit);
return ret;
}
不同 rockchip soc 的复用寄存器布局不同,驱动通过 bank->pin_sel_type 数组(在 gpio bank 驱动初始化时填充)来区分是 2 位复用(如 rk3288)还是 3 位复用(如 rk3399),以及寄存器是否位于 pmu 域。
6. 配置操作(pinconf_ops)的实现
rockchip 的 pinconf_ops 提供引脚电气特性的配置:
static const struct pinconf_ops rockchip_pinconf_ops = {
.pin_config_get = rockchip_pinconf_get,
.pin_config_set = rockchip_pinconf_set,
.is_generic = true,
};
6.1 支持的配置参数
rockchip 驱动通过通用 pinconf 框架支持以下参数(定义在 pinctrl-generic.h):
pin_config_bias_disable:关闭上下拉pin_config_bias_pull_up:上拉pin_config_bias_pull_down:下拉pin_config_drive_strength:驱动能力(ma)pin_config_slew_rate:压摆率pin_config_input_enable:输入使能
驱动中通过 rockchip_pinconf_set 解析这些通用参数,并调用 soc 专用的计算函数(如 pull_calc_reg)找到对应的寄存器地址和位域,最后更新硬件。
6.2rockchip_pinconf_set核心流程
static int rockchip_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
unsigned long *configs, unsigned int num_configs)
{
struct rockchip_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
struct rockchip_pin_bank *bank = pin_to_bank(info, pin);
int i, ret = 0;
for (i = 0; i < num_configs; i++) {
enum pin_config_param param = pinconf_to_config_param(configs[i]);
u32 arg = pinconf_to_config_argument(configs[i]);
switch (param) {
case pin_config_bias_pull_up:
case pin_config_bias_pull_down:
case pin_config_bias_disable:
ret = rockchip_set_pull(info, bank, pin - bank->pin_base, param);
break;
case pin_config_drive_strength:
ret = rockchip_set_drive(info, bank, pin - bank->pin_base, arg);
break;
// ... 其他参数
}
}
return ret;
}
rockchip_set_pull 和 rockchip_set_drive 内部会利用 rockchip_pin_ctrl 提供的回调函数(如 pull_calc_reg)来定位寄存器。
例如,rk3568 的上下拉寄存器位于 pmugrf(部分引脚)或 grf(其他引脚),驱动通过 info->regmap_pmu 和 info->regmap_base 分别访问。
7. 设备树到映射的转换(dt_node_to_map)
客户端设备在解析其 pinctrl-0 属性时,pinctrl core 会调用服务端驱动的 dt_node_to_map 函数。
rockchip 驱动实现了 rockchip_dt_node_to_map,该函数将设备树中的引脚组描述(如 <&i2c3m0_xfer>)转换为 pinctrl_map 数组。
7.1 转换步骤(回顾与补充)
根据设备树节点名查找已有的 rockchip_pin_group
pinctrl_name_to_group(info, np->name) 返回之前在解析 pinctrl 控制器时构建的 group 对象。
分配 pinctrl_map 数组
数量 = 1(复用映射) + group->npins(每个引脚一个配置映射)。
创建复用映射
new_map[0].type = pin_map_type_mux_group new_map[0].data.mux.function = parent->name(即 function 节点名,如 “i2c3”) new_map[0].data.mux.group = np->name(即 group 节点名,如 “i2c3m0_xfer”)
创建配置映射
对 group 中的每个引脚,创建一个 pin_map_type_configs_pin 映射,group_or_pin 设置为引脚名,configs 指向预先解析好的配置数组。
new_map++;
for (i = 0; i < grp->npins; i++) {
new_map[i].type = pin_map_type_configs_pin;
new_map[i].data.configs.group_or_pin = pin_get_name(pctldev, grp->pins[i]);
new_map[i].data.configs.configs = grp->data[i].configs;
new_map[i].data.configs.num_configs = grp->data[i].nconfigs;
}
这些映射最终会被 pinctrl_select_state 使用,依次调用 set_mux 和 pin_config_set 完成硬件配置。
8. 调试实践:利用 debugfs 验证驱动行为
在第二部分最后,我们展示如何利用 debugfs 验证 rockchip pinctrl 驱动的配置是否生效。
查看所有引脚组
cat /sys/kernel/debug/pinctrl/1000000.pinctrl/pingroups
输出示例:
registered pin groups: group: i2c3m0-xfer pin 6 (gpio_6) pin 7 (gpio_7)
查看复用功能
cat /sys/kernel/debug/pinctrl/1000000.pinctrl/pinmux-functions
输出:
function: i2c3, groups = [ i2c3m0-xfer i2c3m1-xfer ] function: uart0, groups = [ uart0-xfer ]
检查某个设备的引脚状态
cat /sys/kernel/debug/pinctrl/pinctrl-handles
会显示类似:
device: fe5c0000.i2c
state: default
type: mux_group controller 1000000.pinctrl group: i2c3m0-xfer function: i2c3
type: configs_group controller 1000000.pinctrl group i2c3m0-xfer 0x00000000 0x00020009
实时修改引脚配置(用于测试)
可以通过 debugfs 直接写入配置,例如:
echo "pin 6 pull-up" > /sys/kernel/debug/pinctrl/1000000.pinctrl/pinconf-pins
(需要驱动支持 debugfs 命令,rockchip 驱动实现了该接口)
9. 总结
第二部分详细剖析了 rockchip pinctrl 驱动的内部实现,涵盖:
- 驱动私有数据结构与 soc 差异化描述
- 初始化的三个关键步骤:获取 regmap、解析设备树、注册 pinctrl 控制器
- 引脚组(groups)和功能(functions)的构建过程
- 复用操作(
set_mux)如何操作 iomux 寄存器 - 配置操作(
pin_config_set)如何设置上下拉、驱动强度等电气特性 - 设备树节点到 pinctrl 映射的完整转换(
dt_node_to_map) - 调试方法:利用 debugfs 检查实际配置
理解这些细节,你就能够:
- 为新的 rockchip soc 移植或修改 pinctrl 驱动
- 排查引脚配置不生效的问题(例如检查
rockchip,pins格式是否正确,寄存器偏移是否匹配) - 在自定义硬件上正确添加新的引脚组和功能
第三部分:电源管理、gpio交互与高级调试
1. 引言
在前两部分中,我们学习了 pinctrl 子系统的通用框架(第一部分)以及 rockchip 平台驱动的具体实现(第二部分)。第三部分将聚焦于 运行时动态管理 的高级主题,包括:
- 电源管理(pm)集成:如何在系统休眠/唤醒时自动或手动切换引脚状态以降低功耗
- 与 gpio 子系统的交互:pinctrl 如何与 gpio 框架协同工作,确保引脚在 gpio 模式下的正确配置
- hog 机制:控制器自身对引脚的默认配置
- setting 应用详解:
add_setting、pinmux_enable_setting等内部函数的完整流程 - 高级调试技巧:利用 debugfs 排查引脚冲突和配置错误
通过学习本部分,你将掌握如何编写支持电源管理的设备驱动,理解 pinctrl 与 gpio 的协同原理,并能独立诊断引脚配置问题。
2. 电源管理中的引脚状态切换
2.1 设备树中定义睡眠状态
在客户端设备树节点中,可以为设备定义多个 pinctrl 状态,常见的包括:
default:正常运行状态sleep:系统 suspend 时使用的低功耗状态idle:运行时 idle 状态(可选)
&uart0 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&uart0_xfer>;
pinctrl-1 = <&uart0_sleep>;
status = "okay";
};
在 sleep 状态中,通常会将引脚切换为 gpio 功能并设置为输入或上拉,以降低漏电流。
2.2 内核自动管理:pinctrl_pm_*接口
pinctrl 子系统提供了两个辅助函数,供设备驱动在 suspend/resume 时自动切换状态:
int pinctrl_pm_select_default_state(struct device *dev); int pinctrl_pm_select_sleep_state(struct device *dev); int pinctrl_pm_select_idle_state(struct device *dev);
这些函数内部会调用 pinctrl_select_state,前提是设备已经在 probe 阶段通过 pinctrl_bind_pins 绑定好了句柄。
典型用法(在设备驱动中):
static int my_drv_suspend(struct device *dev)
{
return pinctrl_pm_select_sleep_state(dev);
}
static int my_drv_resume(struct device *dev)
{
return pinctrl_pm_select_default_state(dev);
}
static const struct dev_pm_ops my_drv_pm_ops = {
.suspend = my_drv_suspend,
.resume = my_drv_resume,
};
2.3 内核自动处理的时机
实际上,对于挂载在标准总线(如 platform、i2c、spi)上的设备,如果驱动没有提供 suspend/resume 回调,内核不会自动调用 pinctrl_pm_* 函数。因此,驱动开发者需要显式地在 suspend/resume 回调中调用这些函数。
但有一种例外:当整个系统进入 suspend 时,pinctrl 控制器驱动本身会收到 pm 事件,并可能对所有已注册的设备尝试切换状态?并非如此——状态切换是按设备进行的,每个设备的引脚状态需要各自管理。
正确的理解:pinctrl_bind_pins 只是在 probe 阶段为设备找到了 default 和 init 状态并激活了其中一个。sleep 和 idle 状态虽然被查找并保存在 dev->pins->sleep_state 中,但不会自动应用,需要驱动在 suspend 时主动调用 pinctrl_select_state。
2.4 状态切换时的内部操作
当调用 pinctrl_select_state(p, new_state) 时,内核执行以下步骤(简化):
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)
{
struct pinctrl_setting *setting, *setting2;
struct pinctrl_state *old_state = p->state;
if (p->state) {
// 1. 禁用旧状态中的所有复用设置
list_for_each_entry(setting, &p->state->settings, node) {
if (setting->type == pin_map_type_mux_group)
pinmux_disable_setting(setting);
}
}
p->state = null;
// 2. 启用新状态中的所有设置
list_for_each_entry(setting, &state->settings, node) {
switch (setting->type) {
case pin_map_type_mux_group:
ret = pinmux_enable_setting(setting);
break;
case pin_map_type_configs_pin:
case pin_map_type_configs_group:
ret = pinconf_apply_setting(setting);
break;
}
}
p->state = state;
return 0;
}
注意:对于复用设置,禁用旧状态时会调用 pinmux_disable_setting,这通常会将该引脚组的功能设置为 gpio(安全默认值),避免功能冲突。
3. pinctrl 与 gpio 子系统的交互
3.1 gpio 与 pinctrl 的关系
在许多 soc 中,gpio 控制器是 pinctrl 硬件的一部分:同一个物理引脚既可以作为通用 gpio,也可以作为外设功能引脚。pinctrl 驱动通常也负责 gpio 控制器的注册,但 gpio 子系统和 pinctrl 子系统是独立的框架,它们通过以下方式协作:
- gpio 请求时,pinctrl 确保引脚已被配置为 gpio 模式
- gpio 释放时,pinctrl 可以将其恢复为默认功能
- gpio 方向设置时,可能需要同时配置引脚的上拉/下拉
3.2gpio-ranges属性
设备树中的 gpio-ranges 属性建立了 gpio 控制器的 gpio 号与 pinctrl 引脚号之间的映射关系。例如:
gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfdd60000 0x0 0x100>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
};
该属性表示:gpio0 控制器的 gpio 0~31 对应 pinctrl 控制器的引脚 0~31。
内核通过 gpiochip_add_pin_range 和 gpiochip_add_pingroup_range 建立映射,这样 gpio 子系统在请求引脚时,可以找到对应的 pinctrl 设备。
3.3 pinctrl 驱动提供的 gpio 相关回调
pinmux_ops 中包含三个 gpio 相关的回调:
struct pinmux_ops {
int (*gpio_request_enable)(struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset);
void (*gpio_disable_free)(struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset);
int (*gpio_set_direction)(struct pinctrl_dev *pctldev,
struct pinctrl_gpio_range *range,
unsigned offset, bool input);
};
gpio_request_enable:当一个 gpio 被请求(gpio_request)时,gpio 子系统会调用该回调,使 pinctrl 驱动将对应的引脚切换为 gpio 功能。gpio_disable_free:当 gpio 被释放时调用,可将引脚恢复为默认状态。gpio_set_direction:当 gpio 方向改变(gpio_direction_input/output)时调用,用于配置引脚的上下拉等。
rockchip 驱动实现了这些回调:
static const struct pinmux_ops rockchip_pmx_ops = {
// ...
.gpio_request_enable = rockchip_gpio_request_enable,
.gpio_disable_free = rockchip_gpio_disable_free,
.gpio_set_direction = rockchip_gpio_set_direction,
};
在 rockchip_gpio_request_enable 中,会调用 rockchip_pinctrl_set_mux 将引脚复用值设置为 gpio(即 mux = 0)。
3.4 gpio 请求的完整流程

4. hog 机制:控制器自身的引脚配置
有时候,某些引脚在系统启动后始终作为 gpio 或特定功能使用,不归属于任何具体的客户端设备。pinctrl 允许在控制器节点中直接定义“hog”引脚组,并在控制器 probe 时自动应用这些配置。
4.1 设备树中的 hog 节点
在 pinctrl 控制器节点下,可以创建没有 pinctrl-* 属性的子节点,并在其中定义 rockchip,pins,这些节点会被视为 hog 组。
&pinctrl {
hog_pins: hog-pins {
rockchip,pins = <0 rk_pa0 rk_func_gpio &pcfg_pull_up>,
<0 rk_pa1 rk_func_gpio &pcfg_pull_up>;
};
};
内核在注册 pinctrl 控制器时,会遍历所有子节点,对于没有 pinctrl-0 属性的节点(即非客户端引用),会调用 pinctrl_hog 将其作为默认状态应用。
4.2 内核实现
pinctrl_register 最后会调用 pinctrl_enable,该函数内部会执行:
int pinctrl_enable(struct pinctrl_dev *pctldev)
{
// ...
if (pctldev->desc->pctlops->dt_node_to_map) {
// 遍历控制器节点下的所有子节点,找出 hog 节点
pinctrl_hog_map(pctldev, np);
}
// ...
}
pinctrl_hog_map 为每个 hog 节点创建一个虚拟的“设备”(控制器自身),并调用 dt_node_to_map 生成映射,然后添加到控制器的 hog 状态中,最后激活该状态。
5. setting 应用详解(add_setting与pinmux_enable_setting)
5.1add_setting完整分析
在 create_pinctrl 函数中,对每个 pinctrl_map 调用 add_setting,将其转换为 pinctrl_setting 并添加到对应状态的链表中。
static int add_setting(struct pinctrl *p, struct pinctrl_dev *pctldev,
const struct pinctrl_map *map)
{
struct pinctrl_state *state;
struct pinctrl_setting *setting;
// 根据 map->name 查找或创建状态
state = find_state(p, map->name);
if (!state)
state = create_state(p, map->name);
// 分配 setting 结构体
setting = kzalloc(sizeof(*setting), gfp_kernel);
setting->type = map->type;
setting->pctldev = pctldev;
setting->dev_name = map->dev_name;
switch (map->type) {
case pin_map_type_mux_group:
setting->data.mux.group = map->data.mux.group; // group 选择器
setting->data.mux.func = map->data.mux.function; // function 选择器
break;
case pin_map_type_configs_pin:
setting->data.configs.group_or_pin = map->data.configs.group_or_pin;
setting->data.configs.configs = map->data.configs.configs;
setting->data.configs.num_configs = map->data.configs.num_configs;
break;
case pin_map_type_configs_group:
// 类似
break;
default:
break;
}
list_add_tail(&setting->node, &state->settings);
return 0;
}
注意:map->data.mux.group 和 map->data.mux.function 在映射阶段是字符串名称,但在 add_setting 中并未转换为选择器。
真正的转换发生在 pinctrl_select_state 调用 pinmux_enable_setting 时,通过 pinctrl_get_group_selector 和 pinctrl_get_function_selector 动态查找。
5.2pinmux_enable_setting实现
int pinmux_enable_setting(struct pinctrl_setting *setting)
{
struct pinctrl_dev *pctldev = setting->pctldev;
const struct pinmux_ops *ops = pctldev->desc->pmxops;
unsigned group = 0, func = 0;
int ret;
// 将字符串名称转换为选择器
group = pinctrl_get_group_selector(pctldev, setting->data.mux.group);
func = pinctrl_get_function_selector(pctldev, setting->data.mux.func);
// 调用驱动的 set_mux
ret = ops->set_mux(pctldev, func, group);
return ret;
}
类似地,pinconf_apply_setting 会调用 pin_config_group_set 或 pin_config_set。
6. 高级调试技巧
6.1 查看引脚当前的配置值
除了之前提到的文件,/sys/kernel/debug/pinctrl/<controller>/pinconf-pins 可以查看每个引脚的当前配置(需要驱动实现 pin_config_dbg_show)。
rockchip 驱动实现了该接口,输出类似:
pin 0 (gpio_0): bias-pull-up, drive-strength = 12 ma pin 1 (gpio_1): bias-disable, drive-strength = 6 ma
6.2 动态修改引脚配置(测试用)
通过 debugfs 可以临时修改某个引脚的配置,无需重新编译内核:
echo "pin 0 bias-pull-up drive-strength=12" > /sys/kernel/debug/pinctrl/1000000.pinctrl/pinconf-pins
这需要驱动实现 pin_config_dbg_parse_modify 回调,rockchip 驱动未实现该功能,但其他平台可能有。
6.3 检查引脚占用冲突
当出现“pin already requested”错误时,使用以下命令查找冲突源:
cat /sys/kernel/debug/pinctrl/pinctrl-handles | grep -a 10 "pin 6"
也可以直接查看某个控制器的引脚占用情况(如果驱动实现了 pin_dbg_show):
cat /sys/kernel/debug/pinctrl/1000000.pinctrl/pins
输出会显示每个引脚是否被占用,以及被哪个设备占用。
6.4 模拟状态切换
手动触发设备的状态切换,验证电源管理逻辑:
# 先找到设备在 debugfs 中的句柄 cat /sys/kernel/debug/pinctrl/pinctrl-handles | grep "device:" # 假设设备是 78b6000.i2c # 无法直接通过命令切换,但可以查看当前状态 cat /sys/kernel/debug/pinctrl/pinctrl-handles
更好的方法是在驱动中增加 sysfs 节点来触发状态切换。
7. 总结
第三部分涵盖了 pinctrl 子系统的运行时管理核心:
- 电源管理:通过
pinctrl_pm_select_*函数在 suspend/resume 中切换引脚状态,降低功耗。驱动需要主动调用这些函数。 - gpio 交互:
gpio-ranges建立映射,gpio_request_enable等回调确保引脚在 gpio 请求时正确配置。 - hog 机制:控制器自身对引脚的默认配置,无需客户端设备。
- 内部流程:
add_setting创建设置,pinmux_enable_setting在激活时解析名称并调用驱动回调。 - 调试:利用 debugfs 查看引脚占用、配置值和状态切换。
.2 动态修改引脚配置(测试用)
通过 debugfs 可以临时修改某个引脚的配置,无需重新编译内核:
echo "pin 0 bias-pull-up drive-strength=12" > /sys/kernel/debug/pinctrl/1000000.pinctrl/pinconf-pins
这需要驱动实现 pin_config_dbg_parse_modify 回调,rockchip 驱动未实现该功能,但其他平台可能有。
6.3 检查引脚占用冲突
当出现“pin already requested”错误时,使用以下命令查找冲突源:
cat /sys/kernel/debug/pinctrl/pinctrl-handles | grep -a 10 "pin 6"
也可以直接查看某个控制器的引脚占用情况(如果驱动实现了 pin_dbg_show):
cat /sys/kernel/debug/pinctrl/1000000.pinctrl/pins
输出会显示每个引脚是否被占用,以及被哪个设备占用。
6.4 模拟状态切换
手动触发设备的状态切换,验证电源管理逻辑:
# 先找到设备在 debugfs 中的句柄 cat /sys/kernel/debug/pinctrl/pinctrl-handles | grep "device:" # 假设设备是 78b6000.i2c # 无法直接通过命令切换,但可以查看当前状态 cat /sys/kernel/debug/pinctrl/pinctrl-handles
更好的方法是在驱动中增加 sysfs 节点来触发状态切换。
7. 总结
第三部分涵盖了 pinctrl 子系统的运行时管理核心:
- 电源管理:通过
pinctrl_pm_select_*函数在 suspend/resume 中切换引脚状态,降低功耗。驱动需要主动调用这些函数。 - gpio 交互:
gpio-ranges建立映射,gpio_request_enable等回调确保引脚在 gpio 请求时正确配置。 - hog 机制:控制器自身对引脚的默认配置,无需客户端设备。
- 内部流程:
add_setting创建设置,pinmux_enable_setting在激活时解析名称并调用驱动回调。 - 调试:利用 debugfs 查看引脚占用、配置值和状态切换。
最后
掌握这些内容后,你不仅可以正确编写使用 pinctrl 的设备驱动,还能够深入调试引脚相关的硬件问题。pinctrl 子系统的设计体现了 linux 内核“分离机制与策略”的哲学,是学习内核子系统的优秀范例。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论