Pinctrl子系统
Pinctrl作用

如图所示,Pinctrl主要完成以下工作
引脚枚举与命名(Enumerating and naming)
芯片原厂提供,定义了芯片引脚定义和复用功能等
引脚复用(Multiplexing):比如用作GPIO、I2C或其他功能
引脚配置(Configuration):比如上拉、下来、open drain、驱动强度等
基本概念

如上图所示:
pin controller图中左边所示,软件上的概念,配置IO复用功能 ,里面会有下面两个概念
Generic pin multiplexing node例如上图中的
state_0_node_a节点中function该节点复用为什么功能
groups需要用到哪些引脚
Generic pin configuration node配置io上下拉等
client device芯片的外设,比如I2C会使用Pinctrl系统,他就是个device,如上图中右边所示,里面有两个概念
pinctrl-0、pinctrl-1里面指明了
pinctrl-0使用state_0_node_a节点,pinctrl-1使用state_0_node_a节点pin state例如上面通过
pinctrl-name指明device 有两种状态default和sleep状态,状态是自定义的 只要驱动能识别就行。
其中
default状态使用pinctrl-0sleep状态使用pinctrl-1
下面是实际的例子,可能和上面的模型不一样,具体如下所示:

代码中何时调用pinctrl
当设备切换状态,比如设备在probe的时候,驱动会调用状态切换如下:

或者设备从default切换为sleep状态的时候,也会进行调用。这些都是自动的过程,无需手动调用,我们也可以自己调用,
devm_pinctrl_get_select_default(struct device *dev); // 使用"default"状态的引脚
pinctrl_get_select(struct device *dev, const char *name); // 根据name选择某种状态的引脚
pinctrl_put(struct pinctrl *p); // 不再使用, 退出时调用
pin controller数据结构
设备树关于pin controller的定义
如图所示:设备树imx6ull.dtsi中iomuxc即使imx6ull的pin controller
imx6ull.dtsi
在imx6ull-14x14-evk.dts里面新增了imx6ul-evk这一个节点,即类似于我们上面模型中的function功能,
在imx6ul-evk下又有很多子节点,用来描述外设组的引脚配置。
imx6ull-14x14-evk.dts
从上面可以得知当 compatible = "fsl,imx6ul-iomuxc"属性在平台设备链表中,被查到时候就会调用对应的probe函数,实际就是imx_6ul_pinctrl_probe函数,如下图所示:

那么在imx_6ul_pinctrl_probe函数中,设备树中的iomuxc节点是如何转换为c语言结构体的呢?继续分析如下。
如何构建pinctrl_dev
pincontroller虽然是一个软件的概念,抽象到Pinctrl子系统后会用pinctrl_dev来描述它在软件层面的定义。
构造出pinctrl_dev直接调用:pinctrl_register函数即可,正如在pinctrl-imx6ul.c文件中,如下代码所示:
imx6ul_pinctrl_probe(struct platform_device *pdev)
imx_pinctrl_probe(struct platform_device *pdev,struct imx_pinctrl_soc_info *info)
// 下面的ipctl->pctl即为pinctrl_dev类型的结构体
// 创建 pinctrl_dev 类型的设备,用来描述iomuxc
ipctl->pctl = pinctrl_register(imx_pinctrl_desc, &pdev->dev, ipctl);
在pinctrl_register里面会把imx_pinctrl_desc 赋值给pinctrl_dev里面的desc,如下图
pinctrl_register
struct pinctrl_dev *pctldev;
pctldev = kzalloc(sizeof(*pctldev), GFP_KERNEL);
pctldev->owner = pctldesc->owner;
pctldev->desc = pctldesc;// 1-------------------------这里赋值
pctldev->driver_data = driver_data;
/* check core ops for sanity */
ret = pinctrl_check_ops(pctldev);
/* If we're implementing pinmuxing, check the ops for sanity */
ret = pinmux_check_ops(pctldev);
/* If we're implementing pinconfig, check the ops for sanity */
ret = pinconf_check_ops(pctldev);
/* Register all the pins */
ret = pinctrl_register_pins(pctldev, pctldesc->pins, pctldesc->npins);
list_add_tail(&pctldev->node, &pinctrldev_list);
按照上面的代码描述:pinctrl_dev里面有个很重要的结构体struct pinctrl_desc

按照上面的代码分析,看下imx_pinctrl_desc的来源,如下代码所示:

总结上面的代码分析:
构建
struct pinctrl_pin_desc imx6ul_pinctrl_pads[]结构体,描述板载资源的所有gpio资源,构建
struct imx_pinctrl_soc_info imx6ul_pinctrl_info结构体里面包含imx6ul_pinctrl_pads,供下面使用构建
struct of_device_id imx6ul_pinctrl_of_match[]结构体,用来和设备树做.compatible = "fsl,imx6ul-iomuxc"属性的匹配,并且设置私有数据为imx6ul_pinctrl_info当设备树
iomuxc节点和imx6ul_pinctrl_of_match匹配时候,会调用imx6ul_pinctrl_probe函数在
imx6ul_pinctrl_probe函数中为imx_pinctrl_desc赋值,这样imx_pinctrl_desc中携带所有的io资源了
这里面还有三个重要的ops,用来存放操作io的函数
imx_pinctrl_desc->pctlops = &imx_pctrl_ops来取出某组的引脚:get_groups_count、get_group_pins
处理设备树中pin controller中的某个节点:dt_node_to_map,把device_node转换为一系列的pinctrl_map

imx_pinctrl_desc->pmxops = &imx_pmx_ops引脚复用相关
image-20221129155111830imx_pinctrl_desc->confops = &imx_pinconf_ops引脚配置

如何解析设备树pincontroller中组信息
前面介绍了pinctrl_dev里面pinctrl_desc中关于设备所有gpio资源的获取和一些ops的操作函数,现在介绍pinctrl_dev中关于info变量的赋值,在代码中体现为下面的位置,
imx_pinctrl_desc->name = dev_name(&pdev->dev);
imx_pinctrl_desc->pins = info->pins;
imx_pinctrl_desc->npins = info->npins;
imx_pinctrl_desc->pctlops = &imx_pctrl_ops;
imx_pinctrl_desc->pmxops = &imx_pmx_ops;
imx_pinctrl_desc->confops = &imx_pinconf_ops;
imx_pinctrl_desc->owner = THIS_MODULE;
ret = imx_pinctrl_probe_dt(pdev, info);//----把设备树&iomuxc 所有组及每个组使用的引脚赋值到info中,
if (ret) {
dev_err(&pdev->dev, "fail to probe dt properties\n");
return ret;
}
ipctl->info = info;// --------然后这里pinctrl_dev-》info 就可以访问这些数据了
具体的解析过程如下:
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
int imx_pinctrl_probe(struct platform_device *pdev,struct imx_pinctrl_soc_info *info)
ret = imx_pinctrl_probe_dt(pdev, info);
// 这个函数用来处理设备节点
static int imx_pinctrl_probe_dt(struct platform_device *pdev,struct imx_pinctrl_soc_info *info)
struct device_node *np = pdev->dev.of_node;
//1.这里指的iomuxc中的只有一个imx6ull-evk节点 所以nfuncs=1
nfuncs = of_get_child_count(np);
info->nfunctions = nfuncs;
//2.这里分配nfuncs个functions空间
info->functions = devm_kzalloc(&pdev->dev, nfuncs * sizeof(struct imx_pmx_func),GFP_KERNEL);
//3.遍历iomuxc下所有child,这里只有一个imx6ull-evk ,然后统计所有的child下有多少个组,
// 因为此时只有一个imx6ull-evk ,所以就是统计imx6ull-evk 下有多少个组,存放在info->ngroups中
for_each_child_of_node(np, child)
info->ngroups += of_get_child_count(child);
//4.有多少个组就分配多少个组的空间
info->groups = devm_kzalloc(&pdev->dev, info->ngroups * sizeof(struct imx_pin_group),GFP_KERNEL);
//5.对每个iomuxc下的孩子,遍历,然后调用imx_pinctrl_parse_functions 来解析,此时也就一个孩子imx6ull-evk
for_each_child_of_node(np, child)
imx_pinctrl_parse_functions(child, info, i++); //这里的i 只能到0 因为只有一个imx6ull-evk
{
struct device_node *child;
struct imx_pmx_func *func;
struct imx_pin_group *grp;
u32 i = 0;
dev_dbg(info->dev, "parse function(%d): %s\n", index, np->name);
func = &info->functions[index];
/* Initialise function */
// 6.此时np->name="imx6ul-evk"
func->name = np->name;
// 7.func->num_groups 就是节点imx6ul-evk下面有多少个组,然后分配多少个组空间
func->num_groups = of_get_child_count(np);
func->groups = devm_kzalloc(info->dev,
func->num_groups * sizeof(char *), GFP_KERNEL);
// 8.遍历imx6ul-evk下所有的组,获取组名并调用imx_pinctrl_parse_groups解析组
for_each_child_of_node(np, child) {
/* 9. 这里面存放imx6ul-evk下的所有组的名字 下面以pinctrl_i2c2组为例说明:
* 此时的 child->name=pinctrl_i2c2
* pinctrl_i2c2: i2c2grp {
* fsl,pins = <
* MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
* MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
* >;
* };
*/
func->groups[i] = child->name;
grp = &info->groups[info->grp_index++];
//10. 解析每个组下的配置
imx_pinctrl_parse_groups(child, grp, info, i++);
{
//11.传进来的每个节点的名字例如pinctrl_i2c2
grp->name = np->name;
// 12. 获取fsl,pins下有多少个size,这里以pinctrl_i2c2为例说明
// size 等于,并且list指向第一个
list = of_get_property(np, "fsl,pins", &size);
// 13. fsl 定义了pin_size为24 也就是24个字节
// 得到有多少个引脚,并分配空间给grp->pins
grp->npins = size / pin_size;
grp->pins = devm_kzalloc(info->dev, grp->npins * sizeof(struct imx_pin),GFP_KERNEL);
grp->pin_ids = devm_kzalloc(info->dev, grp->npins * sizeof(unsigned int),GFP_KERNEL);
//14. 变量所有引脚,访问里面的变量赋值给grp->pins[i]里面的每个成员,
// 到此为止,设备树&iomuxc里面的成员都被赋值给 pinctrl_desc,也就是pinctrl_dev里面包含了
// 设备树&iomuxc的信息和板载所有io信息
for (i = 0; i < grp->npins; i++) {
u32 mux_reg = be32_to_cpu(*list++);
u32 conf_reg;
unsigned int pin_id;
struct imx_pin_reg *pin_reg;
struct imx_pin *pin = &grp->pins[i];
if (!(info->flags & ZERO_OFFSET_VALID) && !mux_reg)
mux_reg = -1;
if (info->flags & SHARE_MUX_CONF_REG) {
conf_reg = mux_reg;
} else {
conf_reg = be32_to_cpu(*list++);
if (!(info->flags & ZERO_OFFSET_VALID) && !conf_reg)
conf_reg = -1;
}
pin_id = (mux_reg != -1) ? mux_reg / 4 : conf_reg / 4;
pin_reg = &info->pin_regs[pin_id];
pin->pin = pin_id;
grp->pin_ids[i] = pin_id;
pin_reg->mux_reg = mux_reg;
pin_reg->conf_reg = conf_reg;
pin->input_reg = be32_to_cpu(*list++);
pin->mux_mode = be32_to_cpu(*list++);
pin->input_val = be32_to_cpu(*list++);
/* SION bit is in mux register */
config = be32_to_cpu(*list++);
if (config & IMX_PAD_SION)
pin->mux_mode |= IOMUXC_CONFIG_SION;
pin->config = config & ~IMX_PAD_SION;
dev_dbg(info->dev, "%s: 0x%x 0x%08lx", info->pins[pin_id].name,
pin->mux_mode, pin->config);
}
}
}
}
总结:经过上面的操作,pinctrl_dev里面就有了结构体struct pinctrl_desc和info结构体,里面包含cpu所有io资源和对这些io复用、配置、设置的三个关键ops函数。
client数据结构
在设备树的理想模型中,设备使用pinctrl如下:
/* For a client device requiring named states */
device {
pinctrl-names = "active", "idle";
pinctrl-0 = <&state_0_node_a>;
pinctrl-1 = <&state_1_node_a &state_1_node_b>;
};
在imx6ull设备树中,以i2c为0例使用pinctrl的方式如下:


这些设备节点要么被转换为platform_device,或者其他结构体(比如i2c_client),但是里面都会有一个device结构体,比如:

在device中有个很重要的结构体struct dev_pin_info *pins,下面重点分析这个结构体。
dev_pin_info 分析
重要结构体
每个device结构体里都有一个dev_pin_info结构体,用来保存设备的pinctrl信息,下面是一些重要的结构体,先混个脸熟:

如何存储device
/* For a client device requiring named states */
device {
pinctrl-names = "active", "idle";
pinctrl-0 = <&state_0_node_a>;
pinctrl-1 = <&state_1_node_a &state_1_node_b>;
};
如上面的代码,一个device可能有多种状态,active、idle、default等等,那么怎么把这些信息转换为C语言结构?下面继续分析。
在装载设备驱动的时候会调用下面的driver_probe_device函数,完成默认状态的设置,这个过程就是把上面的device里面的pinctrl-names的状态转换为C语言的结构体过程。
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
really_probe(dev, drv);
{
pinctrl_bind_pins(dev);
{ //step 1.分配device里面的 struct dev_pin_info 空间
dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);
//step 2.获取struct pinctrl *p
dev->pins->p = devm_pinctrl_get(dev);
//step 3.获取上面弄好的状态
dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,PINCTRL_STATE_DEFAULT);
//step 4.设置状态
ret = pinctrl_select_state(dev->pins->p, dev->pins->default_state);
}
}
}
总结4步:其实就是完成 struct dev_pin_info结构体信息的填充
step 1.分配device里面的struct dev_pin_info 空间
step 2.获取struct pinctrl *p
step 3.获取上面弄好的状态
step 4.设置状态
下面重点分析step 2和step 4如下:
step 2
//step 2.获取struct pinctrl *pdev->pins->p =
devm_pinctrl_get(dev);
{
pinctrl_get(dev);
{
create_pinctrl(dev)
{
//step 1. 分配struct pinctrl类型的空间
struct pinctrl *p = kzalloc(sizeof(*p), GFP_KERNEL);
//step 2. 把dt转换为map
ret = pinctrl_dt_to_map(p)
//step 3. 遍历每个map,然后转换为setting
for_each_maps(maps_node, i, map) {
if (strcmp(map->dev_name, devname))
continue;
//step 4. 把map转换为setting
ret = add_setting(p, map);
}
}
}
}
总结4步骤,其中步骤step 2和step 3比较重要
step 1. 分配struct pinctrl类型的空间
step 2.把dt转换为map
int pinctrl_dt_to_map(struct pinctrl *p) { // step 1. 遍历device下的所有的state 解析 for (state = 0; ; state++) { /* step 2. 查找pinctrl-%d 关键字符串得到有多少个 node 这里的 pinctrl-%d 为pinctrl-0 pinctrl-1 等等, * 根据实际的device里面节点不同而不同,下面以&i2c1节点为例说明 * &i2c1 { * clock_frequency = <100000>; * pinctrl-names = "default"; * pinctrl-0 = <&pinctrl_i2c1>; * status = "okay"; */ propname = kasprintf(GFP_KERNEL, "pinctrl-%d", state); prop = of_find_property(np, propname, &size); // step 3. list 指向&pinctrl_i2c1 且size=1 因为 &i2c1 的pinctrl-0只有一个node &pinctrl_i2c1 list = prop->value; size /= sizeof(*list); // step 4. 得到pinctrl-names 节点内容 ret = of_property_read_string_index(np, "pinctrl-names", state, &statename); for (config = 0; config < size; config++) { phandle = be32_to_cpup(list++); // step 5. 此时的 phandle 即为 &pinctrl_i2c1 根据&pinctrl_i2c1 查找对应的设备节点np_config np_config = of_find_node_by_phandle(phandle); /* Parse the node */ ret = dt_to_map_one_config(p, statename, np_config); { // step 6. 这个ops就是上面我们注册的imx_pctrl_ops里面的 imx_dt_node_to_map 函数 ret = ops->dt_node_to_map(pctldev, np_config, &map, &num_maps); //static int imx_dt_node_to_map(struct pinctrl_dev *pctldev,struct device_node *np,struct pinctrl_map **map, unsigned *num_maps)函数内容如下: { /* * step 7. 根据np_config 找到对应的组,即pinctrl_i2c1,然后统计组中io配置的个数存放在map_num里面 * 此时 pinctrl_i2c1 里面有两个io配置 * pinctrl_i2c1: i2c1grp { * fsl,pins = < * MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0 * MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0 * >; * }; * * mux_reg conf_reg input_reg mux_mode input_val * #define MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x00b4 0x0340 0x05a4 2 1 * #define MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x00b8 0x0344 0x05a8 2 1 * */ const struct imx_pin_group *grp = imx_pinctrl_find_group_by_name(info, np->name); for (i = 0; i < grp->npins; i++) { if (!(grp->pins[i].config & IMX_NO_PAD_CTL)) map_num++; } // step 8. 有多少个引脚就分配多少个空间new_map new_map = kmalloc(sizeof(struct pinctrl_map) * map_num, GFP_KERNEL); // new_map[0] 存放这组引脚用来配置复用功能的 new_map[0].type = PIN_MAP_TYPE_MUX_GROUP; // --------这组引脚配置复用功能的 new_map[0].data.mux.function = parent->name;// --------imx6ul-evk new_map[0].data.mux.group = np->name; // --------i2c1grp /* create config map */ new_map++; // step 9. // new_map[1] 存放第一组引脚的配置 MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0 // new_map[2] 存放第二组引脚的配置 MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0 for (i = j = 0; i < grp->npins; i++) { if (!(grp->pins[i].config & IMX_NO_PAD_CTL)) { new_map[j].type = PIN_MAP_TYPE_CONFIGS_PIN; //--这个引脚是用来配置的 new_map[j].data.configs.group_or_pin =pin_get_name(pctldev, grp->pins[i].pin); new_map[j].data.configs.configs = &grp->pins[i].config; new_map[j].data.configs.num_configs = 1; // -- 这里表明只有一个配置项 j++; } } // step 10.到这里 其实device里面使用pinctrl-%d 的内容 结合pinctrolerdata 就把配置信息存放到new_map中了 } } of_node_put(np_config); } } }
在上面的代码中的setp10 这步时候,已经完成了dt配置到map的转换,实现的功能大致如下:用map串了起来。

step 3. 遍历每个map,然后转换为setting
static int add_setting(struct pinctrl *p, struct pinctrl_map const *map) { switch (map->type) { case PIN_MAP_TYPE_MUX_GROUP: ret = pinmux_map_to_setting(map, setting); { // step1. 根据func name 转换为 func index ret = pinmux_func_name_to_selector(pctldev, map->data.mux.function); setting->data.mux.func = ret; // step2. 该function下有哪些组,为了下面比较用 ret = pmxops->get_function_groups(pctldev, setting->data.mux.func,&groups, &num_groups); // step3. 遍历function下的所有的groups 找到自己需要设置的那个组map->data.mux.group if (map->data.mux.group) { bool found = false; group = map->data.mux.group; for (i = 0; i < num_groups; i++) { if (!strcmp(group, groups[i])) { found = true;// 说明找到了 break; } } } // step4. 把找到的组名转为组索引 存起来 ret = pinctrl_get_group_selector(pctldev, group); setting->data.mux.group = ret; } break; case PIN_MAP_TYPE_CONFIGS_PIN: case PIN_MAP_TYPE_CONFIGS_GROUP: ret = pinconf_map_to_setting(map, setting); { switch (setting->type) { case PIN_MAP_TYPE_CONFIGS_PIN: // step5. name 转换为index pin = pin_get_from_name(pctldev, map->data.configs.group_or_pin); if (pin < 0) { dev_err(pctldev->dev, "could not map pin config for \"%s\"", map->data.configs.group_or_pin); return pin; } setting->data.configs.group_or_pin = pin; break; case PIN_MAP_TYPE_CONFIGS_GROUP: // step6. name 转换为index pin = pinctrl_get_group_selector(pctldev, map->data.configs.group_or_pin); if (pin < 0) { dev_err(pctldev->dev, "could not map group config for \"%s\"", map->data.configs.group_or_pin); return pin; } setting->data.configs.group_or_pin = pin; break; default: return -EINVAL; } // step7. 到这里就完成了map 到 setting的转换 setting->data.configs.num_configs = map->data.configs.num_configs; setting->data.configs.configs = map->data.configs.configs; } break; default: ret = -EINVAL; break; } }
根据上面的代码分析,主要完成的功能如下

总结过程如下:
分析设备树,找到pin controller
对于每个状态,比如default、init,去分析pin controller中的设备树节点
使用pin controller的
pinctrl_ops.dt_node_to_map来处理设备树的pinctrl节点信息,得到一系列的pinctrl_map这些pinctrl_map放在pinctrl.dt_maps链表中
每个pinctrl_map都被转换为pinctrl_setting,放在对应的pinctrl_state.settings链表中
step 4
这个比较简单,当状态发生变化的时候,会根据state下的setting调用ops函数操作gpio。
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)
{
list_for_each_entry(setting, &state->settings, node) {
switch (setting->type) {
case PIN_MAP_TYPE_MUX_GROUP:
ret = pinmux_enable_setting(setting);
{
// 根具setting里面的内容,调用ops函数 操作gpio
ret = ops->set_mux(pctldev, setting->data.mux.func,setting->data.mux.group);
}
break;
case PIN_MAP_TYPE_CONFIGS_PIN:
case PIN_MAP_TYPE_CONFIGS_GROUP:
ret = pinconf_apply_setting(setting);
{
// 根具setting里面的内容,调用ops函数操作gpio
ret = ops->pin_config_set(pctldev,
setting->data.configs.group_or_pin,
setting->data.configs.configs,
setting->data.configs.num_configs);
}
break;
default:
ret = -EINVAL;
break;
}
if (ret < 0) {
goto unapply_new_state;
}
}
}
