嵌入式Linux驱动开发:i.MX6ULL平台设备驱动
嵌入式Linux驱动开发:i.MX6ULL平台设备驱动
1. 概述
本文档详细记录了基于i.MX6ULL开发板的平台设备驱动开发过程。通过实现一个LED控制驱动,深入理解Linux内核中的平台总线(Platform Bus)、平台设备(Platform Device)和平台驱动(Platform Driver)三者之间的关系与工作原理。
本项目包含两个主要模块:
leddevice.c
:平台设备定义与注册leddriver.c
:平台驱动实现platledAPP.c
:用户空间测试应用程序
2. 平台总线、设备与驱动模型
2.1 模型架构
Linux内核采用总线-设备-驱动模型来管理硬件设备。对于SoC内部集成的外设(如GPIO、I2C、SPI控制器等),由于它们直接连接到处理器内部总线,无法被动态探测,因此引入了平台总线(platform_bus_type
)这一虚拟总线概念。
该模型的核心组件包括:
- 平台总线 (
platform_bus
):系统级虚拟总线,所有平台设备都挂载于此 - 平台设备 (
platform_device
):描述具体的硬件设备信息 - 平台驱动 (
platform_driver
):提供设备的操作方法
三者通过匹配机制关联,当设备与驱动的名称一致时,内核会调用驱动的probe
函数完成初始化。
2.2 匹配过程
- 内核启动时扫描设备树或静态注册的平台设备
- 当注册新的平台驱动时,遍历所有未匹配的平台设备
- 比较
platform_device.name
与platform_driver.driver.name
- 名称匹配成功后调用驱动的
probe
函数 - 设备移除时调用
remove
函数
3. 设备树分析 (imx6ull-alientek-emmc.dts)
提供的设备树文件定义了i.MX6ULL开发板的完整硬件配置。以下是与本项目相关的关键节点分析:
3.1 自定义LED节点
alphaled {#address-cells = <1>;#size-cells = <1>;compatible = "alientek,alphaled";status = "okay";reg = <0x020C406C 0x04 0x020E0068 0x04 0x020E02F4 0x04 0x0209C000 0x04 0x0209C004 0x04>;
};
- compatible: 匹配字符串"alientek,alphaled",用于驱动匹配
- reg: 定义了5个寄存器的物理地址和长度:
0x020C406C
: CCM_CCGR1 (时钟控制)0x020E0068
: SW_MUX_GPIO1_IO03 (复用配置)0x020E02F4
: SW_PAD_GPIO1_IO03 (电气特性)0x0209C000
: GPIO1_DR (数据寄存器)0x0209C004
: GPIO1_GDIR (方向寄存器)
3.2 GPIO LED节点
dtsled{compatible = "gpio-leds";led0{label = "red";pinctrl-names = "default";pinctrl-0 = <&pinctrl_gpioled>;gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;default-state = "on";linux,default-trigger = "heartbeat";};
};
使用标准的gpio-leds
兼容性字符串,由内核自带的LED驱动处理。
3.3 引脚控制组
pinctrl_gpioled: ledgrp {fsl,pins = <MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10b0>;
};
定义了GPIO1_IO03引脚的复用功能和电气参数(0x10b0)。
4. 平台设备实现 (leddevice.c)
4.1 寄存器地址定义
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
这些宏定义了LED控制所需的关键寄存器物理地址。
4.2 资源描述符
static struct resource led_resources[] = {[0] = {.start = CCM_CCGR1_BASE,.end = CCM_CCGR1_BASE + REGISTER_LNE - 1,.flags = IORESOURCE_MEM,},// ... 其他寄存器
};
resource
结构体用于描述设备的硬件资源(内存、中断等)。这里定义了5个内存资源,每个长度为4字节(REGISTER_LNE=4)。
4.3 平台设备结构体
static struct platform_device leddevice = {.name = "imx6ull-led",.id = -1,.dev = {.release = leddevice_realease,},.num_resources = ARRAY_SIZE(led_resources),.resource = led_resources,
};
- name: 匹配标识符,必须与驱动的
.driver.name
相同 - id: 设备ID,-1表示单个设备
- dev.release: 设备释放回调函数
- num_resources/resource: 指向资源数组
4.4 设备注册
static int __init leddevice_init(void)
{platform_device_register(&leddevice);return 0;
}
在模块加载时注册平台设备。注意:在设备树环境下,通常不需要手动创建平台设备,设备树会自动解析并创建。
5. 平台驱动实现 (leddriver.c)
5.1 驱动结构体
static struct platform_driver leddriver = {.driver = {.name = "imx6ull-led",},.probe = led_probe,.remove = led_remove,
};
- .driver.name: 匹配字符串,必须与设备的
.name
字段一致 - .probe: 设备匹配成功后的初始化函数
- .remove: 设备移除时的清理函数
5.2 probe函数详解
5.2.1 获取资源
for (i = 0; i < 5; i++) {ledresources[i] = platform_get_resource(drv, IORESOURCE_MEM, i);if (ledresources[i] == NULL) {return -EINVAL;}
}
platform_get_resource()
从设备的资源数组中获取指定类型的资源。这里依次获取5个内存资源。
5.2.2 I/O内存映射
IMX6U_CCM_CCGR1 = ioremap(ledresources[0]->start, resource_size(ledresources[0]));
// ... 其他寄存器映射
ioremap()
将物理地址映射为内核虚拟地址,以便安全访问。resource_size()
获取资源的大小。
5.2.3 硬件初始化
时钟使能
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26);
val |= (3 << 26);
writel(val, IMX6U_CCM_CCGR1);
配置CCM_CCGR1寄存器,使能GPIO1时钟(BIT26-27置为11)。
引脚复用
writel(5, SW_MUX_GPIO1_IO03);
设置SW_MUX_GPIO1_IO03为模式5(GPIO功能)。
电气特性
writel(0x10B0, SW_PAD_GPIO1_IO03);
配置驱动强度、上下拉等电气参数,与设备树中定义一致。
GPIO方向设置
val = readl(GPIO1_GDIR);
val |= (1 << 3);
writel(val, GPIO1_GDIR);
设置GPIO1_3为输出模式。
初始状态
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
默认关闭LED(高电平熄灭,因GPIO_ACTIVE_LOW)。
5.3 字符设备框架
5.3.1 设备号管理
if (newchrled.major) {newchrled.devid = MKDEV(newchrled.major, 0);register_chrdev_region(newchrled.devid, LED_CNT, LED_NAME);
} else {alloc_chrdev_region(&newchrled.devid, 0, LED_CNT, LED_NAME);newchrled.major = MAJOR(newchrled.devid);
}
动态或静态分配设备号。
5.3.2 cdev注册
cdev_init(&newchrled.cdev, &newchrled_fops);
cdev_add(&newchrled.cdev, newchrled.devid, LED_CNT);
初始化并添加字符设备到系统。
5.3.3 设备类与设备节点创建
newchrled.class = class_create(THIS_MODULE, LED_NAME);
newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, LED_NAME);
创建设备类并在/dev/
目录下生成设备节点。
5.4 LED控制函数
void led_switch(u8 sta)
{u32 val = 0;if (sta == LEDON) {val = readl(GPIO1_DR);val &= ~(1 << 3);writel(val, GPIO1_DR);} else if (sta == LEDOFF) {val = readl(GPIO1_DR);val |= (1 << 3);writel(val, GPIO1_DR);}
}
直接操作GPIO数据寄存器控制LED状态。
5.5 文件操作函数
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];retvalue = copy_from_user(databuf, buf, cnt);if (retvalue < 0) {return -EFAULT;}if (databuf[0] == LEDON) {led_switch(LEDON);} else if (databuf[0] == LEDOFF) {led_switch(LEDOFF);}return 0;
}
write
函数从用户空间接收控制命令(0开灯,1关灯)。
5.6 remove函数
static int led_remove(struct platform_device *drv)
{led_switch(LEDOFF);iounmap(IMX6U_CCM_CCGR1);// ... 其他iounmapcdev_del(&newchrled.cdev);unregister_chrdev_region(newchrled.devid, LED_CNT);device_destroy(newchrled.class, newchrled.devid);class_destroy(newchrled.class);return 0;
}
设备移除时的清理工作,确保资源正确释放。
6. 用户空间应用程序 (platledAPP.c)
int main(int argc, char *argv[])
{int fd;unsigned char databuf[1];if (argc != 3) {printf("Error Usage!\r\n");return -1;}fd = open(argv[1], O_RDWR);if (fd < 0) {printf("file open failed!\r\n");return -1;}databuf[0] = atoi(argv[2]);write(fd, databuf, sizeof(databuf));close(fd);return 0;
}
用法:./platledAPP /dev/platled 0
(开灯)或 ./platledAPP /dev/platled 1
(关灯)
7. Makefile分析
KERNERDIR := /home/ubuntu2004/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENTDIR := $(shell pwd)obj-m := leddevice.o leddriver.obuild : kernel_moduleskernel_modules:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) modulesclean:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) clean
- KERNERDIR: 内核源码路径
- obj-m: 指定编译为模块的目标文件
- M=$(CURRENTDIR): 告诉内核构建系统在当前目录查找模块源码
8. 编译与测试流程
8.1 编译模块
make
生成leddevice.ko
和leddriver.ko
。
8.2 加载模块
# 先加载设备模块
insmod leddevice.ko
# 再加载驱动模块
insmod leddriver.ko
8.3 查看日志
dmesg | tail
应看到类似输出:
newcheled major=250,minor=0
8.4 测试功能
# 开灯
./platledAPP /dev/platled 0
# 关灯
./platledAPP /dev/platled 1
8.5 卸载模块
rmmod leddriver
rmmod leddevice
9. 总结
本项目完整实现了基于i.MX6ULL的平台设备驱动开发,涵盖了以下核心知识点:
- 平台总线模型:理解设备、驱动、总线三者的关系
- 资源管理:使用
resource
结构体描述硬件资源 - I/O内存访问:
ioremap()
和iounmap()
的正确使用 - 设备树集成:如何通过设备树传递硬件信息
- 字符设备框架:完整的字符设备驱动实现
- 模块化编程:驱动的加载与卸载机制
10. 源码仓库
https://gitee.com/dream-cometrue/linux_driver_imx6ull