4. Pinctrl子系统配置

任务说明

本案例目标是在Linux设备树中通过pinctrl子系统完成引脚复用(pinmux)和电气属性(pinconf)配置,并在驱动代码中使用pinctrl API切换引脚状态。以i.MX6ULL平台为例,实现一个LED驱动:默认状态下GPIO1_IO03引脚复用为GPIO功能并配置为输出高电平(LED灭),通过pinctrl-1状态切换为输出低电平(LED亮)。

设备树配置

在设备树中,pinctrl节点定义引脚复用和电气属性,驱动节点通过pinctrl-0、pinctrl-1引用不同状态,并指定pinctrl-names。以下为完整设备树片段:


/* 在iomuxc节点内添加引脚配置子节点 */
&iomuxc {
    pinctrl_led: ledgrp {
        fsl,pins = <
            MX6UL_PAD_GPIO1_IO03__GPIO1_IO03  0x10B0  /* 复用为GPIO,电气属性:HYS关闭、100K上拉、速度慢、开漏关闭 */
        >;
    };
};

/* LED驱动节点 */
led_test {
    compatible = "example,led-pinctrl";
    pinctrl-names = "default", "led_on";
    pinctrl-0 = <&pinctrl_led>;   /* 默认状态:输出高电平 */
    pinctrl-1 = <&pinctrl_led>;   /* led_on状态:输出低电平 */
    led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
    status = "okay";
};

说明:0x10B0为i.MX6ULL引脚电气属性配置值(bit[3:0]=0000表示驱动强度,bit[4]=1使能100K上拉,bit[6]=1关闭HYS,bit[7:6]=00慢速,bit[11]=1关闭开漏)。实际项目中需查阅对应SoC的pinctrl寄存器手册确认位定义。

驱动代码实现

驱动核心逻辑:在probe中获取GPIO并请求中断或直接操作;通过dev_pinctrl_get()获取pinctrl句柄,使用pinctrl_lookup_state()查找状态,pinctrl_select_state()切换状态。以下为最小完整实现:


#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/consumer.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>

struct led_pinctrl_dev {
    struct gpio_desc *led_gpio;
    struct pinctrl *pinctrl;
    struct pinctrl_state *pinctrl_default;
    struct pinctrl_state *pinctrl_on;
};

static int led_pinctrl_probe(struct platform_device *pdev)
{
    struct led_pinctrl_dev *priv;
    struct device *dev = &pdev->dev;
    int ret;

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

    /* 获取LED GPIO */
    priv->led_gpio = devm_gpiod_get(dev, "led", GPIOD_OUT_HIGH);
    if (IS_ERR(priv->led_gpio))
        return PTR_ERR(priv->led_gpio);

    /* 获取pinctrl句柄 */
    priv->pinctrl = devm_pinctrl_get(dev);
    if (IS_ERR(priv->pinctrl))
        return PTR_ERR(priv->pinctrl);

    /* 查找default状态(默认高电平) */
    priv->pinctrl_default = pinctrl_lookup_state(priv->pinctrl, "default");
    if (IS_ERR(priv->pinctrl_default))
        return PTR_ERR(priv->pinctrl_default);

    /* 查找led_on状态(低电平) */
    priv->pinctrl_on = pinctrl_lookup_state(priv->pinctrl, "led_on");
    if (IS_ERR(priv->pinctrl_on))
        return PTR_ERR(priv->pinctrl_on);

    /* 先切换到default状态 */
    ret = pinctrl_select_state(priv->pinctrl, priv->pinctrl_default);
    if (ret)
        return ret;

    platform_set_drvdata(pdev, priv);
    dev_info(dev, "LED pinctrl driver probed\n");

    /* 演示:1秒后切换为led_on状态(LED亮) */
    msleep(1000);
    ret = pinctrl_select_state(priv->pinctrl, priv->pinctrl_on);
    if (ret)
        return ret;
    dev_info(dev, "Switched to led_on state\n");

    return 0;
}

static int led_pinctrl_remove(struct platform_device *pdev)
{
    /* devm资源自动释放 */
    return 0;
}

static const struct of_device_id led_pinctrl_of_match[] = {
    { .compatible = "example,led-pinctrl" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, led_pinctrl_of_match);

static struct platform_driver led_pinctrl_driver = {
    .probe  = led_pinctrl_probe,
    .remove = led_pinctrl_remove,
    .driver = {
        .name = "led_pinctrl",
        .of_match_table = led_pinctrl_of_match,
    },
};
module_platform_driver(led_pinctrl_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Pinctrl subsystem configuration example");

编译与运行

将设备树源文件(.dts)中对应节点加入后重新编译设备树,驱动代码编译为内核模块。在目标板上执行:


# 加载驱动模块
insmod led_pinctrl.ko

# 观察内核日志
dmesg | tail -20

验证方法与判定标准

验证步骤:

  1. 加载模块后,使用dmesg查看是否输出“LED pinctrl driver probed”和“Switched to led_on state”。
  2. 使用万用表测量GPIO1_IO03引脚电平:加载模块前应为高电平(默认状态),加载约1秒后变为低电平(led_on状态)。
  3. 通过/sys/kernel/debug/pinctrl/下的调试接口(需开启CONFIG_DEBUG_FS)查看当前引脚复用状态,确认引脚被设置为GPIO功能。

判定标准:

  • 驱动加载无错误,日志显示两次状态切换成功。
  • 引脚电平变化与代码逻辑一致:先高后低。
  • 若在设备树中修改pinctrl-1的电气属性值(如改为0x1B0关闭上拉),重新编译后引脚电平或驱动能力发生变化,说明电气属性配置生效。
Pinctrl子系统配置流程 设备树 pinctrl_led: ledgrp fsl,pins = <...> led_test { pinctrl-0 = <&pinctrl_led> pinctrl-1 = <&pinctrl_led> 解析 pinctrl子系统核心 pinmux: 引脚复用 pinconf: 电气属性 状态管理 default / led_on 调用 LED驱动 devm_pinctrl_get() pinctrl_lookup_state() pinctrl_select_state() gpiod_set_value() 硬件寄存器 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 (复用) IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 (电气) 写入 状态切换时序 probe() → pinctrl_select_state(default) → GPIO输出高 → msleep(1000) → pinctrl_select_state(led_on) → GPIO输出低

关键点说明

  • 状态命名:pinctrl-names中的名称与驱动中pinctrl_lookup_state()的字符串参数必须完全一致。
  • GPIO与pinctrl协同:GPIO子系统在gpiod_get()时会自动应用设备树中pinctrl-0的默认状态,因此probe中先获取GPIO再显式切换状态是安全的。
  • 电气属性验证:修改设备树中fsl,pins的配置值(如上拉/驱动强度),重新编译后观察引脚电平变化或驱动能力差异,即可确认pinconf生效。