驱动篇:ARM Linux 设备树(二)(摘录)

驱动篇:ARM Linux 设备树(二)(摘录)

当一个驱动可以兼容多种设备的时候,除了 of_device_is_compatible ()这种判断方法以外,还可以采用在驱动的of_device_id 表中填充 .data 成员的形式。譬如, arch/arm/mm/cache-l2x0.c 支持 “arm , l210-cache”“arm , pl310-cache”“arm , l220-cache” 等多种设备,其 of_device_id 表如代码如下:
支持多个兼容性以及 .data 成员的 of_device_id 表:

#define L2C_ID(name, fns) { .compatible = name, .data = (void *)&fns }
static const struct of_device_id l2x0_ids[] __initconst = {
 L2C_ID("arm,l210-cache", of_l2c210_data),
 L2C_ID("arm,l220-cache", of_l2c220_data),
 L2C_ID("arm,pl310-cache", of_l2c310_data),
 L2C_ID("brcm,bcm11351-a2-pl310-cache", of_bcm_l2x0_data),
 L2C_ID("marvell,aurora-outer-cache", of_aurora_with_outer_data),
 L2C_ID("marvell,aurora-system-cache", of_aurora_no_outer_data),
 L2C_ID("marvell,tauros3-cache", of_tauros3_data),
 /* Deprecated IDs */
 L2C_ID("bcm,bcm11351-a2-pl310-cache", of_bcm_l2x0_data),
 {}
 };

在驱动中,通过如代码清单的方法拿到了对应于 L2 缓存类型的 .data 成员,其中主要用到了of_match_node ()这个 API 。
通过 of_match_node ()找到 .data:

int __init l2x0_of_init(u32 aux_val, u32 aux_mask)
{
 const struct l2c_init_data *data;
 struct device_node *np;5
 np = of_find_matching_node(NULL, l2x0_ids);
 if (!np)

return -ENODEV;
...
data = of_match_node(l2x0_ids, np)->data;
}

如果电路板的 .dts 文件中 L2 缓存是 arm , pl310-cache ,那么上述代码第 10 行找到的 data 就是 of_l2c310_data ,它是l2c_init_data 结构体的一个实例。 l2c_init_data 是一个由 L2 缓存驱动自定义的数据结构,在其定义中既可以保护数据成员,又可以包含函数指针
与兼容对应的特定 data 实例:

struct l2c_init_data {
	const char *type;
	unsigned way_size_0;
	unsigned num_lock;
	void (*of_parse)(const struct device_node *, u32 *, u32 *);
	void (*enable)(void __iomem *, unsigned);
	void (*fixup)(void __iomem *, u32, struct outer_cache_fns *);
	void (*save)(void __iomem *);
	void (*configure)(void __iomem *);
	void (*unlock)(void __iomem *, unsigned);
	struct outer_cache_fns outer_cache;
};

通过这种方法,驱动可以把与某个设备兼容的私有数据寻找出来,如此体现了一种面向对象的设计思想,避免
了大量的 if , else 或者 switch , case 语句。

设备节点及 label 的命名
根节点 “/” 的 cpus 子节点下面又包含两个 cpu 子节点,描述了此设备上的两个 CPU ,并且两者的兼容属性为: “arm , cortex-a9” 。

注意 cpus 和 cpus 的两个 cpu 子节点的命名,它们遵循的组织形式为 < name>[ @< unit-address>] , <> 中的内容是必选项, [] 中的则为可选项。 name 是一个 ASCII 字符串,用于描述节点对应的设备类型,如 3com Ethernet 适配器对应的节点 name 宜为 ethernet ,而不是 3com509 。如果一个节点描述的设备有地址,则应该给出 @unit-address 。多个相同类型设备节点的 name 可以一样,只要 unit-address 不同即可,如本例中含有 cpu@0 、 cpu@1 以及 serial@101f0000与 serial@101f2000 这样的同名节点。设备的 unit-address 地址也经常在其对应节点的 reg 属性中给出

对于挂在内存空间的设备而言, @ 字符后跟的一般就是该设备在内存空间的基地址,譬如
arch/arm/boot/dts/exynos4210.dtsi 中存在的:

sysram@02020000 {
compatible = "mmio-sram";
reg = <0x02020000 0x20000>;
...
}

上述节点的 reg 属性的开始位置与 @ 后面的地址一样。

对于挂在 I 2 C 总线上的外设而言, @ 后面一般跟的是从设备的 I 2 C 地址,譬如 arch/arm/boot/dts/exynos4210-trats.dts 中的 mms114-touchscreen :

i2c@13890000 {
...
mms114-touchscreen@48 {
compatible = "melfas,mms114";
reg = <0x48>;
...
};
};

上述节点的 reg 属性标示的 I 2 C 从地址与 @ 后面的地址一样。

我们还可以给一个设备节点添加 label ,之后可以通过 &label 的形式访问这个 label ,这种引用是通过phandle ( pointer handle )进行的。
例如,在 arch/arm/boot/dts/omap5.dtsi 中,第 3 组 GPIO 有 gpio3 这个 label
在设备树中定义 label:

 gpio3: gpio@48057000 {
 compatible = "ti,omap4-gpio";
 reg = <0x48057000 0x200>;
 interrupts = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;
 ti,hwmods = "gpio3";
 gpio-controller;
 #gpio-cells = <2>;
 interrupt-controller;
 #interrupt-cells = <2>;
 };

而 hsusb2_phy 这个 USB 的 PHY 复位 GPIO 用的是这组 GPIO 中的一个,所以它通过 phandle 引用了 “gpio3”
通过 phandle 引用其他节点:

/* HS USB Host PHY on PORT 2 */
hsusb2_phy: hsusb2_phy {
 compatible = "usb-nop-xceiv";
 reset-gpios = <&gpio3 12 GPIO_ACTIVE_LOW>; /* gpio3_76 HUB_RESET */
};

hsusb2_phy 则通过 &gpio3 引用了这个节点,表明自己要使用这一组 GPIO 中的第 12 个 GPIO 。
很显然,这种 phandle 引用其实表明硬件之间的一种关联性。

再举一例,在 arch/arm/boot/dts/omap5.dtsi 中,我们可以看到类似如下的 label ,从这些实例可以看出, label 习惯以< 设备类型 >< index> 进行命名:

i2c1: i2c@48070000 {
}
i2c2: i2c@48072000 {
}
i2c3: i2c@48060000 {
} ...

读者也许发现了一个奇怪的现象,就是代码中居然引用了 GPIO_ACTIVE_LOW 这个类似 C 语言的宏。文件 .dts 的编译过程确实支持 C 的预处理,相应的 .dts 文件也包括了包含 GPIO_ACTIVE_LOW 这个宏定义的头文件:

#include <dt-bindings/gpio/gpio.h>

对于 ARM 而言, dt-bindings 头文件位于内核的 arch/arm/boot/dts/include/dt-bindings 目录中。观察该目录的属性,它实际上是一个符号链接
从内核的 scripts/Makefile.lib 这个文件可以看出,文件 .dts 的编译过程确实是支持 C 预处理的。

cmd_dtc = $(CPP) $(dtc_cpp_flags) -x assembler-with-cpp -o $(dtc-tmp) $< ; \
$(objtree)/scripts/dtc/dtc -O dtb -o $@ -b 0 \
-i $(dir $<) $(DTC_FLAGS) \
-d $(depfile).dtc.tmp $(dtc-tmp) ; \
cat $(depfile).pre.tmp $(depfile).dtc.tmp > $(depfile)

它是先做了 $ ( CPP ) $ ( dtc_cpp_flags ) -x assembler-with-cpp-o$ ( dtc-tmp ) $< ,
再做的 .dtc 编译。

地址编码
可寻址的设备使用如下信息在设备树中编码地址信息:

reg
#address-cells
#size-cells

其中, reg 的组织形式为 reg=< address1length1[ address2length2][address3length3]…> ,其中的每一组 address length 表明了设备使用的一个地址范围。address 为 1 个或多个 32 位的整型(即 cell ),而 length 的意义则意味着从 address 到address+length–1 的地址范围都属于该节点
若 #size-cells=0 ,则 length 字段为空。address 和 length 字段是可变长的,父节点的 #address-cells 和 #size-cells 分别决定了子节点 reg 属性的 address 和 length 字段的长度

在代码清单 18.2 中,根节点的 #address-cells=<1> ;和 #size-cells=<1> ;决定了 serial 、 gpio 、 spi 等节点的 address 和length 字段的长度分别为 1 。cpus 节点的 #address-cells=<1> ;和 #size-cells=<0> ;决定了两个 cpu 子节点的 address 为 1 ,而 length 为空,于是形成了两个 cpu 的 reg=<0> ;和 reg=<1> ;

external-bus 节点的 #address-cells=<2> 和 #size-cells=<1> ;决定了其下的 ethernet 、 i2c 、 flash 的 reg 字段形如 reg=<0 0 0x1000> ;、 reg=<1 0 0x1000> ;和 reg=<2 0 0x4000000> ;。其中, address 字段长度为 2 ,开始的第一个cell (即 “<” 后的 0 、 1 、 2 )是对应的片选,第 2 个 cell (即 <0 0 0x1000> 、 <1 0 0x1000> 和 <2 0 0x1000000> 中间的0 , 0 , 0 )是相对该片选的基地址,第 3 个 cell (即 “>” 前的0x1000 、 0x1000 、 0x1000000 )为 length 。

特别要留意的是 i2c 节点中定义的 #address-cells=<1> ;和 #size-cells=<0> ;,其作用到了 I 2 C 总线上连接的 RTC ,它的 address 字段为 0x58 ,是 RTC 设备的 I 2 C 地址

根节点的直接子书点描述的是 CPU 的视图,因此根子节点的 address 区域就直接位于 CPU 的内存区域。但是,经过总线桥后的 address 往往需要经过转换才能对应 CPU 的内存映射。 external-bus 的 ranges 属性定义了经过 external-bus 桥后的地址范围如何映射到 CPU 的内存区域。

    ranges = <0 0 0x10100000 0x10000   // Chipselect 1, Ethernet
    1 0 0x10160000 0x10000    // Chipselect 2, i2c controller
    2 0 0x30000000 0x1000000>;   // Chipselect 3, NOR Flash

ranges 是地址转换表,其中的每个项目是一个子地址、父地址以及在子地址空间的大小的映射。映射表中的子地址、父地址分别采用子地址空间的 #address-cells 和父地址空间的 #address-cells 大小。对于本例而言,子地址空间的 #address-cells 为 2 ,父地址空间的 #address-cells 值为 1 ,因此 000x10100000 0x10000 的前 2 个 cell 为 external-bus 桥后external-bus 上片选 0 偏移 0 ,第 3 个 cell 表示 external-bus 上片选 0 偏移 0 的地址空间被映射到 CPU 的本地总线的0x10100000 位置,第 4 个 cell 表示映射的大小为 0x10000 。 ranges 后面两个项目的含义可以类推。
中断连接
设备树中还可以包含中断连接信息,对于中断控制器而言,它提供如下属性:
interrupt-controller– 这个属性为空,中断控制器应该加上此属性表明自己的身份;
#interrupt-cells– 与 #address-cells 和 #size-cells 相似,它表明连接此中断控制器的设备的中断属性的 cell 大小。
在整个设备树中,与中断相关的属性还包括:
interrupt-parent– 设备节点通过它来指定它所依附的中断控制器的 phandle ,当节点没有指定 interrupt-parent 时,则从父级节点继承。对于本例(代码清单 18.2 )而言,根节点指定了 interrupt-parent=<&intc> ;,其对应于 intc :interrupt-controller@10140000 ,而根节点的子节点并未指定 interrupt-parent ,因此它们都继承了 intc ,即位于0x10140000 的中断控制器中。

interrupts– 用到了中断的设备节点,通过它指定中断号、触发方法等,这个属性具体含有多少个 cell ,由它依附的中断控制器节点的 #interrupt-cells 属性决定。而每个 cell 具体又是什么含义,一般由驱动的实现决定,而且也会在设备树的绑定文档中说明。譬如,对于 ARM GIC 中断控制器而言, #interrupt-cells 为 3 , 3 个 cell 的具体含义在Documentation/devicetree/bindings/arm/gic.txt 中就有如下文字说明:

The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI
interrupts.
The 2nd cell contains the interrupt number for the interrupt type.
SPI interrupts are in the range [0-987]. PPI interrupts are in the
range [0-15].
The 3rd cell is the flags, encoded as follows:
bits[3:0] trigger type and level flags.
1 = low-to-high edge triggered
2 = high-to-low edge triggered
4 = active high level-sensitive
8 = active low level-sensitive
bits[15:8] PPI interrupt cpu mask. Each bit corresponds to each of
the 8 possible cpus attached to the GIC. A bit set to ‘1’ indicated
the interrupt is wired to that CPU. Only valid for PPI interrupts.

另外,值得注意的是,一个设备还可能用到多个中断号。对于 ARM GIC 而言,若某设备使用了 SPI 的 168 号、 169号两个中断,而且都是高电平触发,则该设备节点的中断属性可定义为 interrupts=<01684> , <01694> ;。
对于平台设备而言,简单的通过如下 API 就可以指定想取哪一个中断,其中的参数 num 就是中断的 index 。

int platform_get_irq(struct platform_device *dev, unsigned int num);

当然在 .dts 文件中可以对中断进行命名,而后在驱动中通过 platform_get_irq_byname ()来获取对应的中断号。譬如代码清单 18.14 演示了在 drivers/dma/fsl-edma.c 中通过 platform_get_irq_byname ()获取 IRQ ,以及arch/arm/boot/dts/vf610.dtsi 与 fsl-edma 驱动对应节点的中断描述。
设备树中的中断名称以及驱动获取中断:

static int fsl_edma_irq_init(struct platform_device *pdev,
struct fsl_edma_engine *fsl_edma)
{
 fsl_edma->txirq = platform_get_irq_byname(pdev, "edma-tx");
 fsl_edma->errirq = platform_get_irq_byname(pdev, "edma-err");
}

edma0: dma-controller@40018000 {

#dma-cells = <2>;
 compatible = "fsl,vf610-edma";
 reg = <0x40018000 0x2000>,
 <0x40024000 0x1000>,
 <0x40025000 0x1000>;

interrupts = <0 8 IRQ_TYPE_LEVEL_HIGH>,
<0 9 IRQ_TYPE_LEVEL_HIGH>;
 interrupt-names = "edma-tx", "edma-err";
 dma-channels = <32>;
 clock-names = "dmamux0", "dmamux1";
 clocks = <&clks VF610_CLK_DMAMUX0>,

<&clks VF610_CLK_DMAMUX1>;
};

platform_get_irq_byname ()的第 2 个参数与 .dts 中的 interrupt-names 是一致的

GPIO 、时钟、 pinmux 连接
除了中断以外,在 ARM Linux 中时钟、 GPIO 、 pinmux 都可以通过 .dts 中的节点和属性进行描述。
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 节点中包含所有引脚群的描述
设备树中 pinctrl 控制器的引脚群:

gpio: pinctrl@b0120000 {

#gpio-cells = <2>;
#interrupt-cells = <2>;
 compatible = "sirf,atlas6-pinctrl";
 ...


  lcd_16pins_a: lcd0@0 {

     lcd {
        sirf,pins = "lcd_16bitsgrp";
        sirf,function = "lcd_16bits";

        };
 };
 ...
   spi0_pins_a: spi0@0 {

  spi {
    sirf,pins = "spi0grp";
    sirf,function = "spi0";

    };
 };
   spi1_pins_a: spi1@0 {

     spi {
        sirf,pins = "spi1grp";
        sirf,function = "spi1";

        };
    };
 ...
};

而 SPI0 这个硬件实际上需要用到 spi0_pins_a 对应的 spi0grp 这一组引脚,因此在 atlas6-evb.dts 中通过 pinctrl-0 引用了它
给设备节点指定引脚群:

 spi@b00d0000 {
 status = "okay";
 pinctrl-names = "default";
 pinctrl-0 = <&spi0_pins_a>;
 ...
 };

到目前为止,我们可以勾勒出一个设备树的全局视图,下图显示了设备树中的节点、属性、 label 以及 phandle 等信息。
在这里插入图片描述

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值