嵌入式学习 day65:Linux驱动进阶——pinctrl 子系统和 platform 驱动框架

一、pinctrl 子系统

1. 为什么需要 pinctrl

一个引脚可以复用成多种功能(GPIO、I2C、PWM 等),还可以配置电气属性(上下拉、驱动能力、速度等)。如果每个驱动都手动配置这些寄存器,代码会非常繁琐。

pinctrl 子系统 就是内核用来统一管理引脚复用和电气属性的框架。

2. pinctrl 的组成

概念 说明
pin controller 对应 IOMUXC,负责复用和电气属性配置
client device 使用 pinctrl 的设备(比如 LED、按键)

3. pinctrl 节点

pinctrl 节点写在 &iomuxc 内部:

dts

复制代码
&iomuxc {
    pinctrl_led: ledgrp {
        fsl,pins = <
            MX6UL_PAD_GPIO1_IO03__GPIO1_IO03  0x10B0
        >;
    };
};
部分 含义
pinctrl_led 标签,供其他节点引用
ledgrp 节点名(自定义)
fsl,pins 固定属性名
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 引脚复用宏
0x10B0 电气属性值

4. 引脚复用宏

引脚复用宏定义在 imx6ul-pinfunc.h 里:

c

复制代码
#define MX6UL_PAD_GPIO1_IO03__GPIO1_IO03  0x0068 0x02F4 0x0000 0x5 0x0
字段 含义
0x0068 复用寄存器偏移(基地址 0x020E0000 + 0x0068 = 0x020E0068)
0x02F4 电气属性寄存器偏移(0x020E0000 + 0x02F4 = 0x020E02F4)
0x5 复用模式(ALT5 = GPIO)

这个宏同时包含了复用寄存器和电气属性寄存器的偏移。

5. 设备节点引用 pinctrl

dts

复制代码
myled0 {
    compatible = "pute,myled";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_led>;
    gpio-myled0 = <&gpio1 3 1>;
    status = "okay";
};
  • pinctrl-names = "default":pinctrl 状态名。

  • pinctrl-0 = <&pinctrl_led>:引用 pinctrl 节点。

6. pinctrl 的作用

用了 pinctrl 后,内核自动配置复用寄存器和电气属性寄存器:

寄存器 谁配
复用寄存器 pinctrl 子系统
电气属性寄存器 pinctrl 子系统
GPIO 方向 驱动里配
GPIO 数据 驱动里配

驱动里只配 GPIO 方向和数据,复用和电气属性交给 pinctrl。

二、platform 驱动框架

1. 为什么需要 platform 驱动

之前的驱动把硬件信息(寄存器地址、GPIO 编号)写死在代码里,换一个开发板就要改代码。platform 总线把设备和驱动分离:

text

复制代码
设备(platform_device) ← 描述硬件信息(在设备树里)
        ↓
     总线(platform_bus) ← 负责匹配设备和驱动
        ↓
驱动(platform_driver) ← 拿到设备信息后操作硬件

设备信息放设备树,驱动代码不写死硬件地址。

2. platform 驱动的结构

c

复制代码
static int led_probe(struct platform_device *pdevice)
{
    // 匹配成功时调用
    return 0;
}

static int led_remove(struct platform_device *pdevice)
{
    // 卸载时调用
    return 0;
}

static struct of_device_id led_of_match_table[] = {
    {.compatible = "pute,myled"},
    {},
};

struct platform_driver led_drv = {
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
        .name = "myled",
        .owner = THIS_MODULE,
        .of_match_table = led_of_match_table,
    },
};

module_platform_driver(led_drv);
成员 作用
probe 设备和驱动匹配成功时调用
remove 卸载时调用
of_match_table 设备树匹配表,按 compatible 匹配
module_platform_driver 宏,简化注册和注销

3. 匹配流程

text

复制代码
1. 设备树里定义 myled0 节点,compatible = "pute,myled"
2. 内核启动时,把设备树节点转换成 platform_device
3. 加载驱动时,platform_driver_register 注册驱动
4. 内核拿驱动的 of_match_table 和 platform_device 的 compatible 比较
5. 匹配成功,调用 probe 函数
6. 卸载驱动时,调用 remove 函数

4. probe 函数做什么

c

复制代码
static int led_probe(struct platform_device *pdevice)
{
    int ret = 0;

    // 1. 注册 misc 设备
    ret = misc_register(&led_misc);

    // 2. 从设备树读 GPIO 编号
    ledgpiono = of_get_named_gpio(pdevice->dev.of_node, "gpio-myled0", 0);

    // 3. 申请 GPIO
    ret = devm_gpio_request(led_misc.this_device, ledgpiono, "led_platform_drv");

    // 4. 配置 GPIO 方向
    gpio_direction_output(ledgpiono, 1);

    return 0;
}
  • pdevice->dev.of_node:从 platform 设备拿到设备树节点。

  • of_get_named_gpio:从设备树节点读 GPIO 编号。

  • devm_gpio_request :申请 GPIO,devm_ 前缀表示卸载时自动释放。

5. 设备树节点

dts

复制代码
myled0 {
    compatible = "pute,myled";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_led>;
    gpio-myled0 = <&gpio1 3 1>;
    status = "okay";
};
  • compatible:驱动匹配的关键。

  • gpio-myled0 :驱动里 of_get_named_gpio 读这个属性。

三、LED 和按键的输入输出对比

1. GPIO 方向

设备 方向 函数
LED 输出 gpio_direction_output(gpio, 1)
按键 输入 gpio_direction_input(gpio)
  • LED :CPU 控制电平,所以是输出。

  • 按键 :CPU 读取外部电平,所以是输入。

2. GPIO 读写

设备 操作 函数
LED 设置电平 gpio_set_value(gpio, 0/1)
按键 读取电平 gpio_get_value(gpio)

3. file_operations

设备 操作 函数
LED 写 .write = led_write
按键 读 .read = key_read

4. 应用层

设备 操作 应用层
LED 写 write(fd, &ledstat, sizeof(ledstat))
按键 读 read(fd, &keystat, sizeof(keystat))

5. 完整对比表

项目 LED 按键
方向 输出 输入
方向函数 gpio_direction_output(gpio, 1) gpio_direction_input(gpio)
读写函数 gpio_set_value(gpio, 0/1) gpio_get_value(gpio)
file_operations .write .read
应用层 write read
有效电平 低电平点亮 低电平按下

6. 设备树对比

dts

复制代码
// LED
myled0 {
    compatible = "pute,myled";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_led>;
    gpio-myled0 = <&gpio1 3 1>;
    status = "okay";
};

// 按键
mykey0 {
    compatible = "pute,mykey";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_key>;
    gpio-mykey0 = <&gpio1 18 1>;
    status = "okay";
};

框架完全一样,只是 GPIO 编号和方向不同。

四、总结

pinctrl 子系统

要点 说明
位置 &iomuxc 节点内部
引脚宏 定义在 imx6ul-pinfunc.h
设备节点引用 pinctrl-0 = <&pinctrl_xxx>
作用 自动配置复用寄存器和电气属性寄存器
驱动里 只配 GPIO 方向和数据

platform 驱动框架

要点 说明
设备与驱动分离 设备信息放设备树,驱动代码不写死地址
匹配方式 按 compatible 匹配
probe 匹配成功时调用,初始化硬件
remove 卸载时调用,清理资源
注册 module_platform_driver 宏

LED 和按键对比

项目 LED 按键
方向 输出 输入
函数 gpio_direction_output gpio_direction_input
操作 gpio_set_value gpio_get_value
file_operations .write .read
应用层 write read

今日总结:

pinctrl 负责引脚复用和电气属性,platform 负责设备与驱动分离。两者的核心目的都是让驱动代码不依赖具体硬件,提高可移植性。

相关推荐
逆风的蚂蚁2 小时前
linux服务器性能调整怎么落地?我把 CPU 调度、I/O 优先级和内核参数这几个旋钮都拧了一遍
linux·运维·服务器
apple-mapping2 小时前
网络RTK定位学习总结
学习·网络rtk
楚山之石3 小时前
【Lancet Neurology 】多发性硬化认知障碍的 SDMT 筛查、灰质萎缩标志与认知康复循证路径
图像处理·人工智能·学习·mri
海海不掉头发4 小时前
CSDN博客_时序预测9模型复现对比
人工智能·学习·软件工程
沫璃染墨5 小时前
从零入门计算机网络系列(十一):HTTP协议进阶——从重定向原理到GET/POST与Fiddler抓包
linux·服务器·开发语言·网络·c++·tcp/ip·html
蓝祁子yoky6 小时前
Qt学习日记 Day13:网络编程(一)-TCP基础与Socket实战
网络·qt·学习
架构师伯子明7 小时前
Ros 学习十三:IK Demo
学习
AI_Lab017 小时前
宝塔用了两年,有没有必要换成轻量方案?
linux·运维·服务器
by209997 小时前
Linux GDB 调试与 CGDB 使用
linux·运维·服务器·经验分享
小羊没烦恼!7 小时前
关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁
java·服务器·网络·学习·负载均衡