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