linux设备树(设备驱动)
一:设备树的简单概念设备树:由一系列的节点,属性组成,节点本身包含子节点(属性:成对出现的名称和值)设备树可描述的信息:(原先大多数被编码在内核中)1:CPU的数量和类,2:内存基地址和大小 ,3:总线和桥,4:外设连接,5:中断控制和中断使用,6:GPIO控制器,7:时钟控制器,时钟使用情况。它是电路板上CPU,总线,设备组成的树,Bootloader会将这棵树传递给内核,并根据...
一:设备树的简单概念
设备树:由一系列的节点,属性组成,节点本身包含子节点(属性:成对出现的名称和值)
设备树可描述的信息:(原先大多数被编码在内核中)
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 =<address1 length1 [address2 length2] [address3 length3]>
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 。
更多推荐
所有评论(0)