前言
学习Linux驱动,LED是经典入门案例。本文从最基础的硬编码寄存器字符设备驱动,过渡到设备树+platform平台驱动,完整梳理IMX6ULL上的实现思路,理解软硬件解耦思想。
一、传统字符设备驱动(无设备树)
原理
直接在驱动代码写死寄存器物理地址,使用ioremap完成地址映射,手动注册字符设备,提供open/release/write接口,应用层通过/dev/led操作LED。
核心代码片段
c
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.write = led_write,
};
static int __init led_init(void)
{
register_chrdev(LED_MAJOR, "led", &led_fops);
gpio_base = ioremap(GPIO_PHYS_ADDR, REG_SIZE);
writel(OUT_MODE, gpio_base + GPIO_DIR);
return 0;
}
缺点:硬件信息硬编码,更换硬件就要修改驱动源码、重新编译模块,可移植性差,仅适合入门理解字符设备底层原理。
二、设备树版本 platform 驱动(工程标准写法)
设备树将硬件信息剥离到dts文件,驱动只保留业务逻辑,依靠compatible属性完成设备与驱动匹配,实现软硬件分离。
dts节点片段
dts
led_test {
compatible = "myimx6ull,led";
gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
平台驱动核心片段
c
static const struct of_device_id led_of_match[] = {
{ .compatible = "myimx6ull,led" },
{}
};
static struct platform_driver led_platform_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "led-drv",
.of_match_table = led_of_match,
},
};
module_platform_driver(led_platform_driver);
匹配成功后自动执行probe函数,解析设备树GPIO属性、申请引脚资源并完成初始化。
三、应用层测试代码
c
fd = open("/dev/led", O_RDWR);
write(fd, &val, 1);
close(fd);
写入1点亮LED,写入0熄灭LED。
四、完整实操流程
- 修改dts添加led节点,编译生成dtb并更新到开发板
- 编写platform驱动,Makefile编译生成ko模块
- 开发板
insmod加载驱动,内核匹配设备树,自动创建设备节点 - 运行应用程序控制LED;测试完毕
rmmod卸载驱动
五、两种方案对比
传统字符设备:代码简单,适合初学;硬件写死,移植性差。
设备树+platform驱动:工业主流方案,硬件描述和驱动逻辑分离,换硬件只需修改设备树,不用改动驱动代码。
六、常见问题
compatible字符串驱动和设备树必须完全一致,大小写敏感,字符串不匹配会导致probe不执行- GPIO电平极性容易颠倒,
GPIO_ACTIVE_LOW代表低电平点亮 - 修改设备树后需要重新编译dtb,只编译ko不会更新硬件配置
- 内核模块版本要和开发板内核版本保持一致,否则加载模块报错