Zephyr Pinctrl -- 3配置原理

本文简要分析Pinctrl设备树的配置是如何通过hal生效,本文只分析设备树配置转换和配置的关键路线,不分析具体的pinctrl和device设备驱动代码。

Zephyr Pinctrl – 1基础配置使用一文中我们看到通过设备树可以很方便的将引脚和设备进行绑定,并配置引脚的硬件属性。在这背后Zephyr做了如下工作:

  1. 通过设备树脚本将设备树转化成C宏
  2. 在设备驱动中将设备树的宏填充到pinctrl配置结构体中
  3. 在设备驱动将pinctrl配置结构传递给pinctrl驱动
  4. 在pinctrl驱动中调用soc hal接口对pin mux和pin硬件进行配置

流程分析 链接到标题

1.设备树转换成宏 链接到标题

设备树通过zephyr/scripts/dts/下的脚本被转换成宏放到build/zephyr/include/generated/devicetree_generated.h中 mm_feather中drive-strength为例:

pinmux_lpuart1: pinmux_lpuart1 {
	group0 {
		pinmux = <&iomuxc_gpio_ad_b0_13_lpuart1_rx>,
			<&iomuxc_gpio_ad_b0_12_lpuart1_tx>;
		drive-strength = "r0-6";
		slew-rate = "slow";
		nxp,speed = "100-mhz";
	};
};

将被转换为

#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 */}

#define DT_N_S_soc_S_iomuxc_401f8000_S_iomuxc_gpio_ad_b0_12_lpuart1_tx_P_pinmux {1075806444 /* 0x401f80ec */, 2 /* 0x2 */, 0 /* 0x0 */, 0 /* 0x0 */, 1075806940 /* 0x401f82dc */}

#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_drive_strength "r0-6"
#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_drive_strength_STRING_TOKEN r0_6
#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_drive_strength_STRING_UPPER_TOKEN R0_6
#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_drive_strength_ENUM_IDX 6
#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_drive_strength_ENUM_TOKEN r0_6
#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_drive_strength_ENUM_UPPER_TOKEN R0_6

#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_slew_rate "slow"
#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_slew_rate_STRING_TOKEN slow
#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_slew_rate_STRING_UPPER_TOKEN SLOW
#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_slew_rate_ENUM_IDX 0
#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_slew_rate_ENUM_TOKEN slow
#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_slew_rate_ENUM_UPPER_TOKEN SLOW

#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_nxp_speed "100-mhz"
#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_nxp_speed_STRING_TOKEN 100_mhz
#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_nxp_speed_STRING_UPPER_TOKEN 100_MHZ
#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_nxp_speed_ENUM_IDX 1
#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_nxp_speed_ENUM_TOKEN 100_mhz
#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_nxp_speed_ENUM_UPPER_TOKEN 100_MHZ
#define DT_N_S_soc_S_iomuxc_401f8000_S_pinctrl_S_pinmux_lpuart1_S_group0_P_nxp_speed_FOREACH_PROP_ELEM(fn) fn

转换的原理不是本文分析的目标,可以参考 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为例

#define LPUART_MCUX_DECLARE_CFG(n)                                      \
static const struct mcux_lpuart_config mcux_lpuart_##n##_config = {     \
	.base = (LPUART_Type *) DT_INST_REG_ADDR(n),                          \
	.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)),                   \
	.clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name),	\
	.baud_rate = DT_INST_PROP(n, current_speed),                          \
	.flow_ctrl = FLOW_CONTROL(n),                                         \
	.rs485_de_active_low = DT_INST_PROP(n, nxp_rs485_de_active_low),      \
	.loopback_en = DT_INST_PROP(n, nxp_loopback),                         \
	PINCTRL_INIT(n)         \	//将PINCTRL_DEFINE定义的`struct pinctrl_dev_config`全局变量和uart的配置结构体关联
	MCUX_LPUART_IRQ_INIT(n) \
	RX_DMA_CONFIG(n)        \
	TX_DMA_CONFIG(n)        \
};

#define LPUART_MCUX_INIT(n)						\
									\
	static struct mcux_lpuart_data mcux_lpuart_##n##_data;		\
									\
	PINCTRL_DEFINE(n)						\  //定义`struct pinctrl_dev_config`全局变量数组并用宏进行初始化
	MCUX_LPUART_IRQ_DEFINE(n)					\
									\
	LPUART_MCUX_DECLARE_CFG(n)					\
									\
	DEVICE_DT_INST_DEFINE(n,					\
			    &mcux_lpuart_init,				\
			    NULL,					\
			    &mcux_lpuart_##n##_data,			\
			    &mcux_lpuart_##n##_config,			\
			    PRE_KERNEL_1,				\
			    CONFIG_SERIAL_INIT_PRIORITY,		\
			    &mcux_lpuart_driver_api);			\

DT_INST_FOREACH_STATUS_OKAY(LPUART_MCUX_INIT)

PINCTRL_DEFINEPINCTRL_INIT的定义如下,直接引用的是pinctrl.h中的宏

#define PINCTRL_DEFINE(n) PINCTRL_DT_INST_DEFINE(n);
#define PINCTRL_INIT(n) .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),

PINCTRL_DT_INST_DEFINE就是定义一个struct pinctrl_dev_config结构体和一组struct pinctrl_state结构体变量,并使用设备树中的值初始化它

#define PINCTRL_DT_DEFINE(node_id)					       \
	LISTIFY(DT_NUM_PINCTRL_STATES(node_id),				       \
		     Z_PINCTRL_STATE_PINS_DEFINE, (;), node_id);	       \	//定义一组`struct pinctrl_state`
	Z_PINCTRL_STATES_DEFINE(node_id)				       \
	Z_PINCTRL_DEV_CONFIG_STATIC Z_PINCTRL_DEV_CONFIG_CONST		       \
	struct pinctrl_dev_config Z_PINCTRL_DEV_CONFIG_NAME(node_id) =	       \ //定义一个`struct pinctrl_dev_config`
	Z_PINCTRL_DEV_CONFIG_INIT(node_id)										//指向一组

LISTIFY将宏扩展为多个相当于是Z_PINCTRL_STATE_PINS_DEFINE执行了多次,一个pinctrl的属性node就会执行一次,也就是会生成多个pinctrl_soc_pin_t

#define Z_PINCTRL_STATE_PINS_DEFINE(state_idx, node_id)			       \
	COND_CODE_1(Z_PINCTRL_SKIP_STATE(state_idx, node_id), (),	       \
	(static const pinctrl_soc_pin_t					       \
	Z_PINCTRL_STATE_PINS_NAME(state_idx, node_id)[] =		       \
	Z_PINCTRL_STATE_PINS_INIT(node_id, pinctrl_ ## state_idx)))

pinctrl_soc_pin_t来源于zephyr/soc/arm/nxp_imx/rt/pinctrl_rt10xx.h 不同的soc会引用对应soc目录下的头文件,因此pinctrl_soc_pin_t也不一样, rt1062的如下

struct pinctrl_soc_pin {
	struct pinctrl_soc_pinmux pinmux;
	uint32_t pin_ctrl_flags; /* value to write to IOMUXC_SW_PAD_CTL register */
};

typedef struct pinctrl_soc_pin pinctrl_soc_pin_t;

在这里pinmux用于控制pin复用,pin_ctrl_flags用于pin硬件特性配置, 初始化方式为

#define Z_PINCTRL_STATE_PIN_INIT(group_id, pin_prop, idx)			\
	{									\
	  .pinmux = Z_PINCTRL_PINMUX(group_id, pin_prop, idx),			\
	  .pin_ctrl_flags = Z_PINCTRL_MCUX_IMX_PINCFG_INIT(group_id),		\
	},

pin复用的结构体初始化 链接到标题

pin复用结构如下,也定义在zephyr/soc/arm/nxp_imx/rt/pinctrl_rt10xx.h

struct pinctrl_soc_pinmux {
	uint32_t mux_register; /* IOMUXC SW_PAD_MUX register */
	uint32_t config_register; /* IOMUXC SW_PAD_CTL register */
	uint32_t input_register; /* IOMUXC SELECT_INPUT DAISY register */
	uint32_t gpr_register; /* IOMUXC GPR register */
	uint8_t gpr_shift: 5; /* bitshift  for GPR register write */
	uint8_t mux_mode: 4; /* Mux value for SW_PAD_MUX register */
	uint32_t input_daisy:4; /* Mux value for SELECT_INPUT_DAISY register */
	uint8_t gpr_val: 1; /* value to write to GPR register */
};

对上述结构体struct pinctrl_soc_pinmux变量的初始化,是由对应soc提供的宏Z_PINCTRL_PINMUX来完成,zephyr/soc/arm/nxp_imx/rt/pinctrl_rt10xx.h

#define Z_PINCTRL_PINMUX(group_id, pin_prop, idx)				\
	MCUX_IMX_PINMUX(DT_PHANDLE_BY_IDX(group_id, pin_prop, idx))

#define MCUX_IMX_PINMUX(node_id)						\
	{									\
	  .mux_register = DT_PROP_BY_IDX(node_id, pinmux, 0),			\
	  .config_register = DT_PROP_BY_IDX(node_id, pinmux, 4),		\
	  .input_register = DT_PROP_BY_IDX(node_id, pinmux, 2),			\
	  .mux_mode = DT_PROP_BY_IDX(node_id, pinmux, 1),			\
	  .input_daisy = DT_PROP_BY_IDX(node_id, pinmux, 3),			\
	  IF_ENABLED(DT_PROP_HAS_IDX(node_id, gpr, 0),				\
	    (.gpr_register = DT_PROP_BY_IDX(node_id, gpr, 0),))			\
	  IF_ENABLED(DT_PROP_HAS_IDX(node_id, gpr, 1),				\
	    (.gpr_shift = DT_PROP_BY_IDX(node_id, gpr, 1),))			\
	  IF_ENABLED(DT_PROP_HAS_IDX(node_id, gpr, 2),				\
	    (.gpr_val = DT_PROP_BY_IDX(node_id, gpr, 2),))			\
	}

上面的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 2 0x0 0 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个元素, 也就是

.mux_register = 0x401f80f0,
.config_register = 0x401f82e0,
.input_register = 0x0,
.mux_mode = 2,
.input_daisy = 0,

由于设备树中没有使用gpr,这里就没有gpr的初始化设置。

pin硬件配置 链接到标题

pin硬件配置的信息放在pin_ctrl_flags内,其初始化使用zephyr/soc/arm/nxp_imx/rt/pinctrl_rt10xx.h提供的宏Z_PINCTRL_MCUX_IMX_PINCFG_INIT

#define Z_PINCTRL_MCUX_IMX_PINCFG_INIT(node_id)							\
	((DT_PROP(node_id, input_schmitt_enable) << MCUX_IMX_INPUT_SCHMITT_ENABLE_SHIFT) |	\
	IF_ENABLED(DT_PROP(node_id, bias_pull_up), (DT_ENUM_IDX(node_id, bias_pull_up_value)	\
		<< MCUX_IMX_BIAS_PULL_UP_SHIFT) |)						\
	IF_ENABLED(DT_PROP(node_id, bias_pull_down), (DT_ENUM_IDX(node_id, bias_pull_down_value)\
		<< MCUX_IMX_BIAS_PULL_DOWN_SHIFT) |)					\
	((DT_PROP(node_id, bias_pull_down) | DT_PROP(node_id, bias_pull_up))		\
		<< MCUX_IMX_BIAS_BUS_HOLD_SHIFT) |					\
	 ((!DT_PROP(node_id, bias_disable)) << MCUX_IMX_PULL_ENABLE_SHIFT) |		\
	 (DT_PROP(node_id, drive_open_drain) << MCUX_IMX_DRIVE_OPEN_DRAIN_SHIFT) |	\
	 (DT_ENUM_IDX(node_id, nxp_speed) << MCUX_IMX_SPEED_SHIFT) |			\
	 (DT_ENUM_IDX(node_id, drive_strength) << MCUX_IMX_DRIVE_STRENGTH_SHIFT) |	\
	 (DT_ENUM_IDX(node_id, slew_rate) << MCUX_IMX_SLEW_RATE_SHIFT) |		\
	 (DT_PROP(node_id, input_enable) << MCUX_IMX_INPUT_ENABLE_SHIFT))

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配置, 摘要如下

static int mcux_lpuart_init(const struct device *dev)
{
	const struct mcux_lpuart_config *config = dev->config;
	...

	#ifdef CONFIG_PINCTRL
	err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
	if (err < 0) {
		return err;
	}
	#endif

	....
}

就是将前面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,而是直接对寄存器进行操作

int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt,
			   uintptr_t reg)
{
	/* configure all pins */
	for (uint8_t i = 0U; i < pin_cnt; i++) {
		//计算出寄存器地址和值
		uint32_t mux_register = pins[i].pinmux.mux_register;
		uint32_t mux_mode = pins[i].pinmux.mux_mode;
		uint32_t input_register = pins[i].pinmux.input_register;
		uint32_t input_daisy = pins[i].pinmux.input_daisy;
		uint32_t config_register = pins[i].pinmux.config_register;
		uint32_t pin_ctrl_flags = pins[i].pin_ctrl_flags;
#if defined(CONFIG_SOC_SERIES_IMX_RT10XX) || defined(CONFIG_SOC_SERIES_IMX_RT11XX)
		volatile uint32_t *gpr_register =
			(volatile uint32_t *)((uintptr_t)pins[i].pinmux.gpr_register);
		if (gpr_register) {
			/* Set or clear specified GPR bit for this mux */
			if (pins[i].pinmux.gpr_val) {
				*gpr_register |=
					(pins[i].pinmux.gpr_val << pins[i].pinmux.gpr_shift);
			} else {
				*gpr_register &= ~(0x1 << pins[i].pinmux.gpr_shift);
			}
		}
#endif

		//直接对寄存器进行操作
		*((volatile uint32_t *)((uintptr_t)mux_register)) =
			IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(mux_mode) |
			IOMUXC_SW_MUX_CTL_PAD_SION(MCUX_IMX_INPUT_ENABLE(pin_ctrl_flags));
		if (input_register) {
			*((volatile uint32_t *)((uintptr_t)input_register)) =
				IOMUXC_SELECT_INPUT_DAISY(input_daisy);
		}
		if (config_register) {
			*((volatile uint32_t *)((uintptr_t)config_register)) =
				pin_ctrl_flags & (~(0x1 << MCUX_IMX_INPUT_ENABLE_SHIFT));
		}

	}
	return 0;
}

结构体包含关系总结 链接到标题

对于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

&gpio5{
	pinmux = <&iomuxc_snvs_wakeup_gpio5_io00>,
		<&iomuxc_snvs_pmic_on_req_gpio5_io01>,
		<&iomuxc_snvs_pmic_stby_req_gpio5_io02>;
};

modules/hal/nxp/dts/nxp/nxp_imx/rt/mimxrt1062cvj5a-pinctrl.dtsi

	/omit-if-no-ref/ iomuxc_snvs_pmic_on_req_gpio5_io01: IOMUXC_SNVS_PMIC_ON_REQ_GPIO5_IO01 {
		pinmux = <0x400a8004 5 0x0 0 0x400a801c>;
	};

board内的设备树配置GPIO

	gpio_keys {
		compatible = "gpio-keys";
		user_button: button-1 {
			label = "User SW8";
			gpios = <&gpio5 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
		};
	};

对于rt1062来说,GPIO内的Pinmux使用了Pinctrl的设备树机制并用pinctrl_configure_pins进行设置, 硬件配置为直接写寄存器,GPIO的实现细节不在本文范围内,有兴趣的同学可以查看zephyr/drivers/gpio/gpio_imx.c相关代码。

参考 链接到标题

https://docs.zephyrproject.org/latest/hardware/pinctrl/index.html