嵌入式Linux驱动开发:设备树与平台设备驱动
引言
本笔记旨在详细记录嵌入式Linux驱动开发中设备树(Device Tree)和平台设备驱动(Platform Driver)的核心概念与实现。通过分析提供的代码与设备树文件,我们将深入理解如何在i.MX6ULL平台上使用设备树来描述硬件,并通过平台设备驱动来管理这些硬件资源。本文档将结合代码实例,详细解释每个关键部分的作用与实现细节。
设备树(Device Tree)
设备树是一种数据结构,用于描述硬件的配置和连接方式,使得操作系统可以在不硬编码硬件信息的情况下初始化和使用硬件。设备树文件通常以.dts
为扩展名,编译后生成.dtb
文件,由引导加载程序传递给内核。
设备树文件结构
设备树文件由一系列节点(node)和属性(property)组成。每个节点代表一个硬件设备或子系统,属性则描述该设备的具体配置。
根节点
设备树的根节点用/
表示,包含全局属性和子节点。
dts
/dts-v1/;
#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"
/ {
model = "Freescale i.MX6 ULL 14x14 EVK Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
...
};
model
:描述开发板的型号。compatible
:指定设备的兼容性,用于匹配驱动程序。
子节点
子节点描述具体的硬件设备,如内存、外设等。
dts
memory {
reg = <0x80000000 0x20000000>;
};
memory
节点描述系统的物理内存布局,reg
属性指定内存的起始地址和大小。
设备树源文件(DTSI)
设备树源文件(.dtsi
)类似于C语言的头文件,包含多个.dts
文件共享的通用定义。通过#include
指令引入。
dts
#include "imx6ull.dtsi"
设备树编译
设备树文件需要编译成二进制格式(.dtb
),通常使用dtc
(Device Tree Compiler)工具完成。
bash
dtc -I dts -O dtb -o imx6ull-alientek-emmc.dtb imx6ull-alientek-emmc.dts
平台设备驱动
平台设备驱动是Linux内核中用于管理平台特定设备的一种机制。它通过设备树描述硬件资源,并在驱动程序中解析这些资源。
平台设备(Platform Device)
平台设备由platform_device
结构体表示,包含设备的名称、资源和私有数据。
资源定义
资源定义了设备所需的内存、中断等硬件资源。
c
static struct resource led_resources[] = {
[0] = {
.start = CCM_CCGR1_BASE,
.end = CCM_CCGR1_BASE + REGISTER_LNE - 1,
.flags = IORESOURCE_MEM,
},
...
};
start
和end
:资源的起始和结束地址。flags
:资源类型,如IORESOURCE_MEM
表示内存资源。
平台设备注册
平台设备通过platform_device_register
函数注册到内核。
c
static struct platform_device leddevice = {
.name = "imx6ull-led",
.id = -1,
.dev = {
.release = leddevice_realease,
},
.num_resources = ARRAY_SIZE(led_resources),
.resource = led_resources,
};
static int __init leddevice_init(void)
{
platform_device_register(&leddevice);
return 0;
}
平台驱动(Platform Driver)
平台驱动由platform_driver
结构体表示,包含驱动的名称、匹配表、探测和移除函数。
匹配表
匹配表用于将驱动与设备树中的节点关联。
c
struct of_device_id led_of_match[] = {
{.compatible = "alientek,gpioled"},
{/* sentinel */}
};
compatible
:与设备树中节点的compatible
属性匹配。
探测函数
探测函数在设备与驱动匹配成功后调用,用于初始化设备。
c
static int led_probe(struct platform_device *dev)
{
gpioled.nd = of_find_node_by_path("/gpioled");
if (gpioled.nd == NULL) {
printk("gpioled node not find!\r\n");
return -EINVAL;
}
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);
if (gpioled.led_gpio < 0) {
printk("can't get led-gpio");
return -EINVAL;
}
ret = gpio_direction_output(gpioled.led_gpio, 1);
if (ret < 0) {
printk("can't set gpio!\r\n");
}
// 注册字符设备驱动
...
return 0;
}
of_find_node_by_path
:根据路径查找设备树节点。of_get_named_gpio
:从设备树节点中获取GPIO编号。gpio_direction_output
:设置GPIO为输出模式。
移除函数
移除函数在设备卸载时调用,用于释放资源。
c
static int led_remove(struct platform_device *dev)
{
cdev_del(&gpioled.cdev);
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
device_destroy(gpioled.class, gpioled.devid);
class_destroy(gpioled.class);
return 0;
}
字符设备驱动
字符设备驱动通过file_operations
结构体定义设备的操作函数。
c
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
open
:打开设备。read
:读取设备数据。write
:写入设备数据。release
:关闭设备。
应用程序
应用程序通过系统调用与驱动程序交互,控制硬件设备。
c
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if (argc != 3) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]);
retvalue = write(fd, databuf, sizeof(databuf));
if (retvalue < 0) {
printf("LED Control Failed!\r\n");
close(fd);
return -1;
}
close(fd);
return 0;
}
open
:打开设备文件。write
:向设备写入数据,控制LED状态。close
:关闭设备文件。