设备树模型引入
设备树作用:
设备树只是用来给内核里的驱动程序,指定硬件的信息。比如LED驱动,在内核的驱动程序里去操作寄存器,但是操作
哪一个引脚?这由设备树指定。
为什么引入设备树?
总线驱动模型通过驱动工程师编写broad.c注册platform_device指定硬件资源,但是这样带来一个问题?
以LED驱动为例,如果你要更换LED所用的GPIO引脚,需要修改驱动程序源码broad.c、重新编译驱动、重新加载驱动。
随着ARM芯片的流行,内核中针对这些ARM板保存有大量的、没有技术含量的文件
一个单板启动时,u-boot先运行,它的作用是启动内核。U-boot会把内核和设备树文件都读入内存,然后启动内核。在启动
内核时会把设备树在内存中的地址告诉内核。
我们需要编写设备树文件(dts: device tree source),它需要编译为dtb(device tree blob)文件,内核。
设备树的语法
设备树中的基本单元,被称为“node”,其格式为:
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
};
label是标号,可以省略。label的作用是为了方便地引用node
/ {
uart0: uart@fe001000 {
compatible="ns16550";
reg=<0xfe001000 0x100>;
};
};
属性的格式:简单地说,properties就是“name=value”,value有多种取值方式
Property取值只有3种:
arrays of cells(1个或多个32位数据, 64位数据使用2个32位数据表示),
cell就是一个32位的数据,用尖括号包围起来 interrupts = <17 0xc>;
64bit数据使用2个cell来表示,用尖括号包围起来: clock-frequency = <0x00000001 0x00000000>;
string(字符串)
有结束符的字符串),用双引号包围起来: compatible = "simple-bus";
bytestring(1个或多个字节)
bytestring(字节序列) ,用中括号包围起来:
local-mac-address = [00 00 12 34 56 78];
local-mac-address = [000012345678];
常用的属性:
#address-cells、#size-cells
cell指一个32位的数值,
address-cells:address要用多少个32位数来表示;
size-cells:size要用多少个32位数来表示。
比如一段内存,怎么描述它的起始地址和大小?
下例中,address-cells为1,所以reg中用1个数来表示地址,即用0x80000000来表示地址;size-cells为1,所以
reg中用1个数来表示大小,即用0x20000000表示大小:
/ {
#address-cells = <1>;
#size-cells = <1>;
memory {
reg = <0x80000000 0x20000000>;
};
};
compatible:“compatible”表示“兼容”,对于某个LED,内核中可能有A、B、C三个驱动都支持它,那可以这样写:
led {
compatible = “A”, “B”, “C”;
};
内核启动时,就会为这个LED按这样的优先顺序为它找到驱动程序:A、B、C。
status
dtsi文件中定义了很多设备,但是在你的板子上某些设备是没有的。这时你可以给这个设备节点添加一个status属性,设置为“disabled”:
&uart1 {
status = "disabled";
};
reg:reg的本意是register,用来表示寄存器地址。
但是在设备树里,它可以用来描述一段空间。反正对于ARM系统,寄存器和内存是统一编址的,即访问寄存器时用某块
地址,访问内存时用某块地址,在访问方法上没有区别。reg属性的值,是一系列的“address
size”,用多少个32位的数来表示address和size,由其父节点的#address-cells、#size-cells决定。
/ {
#address-cells = <1>;
#size-cells = <1>;
memory {
reg = <0x80000000 0x20000000>;
};
};
dts文件中必须有一个根节点:
/ {
model = "SMDK24440";
compatible = "samsung,smdk2440";
#address-cells = <1>;
#size-cells = <1>;
};
根节点中必须有这些属性:
#address-cells
#size-cells
compatible
model
内核对设备树的处理
dts在PC机上被编译为dtb文件;
u-boot把dtb文件传给内核;
内核解析dtb文件,把每一个节点都转换为device_node结构体;
对于某些device_node结构体,会被转换为platform_device结构体。
device_node怎么转换为platform_device?
A. platform_device中含有resource数组, 它来自device_node的reg, interrupts属性;
B. platform_device.dev.of_node指向device_node, 可以通过它获得其他属性
引入设备树后platform_device如何与platform_driver配对? 内核自动注册platform_device
从设备树转换得来的platform_device会被注册进内核里,以后当我们每注册一个platform_driver时,它们就会
两两确定能否配对,如果能配对成功就调用platform_driver的probe函数。
匹配规则
比较:platform_device. dev.of_node和platform_driver.driver.of_match_table。
常用函数
对于没有转换为platform_device的节点,如何使用? 内核提供函数直接访问设备树。内核里操作设备树的常用函数:
设备树中的每一个节点,在内核里都有一个device_node;你可以使用device_node去找到对应的platform_device。
extern struct platform_device *of_find_device_by_node(struct device_node *np)
设备树中的节点被转换为platform_device后,设备树中的reg属性、interrupts属性也会被转换为“resource”。
可以使用platform_get_resource取出这些资源
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);
找节点函数:
找属性函数:在这个节点中找到名为name的属性lenp用来保存这个属性的长度,即它的值的长度
extern struct property *of_find_property(const struct device_node *np,const char *name, int *lenp);
根据名字找到节点的属性,确定它的值有多少个元素(elem)
int of_property_count_elems_of_size(const struct device_node *np,const char *propname, int elem_size)
读整数u32/u64
extern int of_property_read_u64(const struct device_node *np,const char *propname, u64 *out_value);
xxx_node {
name1 = <0x50000000>;
name2 = <0x50000000 0x60000000>;
};
调用of_property_read_u32 (np, “name1”, &val)时,val将得到值0x50000000;
调用of_property_read_u64 (np, “name2”, &val)时,val将得到值0x0x6000000050000000。
读字符串
int of_property_read_string(const struct device_node *np, const char *propname,const char**out_string);
设备树模型实现LED驱动模型
led_drv.c led_drv_test.c 不变
丢弃braod.c, 编写设备树文件
chipY_gpio.c 通过内核提供的函数获取硬件资源
注意事项:
设备树节点要与platform_driver能匹配
设备树要有compatible属性,它的值是一个字符串
platform_driver中要有of_match_table,其中一项的.compatible成员设置为一个字符串
上述2个字符串要一致
设备树节点指定资源,platform_driver获得资源
驱动程序中根据pin属性来确定引脚,那么我们就在设备树节点中添加pin属性。
驱动程序中,可以从platform_device中得到device_node,再用of_property_read_u32得到属性的值:
dts文件
#define GROUP_PIN(g,p) ((g<<16) | (p))
/ {
100ask_led@0 {
compatible = "100as,leddrv";
pin = <GROUP_PIN(3, 1)>;
};
100ask_led@1 {
compatible = "100as,leddrv";
pin = <GROUP_PIN(5, 8)>;
};
};
chipY_gpio.c
#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/platform_device.h>
#include <linux/of.h>
#include "led_opr.h"
#include "leddrv.h"
#include "led_resource.h"
static int g_ledpins[100];
static int g_ledcnt = 0;
static int board_demo_led_init (int which)
{
printk("init gpio: group %d, pin %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
switch(GROUP(g_ledpins[which]))
{
case 0:
{
printk("init pin of group 0 ...\n");
break;
}
case 1:
{
printk("init pin of group 1 ...\n");
break;
}
case 2:
{
printk("init pin of group 2 ...\n");
break;
}
case 3:
{
printk("init pin of group 3 ...\n");
break;
}
}
return 0;
}
static int board_demo_led_ctl (int which, char status)
{
printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
switch(GROUP(g_ledpins[which]))
{
case 0:
{
printk("set pin of group 0 ...\n");
break;
}
case 1:
{
printk("set pin of group 1 ...\n");
break;
}
case 2:
{
printk("set pin of group 2 ...\n");
break;
}
case 3:
{
printk("set pin of group 3 ...\n");
break;
}
}
return 0;
}
static struct led_operations board_demo_led_opr = {
.init = board_demo_led_init,
.ctl = board_demo_led_ctl,
};
struct led_operations *get_board_led_opr(void)
{
return &board_demo_led_opr;
}
static int chip_demo_gpio_probe(struct platform_device *pdev)
{
struct device_node *np;
int err = 0;
int led_pin;
np = pdev->dev.of_node;
if (!np)
return -1;
err = of_property_read_u32(np, "pin", &led_pin);
g_ledpins[g_ledcnt] = led_pin;
led_class_create_device(g_ledcnt);
g_ledcnt++;
return 0;
}
static int chip_demo_gpio_remove(struct platform_device *pdev)
{
int i = 0;
int err;
struct device_node *np;
int led_pin;
np = pdev->dev.of_node;
if (!np)
return -1;
err = of_property_read_u32(np, "pin", &led_pin);
for (i = 0; i < g_ledcnt; i++)
{
if (g_ledpins[i] == led_pin)
{
led_class_destroy_device(i);
g_ledpins[i] = -1;
break;
};
}
for (i = 0; i < g_ledcnt; i++)
{
if (g_ledpins[i] != -1)
break;
}
if (i == g_ledcnt)
g_ledcnt = 0;
return 0;
}
static const struct of_device_id ask100_leds[] = {
{ .compatible = "100as,leddrv" },
{ },
};
static struct platform_driver chip_demo_gpio_driver = {
.probe = chip_demo_gpio_probe,
.remove = chip_demo_gpio_remove,
.driver = {
.name = "100ask_led",
.of_match_table = ask100_leds,
},
};
static int __init chip_demo_gpio_drv_init(void)
{
int err;
err = platform_driver_register(&chip_demo_gpio_driver);
register_led_operations(&board_demo_led_opr);
return 0;
}
static void __exit lchip_demo_gpio_drv_exit(void)
{
platform_driver_unregister(&chip_demo_gpio_driver);
}
module_init(chip_demo_gpio_drv_init);
module_exit(lchip_demo_gpio_drv_exit);
MODULE_LICENSE("GPL");
上机测试
重新编译设备树文件,不同单板不一样
imx6ull MINI
设备树文件是:内核源码目录中arch/arm/boot/dts/100ask_imx6ull_mini.dts
修改、编译后得到arch/arm/boot/dts/100ask_imx6ull_mini.dtb文件