对于写这一篇的原因是感慨自己在上一篇写Linux驱动之模拟PWM驱动时对设备树的浅薄了解,需要对Linux设备树的认识加强一番。本文主要参考了《Linux设备驱动开发详解:基于最新的Linux4.0内核》第18章ARMLinux设备树。首先先来看一个设备树结构的模板,我认为这个模板很有参考意义,特不辞辛劳从书上码到博客。
/{
node1 {
a-string-property = "a string"
a-string-list-property = "string1","string2"
a-byte-data-property = [0x01 0x23 0x34 0x56]
child-node1 {
child1-property;
child2-property = <1>;
a-string-property = "hello world"
};
child-node2 {
};
};
node2 {
an-empty-property;
a-cell-property = <1 2 3 4>
child-node1 {
};
}
}
这个模板例子基本表征了一个设备树源文件的结构:1个root节点“/”,root节点下面含一系列子节点,本例中位node1和node2;节点node1下又含有一系列子节点,本例中位child-node1和cchild-node2;各有一系列属性。这些属性可能为空,如:an-empty-property;可能为字符串,如a-string-property;可能为字符串数组,如a-string-list-property;可能为cells(由u32整数组成),如child2-property;可能为二进制数,如a-byte-data-property。首先了解一下什么是兼容属性,兼容属性用于驱动和设备的绑定。驱动里兼容属性若是在设备树文件里没有对应相同的兼容属性,设备和驱动是不能完成绑定的,更不能从设备树获得信息。
兼容属性:
在.dts文件的每一个设备节点都有一个兼容属性,兼容属性用于驱动和设备的绑定。兼容属性是一个字符串的列表。
下面我们结合几个实际的设备树例子leds(一个节点下又几个子节点)、rtc-pcf8563(挂在i2c总线下,没有子节点)、tsc(电阻触摸屏的设备树节点,没有子节点)讲下设备树。这几个例子熟了我认为基本上对设备树的运用也会很了解了。不懂的也可以结合Linux内核来运用设备树,Linux内核是一个学习宝库。
1.获得设备树一个节点下有几个子节点的信息
/{
leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_leds>;
led1 {
label = "LED1";
gpios = <&gpio5 7 GPIO_ACTIVE_LOW>; //led 使用的引脚,与触发电平
};
led2 {
label = "LED2";
gpios = <&gpio5 2 GPIO_ACTIVE_LOW>;
};
};
};
这里的设备树代码描述了根节点下的leds节点,下有两个子节点,兼容属性为"gpio-leds"。子节点属性有label和gpios。
对应在Linux内核leds-gpio.c驱动程序中通过平台设备驱动框架读取leds节点信息的代码为:
static const struct of_device_id of_gpio_leds_match[] = {
{ .compatible = "gpio-leds", },//节点信息,是与设备树leds节点匹配的依据
{},
};
static struct platform_driver gpio_led_driver = {
.probe = gpio_led_probe,
.remove = gpio_led_remove,
.driver = {
.name = "leds-gpio",
.of_match_table = of_gpio_leds_match,
},
};
void init_devicetree_example(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct fwnode_handle *child;
int count, ret;
struct device_node *np;
count = device_get_child_node_count(dev);//获取leds节点的子节点数量
if (!count)
return ERR_PTR(-ENODEV);
device_for_each_child_node(dev, child) {//遍历子节点
struct gpio_led led = {};
const char *state = NULL;
led.gpiod = devm_get_gpiod_from_child(dev, NULL, child);//获得子节点GPIO信息
if (IS_ERR(led.gpiod)) {
fwnode_handle_put(child);
ret = PTR_ERR(led.gpiod);
goto err;
}
np = of_node(child);
if (fwnode_property_present(child, "label")) { //获得属性label的值
fwnode_property_read_string(child, "label", &led.name);
} else {
if (IS_ENABLED(CONFIG_OF) && !led.name && np)
led.name = np->name;
if (!led.name)
return ERR_PTR(-EINVAL);
}、
fwnode_property_read_string(child, "linux,default-trigger",//空
&led.default_trigger);
if (!fwnode_property_read_string(child, "default-state",//空
&state)) {
if (!strcmp(state, "keep"))
led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
else if (!strcmp(state, "on"))
led.default_state = LEDS_GPIO_DEFSTATE_ON;
else
led.default_state = LEDS_GPIO_DEFSTATE_OFF;
}
if (fwnode_property_present(child, "retain-state-suspended"))//空
led.retain_state_suspended = 1;
}
2.获得设备树一个节点下没有子节点的信息
这里以rtc时钟节点为例,对应在设备树里的代码为:
&i2c2 {
pcf8563@51 {
compatible = "nxp,pcf8563";
reg = <0x51>;
interrupt-parent = <&gpio5>;
interrupts = <9 IRQ_TYPE_EDGE_FALLING>;
};
};
这里的设备树代码描述了i2c2节点下的pcf8563节点,没有子节点,兼容属性为"nxp,pcf8563"。子节点属性有reg、interrupts等。表明了在tq-imx6ul上的rtc时钟是挂在i2c2总线上的,总线地址为0x51,中断口为gpio5,触发方式为下降沿触发。
对于i2c和spi外设驱动和设备树中设备节点的兼容属性还有一种弱式匹配的方法,就是别名匹配。兼容属性的组织形式为<manufacture>,<model>,别名其实就是去掉兼容属性中manufacture后的model部分。通过别名匹配,spi和i2c即使没有of_match_table还是可以和设备树的节点匹配上。
对应在Linux内核rtc-pcf8563.c驱动程序中通过平台设备驱动框架读取pcf8563@51节点信息的代码为:
static const struct i2c_device_id pcf8563_id[] = {
{ "pcf8563", 0 },
{ "rtc8564", 0 },
{ }
};
static const struct of_device_id pcf8563_of_match[] = {
{ .compatible = "nxp,pcf8563" },
{}
};
static struct i2c_driver pcf8563_driver = {
.driver = {
.name = "rtc-pcf8563",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(pcf8563_of_match),
},
.probe = pcf8563_probe,
.id_table = pcf8563_id,
};
对于挂在i2c总线上的设备节点来说,似乎没有什么特别从设备树读取节点信息的函数,具体使用方法可以参考rtc-pcf8563.c这个驱动程序。
3.获得设备树下挂在某一节点下的子节点的信息
设备树代码如下:
tsc: tsc@02040000 {
compatible = "fsl,imx6ul-tsc";
reg = <0x02040000 0x4000>, <0x0219c000 0x4000>;
interrupts = <GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 101 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_IPG>,
<&clks IMX6UL_CLK_ADC2>;
clock-names = "tsc", "adc";
status = "disabled";
};
这个节点描述了tq-imx6ul下的电阻屏信息。
对应在Linux内核imx6ul_tsc.c驱动程序中通过平台设备驱动框架读取tsc节点信息的代码为:
static const struct of_device_id imx6ul_tsc_match[] = {
{ .compatible = "fsl,imx6ul-tsc", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx6ul_tsc_match);
static struct platform_driver imx6ul_tsc_driver = {
.driver = {
.name = "imx6ul-tsc",
.of_match_table = imx6ul_tsc_match,
.pm = &imx6ul_tsc_pm_ops,
},
.probe = imx6ul_tsc_probe,
};
int init_devicetree_example(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct imx6ul_tsc *tsc;
tsc = devm_kzalloc(&pdev->dev, sizeof(struct imx6ul_tsc), GFP_KERNEL);
if (!tsc)
return -ENOMEM;
//在匹配兼容属性"imx6ul-tsc"probe成功后,就可以通过of_property_read_u32获得节点tsc下的属性的值
err = of_property_read_u32(np, "measure-delay-time",
&tsc->measure_delay_time);
if (err)
tsc->measure_delay_time = 0xffff;
err = of_property_read_u32(np, "pre-charge-time",
&tsc->pre_charge_time);
if (err)
tsc->pre_charge_time = 0xfff;
}