对于写这一篇的原因是感慨自己在上一篇写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;
}