imx6ull-驱动开发篇10——pinctrl 子系统
目录
前言
pinctrl 子系统
pinctrl简介
PIN 配置信息详解
引脚标识
寄存器值
电气属性值 0x17059
PIN 驱动程序讲解
PIN 配置
of_device_id
驱动探测函数
注册驱动
获取PIN配置
注册PIN控制器
设备树中添加 pinctrl 节点模板
创建对应的节点
添加“fsl,pins”属性
添加 PIN 配置信息
前言
在上一讲内容里,设备树下的 LED 驱动实验,我们配置 LED 灯所使用的 GPIO 寄存器,驱动开发方式和裸机基本没啥区别。
Linux 内核提供了 pinctrl 和 gpio 子系统用于GPIO 驱动,借助 pinctrl 和 gpio 子系统来简化 GPIO 驱动开发。本讲实验,我们主要学习pinctrl子系统的配置信息、理解驱动代码、学会添加pinctrl节点模板。
以控制一个 LED为例:
pinctrl 子系统
Linux 驱动讲究驱动分离与分层, pinctrl 和 gpio 子系统就是驱动分离与分层思想下的产物。
-
分离:将硬件相关的代码(如寄存器操作)与业务逻辑(如设备功能)解耦。
-
分层:通过子系统抽象硬件共性,提供统一接口,避免重复造轮子。
pinctrl简介
pinctrl 子系统:引脚复用与电气属性管理
功能
-
控制引脚的 复用功能(如 GPIO、UART、I2C 等)
-
配置引脚的 电气属性(上下拉、驱动强度、斜率等)
对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成。
pinctrl 子系统源码目录为 drivers/pinctrl。
接下来我们学习以下I.MX6ULL 的 pinctrl 子系统驱动。
PIN 配置信息详解
要使用 pinctrl 子系统,一般会在设备树里面创建一个节点来描述 PIN 的配置信息。
打开 imx6ull.dtsi 文件,找到一个叫做 iomuxc 的节点,如下所示:
iomuxc: iomuxc@020e0000 {compatible = "fsl,imx6ul-iomuxc"; // 兼容性字符串,匹配i.MX6ULL的IOMUXC驱动reg = <0x020e0000 0x4000>; // 寄存器基地址0x020e0000,地址空间长度16KB
};
iomuxc 节点就是 I.MX6ULL 的 IOMUXC 外设对应的节点。
再打开 imx6ull-alientek-emmc.dts,找到iomuxc 节点,如下所示内容:
&iomuxc {pinctrl-names = "default"; // 默认引脚状态名称pinctrl-0 = <&pinctrl_hog_1>; // 默认使用的引脚配置组/* 开发板特定引脚配置 */imx6ul-evk {/* GPIO默认配置组 */pinctrl_hog_1: hoggrp-1 {fsl,pins = </* GPIO1_IO19配置:复用为GPIO,电气属性0x17059 */MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059/* GPIO1_IO05配置:复用为USDHC1_VSELECT */MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059/* GPIO1_IO09配置:复用为GPIO */MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059/* GPIO1_IO00配置:复用为OTG ID检测,电气属性0x13058 */MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058>;};
....../* FlexCAN1控制器引脚配置 */pinctrl_flexcan1: flexcan1grp {fsl,pins = </* CAN_RX信号:复用为FLEXCAN1_RX,电气属性0x1b020 */MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b020/* CAN_TX信号:复用为FLEXCAN1_TX,电气属性0x1b020 */MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b020>;};
....../* 看门狗引脚配置 */pinctrl_wdog: wdoggrp {fsl,pins = </* 看门狗信号:复用为WDOG1_WDOG_ANY,电气属性0x30b0 */MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY 0x30b0>;};};
};
这段代码向 iomuxc 节点追加数据,不同的外设使用的 PIN 不同、其配置也不同,将某个外设所使用的所有 PIN 都组织在一个子节点里面。
以GPIO1_IO19为例:
/* GPIO1_IO19配置:复用为GPIO,电气属性0x17059 */MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
引脚标识
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19
- 前半部分:物理引脚名称,MX6UL_PAD_UART1_RTS_B物理引脚名称,对应芯片手册中的 UART1_RTS_B 引脚
- 后半部分:配置该引脚,复用功能(GPIO1_IO19)
寄存器值
宏定义后面跟着一串数字:
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
对应参数顺序:
<mux_reg,conf_reg,input_reg,mux_mode,input_val>
参数 | 值 | 说明 |
---|---|---|
|
| 复用功能寄存器偏移地址(相对于IOMUXC基地址 |
|
| 电气属性寄存器偏移地址(相对于IOMUXC基地址) |
|
| 输入寄存器偏移地址(此引脚无输入功能,故为0) |
|
| 复用模式值(ALT5模式对应GPIO1_IO19功能) |
|
| 输入功能选择值(未使用) |
以mux_reg来看寄存器偏移,如图:
其它寄存器的具体细节,可以参考芯片手册来分析。
电气属性值 0x17059
/* GPIO1_IO19配置:复用为GPIO,电气属性0x17059 */MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
i.MX6UL 的引脚控制寄存器(IOMUXC_SW_PAD_CTL_PAD_*)按以下位域定义:
位域 | 名称 | 值 | 功能说明 |
---|---|---|---|
16:13 | HYS | 0x1 | 输入滞后使能(抗抖动) |
12 | PUS | 0x7 | 上拉/下拉选择(0=无, 1=下拉, 2=上拉) |
11:10 | PUE | 0x0 | 上下拉使能(0=关闭, 3=保持器模式) |
9:6 | PKE | 0x1 | 输入保持器使能 |
5:3 | ODE | 0x0 | 开漏输出禁用(推挽模式) |
2:1 | SPEED | 0x2 | 驱动强度(00=低, 11=高) |
0 | SRE | 0x1 | 压摆率(0=慢, 1=快) |
0x17059的二进制展开:0001_0111_0000_0101_1001
对应配置:
- HYS=1:启用输入滞后
- PUS=7 (0111):47KΩ 上拉
- PUE=0:关闭上下拉(PUS 优先)
- PKE=1:启用输入保持器
- ODE=0:推挽输出
- SPEED=2:中速驱动
- SRE=1:快速压摆率
PIN 驱动程序讲解
PIN 配置
在文件drivers/pinctrl/freescale/pinctrl-imx6ul.c中,有如下内容:
/*** imx6ul_pinctrl_of_match - 设备树兼容性匹配表* * 用于匹配设备树中的iomuxc节点与对应的驱动数据*/
static struct of_device_id imx6ul_pinctrl_of_match[] = {{ .compatible = "fsl,imx6ul-iomuxc", // 标准IOMUX控制器.data = &imx6ul_pinctrl_info, // 关联i.MX6UL的引脚控制信息},{ .compatible = "fsl,imx6ull-iomuxc-snvs", // SNVS域IOMUX控制器.data = &imx6ull_snvs_pinctrl_info, // 关联i.MX6ULL SNVS引脚信息},{ /* sentinel */ } // 结束标记
};/*** imx6ul_pinctrl_probe - 驱动探测函数* @pdev: 平台设备结构体指针* * 1. 匹配设备树节点* 2. 获取SoC特定引脚信息* 3. 调用通用pinctrl初始化*/
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{const struct of_device_id *match;struct imx_pinctrl_soc_info *pinctrl_info;// 通过设备树匹配表查找对应设备match = of_match_device(imx6ul_pinctrl_of_match, &pdev->dev);if (!match)return -ENODEV; // 未找到匹配项// 获取匹配项中存储的SoC特定数据pinctrl_info = (struct imx_pinctrl_soc_info *)match->data;// 调用i.MX系列通用pinctrl初始化函数return imx_pinctrl_probe(pdev, pinctrl_info);
}/*** imx6ul_pinctrl_driver - pinctrl平台驱动结构* * 注册驱动到内核平台驱动框架*/
static struct platform_driver imx6ul_pinctrl_driver = {.driver = {.name = "imx6ul-pinctrl", // 驱动名称.owner = THIS_MODULE, // 模块所有者.of_match_table = of_match_ptr(imx6ul_pinctrl_of_match), // 设备树匹配表},.probe = imx6ul_pinctrl_probe, // 设备探测回调.remove = imx_pinctrl_remove, // 设备移除回调
};
让我们分析这段代码:
of_device_id
of_device_id结构体数组,保存着这个驱动文件的兼容性值,设备树中的 compatible 属性值会和 of_device_id 中的所有兼容性字符串比较,查看是否可以使用此驱动。
static struct of_device_id imx6ul_pinctrl_of_match[] = {{ .compatible = "fsl,imx6ul-iomuxc", // 标准IOMUX控制器.data = &imx6ul_pinctrl_info, // 关联i.MX6UL的引脚控制信息},{ .compatible = "fsl,imx6ull-iomuxc-snvs", // SNVS域IOMUX控制器.data = &imx6ull_snvs_pinctrl_info, // 关联i.MX6ULL SNVS引脚信息},{ /* sentinel */ } // 结束标记
};
imx6ul_pinctrl_of_match 结构体数组一共有两个兼容性字符串, 分别为“fsl,imx6ul-iomuxc”和“fsl,imx6ull-iomuxc-snvs”,因此 iomuxc 节点与此驱动匹配,所以 pinctrl-imx6ul.c 会完成 I.MX6ULL 的 PIN 配置工作。
驱动探测函数
imx6ul_pinctrl_probe 函数就是 I.MX6ULL 这个 SOC 的 PIN 配置入口函数:
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{const struct of_device_id *match;struct imx_pinctrl_soc_info *pinctrl_info;// 通过设备树匹配表查找对应设备match = of_match_device(imx6ul_pinctrl_of_match, &pdev->dev);if (!match)return -ENODEV; // 未找到匹配项// 获取匹配项中存储的SoC特定数据pinctrl_info = (struct imx_pinctrl_soc_info *)match->data;// 调用i.MX系列通用pinctrl初始化函数return imx_pinctrl_probe(pdev, pinctrl_info);
}
imx6ul_pinctrl_probe 函数调用路径如下:
注册驱动
platform_driver结构体 是平台设备驱动,有个 probe 成员变量。当设备和驱动匹配成功以后, platform_driver 的 probe 成员变量所代表的函数就会执行。
static struct platform_driver imx6ul_pinctrl_driver = {.driver = {.name = "imx6ul-pinctrl", // 驱动名称.owner = THIS_MODULE, // 模块所有者.of_match_table = of_match_ptr(imx6ul_pinctrl_of_match), // 设备树匹配表},.probe = imx6ul_pinctrl_probe, // 设备探测回调.remove = imx_pinctrl_remove, // 设备移除回调
};
获取PIN配置
函数 imx_pinctrl_parse_groups 负责获取设备树中关于 PIN 的配置信息:
/** fsl,pins 属性中每个引脚由5个u32(PIN_FUNC_ID)和1个u32(CONFIG)组成,* 因此每个引脚总共占用24字节。*/
#define FSL_PIN_SIZE 24 // 标准引脚描述符的字节大小
#define SHARE_FSL_PIN_SIZE 20 // 共享引脚描述符的字节大小(无input_reg时)/*** imx_pinctrl_parse_groups - 解析设备树中的引脚组配置* @np: 设备树节点指针* @grp: 存储解析结果的引脚组结构* @info: SoC特定的引脚控制器信息* @index: 引脚组索引** 从设备树的fsl,pins属性中提取引脚复用和配置信息,* 填充到imx_pin_group结构中。*/
static int imx_pinctrl_parse_groups(struct device_node *np,struct imx_pin_group *grp,struct imx_pinctrl_soc_info *info,u32 index)
{int size, pin_size;const __be32 *list; // 指向设备树属性数据的指针int i;u32 config;
....../* 遍历组内所有引脚 */for (i = 0; i < grp->npins; i++) {u32 mux_reg = be32_to_cpu(*list++); // 复用寄存器地址u32 conf_reg; // 配置寄存器地址unsigned int pin_id; // 计算得到的引脚IDstruct imx_pin_reg *pin_reg; // 引脚寄存器信息指针struct imx_pin *pin = &grp->pins[i]; // 当前引脚结构
....../* 计算引脚ID(根据复用或配置寄存器地址) */pin_id = (mux_reg != -1) ? mux_reg / 4 : conf_reg / 4;/* 存储寄存器映射信息 */pin_reg = &info->pin_regs[pin_id];pin->pin = pin_id; // 记录引脚IDgrp->pin_ids[i] = pin_id; // 存储到组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++); // 复用模式(ALTx)pin->input_val = be32_to_cpu(*list++); // 输入功能值
....../* 处理SION位(特殊功能位,位于复用寄存器中) */config = be32_to_cpu(*list++); // 原始配置值if (config & IMX_PAD_SION)pin->mux_mode |= IOMUXC_CONFIG_SION; // 设置SION标志位pin->config = config & ~IMX_PAD_SION; // 存储清理后的配置值}return 0;
}
设备树中的 mux_reg 和 conf_reg 值会保存在 info 参数中, input_reg、mux_mode、 input_val 和 config 值会保存在 grp 参数中。
static int imx_pinctrl_parse_groups(struct device_node *np,struct imx_pin_group *grp,struct imx_pinctrl_soc_info *info,u32 index)
获取 mux_reg、 conf_reg、 input_reg、 mux_mode 和 input_val 值。
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++); // 复用模式(ALTx)pin->input_val = be32_to_cpu(*list++); // 输入功能值
获取 config 值。
pin->config = config & ~IMX_PAD_SION; // 存储清理后的配置值
注册PIN控制器
函数 pinctrl_register,用于向 Linux 内核注册一个 PIN 控制器,原型如下:
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc, // 描述引脚控制器的结构struct device *dev, // 关联的硬件设备void *driver_data // 驱动私有数据
);
参数 | 类型 | 说明 |
---|---|---|
|
| 描述引脚控制器的结构体,包含操作函数集、引脚范围等关键信息 |
|
| 关联的硬件设备(通常为 |
|
| 驱动私有数据,可通过 |
参数 pctldesc ,就是要注册的 PIN 控制器, PIN 控制器用于配置 SOC的 PIN 复用功能和电气特性。参数 pctldesc 是 pinctrl_desc 结构体类型指针。
pinctrl_desc 结构体如下所示:
/** pinctrl_desc - 引脚控制器描述符* 该结构体描述一个引脚控制器及其功能,用于向pinctrl子系统注册引脚控制器*/
struct pinctrl_desc {const char *name; /* 引脚控制器设备名称 */struct pinctrl_pin_desc const *pins; /* 描述每个引脚的引脚描述符数组 */unsigned int npins; /* pins数组中的引脚数量 */const struct pinctrl_ops *pctlops; /* 引脚控制操作(引脚控制核心操作) */const struct pinmux_ops *pmxops; /* 引脚复用操作(多路复用功能配置) */const struct pinconf_ops *confops; /* 引脚配置操作(电气特性配置) */struct module *owner; /* 该引脚控制器的模块所有者 */#ifdef CONFIG_GENERIC_PINCONFunsigned int num_custom_params; /* 自定义配置参数的数量 */const struct pinconf_generic_params *custom_params; /* 自定义配置参数数组 */const struct pin_config_item *custom_conf_items; /* 自定义参数的配置项 */
#endif
};
其中,有三个重要的结构体指针:
const struct pinctrl_ops *pctlops; /* 引脚控制操作(引脚控制核心操作) */
const struct pinmux_ops *pmxops; /* 引脚复用操作(多路复用功能配置) */
const struct pinconf_ops *confops; /* 引脚配置操作(电气特性配置) */
这三个结构体就是 PIN 控制器的“工具”,这三个结构体里面包含了很多操作函数,通过这些操作函数就可以完成对某一个PIN 的配置。
pinctrl_desc 结构体需要由用户提供,结构体里面的成员变量也是用户提供的。但是这个用户并不是我们这些使用芯片的程序员,而是半导体厂商,半导体厂商发布的 Linux 内核源码中已经把这些工作做完了。
比如在 imx_pinctrl_probe 函数中,有以下代码:
int imx_pinctrl_probe(struct platform_device *pdev,struct imx_pinctrl_soc_info *info)
{struct device_node *dev_np = pdev->dev.of_node; // 获取设备树节点struct device_node *np; // 临时设备树节点指针struct imx_pinctrl *ipctl; // i.MX引脚控制器数据结构struct resource *res; // 资源指针struct pinctrl_desc *imx_pinctrl_desc; // 引脚控制器描述符
......// 为引脚控制器描述符分配内存imx_pinctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*imx_pinctrl_desc),GFP_KERNEL);if (!imx_pinctrl_desc)return -ENOMEM; // 内存分配失败返回错误
......// 初始化引脚控制器描述符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; // 设置所属模块
......// 注册引脚控制器ipctl->pctl = pinctrl_register(imx_pinctrl_desc, &pdev->dev, ipctl);// ...其他代码...
}
- 定义结构体指针变量 imx_pinctrl_desc。
- 向指针变量 imx_pinctrl_desc 分配内存。
- 初始化 imx_pinctrl_desc 结构体指针变量,重点是 pctlops、 pmxops 和 confops这三个成员变量,分别对应 imx_pctrl_ops、 imx_pmx_ops 和 imx_pinconf_ops 这三个结构体。
- 调用函数 pinctrl_register 向 Linux 内核注册 imx_pinctrl_desc,注册以后 Linux 内核就有了对 I.MX6ULL 的 PIN 进行配置的工具。
imx_pctrl_ops、 imx_pmx_ops 和 imx_pinconf_ops 这三个结构体定义如下:
/* i.MX 引脚控制操作集合 */
static const struct pinctrl_ops imx_pctrl_ops = {.get_groups_count = imx_get_groups_count, /* 获取引脚组数量 */.get_group_name = imx_get_group_name, /* 获取引脚组名称 */ .get_group_pins = imx_get_group_pins, /* 获取引脚组中的引脚列表 */.pin_dbg_show = imx_pin_dbg_show, /* 调试接口:显示引脚信息 */.dt_node_to_map = imx_dt_node_to_map, /* 从设备树节点生成引脚映射 */.dt_free_map = imx_dt_free_map, /* 释放引脚映射资源 */
};
....../* i.MX 引脚复用操作集合 */
static const struct pinmux_ops imx_pmx_ops = {.get_functions_count = imx_pmx_get_funcs_count, /* 获取复用功能数量 */.get_function_name = imx_pmx_get_func_name, /* 获取复用功能名称 */.get_function_groups = imx_pmx_get_groups, /* 获取支持某功能的引脚组 */.set_mux = imx_pmx_set, /* 设置引脚复用功能 */.gpio_request_enable = imx_pmx_gpio_request_enable, /* GPIO请求使能 */.gpio_set_direction = imx_pmx_gpio_set_direction, /* 设置GPIO方向 */
};
....../* i.MX 引脚配置操作集合 */
static const struct pinconf_ops imx_pinconf_ops = {.pin_config_get = imx_pinconf_get, /* 获取引脚配置参数 */.pin_config_set = imx_pinconf_set, /* 设置引脚配置参数 */.pin_config_dbg_show = imx_pinconf_dbg_show, /* 调试接口:显示引脚配置 */.pin_config_group_dbg_show = imx_pinconf_group_dbg_show, /* 调试接口:显示引脚组配置 */
};
......
这三个结构体下的所有函数就是 I.MX6ULL 的 PIN 配置函数。
设备树中添加 pinctrl 节点模板
我们学习一下如何在设备树中添加某个外设的PIN信息 。
关 于 I.MX 系 列 SOC 的 pinctrl 设 备 树 绑 定 信 息 可 以 参 考 文 档Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt。
这里我们虚拟一个名为“test”的设备, test 使用了GPIO1_IO00这个 PIN的GPIO 功能,
pinctrl 节点添加过程如下:
创建对应的节点
同一个外设的 PIN 都放到一个节点里面,打开 imx6ull-alientek-emmc.dts,在 iomuxc 节点中的“imx6ul-evk”子节点下添加“pinctrl_test”节点。
添加完成以后如下所示:
pinctrl_test: testgrp {/* 具体的 PIN 信息 */
};
添加“fsl,pins”属性
设备树是通过属性来保存信息的,因此我们需要添加一个属性,属性名字一定要为“fsl,pins”。
因为对于 I.MX 系列 SOC 而言, pinctrl 驱动程序是通过读取“fsl,pins”属性值来获取 PIN 的配置信息。
完成以后如下所示:
pinctrl_test: testgrp {fsl,pins = </* 设备所使用的 PIN 配置信息 */>;
};
添加 PIN 配置信息
最后在“fsl,pins”属性中添加具体的 PIN 配置信息,完成以后如下所示:
pinctrl_test: testgrp {fsl,pins = <MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是具体设置值*/>;
};
至此,我们已经在 imx6ull-alientek-emmc.dts 文件中,添加好了 test 设备所使用的 PIN 配置信息。