嵌入式学习 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 负责设备与驱动分离。两者的核心目的都是让驱动代码不依赖具体硬件,提高可移植性。

相关推荐
shixiexunnie1 小时前
Raw NAND 驱动库学习
驱动开发·学习
wixzjsh1 小时前
嵌入式Linux系统启动与内核驱动开发全解
linux·运维·驱动开发
喜欢打篮球的普通人1 小时前
MiniMind 学习笔记(二十二):数学 Cookbook——把概率空间、期望、似然一次讲明白
人工智能·笔记·学习
晴天的雨.9921 小时前
【Linux】基础开发工具---软件包管理器
linux·运维·服务器
JWASX2 小时前
【agent 开发】agent 开发学习 - LangChain(3)
学习·langchain
xiaoqiMikko2 小时前
Linux 实战(三):服务起不来、定时任务不执行 —— CentOS 7 / Rocky 9 / Ubuntu 24.04 实测差异
linux·centos
Hello_Pyhx2 小时前
Ventoy:Linux制盘与排障
linux·运维·服务器
dadaobusi2 小时前
一个有趣的物理假设
学习
Jason_zhao_MR3 小时前
电力数据采集核心板选型指南
linux·嵌入式硬件·fpga开发·嵌入式