一、传统驱动方式存在的问题
在前面的 LED 驱动中,通常采用直接操作寄存器的方法。
例如:
GPIO寄存器地址
MUX寄存器地址
PAD寄存器地址
DATA寄存器地址
DIR寄存器地址
驱动程序中保存:
硬件地址
GPIO编号
寄存器偏移
结构如下:
应用程序
↓
驱动程序
↓
硬编码硬件地址
↓
寄存器
↓
LED
这种方式虽然能够实现功能,但是存在明显缺点。
1. 移植困难
不同芯片:
GPIO地址不同
寄存器不同
引脚不同
导致:
修改驱动源码
重新编译
重新测试
2. 驱动复用性差
例如:
同一个 LED 驱动:
运行在:
i.MX6ULL
RK3568
STM32MP1
由于硬件资源不同,需要修改代码。
因此 Linux 引入:
设备与驱动分离思想。
二、Linux设备与驱动分离思想
Linux驱动设计核心思想:
设备描述硬件
驱动负责控制
结构:
设备树
↓
描述硬件资源
↓
驱动程序
↓
操作硬件
这样:
硬件变化:
修改设备树即可。
驱动代码:
无需大量修改。
三、Device Tree(设备树)
3.1 什么是设备树?
设备树:
英文:
Device Tree
简称:
DT
它是一种描述硬件资源的数据结构。
Linux启动时:
U-Boot
↓
加载dtb
↓
Linux Kernel解析
↓
创建设备对象
3.2 设备树文件
设备树源码:
arch/arm/boot/dts
主要文件:
文件 | 作用 |
dts | 设备树源码 |
dtsi | 公共描述文件 |
dtb | 编译后的二进制文件 |
编译:
make dtbs
生成:
xxx.dtb
四、设备树基本语法
设备树采用树形结构:
node {
property;
};
例如:
led {
compatible = "gpio-led";
status = "okay";
};
五、设备树重要属性
1. compatible
作用:
设备和驱动匹配。
设备树:
compatible="pute-led";
驱动:
of_match_table
通过字符串匹配。
2. status
控制设备状态。
开启:
status="okay";
关闭:
status="disabled";
3. reg
描述寄存器资源。
格式:
地址 + 长度
例如:
reg=<0x0209c000 0x04>;
表示:
寄存器地址
+
占用空间
六、pinctrl子系统
在实际硬件中,一个GPIO不仅仅是编号。
还涉及:
引脚复用
上拉下拉
驱动能力
电气属性
这些由:
pinctrl子系统管理。
pinctrl作用
简单理解:
pinctrl
负责:
这个引脚是什么功能
例如:
复用成GPIO
配置上下拉
设备树:
pinctrl_led {
fsl,pins =
<
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03
>;
};
表示:
将某个引脚配置为 GPIO。
七、GPIO子系统
pinctrl负责:
引脚配置
GPIO负责:
输入输出控制
关系:
pinctrl
↓
GPIO功能
↓
GPIO子系统
↓
控制电平
GPIO常用接口:
获取GPIO
of_get_named_gpio()
申请GPIO
gpio_request()
设置输出
gpio_direction_output()
输出电平
gpio_set_value()
读取状态
gpio_get_value()
八、设备树LED驱动
传统方式:
驱动知道GPIO地址
设备树方式:
设备树提供GPIO信息
↓
驱动解析
↓
控制GPIO
完整流程:
设备树
↓
获取GPIO编号
↓
申请GPIO
↓
设置输出
↓
控制LED
应用:
echo 1 > /dev/led
最终:
用户程序
↓
write()
↓
LED驱动
↓
GPIO
↓
LED
九、Linux中断机制
除了控制LED,嵌入式设备大量使用中断。
例如:
按键
触摸屏
传感器
通信设备
为什么需要中断?
传统查询方式:
CPU不停检查设备状态
缺点:
浪费CPU
实时性差
中断方式:
设备发生事件
↓
通知CPU
↓
执行中断处理函数
十、Linux中断处理流程
完整流程:
硬件产生中断
↓
CPU响应
↓
进入中断处理函数
↓
执行任务
↓
返回
十一、中断相关概念
中断号
每一个中断都有编号:
IRQ Number
Linux通过中断号找到对应处理函数。
中断服务函数
格式:
irqreturn_t handler(
int irq,
void *dev_id
)
作用:
处理中断事件。
十二、中断注册
Linux提供:
request_irq()
注册中断。
例如:
request_irq(
irq,
handler,
flags,
name,
dev
);
参数:
参数 | 作用 |
irq | 中断号 |
handler | 处理函数 |
flags | 触发方式 |
name | 名称 |
十三、中断释放
卸载驱动时:
需要:
free_irq()
释放资源。
十四、按键中断驱动
按键驱动是学习中断最典型案例。
工作流程:
按键按下
↓
GPIO电平变化
↓
产生中断
↓
执行中断函数
↓
记录事件
↓
用户读取
结构:
按键
|
GPIO输入
|
GPIO中断
|
Linux IRQ系统
|
驱动程序
|
用户程序
十五、设备树描述按键
设备树提供:
GPIO编号
中断信息
触发方式
例如:
key {
compatible="gpio-key";
interrupt-parent=<gpio>;
interrupts=<xxx>;
};
驱动:
读取:
GPIO资源
中断号
触发方式
然后:
request_irq()
注册。
十六、驱动开发完整流程
到这里,一个完整 Linux 外设驱动流程已经形成。
第一步:描述硬件
设备树:
描述GPIO
描述中断
描述资源
第二步:驱动匹配
通过:
compatible
找到驱动。
第三步:解析资源
驱动:
读取设备树
获取GPIO
获取IRQ
第四步:初始化设备
例如:
LED:
GPIO输出
按键:
GPIO输入
注册中断
第五步:提供用户接口
例如:
字符设备
misc设备
sysfs