4. 编译Linux内核与设备树
任务说明
本案例目标是在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
验证方法与判定标准
验证步骤:
- 加载模块后,使用
dmesg查看是否输出“LED pinctrl driver probed”和“Switched to led_on state”。 - 使用万用表测量GPIO1_IO03引脚电平:加载模块前应为高电平(默认状态),加载约1秒后变为低电平(led_on状态)。
- 通过
/sys/kernel/debug/pinctrl/下的调试接口(需开启CONFIG_DEBUG_FS)查看当前引脚复用状态,确认引脚被设置为GPIO功能。
判定标准:
- 驱动加载无错误,日志显示两次状态切换成功。
- 引脚电平变化与代码逻辑一致:先高后低。
- 若在设备树中修改
pinctrl-1的电气属性值(如改为0x1B0关闭上拉),重新编译后引脚电平或驱动能力发生变化,说明电气属性配置生效。
关键点说明
- 状态命名:
pinctrl-names中的名称与驱动中pinctrl_lookup_state()的字符串参数必须完全一致。 - GPIO与pinctrl协同:GPIO子系统在
gpiod_get()时会自动应用设备树中pinctrl-0的默认状态,因此probe中先获取GPIO再显式切换状态是安全的。 - 电气属性验证:修改设备树中
fsl,pins的配置值(如上拉/驱动强度),重新编译后观察引脚电平变化或驱动能力差异,即可确认pinconf生效。