目录
设备树可以说是platform平台的升级版,经过前面的学习,我知道了platform分为两部分,平台设备和平台驱动。而设备树就是替代了平台设备。平台设备是以C文件的方式加载的,而设备树是以配置文件加载的(xxx.dtb,就够有点类似于json),在linux系统启动的时候就加载设备树文件。其实平时我们只需要会修改设备树就好了,不需要自己去写出全部内容,芯片厂家会提供相关设备树的,你只需要根据需要去添加外设就好了。
1. 设备树的语法
1.1 设备节点
设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设
备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。
label: node-name@unit-address {
// 各种属性(1.2讲解)
// 子节点(节点是可以嵌套的)
}
- lable: 你给这个节点的别名,设备树的其他要要用到的就可以使用**&label**来引用。
- node-name: 节点名字,一般命名应该与功能挂钩
- unit-address: 一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话“unit-address”可以不要
1.2 属性的数据类型
每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流
1.2.1 字符串
compatible = "arm,cortex-a7" // 这种类型的字符串是内置的,在平台驱动加载到系通过,匹配时的name来源就是这里
1.2.1 32位无符号整数
这种类型一般用来表示寄存器的地址,以及字符串的偏移量。用<>包裹起来,可有多个整数,用空格隔开
reg = <0x2020>;
reg = <0x2020 0x20200 0x12344>;
1.2.2 字符串列表
用逗号隔开
compatible = "fsl,imx6ull-fire-nand", "fsl,imx6ull-gpmi-nand";
1.3 标准属性
1.3.1 compatible属性
这个属性是很重要的,该属性是一个字符串类型的,用于将设备和驱动绑定起来,在驱动被加载到系统的时候会有一个匹配的过程,设备端的依据就是该属性,驱动就是那个of匹配表
// 驱动的of匹配表实例
static const struct of_device_id rgb_led[] = {
{.compatible = "fire,my_rgb_led"},
};
static struct platform_driver rgb_led_platform_driver = {
.probe = rgb_led_probe,
.remove = rgb_led_remove,
.driver = {
.name = "rgb-leds-device-tree",
.owner = THIS_MODULE,
//.remove = rgb_led_remove,
.of_match_table = rgb_led, // match匹配的时候会用到这个列表里面的compatible和设备树里面的compatible对比
}
};
1.3.2 status属性
该属性与设备状态有关
值 | 描述 |
---|---|
“okay” | 设备可操作 |
“disabled” | 设备不可操作 |
“fail” | 设备因为检测到错误而不可操作 |
“fail-sss” | 与"fail"相同,后面的sss部分是检测到的错误内容 |
1.3.3 reg属性
reg属性的值一般是(address, length)对。address表示该设备的起始地址,length表示该设备的地址长度
1.3.4 #address-cells和#size-cells
这两个属性都是unsigned int 类型的,用于描述子节点的地址信息。前者决定了reg里面的地址信息所占的字长(32位),后者决定了reg里面的长度所占的字长(32位)。
1.3.5 model属性
该属性值是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的
2. 设备树常用的OF操作函数
OF操作函数的目的就是为了从设备树中提取驱动需要的资源
2.1 查找节点的OF函数
2.1.1 of_find_node_by_name
通过节点名字查找节点
struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
- from: 开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树
- name: 要查找的节点名字
2.1.2 of_find_compatible_node
通过节点的compatible查找属性(因为device_type已经不使用了,直接赋值为NULL)
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible)
- from: 开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树
- 要查找的device_type 属性值,直接给NULL,忽略掉它
- 要查找的节点所对应的 compatible 属性列表
2.1.3 of_find_node_by_path
通过全路径查找节点
inline struct device_node *of_find_node_by_path(const char *path)
- path: 从根节点开始的路径,类似于绝对路径
2.2 查找父/子节点的OF函数
2.2.1 of_get_parent
获得指定节点的父节点
struct device_node *of_get_parent(const struct device_node *node)
2.2.2 of_get_next_child
用迭代的方式查找子节点
struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)
- node: 父节点
- prev: 该节点为父节点下的一个节点,我们最终获取的就是该节点后面一个节点,如果该节点是父节点最后一个子节点,name就会返回NULL
2.3 提取属性的OF函数
2.3.1 of_find_property
用于查找指定的属性
property *of_find_property(const struct device_node *np, const char *name, int *lenp)
- np: 设备节点
- name: 属性名字
- lenp: 属性的字节数
2.3.2 of_property_count_elems_of_size
用于获取属性中的元素数量,比如reg有多个元素
int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size)
- np: 设备节点
- propname: 需要统计的属性名
- elem_size: 元素长度
- 返回值: 元素数量
2.3.3 of_property_read_u32_index
从属性中获取指定标号的u32类型的数据值
int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value)
- np: 设备节点
- propname: 要读取的属性名字
- index: 要读取的值标号(从0开始)
- out_value: 读取的数据存放地址
- 返回值:0 读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
2.3.4 批量读取
下面四个函数会以数组的方式一次性读取多个值
int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)
int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz)
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz)
int of_property_read_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz)
- np: 设备节点
- propname: 要读取的属性名
- out_values: 存放数据的数组的起始地址
- sz: 要读取的个数
- 返回值:0,读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
2.3.5 单个读取
当reg只有一个值的时候,可以使用下面的函数读取
int of_property_read_u8(const struct device_node *np, const char *propname, u8 *out_value)
int of_property_read_u16(const struct device_node *np, const char *propname, u16 *out_value)
int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value)
int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value)
2.3.6 of_property_read_string
用户读取属性中的字符串值
int of_property_read_string(struct device_node *np, const char *propname, const char **out_string)
- np: 设备节点
- propname: 属性名
- out_string: 读取到的字符串值
2.3.7 of_n_addr_cells
获取#address-cells属性值
int of_n_addr_cells(struct device_node *np)
2.3.8 of_n_size_cells
获取#size-cells属性值
int of_n_size_cells(struct device_node *np)
2.3 其他OF函数
2.3.1 of_iomap
可用于直接内存映射(映射的就是reg的值),比ioremap更加方便,但我不知道我的板子用不了这个函数
void __iomem *of_iomap(struct device_node *np, int index)
- np: 设备节点
- index: reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0
- 返回值:经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败
3. 实例(野火I.MX6ULL pro开发板)
3.1 设备树
在野火官方提供的设备树的根节点下添加如下内容
/* RGB_LED设备节点 */
my_rgb_led {
#address-cells = <1>;
#size-cells = <1>;
compatible = "fire,my_rgb_led"; // 驱动的of_device_id表要和这里的一直
led_red@0x20C406C {
compatible = "fire,led_red";
reg = <0x020C406C 0x00000004
0x020E006C 0x00000004
0x020E02F8 0x00000004
0x0209C004 0x00000004
0x0209C000 0x00000004>;
pin = <4>; /* 引脚*/
clock_offset = <26>; /* 时钟配置位的偏移量 */
status = "okay";
};
led_green@0x020C4074 {
compatible = "fire,led_green";
reg = <0x020C4074 0x00000004
0x020E01E0 0x00000004
0x020E046C 0x00000004
0x020A8004 0x00000004
0x020A8000 0x00000004>;
pin = <20>;
clock_offset = <12>;
status = "okay";
};
led_blue@0x020C4074 {
compatible = "fire,led_blue";
reg = <0x020C4074 0x00000004
0x020E01DC 0x00000004
0x020E0468 0x00000004
0x020A8004 0x00000004
0x020A8000 0x00000004>;
pin = <19>;
clock_offset = <12>;
status = "okay";
};
};
3.2 驱动文件
/* 编写要点
1. 完成平台驱动(platform_driver_register())的注册,probe, remove函数的实现
2. 完成驱动(driver_register())的注册,增加设备节点,实现file_operation结构体的相关函数
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/io.h>
static int major = 0; // 主设备号
struct class *led_class;
// 获取到的资源存在该结构体内
struct led_resource
{
//rgb_led_red 的设备树节点
struct device_node *device_node;
// 存放物理地址转换后的虚拟地址
u32 *virtual_CCM_CCGR;
u32 *virtual_IOMUXC_SW_MUX_CTL_PAD;
u32 *virtual_IOMUXC_SW_PAD_CTL_PAD;
u32 *virtual_GDIR;
u32 *virtual_DR;
int pin;
int clock_offset;
};
struct led_resource rgb_led_resource[3] = {0};
// 配置引脚
static int gpio_init(int which)
{
/* 初始化引脚 */
// 开时钟
*(rgb_led_resource[which].virtual_CCM_CCGR) |= (3<<(rgb_led_resource[which].clock_offset));
// 设置为GPIO模式
*(rgb_led_resource[which].virtual_IOMUXC_SW_MUX_CTL_PAD) &= ~0xf;
*(rgb_led_resource[which].virtual_IOMUXC_SW_MUX_CTL_PAD) |= 5;
// 设置引脚属性
//*(leds[which].va_iomuxc_pad_ctl) = 0x1F838;
// 设置为输出
*(rgb_led_resource[which].virtual_GDIR) |= (1<<(rgb_led_resource[which].pin));
return 0;
}
// 获得资源, 并将物理地址转为虚拟地址
static void pyaddr_to_viaddr(void)
{
int i = 0;
int ret = 0;
u32 regdata[20];
if (rgb_led_resource[i].device_node == NULL) return;
for (i=0;i<ARRAY_SIZE(rgb_led_resource);i++) {
// 获取引脚的位置 , 时钟配置位在寄存器的偏移量
ret = of_property_read_u32(rgb_led_resource[i].device_node, "pin", &(rgb_led_resource[i].pin));
if (ret != 0) {
printk("%s,%s,%d error: not find pin \n", __FILE__, __FUNCTION__, __LINE__);
return;
}
ret = of_property_read_u32(rgb_led_resource[i].device_node, "clock_offset", &(rgb_led_resource[i].clock_offset));
if (ret != 0) {
printk("%s,%s,%d error: not find clock_offset \n", __FILE__, __FUNCTION__, __LINE__);
return;
}
ret = of_property_read_u32_array(rgb_led_resource[i].device_node, "reg", regdata, 10);
if (ret != 0) {
printk("%s,%s,%d error: not find regdata \n", __FILE__, __FUNCTION__, __LINE__);
return;
}
rgb_led_resource[i].virtual_CCM_CCGR = ioremap(regdata[0], 4);
rgb_led_resource[i].virtual_IOMUXC_SW_MUX_CTL_PAD = ioremap(regdata[2], 4);
rgb_led_resource[i].virtual_IOMUXC_SW_PAD_CTL_PAD = ioremap(regdata[4], 4);
rgb_led_resource[i].virtual_GDIR = ioremap(regdata[6], 4);
rgb_led_resource[i].virtual_DR = ioremap(regdata[8], 4);
//rgb_led_resource[i].virtual_CCM_CCGR = of_iomap(rgb_led_resource[i].device_node, 0);
//rgb_led_resource[i].virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(rgb_led_resource[i].device_node, 1);
//rgb_led_resource[i].virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(rgb_led_resource[i].device_node, 2);
//rgb_led_resource[i].virtual_GDIR = of_iomap(rgb_led_resource[i].device_node, 3);
//rgb_led_resource[i].virtual_DR = of_iomap(rgb_led_resource[i].device_node, 4);
//printk("======debug ccgr addr: %x=========\n", rgb_led_resource[i].virtual_CCM_CCGR);
//printk("======debug mux addr: %x=========\n", rgb_led_resource[i].virtual_IOMUXC_SW_MUX_CTL_PAD);
//printk("======debug pad addr: %x=========\n", rgb_led_resource[i].virtual_IOMUXC_SW_PAD_CTL_PAD);
//printk("======debug gdir addr: %x=========\n", rgb_led_resource[i].virtual_GDIR);
//printk("======debug dr addr: %x=========\n", rgb_led_resource[i].virtual_DR);
//printk("======debug pin: %d=========\n", rgb_led_resource[i].pin);
//printk("======debug clock_offset: %d=========\n", rgb_led_resource[i].clock_offset);
printk("%s\n", __FUNCTION__);
}
}
static int led_drv_open(struct inode *node, struct file *file)
{
int minor = iminor(node);
printk("=============%s\n============", __FUNCTION__);
// 配置gpio模式
gpio_init(minor);
printk("===============%s===============\n", __FUNCTION__);
return 0;
}
static int led_drv_close(struct inode *node, struct file *file)
{
printk("%s\n", __FUNCTION__);
return 0;
}
static ssize_t led_drv_read(struct file *file, char __user * buf, size_t size, loff_t *offset)
{
printk("%s\n", __FUNCTION__);
return 0;
}
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int minor;
char status;
struct inode *node = file_inode(file);
minor = iminor(node);
copy_from_user(&status, buf, 1);
if (status) {
// 开灯
*(rgb_led_resource[minor].virtual_DR) &= ~(1<<(rgb_led_resource[minor].pin));
} else {
*(rgb_led_resource[minor].virtual_DR) |= 1<<(rgb_led_resource[minor].pin);
}
printk("%s\n", __FUNCTION__);
return 0;
}
static struct file_operations led_drv = {
.owner = THIS_MODULE, //gcc用法
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
static int rgb_led_probe(struct platform_device *pdev)
{
int err;
// 获取资源
struct device_node *rgb_device_node = NULL;
// led节点的父节点
rgb_device_node = of_find_node_by_path("/my_rgb_led");
if (rgb_device_node == NULL) {
printk("%s,%s,%d error: not find /my_reg_led\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
// red_led资源
rgb_led_resource[0].device_node = of_find_node_by_name(rgb_device_node, "led_red");
if (rgb_led_resource[0].device_node == NULL) {
printk("%s,%s,%d error: not find led_red\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
// red_green资源
rgb_led_resource[1].device_node = of_find_node_by_name(rgb_device_node, "led_green");
if (rgb_led_resource[1].device_node == NULL) {
printk("%s,%s,%d error: not find led_green\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
// red_blue资源
rgb_led_resource[2].device_node = of_find_node_by_name(rgb_device_node, "led_blue");
if (rgb_led_resource[2].device_node == NULL) {
printk("%s,%s,%d error: not find led_blue\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
pyaddr_to_viaddr(); // 地址转换
// 完成字符设备的注册
major = register_chrdev(0, "rgb_led", &led_drv);
// 注册设备类
led_class = class_create(THIS_MODULE, "hxdled_class");
err = PTR_ERR(led_class);
if (IS_ERR(led_class)) {
unregister_chrdev(major, "rgb_led");
return -1;
}
// 注册设备节点,/dev/rgb_led%
device_create(led_class, NULL, MKDEV(major, 0), NULL, "led_red");
device_create(led_class, NULL, MKDEV(major, 1), NULL, "led_green");
device_create(led_class, NULL, MKDEV(major, 2), NULL, "led_blue");
printk("=======================%s========================\n", __FUNCTION__);
return 0;
}
static int rgb_led_remove(struct platform_device *dev)
{
int i =0;
// 释放虚拟地址
for (i=0;i<ARRAY_SIZE(rgb_led_resource);i++) {
iounmap(rgb_led_resource[i].virtual_CCM_CCGR);
iounmap(rgb_led_resource[i].virtual_IOMUXC_SW_MUX_CTL_PAD);
iounmap(rgb_led_resource[i].virtual_IOMUXC_SW_PAD_CTL_PAD);
iounmap(rgb_led_resource[i].virtual_GDIR);
iounmap(rgb_led_resource[i].virtual_DR);
}
// 释放设备节点
device_destroy(led_class, MKDEV(major, 0));
device_destroy(led_class, MKDEV(major, 1));
device_destroy(led_class, MKDEV(major, 2));
// 释放设备类
class_destroy(led_class);
// 释放注册字符设备
unregister_chrdev(major, "rgb_led");
printk("%s\n", __FUNCTION__);
return 0;
}
static const struct of_device_id rgb_led[] = {
{.compatible = "fire,my_rgb_led"},
};
static struct platform_driver rgb_led_platform_driver = {
.probe = rgb_led_probe,
.remove = rgb_led_remove,
.driver = {
.name = "rgb-leds-device-tree",
.owner = THIS_MODULE,
//.remove = rgb_led_remove,
.of_match_table = rgb_led, // match匹配的时候会用到这个列表里面的compatible和设备树里面的compatible对比
}
};
// 平台驱动入口函数
static int __init platform_rgb_led_init(void)
{
int ret = 0;
// 完成平台驱动的注册
ret = platform_driver_register(&rgb_led_platform_driver);
printk("%s\n", __FUNCTION__);
return 0;
}
// 平台驱动出口函数
static void __exit platform_rgb_led_exit(void)
{
// 完成平台驱动的移除
platform_driver_unregister(&rgb_led_platform_driver);
printk("%s\n", __FUNCTION__);
return;
}
module_init(platform_rgb_led_init);
module_exit(platform_rgb_led_exit);
MODULE_LICENSE("GPL");
3.3 应用层
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<string.h>
int main(int argc, char **argv)
{
int fd;
char status;
if (argc != 3) {
printf("Usage %s <dev-path> <on/off>\n", argv[0]);
return -1;
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
perror("open error");
return -1;
}
if (strcmp(argv[2], "on") == 0) {
// 开灯
status = 1;
write(fd, &status, 1);
} else if (strcmp(argv[2], "off") == 0) {
// 关灯
status = 0;
write(fd, &status, 1);
} else {
printf("Usage %s <dev-path> <on/off>\n", argv[0]);
return -1;
}
close(fd);
return 0;
}
3.4 Makefile
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH CROSS_COMPILE
KERN_DIR = /home/hxd/workdir/ebf_linux_kernel_6ull_depth1/build_image/build
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o led_test led_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f hello_drv_test
obj-m += led_driver.o