DTS文件和内核驱动的联系通常是通过设备树机制实现的。设备树机制是一种描述系统硬件的数据结构,它以树形结构组织设备节点,并提供设备节点的属性信息,包括设备地址、中断号、寄存器地址等等。
在Linux内核启动时,内核会使用设备树机制自动加载设备驱动程序,并将设备节点和驱动程序进行匹配,从而实现设备驱动的自动加载和初始化。
设备树源码(Device Tree Source
,DTS
)是用来描述硬件设备信息的一种语言。它是一种中立的表示方式,用于描述硬件设备的物理特性、接口信息和驱动程序的相关信息等,与处理器架构和操作系统无关。设备树被广泛应用于嵌入式系统领域,特别是在使用Linux内核的嵌入式系统中。
设备树是一种树形结构的数据结构,通常使用.dts
文件来描述。.dts
文件包含了设备树节点和属性的定义,其中节点代表硬件设备,属性则包含了硬件设备的相关信息。设备树文件通常被编译成二进制格式(Device Tree Blob
,DTB
),并通过bootloader
加载到内存中,供内核驱动程序解析使用。
设备树的作用是将硬件信息与软件驱动程序分离,可以使驱动程序更加灵活,适应多种硬件平台。使用设备树可以使内核代码更具可移植性,简化了内核的开发和维护,同时也方便了硬件厂商和系统集成商进行硬件设计和系统开发。
在Linux内核中,设备树被广泛应用于各种设备的驱动程序开发。在编写驱动程序时,我们通常需要通过解析设备树文件来获取硬件设备的相关信息,并使用内核提供的接口来控制硬件设备。
驱动程序可以通过内核提供的API来读取DTS文件中的节点和属性信息,以获取硬件设备的相关信息。在Linux内核中,驱动程序通常使用以下API来读取DTS文件:
of_find_node_by_name:通过节点名字查找节点,返回设备树节点的指针。
of_property_read_u32:读取DTS文件中的一个32位整数类型的属性值,并返回读取结果。
of_property_read_string:读取DTS文件中的一个字符串类型的属性值,并返回读取结果。
of_irq_get:获取设备的中断号。
of_clk_get:获取设备的时钟信息。
of_address_to_resource:获取设备的物理地址信息。
of_device_is_available:检查设备是否可用。
以上API是设备树节点和属性的读取接口,驱动程序可以通过这些接口来获取设备树中的信息,并根据需要进行操作。
在驱动程序中,通常会在设备初始化的过程中读取设备树中的节点和属性信息,根据这些信息初始化设备并设置相关参数。例如,一个LED驱动程序可以通过读取设备树中的GPIO节点来获取LED所连接的GPIO引脚信息,从而控制LED的亮灭。
这里以i.MX6ULL
为例讲解一下驱动读取DTS的过程。
首先,我们需要在设备树中定义一个设备节点,该节点包含了LED的相关信息,如下所示:
&iomuxc {
pinctrl_leds: ledsgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x4001b0b0
>;
};
leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_leds>;
led1 {
gpios = <&gpio1 3 0>;
label = "led1";
default-state = "off";
};
};
};
在这个设备树节点中,我们首先定义了一个pinctrl
组,该组用于定义LED的GPIO引脚,然后在设备树中定义了一个leds
节点,该节点指定了与LED相关的设备信息,包括设备类型(compatible
属性)、GPIO引脚配置(pinctrl
属性)、设备节点名称(led1
)、LED的GPIO编号(gpios
属性)、LED的名称(label
属性)和默认状态(default-state
属性)。
接下来,我们需要在内核驱动程序中使用设备树中的设备节点信息来控制LED。在i.MX6ULL
中,LED控制器驱动程序是通过LED框架来实现的,该驱动程序需要在probe
函数中获取LED的GPIO配置信息,然后使用LED框架提供的接口来控制LED。
以下是一个简单的LED控制器驱动程序的示例代码,该驱动程序使用了设备树中定义的led1
设备节点:
#include
#include
#include
#include
#include
#include
static struct gpio_desc *led_gpio_desc;
static int myled_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct led_classdev *led_cdev;
int ret;
// 从设备树中获取LED的GPIO配置
led_gpio_desc = of_get_named_gpio_desc(np, "gpios", 0);
if (!led_gpio_desc) {
dev_err(&pdev->dev, "Failed to get GPIO\n");
return -EINVAL;
}
// 将GPIO引脚设置为输出模式
ret = gpio_direction_output(led_gpio_desc->gpio, 0);
if (ret) {
dev_err(&pdev->dev, "Failed to set GPIO direction\n");
return ret;
}
// 注册LED设备
led_cdev = devm_kzalloc(&pdev->dev, sizeof(*led_cdev), GFP_KERNEL);
if (!led_cdev) {
dev_err(&pdev->dev, "Failed to allocate memory\n");
return -ENOMEM;
}
led_cdev->name = "myled";
led_cdev->brightness_set = led_brightness_set;
led_cdev->max_brightness = 1;
ret = devm_led_classdev_register(&pdev->dev, led_cdev);
if (ret) {
dev_err(&pdev->dev, "Failed to register LED device\n");
return ret;
}
return 0;
}
static int myled_remove(struct platform_device *pdev)
{
devm_led_classdev_unregister(&pdev->dev, &led_cdev);
return 0;
}
static const struct of_device_id myled_of_match[] = {
{ .compatible = "gpio-leds" },
{},
};
MODULE_DEVICE_TABLE(of, myled_of_match);
static struct platform_driver myled_driver = {
.driver = {
.name = "myled",
.of_match_table = myled_of_match,
},
.probe = myled_probe,
.remove = myled_remove,
};
module_platform_driver(myled_driver);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Driver for myled");
MODULE_LICENSE("GPL");
在probe
函数中,我们首先从设备树中获取了LED的GPIO配置信息,然后将GPIO引脚设置为输出模式,接着注册了一个LED设备,并设置了该设备的名称(name
属性)、亮度设置函数(brightness_set
属性)和最大亮度值(max_brightness
属性)。最后,我们通过调用devm_led_classdev_register
函数将该LED设备注册到LED框架中。
在remove
函数中,我们调用devm_led_classdev_unregister
函数来注销已经注册的LED设备。
设备树文件通过定义设备节点来描述硬件设备信息,内核驱动程序可以通过解析设备树文件来获取硬件设备信息,并通过LED框架提供的接口来控制LED。
推荐阅读
01 |加入嵌入式交流群 |
02 |嵌入式资源获取 |
03 |STM32中断优先级详解 |
04 |STM32下载程序新思路--使用串口下载程序 |