# Pinctrl子系统 ## Pinctrl作用 ![](media/image-20221124150346431.png) 如图所示,Pinctrl主要完成以下工作 - 引脚枚举与命名(Enumerating and naming) 芯片原厂提供,定义了芯片引脚定义和复用功能等 - 引脚复用(Multiplexing):比如用作GPIO、I2C或其他功能 - 引脚配置(Configuration):比如上拉、下来、open drain、驱动强度等 ## 基本概念 ![](media/image-20221124155702607.png) 如上图所示: - `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`状态,状态是自定义的 只要驱动能识别就行。 ![](media/image-20221124160715890.png) 其中 - `default` 状态使用`pinctrl-0` - `sleep`状态使用`pinctrl-1` 下面是实际的例子,可能和上面的模型不一样,具体如下所示: ![](media/image-20221124162817695.png) ### 代码中何时调用pinctrl 当设备切换状态,比如设备在probe的时候,驱动会调用状态切换如下: ![](media/image-20221124163215455.png) 或者设备从default切换为sleep状态的时候,也会进行调用。这些都是自动的过程,无需手动调用,我们也可以自己调用, ```c 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](media/image-20221129141155509.png) 在`imx6ull-14x14-evk.dts`里面新增了imx6ul-evk这一个节点,即类似于我们上面模型中的`function`功能, 在`imx6ul-evk`下又有很多子节点,用来描述外设组的引脚配置。 ![imx6ull-14x14-evk.dts](media/image-20221129141232334.png) 从上面可以得知当 `compatible = "fsl,imx6ul-iomuxc"`属性在平台设备链表中,被查到时候就会调用对应的`probe`函数,实际就是`imx_6ul_pinctrl_probe`函数,如下图所示: ![](media/image-20221129141317173.png) 那么在`imx_6ul_pinctrl_probe`函数中,设备树中的`iomuxc`节点是如何转换为c语言结构体的呢?继续分析如下。 ### 如何构建pinctrl_dev pincontroller虽然是一个软件的概念,抽象到Pinctrl子系统后会用`pinctrl_dev`来描述它在软件层面的定义。 构造出`pinctrl_dev`直接调用:`pinctrl_register`函数即可,正如在`pinctrl-imx6ul.c`文件中,如下代码所示: ```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,如下图 ```c 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` ![](media/image-20221129154017328.png) 按照上面的代码分析,看下`imx_pinctrl_desc`的来源,如下代码所示: ![](media/image-20221129153000149.png) 总结上面的代码分析: 1. 构建`struct pinctrl_pin_desc imx6ul_pinctrl_pads[]`结构体,描述板载资源的所有gpio资源, 构建`struct imx_pinctrl_soc_info imx6ul_pinctrl_info`结构体里面包含`imx6ul_pinctrl_pads`,供下面使用 2. 构建`struct of_device_id imx6ul_pinctrl_of_match[]`结构体,用来和设备树做`.compatible = "fsl,imx6ul-iomuxc"`属性的匹配,并且设置私有数据为`imx6ul_pinctrl_info` 3. 当设备树`iomuxc`节点和`imx6ul_pinctrl_of_match`匹配时候,会调用`imx6ul_pinctrl_probe`函数 4. 在`imx6ul_pinctrl_probe`函数中为imx_pinctrl_desc赋值,这样imx_pinctrl_desc中携带所有的io资源了 ![](media/image-20221129153637055.png) 这里面还有三个重要的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 ![](media/image-20221129154729926.png) - `imx_pinctrl_desc->pmxops = &imx_pmx_ops` - 引脚复用相关 ![image-20221129155111830](media/image-20221129155111830.png) - `imx_pinctrl_desc->confops = &imx_pinconf_ops` - 引脚配置 ![](media/image-20221129155148857.png) ### 如何解析设备树pincontroller中组信息 前面介绍了`pinctrl_dev`里面`pinctrl_desc`中关于设备所有gpio资源的获取和一些ops的操作函数,现在介绍`pinctrl_dev`中关于info变量的赋值,在代码中体现为下面的位置, ```c 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 就可以访问这些数据了 ``` 具体的解析过程如下: ```c 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如下: ```bash /* 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的方式如下: ![](media/image-20221129194521757.png) ![](media/image-20221129194649801.png) 这些设备节点要么被转换为`platform_device`,或者其他结构体(比如i2c_client),但是里面都会有一个`device`结构体,比如: ![](media/image-20221129162034312.png) 在device中有个很重要的结构体`struct dev_pin_info *pins`,下面重点分析这个结构体。 ### dev_pin_info 分析 #### 重要结构体 每个device结构体里都有一个`dev_pin_info`结构体,用来保存设备的pinctrl信息,下面是一些重要的结构体,先混个脸熟: ![](media/image-20221129162458223.png) ### 如何存储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语言的结构体过程。 ```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 ```c //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 ```c 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串了起来。 ![](media/image-20221130141212632.png) - step 3. 遍历每个map,然后转换为setting ```c 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; } } ``` 根据上面的代码分析,主要完成的功能如下 ![](media/image-20221130145757830.png) **总结过程如下**: * 分析设备树,找到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。 ```c 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; } } } ``` ![](media/image-20221130150218685.png)