当前位置:首页 > 嵌入式 > 嵌入式分享
[导读]在嵌入式Linux开发中,设备树(Device Tree)已成为描述硬件的标准。它让内核与硬件解耦,实现“一份内核,适配万千板卡”。本文将跳过繁杂的理论,直击设备树节点编写与内核驱动匹配的核心流程。



在嵌入式Linux开发中,设备树(Device Tree)已成为描述硬件的标准。它让内核与硬件解耦,实现“一份内核,适配万千板卡”。本文将跳过繁杂的理论,直击设备树节点编写与内核驱动匹配的核心流程。


一、设备树基础:从“硬件图纸”到DTS


设备树的核心结构是节点(Node)和属性(Property)。一个节点代表一个硬件设备。


1. 标准节点结构


// 在 arch/arm/boot/dts/my_board.dts

/ {

   model = "My Custom Board";

   compatible = "myvendor,my-board-v1";


   chosen {

       bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2";

   };


   memory@80000000 {

       device_type = "memory";

       reg = <0x80000000 0x20000000>; // 起始地址 + 长度 (512MB)

   };

};


关键点:根节点/下的model和compatible用于内核识别板卡;reg属性是地址描述的核心。


二、添加自定义外设节点(I2C设备为例)


假设板上有一个挂载在I2C1控制器下的EEPROM。


1. 查找控制器节点


首先,在SoC的DTSI文件(如soc.dtsi)中找到I2C控制器节点,获取其#address-cells和#size-cells。


2. 编写设备节点


// 在 my_board.dts 中

&i2c1 { // 引用已有的i2c1控制器节点

   status = "okay"; // 确保控制器使能

   clock-frequency = <100000>; // 100kHz


   eeprom@50 {

       compatible = "atmel,24c08"; // 关键:用于匹配驱动

       reg = <0x50>;             // I2C从设备地址

       pagesize = <16>;          // 设备特有属性

       read-only;                // 布尔属性(无值)

   };

};


注意:&i2c1表示追加或修改原有节点;reg的格式和长度必须与父节点的#address-cells匹配。


三、驱动与设备树的“联姻”:Of Match Table


设备树节点如何找到对应的驱动?答案是compatible字符串。


1. 内核驱动中的匹配表


在驱动代码中,定义一个of_device_id结构体数组。

// drivers/misc/my_eeprom.c

static const struct of_device_id eeprom_of_match[] = {

   { .compatible = "atmel,24c08" }, // 必须与DTS中的字符串完全一致

   { .compatible = "atmel,24c256" },

   { /* Sentinel */ }

};

MODULE_DEVICE_TABLE(of, eeprom_of_match);


static struct platform_driver eeprom_driver = {

   .probe = eeprom_probe,

   .remove = eeprom_remove,

   .driver = {

       .name = "my_eeprom",

       .of_match_table = eeprom_of_match, // 绑定匹配表

   },

};



2. 驱动获取设备树属性


当compatible匹配成功后,内核会调用驱动的probe函数,此时可以通过of_系列API读取DTS属性。

static int eeprom_probe(struct platform_device *pdev)

{

   struct device_node *np = pdev->dev.of_node;

   u32 reg_addr;

   int ret;


   // 获取reg属性

   ret = of_property_read_u32(np, "reg", &reg_addr);

   if (ret) {

       dev_err(&pdev->dev, "Failed to get reg property\n");

       return ret;

   }


   // 判断布尔属性是否存在

   if (of_property_read_bool(np, "read-only")) {

       dev_info(&pdev->dev, "Device is read-only\n");

   }


   // 获取pagesize

   of_property_read_u32(np, "pagesize", &priv->page_size);


   // ... 初始化硬件 ...

   return 0;

}



四、Pinctrl与GPIO:复杂硬件的绑定


对于GPIO或引脚复用,设备树需要与pinctrl子系统配合。

// 定义引脚配置

&iomuxc {

   pinctrl_eeprom: eepromgrp {

       fsl,pins = <

           MX6QDL_PAD_EIM_D17__GPIO3_IO17 0x80000000

       >;

   };

};


// 在设备节点中引用

&i2c1 {

   eeprom@50 {

       compatible = "atmel,24c08";

       reg = <0x50>;

       pinctrl-names = "default";

       pinctrl-0 = <&pinctrl_eeprom>; // 绑定引脚配置

       wp-gpios = <&gpio3 17 GPIO_ACTIVE_LOW>; // 写保护GPIO

   };

};


驱动中通过gpiod_get()获取wp-gpios,无需关心具体引脚号。


五、调试与验证技巧


1.  编译与反编译

   # 编译DTB

   make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- my_board.dtb

   # 反编译DTB查看最终节点(非常有用!)

   dtc -I dtb -O dts my_board.dtb > decompiled.dts

   

   很多时候DTS写对了,但被DTSI覆盖了,反编译能看到最终生效的配置。


2.  内核启动后验证

   # 查看设备树节点

   ls /proc/device-tree/

   # 查看特定属性

   hexdump -C /proc/device-tree/soc/i2c@21a0000/eeprom@50/compatible

   # 查看GPIO分配

   cat /sys/kernel/debug/gpio

   


六、结语


编写设备树的关键在于“描述准确”与“匹配无误”。遵循三步法:定义节点与属性 → 驱动中实现of_match_table → 使用of_property_read_* API读取数据。掌握设备树,你就掌握了嵌入式Linux硬件适配的钥匙。


本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭