除了中断以外,在ARM Linux中时钟、GPIO、pinmux都可以通过.dts中的节点和属性进行描述。
(pinmux—Pin Multiplexing缩写,即引脚复用,通常CPU的管脚数量有限,通过复用来实现不同功能)
这设备树真的是很全面?还记得第一篇说dts可以描述的硬件信息有哪些吗?
参考资料:《Linux设备驱动开发详解》宋老师的书
1、GPIO
譬如,对于GPIO控制器而言,其对应的设备节点需声明gpio-controller属性,并设置#gpio-cells的大小。
譬如,对于兼容性为fsl,imx28-pinctrl的pinctrl驱动而言,其GPIO控制器的设备节点类似于:
pinctrl@80018000 { compatible = "fsl,imx28-pinctrl", "simple-bus"; reg = <0x80018000 2000>; gpio0: gpio@0 { compatible = "fsl,imx28-gpio"; interrupts = <127>; gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; }; gpio1: gpio@1 { compatible = "fsl,imx28-gpio"; interrupts = <126>; gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; }; ... };
其中,#gpio-cells为2,第1 个cell为GPIO号,第2个为GPIO的极性。为0的时候是高电平有效,为1的时候则是低电平有效。
使用GPIO的设备则通过定义命名xxx-gpios属性来引用GPIO控制器的设备节点,如:
sdhci@c8000400 { status = "okay"; cd-gpios = <&gpio01 0>; wp-gpios = <&gpio02 0>; power-gpios = <&gpio03 0>; bus-width = <4>; };
而具体的设备驱动则通过类似如下的方法来获取GPIO:
cd_gpio = of_get_named_gpio(np, "cd-gpios", 0); wp_gpio = of_get_named_gpio(np, "wp-gpios", 0); power_gpio = of_get_named_gpio(np, "power-gpios", 0);
of_get_named_gpio()这个API的原型如下:
static inline int of_get_named_gpio(struct device_node *np, const char *propname, int index);
在.dts和设备驱动不关心GPIO名字的情况下,也可以直接通过of_get_gpio()获取GPIO,此函数原型为:
static inline int of_get_gpio(struct device_node *np, int index);
如对于compatible="gpio-control-nand"的基于GPIO的NAND控制器而言,在.dts中会定义多个gpio属性:
gpio-nand@1,0 { compatible = "gpio-control-nand"; reg = <1 0x0000 0x2>; #address-cells = <1>; #size-cells = <1>; gpios = <&banka 1 0 /* rdy */ &banka 2 0 /* nce */ &banka 3 0 /* ale */ &banka 4 0 /* cle */ 0 /* nwp */>; partition@0 { ... }; };
在相应的驱动代码drivers/mtd/nand/gpio.c中是这样获取这些GPIO的:
plat->gpio_rdy = of_get_gpio(dev->of_node, 0); plat->gpio_nce = of_get_gpio(dev->of_node, 1); plat->gpio_ale = of_get_gpio(dev->of_node, 2); plat->gpio_cle = of_get_gpio(dev->of_node, 3); plat->gpio_nwp = of_get_gpio(dev->of_node, 4);
2、时钟
时钟和GPIO也是类似的,时钟控制器的节点被使用时钟的模块引用:
clocks = <&clks 138>, <&clks 140>, <&clks 141>; clock-names = "uart", "general", "noc";
而驱动中则使用上述的clock-names属性作为clk_get()或devm_clk_get()的第二个参数来申请时钟,譬如获取第2个时钟:
devm_clk_get(&pdev->dev, "general");
<&clks 138>里的138这个index是与相应时钟驱动中clk的表的顺序对应的,很多开发者也认为这种数字出现在设备树中不太好,因此他们把clk的index作为宏定义到了arch/arm/boot/dts/include/dt-bindings/clock中。譬如include/dt-bindings/clock/imx6qdl-clock.h中存在这样的宏:
#define IMX6QDL_CLK_STEP 16 #define IMX6QDL_CLK_PLL1_SW 17… #define IMX6QDL_CLK_ARM 104…
而arch/arm/boot/dts/imx6q.dtsi则是这样引用它们的:
clocks = <&clks IMX6QDL_CLK_ARM>, <&clks IMX6QDL_CLK_PLL2_PFD2_396M>, <&clks IMX6QDL_CLK_STEP>, <&clks IMX6QDL_CLK_PLL1_SW>, <&clks IMX6QDL_CLK_PLL1_SYS>;
3、pinmux
在设备树中,**某个设备节点使用的pinmux的引脚群是通过phandle来指定的。**譬如在arch/arm/boot/dts/atlas6.dtsi的pinctrl节点中包含所有引脚群的描述,如代码清单18.15所示。
1 gpio: pinctrl@b0120000 { 2 #gpio-cells = <2>; 3 #interrupt-cells = <2>; 4 compatible = "sirf,atlas6-pinctrl"; 5 … 6 7 lcd_16pins_a: lcd0@0 { 8 lcd { 9 sirf,pins = "lcd_16bitsgrp"; 10 sirf,function = "lcd_16bits"; 11 }; 12 }; 13 … 14 spi0_pins_a: spi0@0 { 15 spi { 16 sirf,pins = "spi0grp"; 17 sirf,function = "spi0"; 18 }; 19 }; 20 spi1_pins_a: spi1@0 { 21 spi { 22 sirf,pins = "spi1grp"; 23 sirf,function = "spi1"; 24 }; 25 }; 26 … 27 };
而SPI0这个硬件实际上需要用到spi0_pins_a对应的spi0grp这一组引脚,因此在atlas6-evb.dts中通过pinctrl-0引用了它,如代码清单18.16所示。
1 spi@b00d0000 { 2 status = "okay"; 3 pinctrl-names = "default"; 4 pinctrl-0 = <&spi0_pins_a>; 5 … 6 };
到目前为止,我们可以勾勒出一个设备树的全局视图,图18.2显示了设备树中的节点、属性、label以及phandle等信息。
下面看看当有了设备树对BSP产生了什么影响?