linux设备树(设备驱动)

一:设备树的简单概念

设备树:由一系列的节点,属性组成,节点本身包含子节点(属性:成对出现的名称和值)

设备树可描述的信息:(原先大多数被编码在内核中)

1:CPU的数量和类,2:内存基地址和大小 ,3:总线和桥,4:外设连接,5:中断控制和中断使用,6:GPIO控制器,7:时钟控制器,时钟使用情况。

它是电路板上CPU,总线,设备组成的树,Bootloader会将这棵树传递给内核,并根据它展开linux内核中的platform_device等设备。设备用到的内存,IRQ等资源,也被传递给内核,内核会将这些资源绑定给展开的相应的设备。

注意BOOTloader需要支持将编译后的设备树传递给linux内核。会将.dtb地址告知内核,之后内核会展开设备树并且创建和注册相关的设备(则板卡级的代码被删除掉)。

.dts:ASC2文本格式的设备树描述,,一个.dts文件对应一个ARM设备。

“/”root节点,子节点,子节点下面又包含各种子节点,各节点包含各种属性,

dtc是将.dts编译为.dtb (.dtb二进制格式的设备树描述文件)。

2:Bootloader支持设备树

Uboot设备v1.1.3以上版本,在config文件中加入 #define CONFIG_OF_LIBFDT(在uboot中可以从flash中将.dtb读入内存中去)。

然后可以通过bootz kernel_addr_initrd_address dtb_address的命令来启动内核,kernel_addr_initrd_address 内核映像地址dtb_address为initrd地址。

2:节点的兼容属性

/ {
    model = "Altera SOCFPGA Arria 10";    /* appended from boardinfo */
    compatible = "altr,socfpga-arria10", "altr,socfpga";    /* appended from boardinfo */
    #address-cells = <1>;
    #size-cells = <1>;

}

 compatible = "altr,socfpga-arria10", "altr,socfpga";    /* appended from boardinfo */表示根节点的兼容属性,定义了整个系统的名称,一般包括两个或者两个以上的兼容性字符串,两个兼容性字符串是板子级别的名字,后一个兼容性是芯片级别的名字。

 int of_machine_is_compatible(const char *compat)//用该函数判断根节点的兼容性。

每个节点都有一个兼容属性,用于驱动和设备的绑定,列表中的字符串表征了节点代表的确切设备,规则是驱动设备ID表中的compatible域的值(字符串),和设备树中设备节点中的compatible属性值完全一致,则节点的内容是给驱动的。

使用设备树后,驱动需要与.dts中描述的设备节点进行匹配,从而使驱动的probe函数执行,对于platform_device而言,需要添加一个of匹配表。通过of_match_table添加匹配的.dts中的相关节点属性

int *of_device_is_compatible(struct device_node *from,const char *type); //判断设备节点的兼容属性是否包含type制定的字符串。

3:设备节点以及label的命名

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        enable-method = "altr,socfpga-a10-smp";    /* appended from boardinfo */

        a10_hps_arm_a9_0: cpu@0x0 {
            device_type = "cpu";
            compatible = "arm,cortex-a9-17.1", "arm,cortex-a9";
            reg = <0x00000000>;
            next-level-cache = <&a10_hps_mpu_reg_l2_MPUL2>;    /* appended from boardinfo */
        }; //end cpu@0x0 (a10_hps_arm_a9_0)

        a10_hps_arm_a9_1: cpu@0x1 {
            device_type = "cpu";
            compatible = "arm,cortex-a9-17.1", "arm,cortex-a9";
            reg = <0x00000001>;
            next-level-cache = <&a10_hps_mpu_reg_l2_MPUL2>;    /* appended from boardinfo */
        }; //end cpu@0x1 (a10_hps_arm_a9_1)
    }; //end cpus

 组织形式a10_hps_arm_a9_0: cpu@0x0  设备节点类型 @设备在内存空间的基地址。

4:地址编码

       #address-cells = <1>;描述子节点reg属性值的地址表中首地址cell数量
        #size-cells = <1>;描述子节点reg属性值的地址表中地址长度cell数量

reg =

address和length是可变长度,父节点的address-cells 和size-cells分别决定的子节点reg属性的address和length字段的长度。(表示初始地址和初始地址的长度)。子节点中reg的值就是一个首地址紧接着一个地址长度为一个单元。

描述一个设备的内存地址的时候,一般使用1个cell(32bits)描述地址,紧接着1个cell(32bits)描述地址长度。

非内存设备地址:没有内存地址那样的地址长度和范围,一般使用1个cell(32bits)描述该地址,而没有描述该地址的长度

地址转换范围:有些设备需要片选以及片选的偏移量,还需要说明地址映射范围,eg:rangs=< 0 0 0x10100000 0x1000>

映射中的子地址,父地址分别采用各自地址空间的address 。

5:中断连接

interrupt-controller 一个空属性用来声明这个node接收中断信号;

#interrupt-cells 这是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符;

interrupt-parent 标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的;

interrupts 一个中断标识符列表,表示每一个中断输出信号。

        a10_hps_arm_gic_0: intc@0xffffd000 {
            compatible = "arm,cortex-a9-gic-17.1", "arm,cortex-a9-gic";
            reg = <0xffffd000 0x00001000>,
                <0xffffc100 0x00000100>;
            reg-names = "axi_slave0", "axi_slave1";
            interrupt-controller;
            #interrupt-cells = <3>;
        }; //end intc@0xffffd000 (a10_hps_arm_gic_0)

        a10_hps_mpu_reg_l2_MPUL2: L2-cache@0xfffff000 {
            compatible = "arm,pl310-cache-17.1", "arm,pl310-cache";
            reg = <0xfffff000 0x00001000>;
            interrupt-parent = <&a10_hps_arm_gic_0>;
            interrupts = <0 18 4>;
            cache-level = <2>;    /* embeddedsw.dts.params.cache-level type NUMBER */
            cache-unified;    /* appended from boardinfo */
            arm,tag-latency = <1 1 1>;    /* appended from boardinfo */
            arm,data-latency = <2 1 1>;    /* appended from boardinfo */
        }; //end L2-cache@0xfffff000 (a10_hps_mpu_reg_l2_MPUL2)

 interrupts = <0 18 4>;:设备节点,中断号,触发方式,(有多少个cell,由它依附的中断控制节点的 interrupt-cell属性决定)。

通过platform_get_irq_byname()获取相应的中断号。

6:GPIO,时钟,pinmux连接

        led_pio: gpio@0x100000010 {
                compatible = "altr,pio-17.1", "altr,pio-1.0";
                reg = <0x00000001 0x00000010 0x00000010>;
                clocks = <&clk_0>;
                altr,gpio-bank-width = <4>;    /* embeddedsw.dts.params.altr,gpio-bank-width type NUMBER */
                resetvalue = <0>;    /* embeddedsw.dts.params.resetvalue type NUMBER */
                #gpio-cells = <2>;
                gpio-controller;
            }; //end gpio@0x100000010 (led_pio)

gpio-controller:说明该节点描述的是一个gpio控制器

#gpio-cells:描述gpio使用节点的属性一个cell的内容

第一个cell为GPIO号,第二个为GPIO极性,为0 高电平有效,为1 低电平有限。

属性名=<&引用GPIO节点别名GPIO标号工作模式>;

通过of_get_gpio来获取GPIO。

时钟:

    clocks = <&clk_0 &clk_0 &dp_0_video_pll 3 &clk_0>;
            clock-names = "h2f_axi_clock", "h2f_lw_axi_clock", "f2sdram0_clock", "f2sdram2_clock";

pinmux:设备节点使用的pinmux的引脚群是通过phandle来制定

7 :由设备树引发的BSP驱动变更

以前会从在大量的platform_device,现在platform_device的resource代码不在需要,而现在这些resource实际来源于.dts设备中的reg,interrupts属性。调用of_platform_bus_probe(NULL,xxx_of_bus_ids,NULL),即可展开platform_device。

使用设备树,驱动需要在.dts中描述的设备节点进行匹配,新的驱动设备的匹配变成了设备节点的兼容属性和设备驱动中的OF匹配表的匹配。

在arch/arm/mach_xxx注册platform_device,i2c_board_info,spi_board_info等的时候绑定platform_data,而后驱动通过API获取平台数据

8 :常用的API函数(位于内核的driver/of目录下)

1:寻找节点

struct device_node *of_find_compatible_node(struct device_node *from,
const char *type, const char *compat);  //遍历设备树的设备节点,查看节点类型(通过compatible属性查找制定节点)

读取属性:

struct property *of_find_property(const struct device_node *np,
const char *name, int *lenp);//读取制定属性的值

int of_property_count_elems_of_size(const struct device_node *np,
const char *propname, int elem_size);//得到制定属性值的数量

int of_property_read_u32_index(const struct device_node *np,
const char *propname, u32 index, u32 *out_value);//读取属性值中制定标号的32 为数据值

int of_property_read_string(struct device_node *np,
const char *propname, const char **out_string);//提取属性值的字符串。

__be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags);//提取io口地址映射

void __iomem *of_iomap(struct device_node *np, int index);//提取io口地址并且转化为细腻地址

void __iomem *of_io_request_and_map(struct device_node *np, int index, const char *name);//提取io口地址并且映射成虚拟地址

struct platform_device *of_find_device_by_node(struct device_node *from,
const char *type, const char *compat); //获取对应节点的platform_device 。

 

你可能感兴趣的:(个人随笔)