ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Linux设备驱动层之Pinctrl 子系统

Linux设备驱动层之Pinctrl 子系统 一、前言在嵌入式 Linux 开发中我们经常需要进行各种开发比如SPI从设备、IIC从设备、UART通信等等。但是学过裸机编程的都知道我们需要配置不同的引脚复用功能以及电气特性。Linux之中也是如此只不过使用了Pinctrl子系统。不得不感叹Linux系统中的分层、解耦、抽象的思想以掰玉米为例原本的话是n个人从掰下一颗玉米然后把玉米秆砍倒之后把玉米秆整理收集起来之后拉玉米回家用机器将玉米粒与玉米棒子分开·····但是现在的话就属于一个人专门掰玉米一个人专门砍玉米秆一个人专门负责整理更加系统和完善了。这次pinctrl子系统没有在复习的范围内本想复习 SPI 驱动框架从设备树编写到 probe 进入再到基础传输函数的实现。但在编写 SPI 设备树的过程中遇到了一个绕不开的节点ecspi3 { fsl,spi-num-chipselects 1; cs-gpios gpio1 20 GPIO_ACTIVE_LOW; pinctrl-0 pinctrl_ecspi3; status okay; spidev: icm206080 { compatible mjk,icm20608; spi-max-frequency 8000000; reg 0; }; };以及它所引用的 pinctrl 节点pinctrl_ecspi3: icm20608 { fsl,pins MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0 /* CS */ MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1 /* SCLK */ MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1 /* MISO */ MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1 /* MOSI */ ; };问题随之而来1pinctrl-0 是什么它在整个设备树体系中处于什么位置2fsl,pins 的写法从何而来为什么和通用 pinctrl 绑定不一样3电气配置值 0x10b0、0x10b1 表示什么由谁解析4pinctrl 与 SPI 控制器、SPI 驱动之间是什么关系5它是在哪个阶段生效的为什么 SPI 驱动代码里看不到任何 pinctrl 调用本来是想学 SPI 子系统结果被 pinctrl 子系统拦住了去路。与其带着疑问继续往下写不如先把这块补上。于是有了这篇笔记。本文以 i.MX6UL 的 ecspi3 为例从“一个设备树属性引发的疑问”出发梳理 pinctrl 子系统的完整面貌。文章按以下框架展开1Pinctrl 是什么引脚复用 电气属性2为什么需要 PinctrlSoC 引脚多功能需要统一管理3三层角色pin controller 驱动、pinctrl 框架、消费者设备4与其他子系统的区别不是总线是框架5设备树写法不同 SoC 不同但都是 pin group 引用6生效流程从设备树解析到 probe 前应用7常见误区不是 SPI 驱动触发、不是总线匹配、不是所有节点都变 platform_device8调试方法/sys/kernel/debug/pinctrl/二、Pinctrl 是什么引脚复用 电气属性2.1 定义Pinctrl 子系统负责两件事1、引脚复用pin muxing1一个物理引脚可以承担多种功能比如 GPIO、UART、I2C、SPI、PWM 等。2Pinctrl 负责把它切换到指定功能。2、引脚电气配置pin configuration1上下拉、驱动能力、开漏、转换速率、迟滞等。2Pinctrl 负责设置这些电气属性。2.2 引脚复用SoC 的引脚通常是复用的。以本文使用的 i.MX6UL 为例一个物理 pad 叫 MX6UL_PAD_UART2_TX_DATA它并不是只能做 UART2_TX而是可以被复用成多种功能1GPIO1_IO202ECSPI3_SCLK3UART2_TX4其他 SoC 支持的功能具体复用成哪一个由 IOMUXCI/O Multiplexing Controller寄存器决定。在裸机时代配置一个引脚通常要经历以下步骤1查 SoC 参考手册找到 IOMUXC 章节。2确定该 pad 对应的复用寄存器地址与偏移。3计算功能选择位的值。4写入寄存器。如果还要配置电气属性则要再找到对应的 pad 配置寄存器再写一次。在 Linux 下这些工作交给 pinctrl 完成1设备树描述“哪个 pad 复用成什么功能”。2内核在合适的时机自动写寄存器。2.3 电气属性复用只是第一步。同一个引脚在不同用途下对电气特性的要求并不相同。常见的电气属性包括1驱动能力2mA / 4mA / 8mA / 16mA 等。2上下拉内部上拉、下拉、悬空。3输出方式开漏或推挽。4转换速率信号边沿快慢影响 EMI。5迟滞输入是否启用施密特触发。6电压模式部分 SoC 支持 1.8V / 3.3V 切换。这些属性统称为“电气配置”。在 i.MX6UL 中电气配置被编码成一个 32 位数值直接写在复用宏后面。例如MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0 MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1这里的 0x10b0、0x10b1 就是电气配置值。它并不是随意指定的数而是把驱动能力、上下拉、转换速率、开漏、迟滞等属性按位打包后的结果。具体每一位的含义需要查阅 i.MX6UL 参考手册的 IOMUXC 章节或者参考 pinctrl-imx6ul.c 中的定义。实际开发中通常不需要从零手算这些值常见做法是1参考同 SoC 其他板级的设备树。2使用 NXP 提供的 Pin Tool 生成。3采用 SDK 中的默认值。理解它的含义即可不必死记具体数值。2.4 一个具体的例子回到本文开头遇到的问题。ECSPI3 的 pinctrl 节点定义如下pinctrl_ecspi3: icm20608 { fsl,pins MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0 /* CS */ MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1 /* SCLK */ MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1 /* MISO */ MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1 /* MOSI */ ; };这四行分别完成了以下配置物理 pad复用功能电气配置值UART2_TX_DATAGPIO1_IO200x10b0UART2_RX_DATAECSPI3_SCLK0x10b1UART2_RTS_BECSPI3_MISO0x10b1UART2_CTS_BECSPI3_MOSI0x10b1可以看出1CS 使用的是 GPIO 功能而不是 ECSPI3 的原生片选。这一点与后面 cs-gpios gpio1 20 GPIO_ACTIVE_LOW; 的引用相互对应。2SCLK、MISO、MOSI 三根信号线直接复用为 ECSPI3 的对应功能。3四行配置的电气值略有差异CS 为 0x10b0其余三根为 0x10b1说明它们在驱动能力、开漏等属性上存在细微差别。这段 pinctrl 节点定义完成后并不会立即生效。它需要在消费者节点这里是 ecspi3中通过 pinctrl-0 引用ecspi3 { pinctrl-0 pinctrl_ecspi3; };只有被引用之后内核才会在 ECSPI3 控制器 probe 之前把这组配置写入 IOMUXC 寄存器。2.5 与裸机配置的对应关系如果把这一节的内容与裸机配置做一次对照可以得到如下对应关系裸机中要做的事Linux 中由谁负责使能模块时钟clock 子系统 驱动配置引脚复用pinctrl 子系统配置引脚电气属性pinctrl 子系统配置控制器寄存器控制器驱动注册设备设备树 总线从这个对应关系可以看到pinctrl 只承担了引脚层面的工作。它不负责时钟不负责控制器本身也不负责任何协议逻辑。它只解决一件事让物理引脚正确地连到对应的功能模块上并具备合适的电气特性。2.6 小结本章从定义出发说明了 pinctrl 子系统的两项核心职责1引脚复用把物理 pad 切换到指定功能。2电气配置设置驱动能力、上下拉、转换速率等电气属性。并通过 ECSPI3 的实例展示了这两项职责在设备树中的具体写法。同时与裸机配置做了对照明确了 pinctrl 在整个系统中的定位——它只负责引脚层面不涉及控制器和协议。三、为什么需要 PinctrlSoC 引脚多功能统一管理3.1 从一个问题说起在上一章中我们明确了 pinctrl 的两项职责引脚复用与电气配置。但紧接着的问题是为什么需要单独一个子系统来管这两件事为什么不让每个驱动自己去配置引脚这个问题的答案要从 SoC 引脚本身的特性说起。3.2 裸机时代的做法在裸机或早期 BSP 中配置一个外设通常要经历以下步骤以 UART 为例使能 UART 时钟。使能 GPIO/IOMUX 时钟。找到 UART TX、RX 对应的物理 pad。写 IOMUXC 复用寄存器把 pad 切到 UART 功能。写 IOMUXC 配置寄存器设置电气属性。配置 UART 控制器波特率、数据位、停止位、校验位。使能 UART 收发。在这七步中第 3、4、5 步属于引脚配置第 1、6、7 步属于控制器配置。裸机时代这些步骤全部由开发者写在同一个文件里甚至同一个函数里。这套做法在小规模、单一板级下可以工作但一旦放到 Linux 这种支持大量 SoC、大量板级、大量驱动的环境里问题就暴露出来了。3.3 裸机做法的问题把引脚配置和驱动代码混在一起会带来以下几个问题第一代码重复。UART、SPI、I2C、PWM 等每一个外设驱动都要自己写一遍引脚配置代码。同一颗 SoC 上同一个 pad 可能被多个驱动反复配置写法还不一定一致。第二与硬件强绑定。引脚配置代码里直接出现 IOMUXC 寄存器地址、位域偏移、电气配置值。这些内容与 SoC 型号强相关。换一颗 SoC驱动代码几乎要重写。第三难以复用。同一个外设驱动在不同板子上可能使用不同的引脚。如果引脚配置写死在驱动里那么驱动就无法跨板子复用只能为每块板子改一遍驱动。第四引脚冲突难以发现。两个驱动如果同时配置同一个 pad裸机下往往没有机制检测。结果可能是后配置的覆盖先配置的或者两个设备都工作不正常排查困难。第五与设备树模型不匹配。Linux 的设备模型是“设备树描述硬件驱动描述行为”。引脚配置属于硬件描述理应放在设备树里而不是驱动代码里。这些问题归结起来就是一句话引脚配置是硬件描述不应该散落在各个驱动里而应该被统一管理。3.4 Linux 的分层解法Linux 的解法是分层把原本混在一起的工作按职责拆给不同的子系统。事项负责方引脚复用pinctrl 子系统引脚电气属性pinctrl 子系统控制器工作方式控制器驱动模块时钟clock 子系统引脚作为 GPIO 使用gpio 子系统中断irqchip / 中断子系统电源域regulator / power domain拆完之后每个子系统只关心自己那一层驱动只关心自己的控制器设备树负责描述硬件连接关系。以 SPI 为例pinctrl 负责把 SCLK、MISO、MOSI、CS 对应的 pad 复用成正确功能并设置电气属性。SPI 控制器驱动负责配置时钟、DMA、中断、传输模式。SPI 从设备驱动负责通过 SPI 核心 API 收发数据。三者互不干扰各司其职。四、 三层角色pin controller 驱动、pinctrl 框架、消费者设备4.1 模型Pinctrl 不是总线它采用框架 提供者 消费者的模型提供者pin controller 驱动框架pinctrl 框架消费者使用引脚的设备4.2 pin controller 驱动提供者SoC 厂商为自家引脚控制器编写的驱动例如drivers/pinctrl/freescale/pinctrl-imx6ul.cdrivers/pinctrl/freescale/pinctrl-imx.c它本身是一个platform 驱动匹配设备树中的 pinctrl 控制器节点iomuxc { pinctrl-names default; pinctrl-0 pinctrl_hog_1; imx6ul-evk { pinctrl_hog_1: hoggrp-1 { fsl,pins MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */ MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 /* SD1 VSELECT */ MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059 /* SD1 RESET */ ; }; pinctrl_csi1: csi1grp { fsl,pins MX6UL_PAD_CSI_MCLK__CSI_MCLK 0x1b088 MX6UL_PAD_CSI_PIXCLK__CSI_PIXCLK 0x1b088 MX6UL_PAD_CSI_VSYNC__CSI_VSYNC 0x1b088 MX6UL_PAD_CSI_HSYNC__CSI_HSYNC 0x1b088 MX6UL_PAD_CSI_DATA00__CSI_DATA02 0x1b088 MX6UL_PAD_CSI_DATA01__CSI_DATA03 0x1b088 MX6UL_PAD_CSI_DATA02__CSI_DATA04 0x1b088 MX6UL_PAD_CSI_DATA03__CSI_DATA05 0x1b088 MX6UL_PAD_CSI_DATA04__CSI_DATA06 0x1b088 MX6UL_PAD_CSI_DATA05__CSI_DATA07 0x1b088 MX6UL_PAD_CSI_DATA06__CSI_DATA08 0x1b088 MX6UL_PAD_CSI_DATA07__CSI_DATA09 0x1b088 ; }; .... };probe 里完成映射 IOMUXC 寄存器。解析自己节点下的 pin group。调用pinctrl_register()注册pinctrl_dev。提供pinctrl_ops/pinmux_ops/pinconf_ops真正写寄存器的是它。4.3 pinctrl 框架内核中 pinctrl 的核心代码位于drivers/pinctrl/core.cdrivers/pinctrl/pinmux.cdrivers/pinctrl/pinconf.c职责维护所有注册进来的pinctrl_dev。解析消费者节点的pinctrl-0、pinctrl-names。在合适时机调用 pin controller 的 ops。对外提供 APIpinctrl_get()、pinctrl_lookup_state()、pinctrl_select_state()。关键调用点是pinctrl_bind_pins()它在really_probe()中被调用也就是任何驱动 probe 之前。这是 pinctrl 对驱动透明的根本原因。注意这里有一个小插曲。我原本以为really_probe函数仅仅是在probe函数中被调用但是之前遇到过一个神奇现象。之前有一次配置GPIO引脚的时候出现了报错下面位置的定位/* driver matched but the probe failed */ printk(KERN_WARNING %s: probe of %s failed with error %d\n, drv-name, dev_name(dev), ret); }它的位置在really_probe函数中但是在这个报错语句之前打印了我probe函数中的打印信息这让我很困惑明明是没有进入probe函数但是却打印出probe函数中的日志。通过查找资料发现really_probe函数是调用probe函数而不是在probe函数之前来执行它。dev-pm_domain-activate(dev);它不是总线没有设备-驱动匹配没有 probe。4.4 消费者设备使用引脚的设备比如ecspi3、uart2、i2c1。设备树里通过pinctrl-0引用 pin groupecspi3 { pinctrl-names default; pinctrl-0 pinctrl_ecspi3; };消费者本身不需要做任何额外的事驱动通常不感知 pinctrl。少数场景如运行时切换 default / sleep 状态才会主动调用 pinctrl API。4.5 协作关系pin controller 驱动 ──注册 pinctrl_dev──▶ pinctrl 框架 ▲ 消费者设备ecspi3 ──pinctrl-0 引用──▶ 内核 probe 前统一应用流程pin controller 驱动 probe注册pinctrl_dev。pinctrl 框架记录信息。消费者设备创建设备树里有pinctrl-0。内核在消费者驱动 probe 前调用pinctrl_bind_pins()。pinctrl 框架解析引用调用 pin controller 的 ops写寄存器。消费者驱动 probe引脚已经配好。4.6 与 platform / SPI 对比项目platformSPIpinctrl是否总线是是否设备-驱动匹配有有无probe有有无注册对象platform_device / platform_driverspi_device / spi_driverpinctrl_dev匹配方式compatiblecompatible设备树引用提供者本身——platform 驱动消费者关联总线匹配总线匹配pinctrl-0引用关键点pin controller 驱动本身是 platform 驱动。但它注册的是pinctrl_dev不是 pinctrl 设备。pinctrl 框架不是总线。消费者通过设备树引用关联。五、 设备树写法i.MX6ULL 的 pinctrl 写法6.1 结构i.MX6ULL 的 pinctrl 设备树分两部分pin group 定义写在iomuxc下描述引脚复用和电气属性。消费者引用设备节点用pinctrl-0引用 pin group。iomuxc { pinctrl_ecspi3: ecspi3grp { fsl,pins ... ; }; }; ecspi3 { pinctrl-names default; pinctrl-0 pinctrl_ecspi3; };6.2 pin group 定义以 ECSPI3 为例iomuxc { pinctrl_ecspi3: ecspi3grp { fsl,pins MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0 /* CS */ MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1 /* SCLK */ MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1 /* MISO */ MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1 /* MOSI */ ; }; };每一行格式PAD复用宏 电气配置值1.PAD复用宏来自 imx6ull-pinfunc.h由 NXP 自动生成。2.电气配置值一个 32 位数描述驱动能力、上下拉、转换速率等。6.3 引脚复用宏来自arch/arm/boot/dts/imx6ull.hNXP 按参考手册自动生成列出了 i.MX6ULL 所有 pad 的所有合法复用功能。格式#define MX6UL_PAD_PAD名__功能名 mux_reg conf_reg input_reg mux_mode input_val举例#define MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x0084 0x0310 0x0000 0x5 0x0含义字段含义mux_regMUX 寄存器偏移conf_regPAD 配置寄存器偏移input_reg输入选择寄存器偏移mux_mode写进 MUX 寄存器的值选 ALT 功能input_val写进 input_reg 的值不需要算这五个值直接选宏。宏名读法MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 └──┬──┘ └──────┬──────┘ └───┬───┘ SoC pad 名 目标功能意思把UART2_TX_DATA这个 pad 复用成GPIO1_IO20功能。6.4 电气配置值跟在宏后面的数比如0x10b0、0x10b1。i.MX6ULL 位域定义位域含义取值bit 16 (HYS)施密特触发0禁用1使能bit 15-14 (PUS)上下拉选择00100K下拉0147K上拉10100K上拉1122K上拉bit 13 (PUE)上下拉/保持器选择0Keeper1上下拉bit 12 (PKE)上下拉/保持器使能0禁用1使能bit 11 (ODE)开漏输出0推挽1开漏bit 7-6 (SPEED)信号带宽0050MHz01/10100MHz11200MHzbit 5-3 (DSE)驱动强度000禁用001R0...111R0/7bit 0 (SRE)压摆率0慢1快以 0x10b0 为例0x10b0 0001 0000 1011 0000实际开发中不需要手算通常抄同 SoC 同外设的例子用 NXP Pin Tool 生成用 SDK 默认值6.5 消费者引用ecspi3 { fsl,spi-num-chipselects 1; cs-gpios gpio1 20 GPIO_ACTIVE_LOW; pinctrl-names default; pinctrl-0 pinctrl_ecspi3; status okay; spidev: icm206080 { compatible mjk,icm20608; spi-max-frequency 8000000; reg 0; }; };pinctrl-names default;状态名。pinctrl-0 pinctrl_ecspi3;引用 pin group。内核在ecspi3probe 前自动应用这些引脚配置。不写pinctrl-names时pinctrl-0默认就是 default 状态。6.7 实际开发建议抄评估板评估板代码经过验证直接沿用最省事。但要带着判断抄功能一样的直接抄功能不一样的改对应的地方拿不准的查原理图确认抄的时候看什么fsl,pins里的 pad 和功能对不对电气配置值适不适合你的硬件cs-gpios的 GPIO 号对不对reg的 CS 编号对不对compatible和你的驱动匹配不匹配必须自己改的情况引脚接法不同外设不同电气需求不同6.8 小结i.MX6ULL 的 pinctrl 写法1在 iomuxc 下定义 pin group用 fsl,pins。2每行是 PAD复用宏 电气配置值。3宏来自 imx6ull-pinfunc.hNXP 生成不需要自己算。4电气配置值是一个打包的 32 位数包含驱动能力、上下拉、速率等。5消费者用 pinctrl-0 引用 pin group。6GPIO 也需要 pinctrl 复用成 GPIO 功能。
返回列表