第102章 pinctrl 子系统的引入
Linux中的 pinctrl子系统是管理和配置GPIO引脚的框架,提供标准化方法以适应不同硬件。
它遵循 Linux内核设备模型,分为设备、驱动等部分。
本章节从设备和驱动角度介绍 pinctrl子系统。
102.1 pinctrl 设备树
在设备树中,RK3568的pinctrl配置被详细定义,
主要包括服务端(pinctrl节点及其下的GPIO控制器)和
客户端(在rk3568-pinctrl.dtsi中定义的引脚复用配置)。
服务端配置(rk3568.dtsi):
在设备树根节点下,定义了pinctrl节点,指定了兼容性和时钟等属性。
包含了五个GPIO控制器(gpio0-gpio4),每个都指定了地址、中断、时钟等配置。
通过 gpio-ranges属性将GPIO引脚映射到 pinctrl系统。
pinctrl: pinctrl { compatible = "rockchip,rk3568-pinctrl"; //兼容性字符串gpio0: gpio@fdd60000 { ... }; gpio1: gpio@fe740000 { ... }; ... #include "rk3568-pinctrl.dtsi"
};
在设备树的最下方通过 include 包含了 rk3568-pinctrl.dtsi 设备树,该设备树中包含了所有复用功能的配置。
客户端配置(rk3568-pinctrl.dtsi):
定义了具体的引脚复用配置,如 acodec的引脚配置。
这些配置由瑞芯微原厂提供,用户通常只需根据需求使用。
&pinctrl { acodec-pins: acodec-pins { rockchip,pins = < 1 RK PB1 5 &pcfg_pull_none>, 1 RK PA1 5 &pcfg_pull_none>, ... >; }; ...
};
驱动实现:
pinctrl 驱动根据设备树中的 compatible属性进行匹配。
RK3568的 pinctrl驱动位于内核源码的 /driver/pinctrl/pinctrl-rockchip.c。
102.2 pinctrl 驱动
在 /drivers/pinctrl/pinctrl-rockchip.c中,
Rockchip pinctrl驱动的入口函数通过platform_driver_register注册了一个platform驱动。
当设备与驱动匹配时,会调用 rockchip_pinctrl_probe()函数进行初始化。
rockchip_pinctrl_probe函数的主要作用包括:
分配并初始化 rockchip_pinctrl结构体。
获取与平台设备相关的 rockchip_pin_ctrl结构体。
解析设备树中的"rockchip,grf"节点,获取寄存器映射基地址。
如果找不到"rockchip,grf"节点,则通过平台资源获取寄存器基地址,并配置寄存器映射。
尝试查找可选的"rockchip,pmu"syscon引用。
对某些SoC进行特殊处理。
注册 rockchip_pinctrl设备。
设置平台设备的私有数据。
注册GPIO设备。
static struct platform_driver rockchip_pinctrl_driver = { .probe = rockchip_pinctrl_probe, .driver = { .name = "rockchip-pinctrl", .of_match_table = rockchip_pinctrl_dt_match, },
}; static int __init rockchip_pinctrl_drv_register(void)
{ return platform_driver_register(&rockchip_pinctrl_driver);
} postcore_initcall(rockchip_pinctrl_drv_register); static int rockchip_pinctrl_probe(struct platform_device *pdev)
{ // 分配并初始化结构体 // 获取并设置soc数据 // 解析设备树,获取寄存器基地址 // 查找可选的pmu syscon引用 // 对某些SoC进行特殊处理 // 注册pinctrl设备 // 设置私有数据 // 注册GPIO设备 return 0;
}
第103章 pinctrl probe 函数讲解
103.1 pinctrl_desc 结构体
pinctrl_desc 结构体用于描述引脚控制器(pinctrl)的属性和操作,
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; // 拥有模块 // 自定义配置参数(可选) unsigned int num_custom_params; const struct pinconf_generic_params *custom_params; const struct pin_config_item *custom_conf_items;
};
103.2 rockchip_pinctrl 结构体
rockchip_pinctrl 结构体是瑞芯微为了适应其芯片特定需求和功能,对标准的 pinctrl_desc 结构体进行的封装。
/*瑞芯微对pinctrl结构体的封装*/
struct rockchip_pinctrl { struct regmap *regmap_base; // 基本寄存器映射 int reg_size; // 寄存器大小 struct regmap *regmap_pull; // 拉取寄存器映射 struct regmap *regmap_pmu; // PMU寄存器映射 struct device *dev; // 设备指针 struct rockchip_pin_ctrl *ctrl; // 瑞芯微引脚控制器指针 struct pinctrl_desc pctl; // 引脚控制器描述符(包含标准pinctrl_desc) struct pinctrl_dev *pctl_dev; // 引脚控制器设备指针 struct rockchip_pin_group *groups; // 引脚组指针 unsigned int ngroups; // 引脚组数量 struct rockchip_pmx_func *functions; // 引脚功能指针 unsigned int nfunctions; // 引脚功能数量
};
103.3 pinctrl probe 函数
在probe()函数中,主要完成了以下任务:
分配并初始化rockchip_pinctrl结构体,
获取SoC特定的配置,
解析设备树,获取寄存器映射,
处理可选的PMU寄存器映射。
进行SoC特定的初始化(如果适用)。
注册 pinctrl设备。
ret = rockchip_pinctrl_register(pdev, info);
if (ret) return ret;
注册GPIO设备(通过of_platform_populate)。
rockchip_pinctrl_register()函数完成了以下任务:
初始化pinctrl_desc结构体,
为每个引脚分配内存并设置编号和名称。
解析设备树中的pinctrl信息。
注册 pinctrl设备。
info->pctl_dev = devm_pinctrl_register(&pdev->dev, ctrldesc, info);
if (IS_ERR(info->pctl_dev)) return PTR_ERR(info->pctl_dev);
devm_pinctrl_register()函数用于注册 pinctrl设备并将其与设备关联:
分配内存以存储 pinctrl_dev指针,
注册 pinctrl设备,
pctldev = pinctrl_register(pctldesc, dev, driver_data);
if (IS_ERR(pctldev)) { devres_free(ptr); return pctldev;
}
将 pinctrl_dev指针存储到设备资源列表中。
pinctrl_register()函数用于注册并启用 pinctrl设备:
初始化 pinctrl控制器,
pctldev = pinctrl_init_controller(pctldesc, dev, driver_data);
if (IS_ERR(pctldev)) return pctldev;
启用 pinctrl控制器。
error = pinctrl_enable(pctldev);
if (error) return ERR_PTR(error);
通过这一系列步骤,瑞芯微的pinctrl驱动成功注册并启用了pinctrl设备,为后续的引脚控制和配置提供了基础。
第104章 pinctrl 子系统函数操作集
probe 函数的实际作用就是注册并启用 pinctrl 设备,pinctrl 设备由 pinctrl_desc 结构体所描述,所以在 probe() 函数中会对 pinctrl_desc 结构体中的内容进行填充。
104.1 groups 和 function
在 Pinctrl 子系统中,关键概念包括引脚组(groups)和功能(function)。
以 rk3568-pinctrl.dtsi 设备树文件中的 can0 和 can1 为例:
can0 功能:
can0m0-pins 组:
<0 RK_PB4 2 &pcfg_pull_none>, <0 RK_PB3 2 &pcfg_pull_none>/*控制器编号、引脚编号、引脚模式编号(代表功能)、引脚配置选项*/ //pcfg_pull_none代表未启动上下拉,即浮空
RK_PB4:CAN0 接收引脚(can0_rxm0)
RK_PB3:CAN0 发送引脚(can0_txm0)
can1m1-pins 组:
<4 RK_PC2 3 &pcfg_pull_none>, <4 RK_PC3 3 &pcfg_pull_none>
RK_PC2:CAN1 接收引脚(can1_rxm1)
RK_PC3:CAN1 发送引脚(can1_txm1)
can0 和 can1 是两个功能,每个功能包含两个引脚组,分别用于配置 CAN0 和 CAN1 的接收和发送引脚。
104.2 函数操作集结构体
在Linux内核中,pinctrl_desc 结构体包含三个函数操作集,
分别用于引脚控制、引脚复用和引脚配置。瑞芯微在源码中对重要函数进行了实现。
pinctrl_ops(),引脚控制操作函数指针。
pinmux_ops(),引脚复用操作函数指针。
pinconf_ops(),引脚配置操作函数指针。
/*引脚控制操作函数*/
struct pinctrl_ops { // 获取引脚控制器支持的引脚组数量 int (*get_groups_count)(struct pinctrl_dev *pctldev); // 根据选择器获取引脚组的名称 const char *(*get_group_name)(struct pinctrl_dev *pctldev, unsigned selector); // 根据选择器获取引脚组中的引脚和数量 int (*get_group_pins)(struct pinctrl_dev *pctldev, unsigned selector, const unsigned **pins, unsigned *num_pins); // 打印引脚信息用于调试 void (*pin_dbg_show)(struct pinctrl_dev *pctldev, struct seq_file *s, unsigned offset); // 将设备树节点转换为引脚控制器映射 int (*dt_node_to_map)(struct pinctrl_dev *pctldev, struct device_node *np_config, struct pinctrl_map **map, unsigned *num_maps); // 释放由设备树节点转换来的引脚控制器映射 void (*dt_free_map)(struct pinctrl_dev *pctldev, struct pinctrl_map *map, unsigned num_maps);
};
/*引脚复用操作函数*/
struct pinmux_ops { // 请求特定的引脚或引脚组进行复用 int (*request)(struct pinctrl_dev *pctldev, unsigned offset); // 释放之前请求的引脚或引脚组 int (*free)(struct pinctrl_dev *pctldev, unsigned offset); // 获取引脚控制器支持的功能数量 int (*get_functions_count)(struct pinctrl_dev *pctldev); // 根据选择器获取功能名称 const char *(*get_function_name)(struct pinctrl_dev *pctldev, unsigned selector); // 根据选择器获取功能对应的引脚组 int (*get_function_groups)(struct pinctrl_dev *pctldev, unsigned selector, const char * const **groups, unsigned *num_groups); // 设置引脚复用功能(选择功能和引脚组) int (*set_mux)(struct pinctrl_dev *pctldev, unsigned func_selector, unsigned group_selector); // 请求并启用GPIO范围内的特定引脚 int (*gpio_request_enable)(struct pinctrl_dev *pctldev, struct pinctrl_gpio_range *range, unsigned offset); // 禁用并释放GPIO范围内的特定引脚 void (*gpio_disable_free)(struct pinctrl_dev *pctldev, struct pinctrl_gpio_range *range, unsigned offset); // 设置GPIO引脚的方向(输入或输出) int (*gpio_set_direction)(struct pinctrl_dev *pctldev, struct pinctrl_gpio_range *range, unsigned offset, bool input); // 指示是否严格遵循某些规则(如:是否要求必须先请求再设置等) bool strict;
};
/*引脚配置操作函数*/
struct pinconf_ops { // 获取单个引脚的配置 int (*pin_config_get)(struct pinctrl_dev *pctldev, unsigned pin, unsigned long *config); // 设置单个引脚的多个配置 int (*pin_config_set)(struct pinctrl_dev *pctldev, unsigned pin, unsigned long *configs, unsigned num_configs); // 获取引脚组的配置 int (*pin_config_group_get)(struct pinctrl_dev *pctldev, unsigned selector, unsigned long *config); // 设置引脚组的多个配置 int (*pin_config_group_set)(struct pinctrl_dev *pctldev, unsigned selector, unsigned long *configs, unsigned num_configs); // 解析并修改引脚配置的调试参数 int (*pin_config_dbg_parse_modify)(struct pinctrl_dev *pctldev, const char *arg, unsigned long *config); // 打印单个引脚配置的调试信息 void (*pin_config_dbg_show)(struct pinctrl_dev *pctldev, struct seq_file *s, unsigned offset); // 打印引脚组配置的调试信息 void (*pin_config_group_dbg_show)(struct pinctrl_dev *pctldev, struct seq_file *s, unsigned selector); // 打印特定配置值的调试信息 void (*pin_config_config_dbg_show)(struct pinctrl_dev *pctldev, struct seq_file *s, unsigned long config);
};
104.3 rockchip_pinctrl 分析
在 pinctrl 的初始化过程中,rockchip_pinctrl 结构体通过一系列函数调用与 pinctrl_dev 关联。
rockchip_pinctrl_register() 函数将rockchip_pinctrl 结构体作为参数传入。
经过函数调用链,rockchip_pinctrl 最终作为私有数据driver_data传入pinctrl_register() 函数。
在 pinctrl_init_controller() 函数中,driver_data 被赋值给 pctldev->driver_data,从而建立了 pinctrl_dev 和 rockchip_pinctrl 之间的关联。
接着,解析设备树中的引脚控制器信息:
rockchip_pinctrl_parse_dt() 函数解析设备树,
首先计算功能数量(functions)和组数量(groups)。
为功能和组分配内存空间。
遍历每个功能节点,调用 rockchip_pinctrl_parse_functions 函数解析功能信息。
rockchip_pinctrl_parse_functions() 函数初始化功能结构体,并为每个功能分配组指针数组。
遍历功能节点的每个子节点(引脚组),
调用 rockchip_pinctrl_parse_groups() 函数解析组信息,将这些信息存储在相应的结构体中。