本文简要分析Pinctrl设备树的配置是如何通过hal生效,本文只分析设备树配置转换和配置的关键路线,不分析具体的pinctrl和device设备驱动代码。
在Zephyr Pinctrl – 1基础配置使用一文中我们看到通过设备树可以很方便的将引脚和设备进行绑定,并配置引脚的硬件属性。在这背后Zephyr做了如下工作:
- 通过设备树脚本将设备树转化成C宏
- 在设备驱动中将设备树的宏填充到pinctrl配置结构体中
- 在设备驱动将pinctrl配置结构传递给pinctrl驱动
- 在pinctrl驱动中调用soc hal接口对pin mux和pin硬件进行配置
流程分析
1.设备树转换成宏
设备树通过zephyr/scripts/dts/
下的脚本被转换成宏放到build/zephyr/include/generated/devicetree_generated.h
中
mm_feather中drive-strength
为例:
1 | pinmux_lpuart1: pinmux_lpuart1 { |
将被转换为
1 | #define DT_N_S_soc_S_iomuxc_401f8000_S_iomuxc_gpio_ad_b0_13_lpuart1_rx_P_pinmux {1075806448 /* 0x401f80f0 */, 2 /* 0x2 */, 0 /* 0x0 */, 0 /* 0x0 */, 1075806944 /* 0x401f82e0 */} |
转换的原理不是本文分析的目标,可以参考
Zephyr设备树生成流程
Zephyr设备树生成C宏规则
最新的细节可以参考
https://docs.zephyrproject.org/latest/build/dts/intro-input-output.html
2.pinctrl配置结构体填充
在设备驱动中对struct pinctrl_dev_config
结构体进行填充,以rt1062的串口驱动zephyr/drivers/serial/uart_mcux_lpuart.c
为例
1 | #define LPUART_MCUX_DECLARE_CFG(n) \ |
PINCTRL_DEFINE
和PINCTRL_INIT
的定义如下,直接引用的是pinctrl.h
中的宏
1 | #define PINCTRL_DEFINE(n) PINCTRL_DT_INST_DEFINE(n); |
PINCTRL_DT_INST_DEFINE
就是定义一个struct pinctrl_dev_config
结构体和一组struct pinctrl_state
结构体变量,并使用设备树中的值初始化它
1 | #define PINCTRL_DT_DEFINE(node_id) \ |
LISTIFY
将宏扩展为多个相当于是Z_PINCTRL_STATE_PINS_DEFINE
执行了多次,一个pinctrl
的属性node就会执行一次,也就是会生成多个pinctrl_soc_pin_t
1 | #define Z_PINCTRL_STATE_PINS_DEFINE(state_idx, node_id) \ |
pinctrl_soc_pin_t
来源于zephyr/soc/arm/nxp_imx/rt/pinctrl_rt10xx.h
不同的soc会引用对应soc目录下的头文件,因此pinctrl_soc_pin_t
也不一样, rt1062的如下
1 | struct pinctrl_soc_pin { |
在这里pinmux
用于控制pin复用,pin_ctrl_flags
用于pin硬件特性配置, 初始化方式为
1 | #define Z_PINCTRL_STATE_PIN_INIT(group_id, pin_prop, idx) \ |
pin复用的结构体初始化
pin复用结构如下,也定义在zephyr/soc/arm/nxp_imx/rt/pinctrl_rt10xx.h
下
1 | struct pinctrl_soc_pinmux { |
对上述结构体struct pinctrl_soc_pinmux
变量的初始化,是由对应soc提供的宏Z_PINCTRL_PINMUX
来完成,zephyr/soc/arm/nxp_imx/rt/pinctrl_rt10xx.h
中
1 | #define Z_PINCTRL_PINMUX(group_id, pin_prop, idx) \ |
上面的pinmux对应的就是设备树中的pinmux,如前面示例使用iomuxc_gpio_ad_b0_13_lpuart1_rx
,时也就是引用了modules/hal/nxp/nxp_imx/rt/mimxrt1062dvl6a-pinctrl.dtsi
, 如下
/omit-if-no-ref/ iomuxc_gpio_ad_b0_13_lpuart1_rx: IOMUXC_GPIO_AD_B0_13_LPUART1_RX {
pinmux = <0x401f80f0 0 2 0x0 0x401f82e0>;
};
被转换成宏DT_N_S_soc_S_iomuxc_401f8000_S_iomuxc_gpio_ad_b0_13_lpuart1_rx_P_pinmux
(参见前文)DT_PROP_BY_IDX(node_id, pinmux, 0)
就是取第0个元素,
也就是
1 | .mux_register = 0x401f80f0, |
由于设备树中没有使用gpr
,这里就没有gpr的初始化设置。
pin硬件配置
pin硬件配置的信息放在pin_ctrl_flags
内,其初始化使用zephyr/soc/arm/nxp_imx/rt/pinctrl_rt10xx.h
提供的宏Z_PINCTRL_MCUX_IMX_PINCFG_INIT
1 | #define Z_PINCTRL_MCUX_IMX_PINCFG_INIT(node_id) \ |
DT_ENUM_IDX(node_id, drive_strength)
就是引用的宏DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_drive_strength_ENUM_IDX
其它的都在devicetree_generated.h
有对应的宏,将这些宏值按其在寄存器中的位置移位后放置在pin_ctrl_flags
内。
3.设备驱动运行时配置Pinctrl驱动
zephyr/drivers/serial/uart_mcux_lpuart.c
中设备驱动初始化时调用pinctrl_apply_state
进行pinctrl配置, 摘要如下
1 | static int mcux_lpuart_init(const struct device *dev) |
就是将前面PINCTRL_INIT
初始化好的结构体变量struct pinctrl_dev_config
传递给pinctrl_apply_state
4. pinctrl驱动调用hal
在zephyr/include/zephyr/drivers/pinctrl.h
中实现pinctrl_apply_state
调用关系pinctrl_apply_state->pinctrl_apply_state_direct->pinctrl_configure_pins
,不同的soc有不同的pinctrl驱动,rt1062对应到zephyr/drivers/pinctrl/pinctrl_imx.c
,对于rt的pin复用并没有直接调用hal,而是直接对寄存器进行操作
1 | int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt, |
结构体包含关系总结
对于rt1062 uart设备驱动的结构体从上到下的包含关系:
zephyr/drivers/serial/uart_mcux_lpuart.c struct mcux_lpuart_config
zephyr/include/zephyr/drivers/pinctrl.h struct pinctrl_dev_config
zephyr/include/zephyr/drivers/pinctrl.h struct pinctrl_state
zephyr/soc/arm/nxp_imx/rt/pinctrl_rt10xx.h pinctrl_soc_pin_t
zephyr/soc/arm/nxp_imx/rt/pinctrl_rt10xx.h struct pinctrl_soc_pinmux
另外可以通过esp32c3 uart来观察结构体的包含关系
zephyr/drivers/serial/uart_esp32.c struct uart_esp32_config
zephyr/include/zephyr/drivers/pinctrl.h struct pinctrl_dev_config
zephyr/include/zephyr/drivers/pinctrl.h struct pinctrl_state
zephyr/soc/riscv/esp32c3/pinctrl_soc.h pinctrl_soc_pin_t
从结构包含关系可以看到Pinctrl时通过不同soc的pinctrl数据结构来屏蔽pin硬件上的差异
GPIO和Pinctrl的差异
Pinctrl涵盖的某些功能与 GPIO 重叠。例如,Pinctrl和GPIO都可以配置上拉/下拉电阻。在Zephyr中,Pinctrl驱动的目的是执行外设信号复用和配置该外设正确操作所需的引脚硬件参数。因此引脚控制驱动器的主要用于SoC外设。相比之下,GPIO 驱动用于引脚的常规用途控制,即读取或控制其逻辑电平。
在GPIO驱动的实现上不同的soc实现不一样,但基本都参考pinctrl使用相同的设备树配置,只是在GPIO中直接根据这些配置对硬件进行配置(写寄存器或者用hal接口)zephyr/dts/arm/nxp/nxp_rt1060.dtsi
中
1 | &gpio5{ |
modules/hal/nxp/dts/nxp/nxp_imx/rt/mimxrt1062cvj5a-pinctrl.dtsi
中
1 | /omit-if-no-ref/ iomuxc_snvs_pmic_on_req_gpio5_io01: IOMUXC_SNVS_PMIC_ON_REQ_GPIO5_IO01 { |
board内的设备树配置GPIO
1 | gpio_keys { |
对于rt1062来说,GPIO内的Pinmux使用了Pinctrl的设备树机制并用pinctrl_configure_pins
进行设置, 硬件配置为直接写寄存器,GPIO的实现细节不在本文范围内,有兴趣的同学可以查看zephyr/drivers/gpio/gpio_imx.c
相关代码。
参考
https://docs.zephyrproject.org/latest/hardware/pinctrl/index.html