基础知识_05DTS设备树和驱动

一个设备树的全景视图

设备树和驱动

特性设备树(Device Tree)驱动(Driver)
职责描述硬件配置控制和管理硬件设备
内容硬件地址、中断号、时钟等初始化代码、操作函数、中断处理等
形式文本文件(.dts)或二进制文件(.dtb)内核模块代码(.c 文件)
加载时机在内核启动时由 Bootloader 传递给内核在内核启动或模块加载时初始化
可移植性提高内核的可移植性,同一内核支持不同硬件依赖设备树提供的硬件信息

设备树与驱动的联系

  • 设备树为驱动提供硬件信息

    • 驱动通过设备树获取硬件的寄存器地址、中断号、时钟等资源。
    • 设备树中的 compatible 属性用于匹配驱动和设备。
  • 驱动依赖设备树

    • 在嵌入式 Linux 中,设备树是驱动获取硬件信息的主要方式。
    • 驱动通过内核提供的 API(如 of_* 系列函数)从设备树中读取信息。
  • 共同完成硬件管理

    • 设备树描述硬件,驱动操作硬件,二者协作完成硬件的初始化和控制。

      例子_简单版

      设备树(.dts)
      1
      2
      3
      4
      5
      6
      7
      &i2c1 {
      status = "okay";
      eeprom@50 {
      compatible = "atmel,24c02";
      reg = <0x50>;
      };
      };
      驱动(.c)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      #include <linux/module.h>
      #include <linux/i2c.h>
      #include <linux/of.h>

      static int eeprom_probe(struct i2c_client *client, const struct i2c_device_id *id) {
      printk("EEPROM probed!\n");
      // 初始化硬件
      return 0;
      }

      static int eeprom_remove(struct i2c_client *client) {
      printk("EEPROM removed!\n");
      // 释放资源
      return 0;
      }

      static const struct of_device_id eeprom_match[] = {
      { .compatible = "atmel,24c02" },
      { }
      };
      MODULE_DEVICE_TABLE(of, eeprom_match);

      static struct i2c_driver eeprom_driver = {
      .probe = eeprom_probe,
      .remove = eeprom_remove,
      .driver = {
      .name = "eeprom",
      .of_match_table = eeprom_match,
      },
      };

      module_i2c_driver(eeprom_driver);

      MODULE_LICENSE("GPL");
      MODULE_AUTHOR("Your Name");
      MODULE_DESCRIPTION("EEPROM Driver");
      总结
      设备树 描述硬件信息,驱动 控制硬件设备。
      设备树通过 compatible 属性与驱动匹配,驱动通过设备树获取硬件资源。
      二者共同协作,完成硬件的初始化和操作。

      例子_复杂版

      举例子稍微复杂例子,能体现出,设备树的配置信息如何被驱动识别,解析,处理的。

这个例子将涉及一个假设的 多功能设备,它包含以下功能:
一个 I2C 接口 的传感器。
一个 GPIO 引脚 用于控制设备状态。
一个 中断引脚 用于触发事件。
设备树(.dts)
设备树描述硬件信息,包括 I2C 地址、GPIO 引脚、中断号等。

1
2
3
4
5
6
7
8
9
10
11
12
13
{
compatible = "mycompany,myboard";
model = "MyBoard";

sensor: sensor@50 {
compatible = "mycompany,my-sensor";
reg = <0x50>; // I2C 地址
interrupt-parent = <&gpio1>; // 中断控制器
interrupts = <5 IRQ_TYPE_EDGE_RISING>; // GPIO1_5,上升沿触发
vdd-supply = <&vdd_3v3>; // 电源
gpios = <&gpio2 3 GPIO_ACTIVE_HIGH>; // GPIO2_3,用于控制设备状态
};
};

设备树解析
compatible:”mycompany,my-sensor”,用于匹配驱动。
reg:I2C 设备的地址为 0x50。
interrupt-parent:中断控制器是 gpio1。
interrupts:中断引脚是 gpio1 的第 5 个引脚,触发方式为上升沿。
vdd-supply:设备的电源由 vdd_3v3 提供。
gpios:设备的控制引脚是 gpio2 的第 3 个引脚,高电平有效。

驱动(.c)
驱动通过设备树获取硬件信息,并初始化设备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>

struct my_sensor_data {
struct i2c_client *client;
struct gpio_desc *ctrl_gpio;
struct regulator *vdd;
int irq;
};

static irqreturn_t my_sensor_interrupt(int irq, void *dev_id) {
printk("Interrupt triggered!\n");
// 处理中断事件
return IRQ_HANDLED;
}

static int my_sensor_probe(struct i2c_client *client, const struct i2c_device_id *id) {
struct device *dev = &client->dev;
struct my_sensor_data *data;
int ret;

// 分配设备数据结构
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;

data->client = client;

// 获取 GPIO 控制引脚
data->ctrl_gpio = devm_gpiod_get(dev, NULL, GPIOD_OUT_LOW);
if (IS_ERR(data->ctrl_gpio)) {
dev_err(dev, "Failed to get control GPIO\n");
return PTR_ERR(data->ctrl_gpio);
}

// 获取电源
data->vdd = devm_regulator_get(dev, "vdd");
if (IS_ERR(data->vdd)) {
dev_err(dev, "Failed to get regulator\n");
return PTR_ERR(data->vdd);
}

// 使能电源
ret = regulator_enable(data->vdd);
if (ret) {
dev_err(dev, "Failed to enable regulator\n");
return ret;
}

// 获取中断号
data->irq = gpiod_to_irq(data->ctrl_gpio);
if (data->irq < 0) {
dev_err(dev, "Failed to get IRQ number\n");
return data->irq;
}

// 注册中断处理函数
ret = devm_request_irq(dev, data->irq, my_sensor_interrupt,
IRQF_TRIGGER_RISING, "my-sensor", data);
if (ret) {
dev_err(dev, "Failed to request IRQ\n");
return ret;
}

// 初始化设备
gpiod_set_value(data->ctrl_gpio, 1); // 设置 GPIO 为高电平
printk("Sensor initialized!\n");

return 0;
}

static int my_sensor_remove(struct i2c_client *client) {
struct my_sensor_data *data = i2c_get_clientdata(client);

// 关闭电源
regulator_disable(data->vdd);

// 设置 GPIO 为低电平
gpiod_set_value(data->ctrl_gpio, 0);

printk("Sensor removed!\n");
return 0;
}

static const struct of_device_id my_sensor_of_match[] = {
{ .compatible = "mycompany,my-sensor" },
{ }
};
MODULE_DEVICE_TABLE(of, my_sensor_of_match);

static struct i2c_driver my_sensor_driver = {
.probe = my_sensor_probe,
.remove = my_sensor_remove,
.driver = {
.name = "my-sensor",
.of_match_table = my_sensor_of_match,
},
};

module_i2c_driver(my_sensor_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("My Sensor Driver");

驱动解析设备树的过程
匹配设备:
驱动通过 of_device_id 中的 compatible 属性与设备树中的节点匹配。
匹配成功后,调用 probe 函数。

获取 GPIO:
使用 devm_gpiod_get 从设备树中获取 GPIO 控制引脚。

获取电源:
使用 devm_regulator_get 从设备树中获取电源。

获取中断:
使用 gpiod_to_irq 将 GPIO 引脚转换为中断号。
使用 devm_request_irq 注册中断处理函数。

初始化设备:
设置 GPIO 引脚状态。
使能电源。

总结
设备树 描述了硬件的详细信息(如 I2C 地址、GPIO 引脚、中断号等)。
驱动 通过内核提供的 API(如 devm_gpiod_get、devm_regulator_get、gpiod_to_irq 等)解析设备树中的信息,并初始化硬件。
设备树和驱动共同协作,完成硬件的配置和管理。
通过这个复杂一点的例子,可以更清楚地看到设备树和驱动如何配合工作,实现硬件的识别、解析和处理。

dts和硬件层的衔接

dts文件,靠软件层衔接的是驱动,那么另一层次呢?靠近硬件那一层和谁是关联(或者什么文档,也就是谁来决定dts的,或者dts应该根据谁来写类似这样的关联方)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
在Linux系统中,设备树(Device Tree)用于描述硬件平台的配置信息,特别是对于嵌入式系统。设备树的描述文件通常以`.dts`(Device Tree Source)格式存在,并通过设备树编译器(DTC)编译为二进制格式的`.dtb`(Device Tree Blob),供内核在启动时使用。

### 设备树的两层关系:
1. **软件层(驱动层)**:
- 设备树通过内核的设备树解析机制与驱动程序进行衔接。
- 内核会根据设备树中的节点信息,匹配对应的驱动程序,并初始化硬件设备。
- 驱动程序通过设备树接口(如`of_*`系列函数)从设备树中获取硬件配置信息。

2. **硬件层**:
- 设备树描述的是硬件平台的配置,因此它需要与硬件的实际设计保持一致。
- 设备树的内容通常由硬件设计团队或硬件工程师提供,他们根据硬件的实际设计(如SoC芯片手册、外设连接方式等)来决定设备树的内容。
- 设备树的编写需要参考硬件的技术文档,如芯片手册(Datasheet)、参考设计(Reference Design)等。

### 设备树的编写依据:
- **硬件设计文档**:设备树的编写需要根据硬件设计文档来进行,这些文档包括:
- SoC芯片手册(Datasheet):描述了芯片的寄存器、外设、中断等信息。
- 硬件原理图(Schematic):描述了硬件平台的电路连接方式。
- 参考设计(Reference Design):提供了标准的硬件配置和连接方式。

- **硬件工程师**:硬件工程师通常会根据硬件设计文档提供设备树的初始版本,或者与软件工程师合作完成设备树的编写。

### 设备树的决定因素:
- **硬件设计**:设备树的节点和属性必须与硬件设计一致,硬件设计决定了设备树的内容。
- **内核支持**:内核的驱动程序需要能够识别和解析设备树中的节点和属性,因此设备树的编写也需要考虑内核的支持情况。

### 设备树的维护:
- **硬件团队**:负责提供硬件设计的更新信息,确保设备树与硬件设计一致。
- **软件团队**:负责根据硬件设计更新设备树,并确保内核能够正确解析和使用设备树。

总结来说,设备树的编写主要依赖于硬件设计文档和硬件工程师的输入,同时需要与内核的驱动支持保持兼容。硬件设计是设备树的最终决定因素,而软件层(内核和驱动)则是设备树的使用方。

举例_硬件和dts

硬件结构
1个双核ARM Cortex-A932位处理器;
ARM本地总线上的内存映射区域分布有两个串口(分别位于0x101F10000x101F2000
GPIO控制器(位于0x101F3000
SPI控制器(位于0x10170000
中断控制器(位于0x10140000
外部总线桥上连接的设备如下:
SMC SMC91111以太网(位于0x10100000
I2C控制器(位于0x10160000
64MB NOR Flash(位于0x30000000
外部总线桥上连接的I2C控制器所对应的I2C总线上又连接了Maxim DS1338实时钟(I2C地址为0x58
具体如下图所示;

设备树dts文件
那么,如何将上面的硬件结构,通过设备树语言描述成dts文件呢?具体我实现在下图,并且做出了详细的解释。其中需要注意的有以下几个属性:

  • compatitable:兼容性属性;
  • #address-cells,#size-cells:地址编码所采用的格式;
  • 节点名称@节点地址:例如gpio@101f3000,这里名称和地址要和实际的对应起来;
  • 标签:例如interrupt-parent = <&intc>;,这这里的intc就是一个标签(label),通过&可以获取它的值,这里可以简单的理解成一个变量,然后在下面需要对这个标签进行另外的解析,例如intc:interrupt-controller@10140000;所以,这两个地方的intc都是对应起来的。
    最后,具体的实现可以参考下图;

参考

设备树与驱动的关系、设备树参数的介绍:https://zhuanlan.zhihu.com/p/8598598018
linux设备树dts文件详解:https://blog.csdn.net/weixin_42031299/article/details/125813060
linux 驱动简单案例:https://www.cnblogs.com/han-guang-xue/p/15769229.html
在Linux下写一个简单的驱动程序:https://www.cnblogs.com/kn-zheng/p/17168166.html
Linux 设备树语法(.dts)及如何从设备树获取节点信息:https://www.cnblogs.com/fortunely/p/16405592.html#a-of_get_property
Linux dts 设备树详解(二) 动手编写设备树dts:https://blog.csdn.net/u010632165/article/details/91488811
Device Tree (一) - dts基本概念和语法:https://blog.csdn.net/u011456016/article/details/136665769
Linux driver dts使用,实例驱动编写:https://blog.csdn.net/songyulong8888/article/details/78115512

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×