查看: 1277|回复: 4

[原创] 【i.MX6ULL分享】设备树原理与点亮LED

[复制链接]
  • TA的每日心情
    开心
    2022-4-14 11:08
  • 签到天数: 47 天

    [LV.5]常住居民I

    23

    主题

    131

    帖子

    0

    高级会员

    Rank: 4

    积分
    724
    最后登录
    2024-1-20
    发表于 2022-4-26 15:19:46 | 显示全部楼层 |阅读模式
    本帖最后由 h12121 于 2022-4-26 15:23 编辑

    【i.MX6ULL分享】设备树原理与点亮LED


    1 什么是设备树
    1.1 背景介绍
    Linux3.x之前是没有设备树的,设备树是用来描述一个硬件平台的板级细节。对应ARM-Linux开发,这些板级描述文件存放在linux内核的  /arch/arm/plat-xxx和/arch/arm/mach-xxx 中。随着ARM硬件设备的种类增多,与板子相关的设备文件也越来越多,这就导致Linux内核越来越大,而实际这些ARM硬件相关的板级信息与Linux内核并无相关关系。

    1.2 设备树介绍
    设备树的作用就是描述硬件平台的硬件资源。它可以被bootloader传递到内核,内核可以从设备树中获取硬件信息。

    设备树描述硬件资源时有两个特点:

    以树状结构描述硬件资源。以系统总线为树的主干,挂载到系统地总线的IIC控制器、SPI控制器等为树的枝干,IIC控制器下的IIC设备资源,又可以再分IIC1和IIC2,而IIC1上又可以连接MPU6050这类的IIC器件...

    可以像头文件那样,一个设备树文件引用另外一个设备树文件,实现代码重用。例如多个硬件平台都使用i.MX6ULL作为主控芯片,可以将 i.MX6ULL 芯片的硬件资源写到一个单独的设备树文件中(.dtsi文件)。
    11.png
    1.3 DTS、DTSI、DTB、DTC
    12.png
    DTS ,Device Tree Source,是设备树源码文件
    DTSI ,Device Tree Source Include,是设备树源码文件要用到的头文件
    DTB ,Device Tree Binary,是将DTS 编译以后得到的二进制文件
    DTC ,Device Tree Compiler,是将.dts 编译为.dtb需要用到的编译工具
    DTC工具源码在Linux内核的scripts/dtc目录下,scripts/dtc/文件夹下Makefile的内容为:
    1. hostprogs-y:= dtc
    2. always:= $(hostprogs-y)

    3. dtc-objs:= dtc.o flattree.o fstree.o data.o livetree.o treesource.o srcpos.o checks.o util.o
    4. dtc-objs+= dtc-lexer.lex.o dtc-parser.tab.o
    5. ......省略
    复制代码
    可以看出,DTC工具依赖于dtc.c、flattree.c、fstree.c等文件,最终编译并链接出DTC这个主机文件

    2 设备树框架与DTS语法
    2.1 设备树代码分析
    在学习设备树时,可以先看一下NXP关于i.MX6ULL已有的设备树文件,来大致了解一下设备树文件是什么样子的。

    2.1.1 imx6ull-14x14-evk-emmc.dts
    下面是/arch/arm/boot/dts/imx6ull-14x14-evk-emmc.dts
    1. #include "imx6ull-14x14-evk.dts"

    2. &usdhc2 {
    3. pinctrl-names = "default", "state_100mhz", "state_200mhz";
    4. pinctrl-0 = <&pinctrl_usdhc2_8bit>;
    5. pinctrl-1 = <&pinctrl_usdhc2_8bit_100mhz>;
    6. pinctrl-2 = <&pinctrl_usdhc2_8bit_200mhz>;
    7. bus-width = <8>;
    8. non-removable;
    9. status = "okay";
    10. };
    复制代码
    该文件就这几行,描述了emmc版本板子的usdhc信息。该文件的主要的功能是通过头文件的形式包含了另一个imx6ull-14x14-evk.dts设备树文件。

    DTS语法:设备树是可以使用“#include”引用其它文件(.dts、.h、.dtsi)。

    2.1.2 imx6ull-14x14-evk.dts
    下面是/arch/arm/boot/dts/imx6ull-14x14-evk.dts
    1. /dts-v1/;

    2. #include <dt-bindings/input/input.h>
    3. #include "imx6ull.dtsi"

    4. / {
    5. model = "Freescale i.MX6 ULL 14x14 EVK Board";
    6. compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

    7. chosen {
    8. stdout-path = &uart1;
    9. };

    10. memory {
    11. reg = <0x80000000 0x20000000>;
    12. };

    13. reserved-memory {
    14. #address-cells = <1>;
    15. #size-cells = <1>;
    16. ranges;

    17. linux,cma {
    18.   compatible = "shared-dma-pool";
    19.   reusable;
    20.   size = <0x14000000>;
    21.   linux,cma-default;
    22. };
    23. };

    24. backlight {
    25. compatible = "pwm-backlight";
    26. pwms = <&pwm1 0 5000000>;
    27. brightness-levels = <0 4 8 16 32 64 128 255>;
    28. default-brightness-level = <6>;
    29. status = "okay";
    30. };

    31. pxp_v4l2 {
    32. compatible = "fsl,imx6ul-pxp-v4l2", "fsl,imx6sx-pxp-v4l2", "fsl,imx6sl-pxp-v4l2";
    33. status = "okay";
    34. };

    35. regulators {
    36. compatible = "simple-bus";
    37. //省略...
    38. };

    39. //省略...
    40. };

    41. &cpu0 {
    42. arm-supply = <®_arm>;
    43. soc-supply = <®_soc>;
    44. dc-supply = <®_gpio_dvfs>;
    45. };

    46. &clks {
    47. assigned-clocks = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
    48. assigned-clock-rates = <786432000>;
    49. };

    50. //省略...

    51. &wdog1 {
    52. pinctrl-names = "default";
    53. pinctrl-0 = <&pinctrl_wdog>;
    54. fsl,wdog_b;
    55. };
    复制代码
    该文件也是先包含一些头文件,然后是一个斜杠+一些大括号,后面还出现了&符号。

    DTS语法:
    / {⋯} 斜杠+大括号,表示根节点,一个设备只有一个根节点
    (注:一个dts包含另一个dts,两个文件里的根节点,其实也是同一个根节点)
    xxx {⋯} 根节点内部单独的大括号,表示子节点,如reserved-memory {...}、pxp_v4l2 {...}等
    &xxx {⋯} 根节点外部单独的&符号与大括号,表示节点的追加内容,如&cpu0 {...}等

    2.1.3 imx6ull.dtsi
    1. #include <dt-bindings/interrupt-controller/irq.h>
    2. #include "imx6dl-pinfunc.h"
    3. #include "imx6qdl.dtsi"

    4. / {
    5. aliases {
    6. i2c3 = &i2c4;
    7. };

    8. cpus {
    9. #address-cells = <1>;
    10. #size-cells = <0>;

    11. cpu0: cpu@0 {
    12.   compatible = "arm,cortex-a9";
    13.   device_type = "cpu";
    14.   //省略...
    15. };

    16. cpu@1 {
    17.   compatible = "arm,cortex-a9";
    18.   device_type = "cpu";
    19.   reg = <1>;
    20.   next-level-cache = <&L2>;
    21. };
    22. };

    23. reserved-memory {
    24. //省略...
    25. };

    26. soc {
    27. //省略...
    28. ocram: sram@00905000 {
    29.   compatible = "mmio-sram";
    30.   reg = <0x00905000 0x1B000>;
    31.   clocks = <&clks IMX6QDL_CLK_OCRAM>;
    32. };
    33. //省略...
    34. };
    35. };

    36. //省略...

    37. &vpu_fsl {
    38. iramsize = <0>;
    39. };
    复制代码
    该文件是设备树的头文件,其格式与设备树基本相同。

    DTS语法:节点标签
    节点名“cpu”前面多了个“cpu0”, 这个“cpu0”就是我们所说的节点标签。通常节点标签是节点名的简写,它的作用是当其它位置需要引用时可以使用节点标签来向该节点中追加内容。
    2.2 设备节点基本格式
    设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键-值对。
    node-name@unit-address{
    属性1 = ...
    属性2 = ...
    子节点...
    }
    2.2.1 节点名称
    node-name用于指定节点名称,其长度为1~31个字符:
    数字:0~9
    字母:a~z A~Z
    英文符号:, . _ + -
    节点名应使用字母开头,并能描述设备类别(根节点用斜杠表示,不需要节点名)

    2.2.2 单元地址
    @unit-address用于指定单元地址,其中@符号表示一个分隔符,unit-address是实际的单元地址,它的值要和节点reg属性的第一个地址一致,如果没有reg属性值,则可以省略单元地址。

    2.2.3 节点属性
    在节点的大括号“{}”中包含的内容是节点属性, 一个节点可以包含多个属性信息,例如根节点的属性model = "Freescale i.MX6 ULL 14x14 EVK Board",编写设备树最主要的内容是编写节点的节点属性。属性包括自定义属性和标准属性,下面来看几个标准属性:

    model属性:用于指定设备的制造商和型号,多个字符串使用“,”分隔开
    compatible 属性:由一个或多个字符串组成,是用来查找节点的方法之一
    status属性:用于指示设备的“操作状态” ,通过status可以禁用或启用设备
    reg属性:描述设备资源在其父总线定义的地址空间内的地址,通常情况下用于表示一块寄存器的起始地址(偏移地址)和长度
    #address-cells 和 #size-cells:这两个属性同时存在,在设备树ocrams结构中,用在有子节点的设备节点,用于设置子节的“reg”属性的“书写格式”
    ranges属性:它是一个地址映射/转换表,由子地址、父地址和地址空间长度这三部分组成:
    child-bus-address: 子总线地址空间的物理地址, 由父节点的#address-cells 确定此物理地址所占用的字长
    parent-bus-address:父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长
    length:子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长

    2.2.4 特殊节点
    aliases子节点:其作用是为其他节点起一个别名,例如:
    1. aliases {
    2. i2c3 = &i2c4;
    3. };
    复制代码
    chosen子节点:该节点位于根节点下,它不代表实际硬件, 它主要用于给内核传递参数,例如:
    1. chosen {
    2.   stdout-path = &uart1;
    3. };
    复制代码
    表示系统标准输出 stdout 使用串口 uart1。
    3 设备树编程之OF函数
    内核提供了一系列函数用于从设备节点获取设备节点中定义的属性,这些函数以 of_ 开头,称为OF函数。在编写设备树版的LED驱动时,在进行硬件配置方面,就是要用这些OF函数,将寄存器地址等信息从设备树文件中获取出来,然后进行GPIO配置。
    先来列举一下这些函数:
    13.png
    3.1 查找节点的OF函数
    of_find_node_by_name
    通过节点名字查找指定的节点
    1. /**
    2. * from: 开始查找的节点,若为NULL表示从根节点开始查找整个设备树
    3. * name: 要查找的节点名字
    4. * return: 找到的节点,若为NULL表示查找失败
    5. */
    6. struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
    复制代码
    of_find_node_by_type
    通过device_type属性查找指定的节点
    1. /**
    2. * from: 开始查找的节点,若为NULL表示从根节点开始查找整个设备树
    3. * type: 要查找的节点对应的type字符串,也就是device_type属性值
    4. * return: 找到的节点,若为NULL表示查找失败
    5. */
    6. struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
    复制代码
    of_find_compatible_node
    根据device_type和compatible这两个属性查找指定的节点
    1. /**
    2. * from: 开始查找的节点,若为NULL表示从根节点开始查找整个设备树
    3. * type: 要查找的节点对应的type字符串,也就是device_type属性值,为NULL表示忽略掉device_type属性
    4. * compatible: 要查找的节点所对应的compatible属性列表
    5. * return: 找到的节点,若为NULL表示查找失败
    6. */
    7. struct device_node *of_find_compatible_node(struct device_node *from,
    8.                                             const char *type,
    9.                                             const char *compatible)
    复制代码
    of_find_matching_node_and_match
    通过of_device_id匹配表来查找指定的节点
    1. /**
    2. * from: 开始查找的节点,若为NULL表示从根节点开始查找整个设备树
    3. * matches: of_device_id匹配表,也就是在此匹配表里面查找节点
    4. * match: 找到的匹配的of_device_id
    5. * return: 找到的节点,若为NULL表示查找失败
    6. */
    7. struct device_node *of_find_matching_node_and_match(struct device_node  *from,
    8.                                              const struct of_device_id *matches,
    9.                                              const struct of_device_id **match)
    复制代码
    of_find_node_by_path
    通过路径来查找指定的节点
    1. /**
    2. * path: 带有全路径的节点名
    3. * return: 找到的节点,若为NULL表示查找失败
    4. */
    5. inline struct device_node *of_find_node_by_path(const char *path)
    复制代码
    3.2 查找父/子节点的OF函数
    of_get_parent
    用于查找父节点
    1. <p>/**</p><p>* node: 要查找的父节点的节点</p><p>* return: 找到的父节点</p><p>*/</p><p>struct device_node *of_get_parent(const struct device_node *node)</p>
    复制代码
    of_get_next_child
    用迭代的方式查找子节点
    1. <p>/**</p><p>* node: 父节点</p><p>* prev: 前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点,为NULL表示从第一个子节点开始</p><p>* return: 找到的下一个子节点</p><p>*/</p><p>struct device_node *of_get_next_child(const struct device_node *node,</p><p>                                           struct device_node *prev)</p>
    复制代码
    3.3 提取属性值的OF函数
    of_find_property
    查找指定的属性
    1. /**
    2. * np: 设备节点
    3. * name: 属性名字
    4. * lenp: 属性值的字节数
    5. * return: 找到的属性
    6. */
    7. property *of_find_property(const struct device_node *np,
    8.                                         const char *name,
    9.                                                int *lenp)
    复制代码
    of_property_count_elems_of_size
    用于获取属性中元素的数量

    1. <p>/**</p><p>* np: 设备节点</p><p>* propname: 属性名字</p><p>* elem_size: 元素长度</p><p>* return: 属性元素数量</p><p>*/</p><p>int of_property_count_elems_of_size(const struct device_node *np,</p><p>                                                 const char *propname,</p><p>                                                        int elem_size)</p><p>of_property_read_u32_index</p>
    复制代码
    用于从属性中获取指定标号的u32类型数据值
    1. <p>/**</p><p>* np: 设备节点</p><p>* propname: 属性名字</p><p>* index: 要读取的值标号</p><p>* out_value: 读取到的值</p><p>* return: 0读取成功,负值读取失败  */</p><p>int of_property_read_u32_index(const struct device_node *np,</p><p>                               const char *propname,</p><p>                               u32 index,</p><p>                               u32 *out_value)</p><p>of_property_read_u8_array</p>
    复制代码
    用于读取属性中 u8类型的数组数据(类似的函数还有u16、u32 和 u64)
    1. <p>/**</p><p>* np: 设备节点</p><p>* propname: 属性名字</p><p>* out_values: 读取到的数组值</p><p>* return: 0读取成功,负值读取失败  */</p><p>int of_property_read_u8_array(const struct device_node *np,</p><p>                              const char *propname,</p><p>                              u8 *out_values,</p><p>                              size_t sz)</p>
    复制代码
    of_property_read_u8
    用于读取只有一个整形值的属性(类似的函数还有u16、u32 和 u64)
    1. <p>/**</p><p>* np: 设备节点</p><p>* propname: 属性名字</p><p>* out_values: 读取到的数组值</p><p>* return: 0读取成功,负值读取失败  */</p><p>int of_property_read_u8(const struct device_node *np,</p><p>                        const char *propname,</p><p>                        u8 *out_value)</p>
    复制代码
    of_property_read_string
    用于读取属性中字符串值
    1. <p>/**</p><p>* np: 设备节点</p><p>* propname: 属性名字</p><p>* out_values: 读取到的字符串值</p><p>* return: 0读取成功,负值读取失败  */</p><p>int of_property_read_string(struct device_node *np,</p><p>                            const char *propname,</p><p>                            const char **out_string)</p>
    复制代码
    of_n_addr_cells
    用于获取#address-cells 属性值
    1. <p>/**</p><p>* np: 设备节点</p><p>* return: 获取到的#address-cells属性值  */</p><p>int of_n_addr_cells(struct device_node *np)</p><p>of_n_size_cells</p>
    复制代码
    用于获取#size-cells 属性值
    1. <p>/**</p><p>* np: 设备节点</p><p>* return: 获取到的#size-cells属性值  */</p><p>int of_n_size_cells(struct device_node *np)</p><p>3.4 其他常用的OF函数</p>
    复制代码
    of_device_is_compatible
    用于查看节点的compatible属性是否有包含compat指定的字符串,也就是检查设备节点的兼容性
    1. <p>/**</p><p>* device: 设备节点</p><p>* compat: 要查看的字符串</p><p>* return: 0不包含,正数包含  */</p><p>int of_device_is_compatible(const struct device_node *device,                                            const char *compat)</p>
    复制代码
    of_get_address
    用于获取地址相关属性
    1. <p>/**</p><p>* dev: 设备节点</p><p>* index: 要读取的地址标号</p><p>* size: 要读取的地址标号</p><p>* flags: 参数</p><p>* return: 读取到的地址数据首地址,NULL表示失败  */</p><p>const __be32 *of_get_address(struct device_node *dev,</p><p>                             int index,  u64 *size,</p><p>                             unsigned int *flags)</p>
    复制代码
    of_translate_address
    用于将设备树读取到的地址转换为物理地址
    1. <p>/**</p><p>* dev 设备节点</p><p>* in_addr: 要转换的地址</p><p>* return: 得到的物理地址  */</p><p>u64 of_translate_address(struct device_node *dev,</p><p>                         const __be32 *in_addr)</p>
    复制代码
    of_address_to_resource
    用于将reg属性值,转换为resource结构体类型
    1. <p>/**</p><p>* dev: 设备节点</p><p>* index: 地址资源标号</p><p>* r: 得到的 resource 类型的资源值</p><p>* return: 0成功,负值失败  */</p><p>int of_address_to_resource(struct device_node *dev,</p><p>                           int index,</p><p>                           struct resource *r)</p>
    复制代码
    of_iomap  
    用于直接内存映射
    1. <p>/**</p><p>* np: 设备节点</p><p>* index: reg属性中要完成内存映射的段</p><p>* return: 经过内存映射后的虚拟内存首地址,为NULL表示失败  */</p><p>void __iomem *of_iomap(struct device_node *np,</p><p>                       int index)</p>
    复制代码
    4 设备树LED驱动程序与实验
    回忆之前的LED字符设备驱动的编写方法:直接在驱动文件regled.c中定义有关寄存器物理地址,然后使用io_remap函数进行内存映射得到对应的虚拟地址,最后操作寄存器对应的虚拟地址完成对GPIO的初始化。
    使用设备树编写字符设备驱动,主要的一点区别是:使用设备树向Linux内核传递相关的寄存器物理地址,Linux驱动文件使用OF函数从设备树中获取所需的属性值,然后使用获取到的属性值来初始化相关的IO,所以,其本质还是配置寄存器。
    所以,使用设备树进行LED驱动,需要的修改主要为:
    修改imx6ull-myboard.dts设备树文件,在其中添加RGB-LED的设备节点
    编写RGB-LED驱动程序,获取设备树中的相关属性值,并使用相关的属性值进行GPIO的初始化
    编写RGB-LED应用程序,控制RGB-LED的亮灭

    4.1 修改设备树文件
    1. <p>/ {</p><p>  model = "Freescale i.MX6 ULL 14x14 EVK Board";</p><p>  compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";</p><p>  //省略...</p><p>  /*myboard led*/</p><p>  myboardled {</p><p>    #address-cells = <1>;</p><p>    #size-cells = <1>;</p><p>    compatible = "myboard-led";</p><p>    status = "okay"; reg = < 0X020C406C 0x04    /*CCM_CCGR1_BASE*/</p><p>      0X02290014 0x04    /*SW_MUX_SNVS_TAMPER3_BASE*/</p><p>      0X02290058 0x04    /*SW_PAD_SNVS_TAMPER3_BASE*/</p><p>      0X020AC000 0x04    /*GPIO5_DR_BASE*/</p><p>      0X020AC004 0x04 >; /*GPIO5_GDIR_BASE*/</p><p>  };</p><p>};</p>
    复制代码
    编译设备树,在内核源码的根目录下(我的是~/myTest/imx6ull/kernel/nxp_kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga),执行如下make命令即可单独编译自己修改的设备树:
    make imx6ull-myboard.dtb

    4.2 测试设备树
    4.2.1 测试环境切换
    由于这次是修改了设备树文件,而我的板子已经烧录了固件到emmc,因此,这次实验,重新将板子设为从SD卡启动uboot并从网络启动NFS文件系统的方式,方便修改测试设备树。(板子从网络启动的方式,可参考之前的文章i.MX6ULL嵌入式Linux开发4-根文件系统构建),若之前SD的uboot配置还在,将板子切换到SD卡启动,并确保网络畅通,即可从网络启动。

    若nfs服务器(ubuntu虚拟器)的IP发生变化,需要和之前一样进行类似如下的bootargs和bootcmd配置:

    setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.5.104:/home/xxpcb/myTest/nfs/rootfs,proto=tcp,nfsvers=4 rw ip=192.168.5.102:192.168.5.104:192.168.5.1:255.255.255.0::eth1ff'
    setenv bootcmd 'tftp 80800000 nxp/zImage; tftp 83000000 nxp/imx6ull-myboard.dtb; bootz 80800000 - 83000000'
    saveenv boot
    注意这里的192.168.5.104是我的ubuntu的IP,192.168.5.102是板子的IP。
    14.png
    4.2.2 设备树修改后的效果
    在测试设备树之前,可以先看一下目前板子的设备树中都有什么:
    将编译后的dtb文件放到网络启动位置,比如我的是复制到这里:

    xxpcb@ubuntuTest:~/myTest/imx6ull/kernel/nxp_kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/boot/dts$ cp imx6ull-myboard.dtb ~/myTest/tftpboot/nxp/
    然后重启板子,再次查看/proc/device-tree/目录:
    15.png
    可以看到,出现了新加的myboardled节点,进入myboardled目录下,可以看到其属性信息。

    4.3 修改LED驱动程序
    驱动程序整体框架和上一篇的寄存器版配置程序基本相同,主要的不同是修改硬件配置的方式,
    1. /*
    2. * @description   : LED硬件初始化(IO映射、时钟、GPIO配置)
    3. * @param         : 无
    4. * @return        : 0 成功;其他 失败
    5. */
    6. static int dtsled_hardware_init(void)
    7. {
    8.     u32 val = 0;
    9.     int ret;
    10.     u32 regdata[14];
    11.     const char *str;
    12.     struct property *proper;

    13.     /* 获取设备树中的属性数据 */
    14.     /* 1、获取设备节点:myboardled */
    15.     dtsled.nd = of_find_node_by_path("/myboardled");
    16.     if(dtsled.nd == NULL)
    17.     {
    18.         printk("myboardled node nost find!\r\n");
    19.         return -EINVAL;
    20.     }
    21.     else
    22.     {
    23.         printk("myboardled node find!\r\n");
    24.     }

    25.     /* 2、获取compatible属性内容 */
    26.     proper = of_find_property(dtsled.nd, "compatible", NULL);
    27.     if(proper == NULL)
    28.     {
    29.         printk("compatible property find failed\r\n");
    30.     }
    31.     else
    32.     {
    33.         printk("compatible = %s\r\n", (char*)proper->value);
    34.     }

    35.     /* 3、获取status属性内容 */
    36.     ret = of_property_read_string(dtsled.nd, "status", &str);
    37.     if(ret < 0)
    38.     {
    39.         printk("status read failed!\r\n");
    40.     }
    41.     else
    42.     {
    43.         printk("status = %s\r\n",str);
    44.     }

    45.     /* 4、获取reg属性内容 */
    46.     ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
    47.     if(ret < 0)
    48.     {
    49.         printk("reg property read failed!\r\n");
    50.     }
    51.     else
    52.     {
    53.         u8 i = 0;
    54.         printk("reg data:\r\n");
    55.         for(i = 0; i < 10; i++)
    56.         {
    57.             printk("%#X ", regdata[i]);
    58.         }
    59.         printk("\r\n");
    60.     }

    61.     /* 初始化LED */
    62. #if 0
    63.     /* 1、寄存器地址映射(使用ioremap) */
    64.     IMX6U_CCM_CCGR1      = ioremap(regdata[0], regdata[1]);
    65.     SW_MUX_SNVS_TAMPER3  = ioremap(regdata[2], regdata[3]);
    66.     SW_PAD_SNVS_TAMPER3  = ioremap(regdata[4], regdata[5]);
    67.     GPIO5_DR             = ioremap(regdata[6], regdata[7]);
    68.     GPIO5_GDIR           = ioremap(regdata[8], regdata[9]);
    69. #else
    70.     /* 1、寄存器地址映射(直接使用of_iomap) */
    71.     IMX6U_CCM_CCGR1      = of_iomap(dtsled.nd, 0);
    72.     SW_MUX_SNVS_TAMPER3  = of_iomap(dtsled.nd, 1);
    73.     SW_PAD_SNVS_TAMPER3  = of_iomap(dtsled.nd, 2);
    74.     GPIO5_DR             = of_iomap(dtsled.nd, 3);
    75.     GPIO5_GDIR           = of_iomap(dtsled.nd, 4);
    76. #endif

    77.     /* 2、使能GPIO1时钟 */
    78.     //省略... 后面的配置与上一篇的相同
    79. }
    复制代码
    上面的程序修改部分,从整个LED驱动的框架来看,修改的只是如下图中的黄色框部分:
    16.png
    4.4 实验测试
    编译设备树版的LED驱动程序,并将编译好的ko文件发送到nfs文件系统对应的文件夹下。


    LED是应用程序不需要修改,仍使用上一篇文章中的程序即可。
    17.png
    测试方法与之前基本相同:
    18.png
    使用设备树的方式,再次点亮LED:
    19.png


    5 总结
    本篇介绍了设备树的基本原理以及设备树的使用方法,在上一篇点亮LED的代码基础上,通过设备树的方式,实现了LED点灯,总结一下主要的修改就是先在设备树中添加LED节点,然后在驱动文件中通过OF函数来读取设备树中的寄存器信息,再进行GPIO的初始化,其它部分的程序与上一篇的基本一样。
    20.png


    签到签到
    回复

    使用道具 举报

  • TA的每日心情
    奋斗
    7 小时前
  • 签到天数: 1934 天

    [LV.Master]伴坛终老

    61

    主题

    1万

    帖子

    3

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    17227
    最后登录
    2024-4-20
    发表于 2022-4-26 15:28:52 | 显示全部楼层
    学习学习,正好试试iMAX6LL
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    慵懒
    2024-4-9 17:01
  • 签到天数: 1478 天

    [LV.10]以坛为家III

    203

    主题

    2万

    帖子

    64

    超级版主

    Rank: 8Rank: 8

    积分
    92609
    最后登录
    2024-4-9
    发表于 2022-4-27 17:44:47 | 显示全部楼层
    感谢分享,写的非常详细了
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    27 分钟前
  • 签到天数: 591 天

    [LV.9]以坛为家II

    51

    主题

    2211

    帖子

    0

    金牌会员

    Rank: 6Rank: 6

    积分
    7006
    最后登录
    2024-4-20
    发表于 2022-4-28 12:01:10 | 显示全部楼层
    感谢分享
    该会员没有填写今日想说内容.
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2022-1-4 14:25
  • 签到天数: 1 天

    [LV.1]初来乍到

    0

    主题

    172

    帖子

    0

    高级会员

    Rank: 4

    积分
    567
    最后登录
    2024-4-10
    发表于 2024-4-9 17:29:47 | 显示全部楼层
    学习了,谢谢
    每天登陆学习一下
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /4 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2024-4-20 18:37 , Processed in 0.146511 second(s), 24 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表