# 前置知识
在图中,树的主干就是系统总线, IIC 控制器、 SPI 控制器等都是接到系统主线上的分支。其中 IIC1 上接了 AT24C02这个 IIC 设备, DTS 文件的主要功能就是按照图所示的结构来描述板子上的设备信息。
1. Device格式
DTS文件格式
/dts-v1/; // 表示版本
[memory reservations] // 格式为: /memreserve/ <address> <length>;
/ {
[property definitions]
[child nodes]
};
node格式
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
};
label:标号,可以省略,可以使用&label来引用node;
node-name:节点的名称,长度应该为 1-31 个字符,命名应该以小写或者大写字母开头
unit-address:
表示设备地址或者寄存器首地址
properties
简单地说, properties 就是“name=value”, value 有多种取值方式
- Property 格式 1:
[label:] property-name = value;
- Property 格式 2(没有值):
[label:] property-name;
- Property 取值只有 3 种:
arrays of cells(1 个或多个 32 位数据, 64 位数据使用 2 个 32 位数据表示, eg:interrupts = <17 0xc>;),
string(字符串,eg:compatible = "simple-bus"),
bytestring(1 个或多个字节,eg:local-mac-address = [00 00 12 34 56 78]; // 每个 byte 使用 2 个 16 进制数来表示)
2. 标准属性
2.1 compatible
compatible 属性值由 string list 组成,定义了设备的兼容性,推荐格式为manufacturer,model
,manufacturer 描述了生产商,model 描述了型号:
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
根节点下 compatible 属性,用来选择哪一个“ machine desc”:一个内核可以支持 machine A,也支持 machine B,内核启动后会根据根节点的compatible 属性找到对应的 machine desc 结构体,执行其中的初始化函数
compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960"
设备节点下的compatible属性,是用来匹配驱动程序的,一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值核 OF 匹配表中的任何一个值相等,那么就表示这个设备可以使用这个驱动。如下:
static const struct of_device_id imx_wm8960_dt_ids[] = {
{ .compatible = "fsl,imx-audio-wm8960", },
{ /* sentinel */ }
};
2.2 model
model 属性值是一个 string,指明了设备的厂商和型号,推荐格式为manufacturer,model
。
model = "Freescale i.MX6 ULL 14x14 EVK Board";
2.3 phandle
phandle 属性值是一个 u32,为设备树中唯一的节点指定一个数字标识符,用于其它节点指明关系。
2.4 status
status 属性值是一个 string,表示设备的运行状态,可用值如下表:
2.5 #address-cells 和 #size-cells
#address-cells and #size-cells 属性值是一个 u32,可以用在任何拥有子节点的设备中,并描述子设备节点应该如何寻址。
#address-cells
属性定义子节点 reg 属性中地址字段所占用的字长,也就是占用 u32 单元格的数量。
#size-cells
属性定义子节点 reg 属性值的长度所占用的 u32 单元格的数量。
2.6 reg
reg 属性值是一个 prop-encoded-array,用来描述设备地址空间资源信息,一般是某个外设的寄存器地址范围信息,包括起始地址和地址长度。
reg = <address1 length1 address2 length2 address3 length3……>
2.7 name(过时了,建议不用)
它的值是字符串,用来表示节点的名字。在跟 platform_driver 匹配时,优先级最低。
compatible 属性在匹配过程中,优先级最高
3. 常用节点
3.1 根节点
树是由树根开始的,在设备树中称之为根节点,路径为/
,根节点不需要节点名称,所有子节点都是挂在根节点上的,可以看到最简单的根节点如下:
/ {
};
根节点的属性有:
3.2 aliases
aliases 节点用来定义别名,为了内核方便访问节点。
aliases {
can0 = &flexcan1;
can1 = &flexcan2;
ethernet0 = &fec1;
ethernet1 = &fec2;
gpio0 = &gpio1;
gpio1 = &gpio2;
gpio2 = &gpio3;
gpio3 = &gpio4;
gpio4 = &gpio5;
i2c0 = &i2c1;
i2c1 = &i2c2;
i2c2 = &i2c3;
i2c3 = &i2c4;
mmc0 = &usdhc1;
mmc1 = &usdhc2;
serial0 = &uart1;
serial1 = &uart2;
serial2 = &uart3;
serial3 = &uart4;
serial4 = &uart5;
serial5 = &uart6;
serial6 = &uart7;
serial7 = &uart8;
spi0 = &ecspi1;
spi1 = &ecspi2;
spi2 = &ecspi3;
spi3 = &ecspi4;
usbphy0 = &usbphy1;
usbphy1 = &usbphy2;
};
3.3 chosen
chosen 节点是为了uboot 向 Linux 内核传递数据,重点是 bootargs 参数,一般.dts 文件中 chosen 节点通常为空或者内容很少。
chosen {
stdout-path = &uart1;
};
3.4 CPU
一般不需要我们设置,在 dtsi 文件中都定义好了:
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
.......
}
};
3.5 memory
芯片厂家不可能事先确定你的板子使用多大的内存,所以 memory 节点需要板厂设置,比如:
memory {
reg = <0x80000000 0x20000000>;
};
4. OF 操作函数
of.h // 提供设备树的一般处理函数,
// 比如 of_property_read_u32(读取某个属性的 u32 值),
// of_get_child_count(获取某个 device_node 的子节点数)
of_address.h // 地址相关的函数,
// 比如 of_get_address(获得 reg 属性中的 addr, size 值)
// of_match_device (从 matches 数组中取出与当前设备最匹配的一项)
of_dma.h // 设备树中 DMA 相关属性的函数
of_gpio.h // GPIO 相关的函数
of_graph.h // GPU 相关驱动中用到的函数, 从设备树中获得 GPU 信息
of_iommu.h // 很少用到
of_irq.h // 中断相关的函数
of_mdio.h // MDIO (Ethernet PHY) API
of_net.h // OF helpers for network devices.
of_pci.h // PCI 相关函数
of_pdt.h // 很少用到
of_reserved_mem.h // reserved_mem 的相关函数
4.1 查找节点
通过节点名字查找节点
extern struct device_node *of_find_node_by_name(struct device_node *from,
const char *name);
参数意义如下:
- from:开始查找的节点,NULL 表示根节点
- name:要查找的节点名称
返回值为找到的节点,NULL 为查找失败。
通过节点类型查找节点
extern struct device_node *of_find_node_by_type(struct device_node *from,
const char *type);
type 参数指定要查看节点对应的 type 字符串,也就是 device_type 属性值。
通过 device_type 和 compatible 查找节点
extern struct device_node *of_find_compatible_node(struct device_node *from,
const char *type, const char *compat);
通过 of_device_id 匹配表来查找节点
extern struct device_node *of_find_matching_node_and_match(
struct device_node *from,
const struct of_device_id *matches,
const struct of_device_id **match);
通过路径来查找节点
static inline struct device_node *of_find_node_by_path(const char *path)
{
return of_find_node_opts_by_path(path, NULL);
}
4.2 获取父子节点
获取父节点
extern struct device_node *of_get_parent(const struct device_node *node);
迭代查找子节点
extern struct device_node *of_get_next_child(const struct device_node *node,
struct device_node *prev);
4.3 提取属性值
查找指定节点的属性
extern struct property *of_find_property(const struct device_node *np,
const char *name,
int *lenp);
参数 name 指属性名字,lenp 指属性值的字节数。
获取属性中元素的数量
extern int of_property_count_elems_of_size(const struct device_node *np,
const char *propname, int elem_size);
参数 propname 是需要统计元素数量的属性名字,参数 elem_size 是元素的长度。
返回值是获取到的属性元素数量。
eg. reg 属性的值通常是一个数组,使用此函数可以获取的数组的大小。
从属性中获取指定索引的 u32 类型数据值
extern int of_property_read_u32_index(const struct device_node *np,
const char *propname,
u32 index, u32 *out_value);
参数 out_value 用来返回获取到的值。
返回值用来表示是否获取成功。
从属性中获取数组值
extern int of_property_read_u8_array(const struct device_node *np,
const char *propname, u8 *out_values, size_t sz);
extern int of_property_read_u16_array(const struct device_node *np,
const char *propname, u16 *out_values, size_t sz);
extern int of_property_read_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values,
size_t sz);
extern int of_property_read_u64_array(const struct device_node *np,
const char *propname,
u64 *out_values,
size_t sz);
eg. reg 属性的值通常是一个数组,使用这个函数可以一次读取出一个数组,也就是 reg 属性的全部值。
从属性中获取布尔值/整形值
/**
* of_property_read_bool - Findfrom a property
* @np: device node from which the property value is to be read.
* @propname: name of the property to be searched.
*
* Search for a property in a device node.
* Returns true if the property exist false otherwise.
*/
static inline bool of_property_read_bool(const struct device_node *np,
const char *propname)
{
struct property *prop = of_find_property(np, propname, NULL);
return prop ? true : false;
}
static inline int of_property_read_u8(const struct device_node *np,
const char *propname,
u8 *out_value)
{
return of_property_read_u8_array(np, propname, out_value, 1);
}
static inline int of_property_read_u16(const struct device_node *np,
const char *propname,
u16 *out_value)
{
return of_property_read_u16_array(np, propname, out_value, 1);
}
static inline int of_property_read_u32(const struct device_node *np,
const char *propname,
u32 *out_value)
{
return of_property_read_u32_array(np, propname, out_value, 1);
}
static inline int of_property_read_s32(const struct device_node *np,
const char *propname,
s32 *out_value)
{
return of_property_read_u32(np, propname, (u32*) out_value);
}
从属性中获取字符串
extern int of_property_read_string(struct device_node *np,
const char *propname,
const char **out_string);
获取#address-cells 和#size-cells 属性值
extern int of_n_addr_cells(struct device_node *np);
extern int of_n_size_cells(struct device_node *np);
# 驱动示例
atk-led{
#address-cells = <1>;
#size-cells = <1>;
compatible = "atk,led";
status = "okay";
reg = < 0x20C406C 4
0x20E0068 4
0x20E02F4 4
0x209C000 4>;
};
#include <linux/console.h>
#include <linux/dma-mapping.h>
#include <linux/init.h>
#include <linux/ipu-v3.h>
#include <linux/module.h>
#include <linux/mxcfb.h>
#include <linux/mxc_v4l2.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/videodev2.h>
#include <linux/vmalloc.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/of_platform.h>
#include <linux/uaccess.h>
typedef struct
{
volatile uint32_t GPIO_DR;
volatile uint32_t GPIO_GDIR;
volatile uint32_t GPIO_PSR;
volatile uint32_t GPIO_ICR1;
volatile uint32_t GPIO_ICR2;
volatile uint32_t GPIO_IMR;
volatile uint32_t GPIO_ISR;
volatile uint32_t GPIO_EDGE_SEL;
}GPIO_TypeDef;
static GPIO_TypeDef *GPIO;
static volatile uint32_t *CCM_CCGR;
static volatile uint32_t *IOMUXC_SW_MUX_CTL_PAD;
static volatile uint32_t *IOMUXC_SW_PAD_CTL_PAD;
typedef struct
{
struct device_node *devNode; /* 设备节点 */
int reg_num; /* reg长度 */
u32 *reg_value;
char drv_name[50]; /* 驱动名称 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
dev_t devt; /* 设备号 */
struct device *device; /* 设备 */
char device_name[50]; /* 设备名称 */
struct class *class; /* 类 */
char class_name[50]; /* 类名称 */
}atk_led_private;
atk_led_private led_pri = {
.devNode = NULL,
.drv_name = "led_drv",
.reg_value = NULL,
.major = 0,
.minor = 0,
.devt = 0,
.device = NULL,
.device_name = "led_dev",
.class = NULL,
.class_name = "led_class",
};
static int led_open(struct inode *inode, struct file *file)
{
uint32_t val;
/* 使能GPIO1时钟 */
*CCM_CCGR |= (3 << 26);
/* 设置IO复用 */
val = *IOMUXC_SW_MUX_CTL_PAD;
val &= ~(0x0F);
val |= 5;
*IOMUXC_SW_MUX_CTL_PAD = val;
/* 设置IO属性 */
/* 设置IO方向,设置GPIO1_IO03为输出 */
GPIO->GPIO_GDIR |= (1 << 3);
return 0;
}
static int led_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t led_write(struct file *file, const char __user *buff, size_t size, loff_t *ppos)
{
uint8_t status;
int err;
err = copy_from_user(&status, buff, 1);
if(status == 1)
{
GPIO->GPIO_DR &= ~(1<<3);
}
else
{
GPIO->GPIO_DR |= (1<<3);
}
return 1;
}
static const struct file_operations led_op = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.write = led_write,
};
static int atk_led_probe(struct platform_device *device)
{
int err;
int i;
printk("atk_led_probe\r\n");
/* 获取节点 */
led_pri.devNode = of_find_node_by_path("/atk-led");
if(led_pri.devNode == NULL)
{
printk("of_find_node_by_path fail\r\n");
return -1;
}
/* 获取数字属性的长度 */
led_pri.reg_num = of_property_count_elems_of_size(led_pri.devNode, "reg", sizeof(uint32_t));
if(led_pri.reg_num < 0) {
printk("of_property_count_elems_of_size error\r\n");
return -1;
}
/* 读取数字属性 */
led_pri.reg_value = (u32*)kmalloc(sizeof(u32)*led_pri.reg_num, GFP_KERNEL);
err = of_property_read_u32_array(led_pri.devNode, "reg", led_pri.reg_value, led_pri.reg_num);
if(err != 0) {
printk("of_property_read_u32_array fail\r\n");
return -1;
}
/* 打印属性值 */
for(i=0; i<led_pri.reg_num; i+=2) {
printk("reg = %x %x \r\n", led_pri.reg_value[i], led_pri.reg_value[i+1]);
}
CCM_CCGR = ioremap(led_pri.reg_value[0], led_pri.reg_value[1]);
IOMUXC_SW_MUX_CTL_PAD = ioremap(led_pri.reg_value[2], led_pri.reg_value[3]);
IOMUXC_SW_PAD_CTL_PAD = ioremap(led_pri.reg_value[4], led_pri.reg_value[5]);
GPIO = ioremap(led_pri.reg_value[6], led_pri.reg_value[7]);
led_pri.major = register_chrdev(0, led_pri.drv_name, &led_op);
led_pri.devt = MKDEV(led_pri.major, led_pri.minor);
led_pri.class = class_create(THIS_MODULE, led_pri.class_name);
if(IS_ERR(led_pri.class))
{
printk("class_create error\r\n");
err = PTR_ERR(led_pri.class);
goto err_class_create_out;
}
led_pri.device = device_create(led_pri.class, NULL, led_pri.devt, NULL, led_pri.device_name);
if(IS_ERR(led_pri.device))
{
printk("device_create error\r\n");
err = PTR_ERR(led_pri.device);
goto err_device_create_out;
}
return 0;
err_device_create_out:
class_destroy(led_pri.class);
err_class_create_out:
unregister_chrdev(led_pri.major, led_pri.drv_name);
return err;
}
static int atk_led_remove(struct platform_device *device)
{
printk("atk_led_drv_remove\r\n");
device_destroy(led_pri.class, led_pri.devt);
class_destroy(led_pri.class);
unregister_chrdev(led_pri.major, led_pri.drv_name);
return 0;
}
static struct of_device_id atk_led_id[] = {
{.compatible = "atk,led"},
{}
};
static struct platform_driver atk_led_driver =
{
.probe = atk_led_probe,
.remove = atk_led_remove,
.driver = {
.owner = THIS_MODULE,
.name = "atk,led",
.of_match_table = atk_led_id,
},
};
static int __init atk_led_init(void)
{
int err;
printk("atk_led_init");
err = platform_driver_register(&atk_led_driver);
return 0;
}
static void __exit atk_led_exit(void)
{
platform_driver_unregister(&atk_led_driver);
}
module_init(atk_led_init);
module_exit(atk_led_exit);
MODULE_LICENSE("GPL");
# 补充信息
1. 查看设备树信息
cd /sys/firmware/devicetree/base/
节点是目录,属性是文件,比如当前设备树下既有个atk-led的设备节点,这个就是书写的LED的设备树
还可以使用 cat 指令查看属性值:
2. 查看 platform_device 的信息
cd /sys/devices/platform
20c406c.atk-led就是编写的led的platform_device,进去看一下
其中driver就是和atk-led驱动设备匹配的驱动程序了
3. 查看 platform_driver 的信息
cd /sys/bus/platform/drivers/
这个就是和atk-led平台设备匹配的驱动程序啦