Pinctrl 子系统的原理与实战

75 阅读4分钟

一、Pinctrl 子系统:为什么不可或缺?

现代 SOC 的引脚通常具备多种功能,例如 RK3568 的某个引脚可能既可用作 GPIO,也能作为 UART、SPI 等外设的信号传输通道。同时,引脚还需配置上拉 / 下拉电阻、驱动强度等电气参数。Pinctrl 子系统应运而生,解决以下核心问题:

统一管理:为所有引脚资源提供统一的管理框架,避免驱动开发中的混乱。 引脚复用: 灵活切换引脚功能,适配不同外设需求。 配置标准化: 通过设备树(DTS)描述引脚配置,实现硬件与软件的解耦。

二、实战:从设备树到内核驱动的完整代码实现

1. 设备树(DTS)配置

设备树是 Pinctrl 配置的起点,以 GPIO配置为例:

// kernel/arch/arm64/boot/dts/rockchip/rk3568.dtsi
...
	pinctrl: pinctrl {
		compatible = "rockchip,rk3568-pinctrl";
		rockchip,grf = <&grf>;
		rockchip,pmu = <&pmugrf>;
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;

		gpio0: gpio@fdd60000 {
			compatible = "rockchip,gpio-bank";
			reg = <0x0 0xfdd60000 0x0 0x100>;
			interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
			clocks = <&pmucru PCLK_GPIO0>, <&pmucru DBCLK_GPIO0>;

			gpio-controller;
			#gpio-cells = <2>;
			gpio-ranges = <&pinctrl 0 0 32>;
			interrupt-controller;
			#interrupt-cells = <2>;
		};
...
};
// kernel/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dtsi
...
&pinctrl {
	rc522 {
		rc522_cs: rc522-cs {
			rockchip,pins = <2 RK_PD2 RK_FUNC_GPIO &pcfg_pull_up>;
		};
	};
...
};

上述代码定义了rc522 作为GPIO引脚,并且设置为上拉电阻等参数。

2. 内核驱动实现

内核驱动负责将设备树配置转化为硬件操作,核心文件为drivers/pinctrl/pinctrl-rockchip.c:

初始化Pinctrl设备


static int rockchip_pinctrl_probe(struct platform_device *pdev)
{
	struct rockchip_pinctrl *info;
	struct device *dev = &pdev->dev;
	struct rockchip_pin_ctrl *ctrl;
	struct device_node *np = pdev->dev.of_node, *node;
	struct resource *res;
	void __iomem *base;
	int ret;

	if (!dev->of_node) {
		dev_err(dev, "device tree node not found\n");
		return -ENODEV;
	}

	info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
	if (!info)
		return -ENOMEM;

	info->dev = dev;

	ctrl = rockchip_pinctrl_get_soc_data(info, pdev);
	if (!ctrl) {
		dev_err(dev, "driver data not available\n");
		return -EINVAL;
	}
	info->ctrl = ctrl;

	node = of_parse_phandle(np, "rockchip,grf", 0);
	if (node) {
		info->regmap_base = syscon_node_to_regmap(node);
		if (IS_ERR(info->regmap_base))
			return PTR_ERR(info->regmap_base);
	} else {
		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
		base = devm_ioremap_resource(&pdev->dev, res);
		if (IS_ERR(base))
			return PTR_ERR(base);

		rockchip_regmap_config.max_register = resource_size(res) - 4;
		rockchip_regmap_config.name = "rockchip,pinctrl";
		info->regmap_base = devm_regmap_init_mmio(&pdev->dev, base,
						    &rockchip_regmap_config);

		/* to check for the old dt-bindings */
		info->reg_size = resource_size(res);

		/* Honor the old binding, with pull registers as 2nd resource */
		if (ctrl->type == RK3188 && info->reg_size < 0x200) {
			res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
			base = devm_ioremap_resource(&pdev->dev, res);
			if (IS_ERR(base))
				return PTR_ERR(base);

			rockchip_regmap_config.max_register =
							resource_size(res) - 4;
			rockchip_regmap_config.name = "rockchip,pinctrl-pull";
			info->regmap_pull = devm_regmap_init_mmio(&pdev->dev,
						    base,
						    &rockchip_regmap_config);
		}
	}

	/* try to find the optional reference to the pmu syscon */
	node = of_parse_phandle(np, "rockchip,pmu", 0);
	if (node) {
		info->regmap_pmu = syscon_node_to_regmap(node);
		if (IS_ERR(info->regmap_pmu))
			return PTR_ERR(info->regmap_pmu);
	}

	/* Special handle for some Socs */
	if (ctrl->soc_data_init) {
		ret = ctrl->soc_data_init(info);
		if (ret)
			return ret;
	}

	ret = rockchip_pinctrl_register(pdev, info);
	if (ret)
		return ret;

	platform_set_drvdata(pdev, info);

	ret = of_platform_populate(np, rockchip_bank_match, NULL, NULL);
	if (ret) {
		dev_err(&pdev->dev, "failed to register gpio device\n");
		return ret;
	}
	dev_info(dev, "probed %s\n", dev_name(dev));

	return 0;
}

为指定的引脚设置引脚配置设置

/* set the pin config settings for a specified pin */
static int rockchip_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
				unsigned long *configs, unsigned num_configs)
{
	struct rockchip_pinctrl *info = pinctrl_dev_get_drvdata(pctldev);
	struct rockchip_pin_bank *bank = pin_to_bank(info, pin);
	struct gpio_chip *gpio = &bank->gpio_chip;
	enum pin_config_param param;
	u32 arg;
	int i;
	int rc;

	for (i = 0; i < num_configs; i++) {
		param = pinconf_to_config_param(configs[i]);
		arg = pinconf_to_config_argument(configs[i]);

		switch (param) {
		case PIN_CONFIG_BIAS_DISABLE:
			rc =  rockchip_set_pull(bank, pin - bank->pin_base,
				param);
			if (rc)
				return rc;
			break;
		case PIN_CONFIG_BIAS_PULL_UP:
		case PIN_CONFIG_BIAS_PULL_DOWN:
		case PIN_CONFIG_BIAS_PULL_PIN_DEFAULT:
		case PIN_CONFIG_BIAS_BUS_HOLD:
			if (!rockchip_pinconf_pull_valid(info->ctrl, param))
				return -ENOTSUPP;

			if (!arg)
				return -EINVAL;

			rc = rockchip_set_pull(bank, pin - bank->pin_base,
				param);
			if (rc)
				return rc;
			break;
		case PIN_CONFIG_OUTPUT:
			rc = rockchip_get_mux(bank, pin - bank->pin_base);
			if (rc != 0) {
				dev_err(info->dev, "pin-%d has been mux to func%d\n", pin, rc);
				return -EINVAL;
			}

			rc = gpio->direction_output(gpio, pin - bank->pin_base, arg);
			if (rc)
				return rc;
			break;
		case PIN_CONFIG_DRIVE_STRENGTH:
			/* rk3288 is the first with per-pin drive-strength */
			if (!info->ctrl->drv_calc_reg)
				return -ENOTSUPP;

			rc = rockchip_set_drive_perpin(bank,
						pin - bank->pin_base, arg);
			if (rc < 0)
				return rc;
			break;
		case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
			if (!info->ctrl->schmitt_calc_reg)
				return -ENOTSUPP;

			rc = rockchip_set_schmitt(bank,
						  pin - bank->pin_base, arg);
			if (rc < 0)
				return rc;
			break;
		case PIN_CONFIG_SLEW_RATE:
			if (!info->ctrl->slew_rate_calc_reg)
				return -ENOTSUPP;

			rc = rockchip_set_slew_rate(bank,
						    pin - bank->pin_base, arg);
			if (rc < 0)
				return rc;
			break;
		default:
			return -ENOTSUPP;
		}
	} /* for each config */

	return 0;
}

驱动代码通过读写硬件寄存器,实现引脚模式、方向等配置。

三、总结

掌握 Pinctrl 子系统的原理与实践,是深入 Linux开发的必经之路。从设备树的精准配置,到内核驱动的高效实现,再到用户空间的便捷调用,每个环节都影响着硬件控制的稳定性和灵活性。希望本文的代码示例对你在Linux开发中有所帮助。