一、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开发中有所帮助。