[ARM Linux 驱动开发] 设备树下的 LED 驱动

这篇文章我们介绍如何在利用设备树编写 LED 驱动。这节的内容不是很复杂,可以说是之前两篇文章:[ARM Linux 驱动开发] “新”字符设备驱动[ARM Linux 驱动开发] linux 设备树 的结合版本。

借此机会我们正好巩固一下如何自动分配设备号和注册设备、如何自动创建设备文件、如何获取设备树数据。

修改设备树文件

我们之前进行 linux 系统移植的时候,使用的设备树文件是 arch/arm/boot/dts/imx6ull-my-emmc.dts。因此,我们现在对它进行修改,在根节点的末尾添加自己 led 节点:

  • / {
  •     /* ………… */
  •     hanhanled {
  •         #address-cells = <1>;
  •         #size-cells = <1>;
  •         compatible = "hanhan-led";
  •         status = "okay";
  •         reg = <0x020c406c 0x04
  •                0x020e0068 0x04
  •                0x020e02f4 0x04
  •                0x0209c004 0x04
  •                0x0209c000 0x04>;
  •     };
  • };

开发板教程中的这个 #address-cells 和 #size-cells 应该是有的问题,因为它们只是针对当前节点的子节点。

但也不影响我们后续驱动程序的编写,因为后续编写的驱动程序中压根没有利用到这个属性😂。(但后续还是要注意)

本着之前所说的“用到再看”的原则,下面抄录一下自定义节点中用到的属性说明:

#address-cells 和 #size-cells 1

属性名称:#address-cells#size-cells

值类型:<u32>

描述:

#address-cells#size-cells 属性可用于设备树层次结构中有子节点的任何设备节点,并描述子设备节点应该如何寻址。#address-cells 属性定义了 <u32> 单元格的数量,其用于对子节点 reg 属性中的地址字段进行编码。#size-cells 属性定义了 <u32> 单元格的数量,其用于对子节点 reg 属性中的大小字段进行编码。

#address-cells#size-cells 属性不继承于设备树的祖先节点。应该明确定义它们。

一个设备树标准兼容的引导程序需要在所有有孩子的节点上提供 #address-cells#size-cells

如果这些属性丢失,则客户端程序应假定 #address-cells 的默认值为 2,#size-cells 的默认值为 1。

样例:

  • soc {
  •     #address-cells = <1>;
  •     #size-cells = <1>;
  •  
  •     serial@4600 {
  •         compatible = "ns16550";
  •         reg = <0x4600 0x100>;
  •         clock-frequency = <0>;
  •         interrupts = <0xA 0x8>;
  •         interrupt-parent = <&ipic>;
  •     };
  • };

在这个样例中,soc 节点的 #address-cells#size-cells 属性都设置为 1。这个设置指定,该节点的所有孩子节点需要一个单元格来表示地址,且一个单元格来表示大小。

这个串行设备的 reg 属性必须遵循其父节点(soc)中设置的此规范——地址由一个单元格表示(0x4600),大小由一个单元格表示(0x100)。

reg 2

属性名称:reg

值类型:<prop-encoded-array> 编码为任意数量的(地址,长度)对。

描述:

reg 属性描述设备资源的地址,其所在地址空间由其父总线定义。最常见的是指内存映射 IO 寄存器块的偏移量和长度,但在某些总线类型上可能有不同的含义。根节点所定义的地址空间中的地址是 CPU 真实地址。

reg 该值是一个 <prop-encoded-array>,由任意数量的地址和长度对组成,<地址,长度>。指定地址和长度所需的 <u32> 单元数量是特定于总线的,由设备节点的父节点中 #address-cells#size-cells 属性指定。如果父节点指定 #size-cells 为值 0,则 reg 值中的长度字段将被省略。

样例:

假设片上系统中的一个器件有两个寄存器块:SOC 中偏移 0x3000 处的 32 字节块和偏移量 0xFE00 处的 256 字节块。reg 属性的编码如下(假设 #address-cells#size-cells 的值为 1):

  • reg = <0x3000 0x20 0xFE00 0x100>;

compatible 3

属性名称:compatible

值类型:<stringlist>

描述:

compatible 属性值由一个或多个字符串组成,其定义设备特定编程模型。客户端程序应使用这个字符串列表来选择设备驱动程序。属性值由 null 结尾的字符串的串联列表组成,从最具体到最一般。它们允许一个设备表达其与一系列类似设备的兼容性,潜在地允许单个设备驱动程序匹配多个设备。

推荐的格式是制造商,型号,其中制造商是描述制造商名称的字符串(如股票代码符号),型号指定模型编号。

样例:

  • compatible = "fsl,mpc8641""ns16550";

在本例中,操作系统将首先尝试定位支持 fsl,mpc8641 的设备驱动程序。如果未找到驱动程序,则尝试定位支持更通用的 ns16550 设备类型的驱动程序。


以上内容再“白话”的总结一下。#address-cells#size-cellsreg 属性是一组的,reg 中可以定义任意对(地址,长度)对,而地址字段的大小由 #address-cells 指定,大小字段的大小由 #size-cells 指定。compatible 属性定义设备的兼容性信息,有点类似 html 里的字体匹配,前面定义的匹配不上就匹配后面的。status 属性上面介绍中没有给出,但看名字也容易理解,用作指明设备的状态。

修改好设备树文件之后,使用

  • > make dtbs ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

命令进行编译,会产生想要的 arch/arm/boot/dts/imx6ull-my-emmc.dtb 文件。将其放在服务器上,然后重启开发板来重新引导。如图 1 所示,我们可以通过查看 /proc/device-tree/ 目录,来检查新添加的节点是否添加成功。

图4 打印节点属性内容

1 《Devicetree specification》 : 2.3.5 #address-cells and #size-cells

2 《Devicetree specification》 : 2.3.6 reg

3 《Devicetree specification》 : 2.3.1 compatible

驱动编写

修改好设备树文件之后,我们就可以开始编写驱动设备了。总体框架和 [ARM Linux 驱动开发] “新”字符设备驱动 中的一样:自动分配设备号、自动创建设备文件。设备树操作函数和 [ARM Linux 驱动开发] linux 设备树 中的一样。

  1. #include <linux/module.h>
  2. #include <linux/cdev.h>
  3. #include <linux/device.h>
  4. #include <linux/fs.h>
  5. #include <asm/io.h>
  6. #include <asm/uaccess.h>
  7. #include <linux/of.h>
  8. #include <linux/slab.h>
  9. #include <linux/of_address.h>
  10.  
  11. #define LED_CNT 1
  12. #define LED_NAME "dtsled"
  13.  
  14. void __iomem* CCM_CCGR1;
  15. void __iomem* SW_MUX_GPIO1_IO03;
  16. void __iomem* SW_PAD_GPIO1_IO03;
  17. void __iomem* GPIO1_GDIR;
  18. void __iomem* GPIO1_DR;
  19.  
  20. struct LedDev
  21. {
  22.     /* 设备号相关 */
  23.     dev_t devId;
  24.     int major;
  25.     int minor;
  26.     /* cdev */
  27.     struct cdev cdev;
  28.     /* mdev */
  29.     struct class* class;
  30.     struct device* device;
  31. };
  32.  
  33. struct LedDev ledDev;
  34.  
  35. int led_open(struct inode* indoe, struct file* filp)
  36. {
  37.     printk("led_open\n");
  38.     return 0;
  39. }
  40.  
  41. int led_release(struct inode* indoe, struct file* filp)
  42. {
  43.     printk("led_release\n");
  44.     return 0;
  45. }
  46.  
  47. void turn_on_led(void)
  48. {
  49.     int val = readl(GPIO1_DR);
  50.     val &= ~(1 << 3);
  51.     writel(val, GPIO1_DR);
  52. }
  53.  
  54. void turn_off_led(void)
  55. {
  56.     int val = readl(GPIO1_DR);
  57.     val |= 1 << 3;
  58.     writel(val, GPIO1_DR); 
  59. }
  60.  
  61. ssize_t led_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
  62. {
  63.     int res;
  64.     char status;
  65.  
  66.     res = copy_from_user(&status, buf, sizeof(status));
  67.     if (res < 0)
  68.     {
  69.         printk("copy_from_user error\n");
  70.         return -EFAULT;
  71.     }
  72.  
  73.     if (status == 1)
  74.         turn_on_led();
  75.     else
  76.         turn_off_led();
  77.  
  78.     return 0;
  79. }
  80.  
  81. const struct file_operations led_fops =
  82. {
  83.     .owner = THIS_MODULE,
  84.     .open = led_open,
  85.     .release = led_release,
  86.     .write = led_write,
  87. };
  88.  
  89. static int led_init(void)
  90. {
  91.     /* 获取设备树数据 */
  92.     int res, i, val;
  93.     const char* str;
  94.     u32 regData[10];
  95.     struct device_node* nd = of_find_node_by_path("/hanhanled");
  96.     if (nd == NULL)
  97.     {
  98.         printk("/hanhanled node find failed\n");
  99.         return -EINVAL;
  100.     }
  101.     // compatible
  102.     res = of_property_read_string(nd, "compatible", &str);
  103.     if (res >= 0)
  104.     {
  105.         printk("compatible = %s\n", str);
  106.     }
  107.     // status
  108.     res = of_property_read_string(nd, "status", &str);
  109.     if (res >= 0)
  110.     {
  111.         printk("status = %s\n", str);
  112.     }
  113.     // reg
  114.     res = of_property_read_u32_array(nd, "reg", regData, 10);
  115.     if (res >= 0)
  116.     {
  117.         for (i = 0; i < 10; i++)
  118.         {
  119.             printk("%d ", regData[i]);
  120.         }
  121.         printk("\n");
  122.     }
  123.  
  124.     /* 寄存器映射 */
  125.     CCM_CCGR1 = ioremap(regData[0], regData[1]);
  126.     SW_MUX_GPIO1_IO03 = ioremap(regData[2], regData[3]);
  127.     SW_PAD_GPIO1_IO03 = ioremap(regData[4], regData[5]);
  128.     GPIO1_GDIR = ioremap(regData[6], regData[7]);
  129.     GPIO1_DR = ioremap(regData[8], regData[9]);
  130.  
  131.     // 使能 GPIO1 时钟
  132.     val = readl(CCM_CCGR1);
  133.     val |= (3 << 26);
  134.     writel(val, CCM_CCGR1);
  135.  
  136.     // 复用为 GPIO
  137.     writel(5, SW_MUX_GPIO1_IO03);
  138.  
  139.     // IO 属性
  140.     writel(0x10b0, SW_PAD_GPIO1_IO03);
  141.  
  142.     // GPIO 设置为输出
  143.     val = readl(GPIO1_GDIR);
  144.     val |= (1 << 3);
  145.     writel(val, GPIO1_GDIR);
  146.  
  147.     // led 默认关闭
  148.     val = readl(GPIO1_DR);
  149.     val |= (1 << 3);
  150.     writel(val, GPIO1_DR);
  151.  
  152.     /* 注册设备号 */
  153.     if (ledDev.major)
  154.     {
  155.         ledDev.devId = MKDEV(ledDev.major, ledDev.minor);
  156.         register_chrdev_region(ledDev.devId, LED_CNTLED_NAME);
  157.     }
  158.     else
  159.     {
  160.         alloc_chrdev_region(&ledDev.devId, 0LED_CNTLED_NAME);
  161.         ledDev.major = MAJOR(ledDev.devId);
  162.         ledDev.minor = MINOR(ledDev.devId);
  163.     }
  164.     printk("major = %d, minor = %d\n", ledDev.major, ledDev.minor);
  165.  
  166.     /* 设置 cdev */
  167.     ledDev.cdev.owner = THIS_MODULE;
  168.     cdev_init(&ledDev.cdev, &led_fops);
  169.     cdev_add(&ledDev.cdev, ledDev.devId, LED_CNT);
  170.  
  171.     /* 设置 mdev */
  172.     ledDev.class = class_create(THIS_MODULELED_NAME);
  173.     if (IS_ERR(ledDev.class))
  174.     {
  175.         return PTR_ERR(ledDev.class);
  176.     }
  177.  
  178.     ledDev.device = device_create(ledDev.class, NULL, ledDev.devId, NULLLED_NAME);
  179.     if (IS_ERR(ledDev.device))
  180.     {
  181.         return PTR_ERR(ledDev.device);
  182.     }
  183.  
  184.     return 0;
  185. }
  186.  
  187. static void __exit led_exit(void)
  188. {
  189.     cdev_del(&ledDev.cdev);
  190.     unregister_chrdev_region(ledDev.devId, LED_CNT);
  191.  
  192.     device_destroy(ledDev.class, ledDev.devId);
  193.     class_destroy(ledDev.class);
  194. }
  195.  
  196. module_init(led_init);
  197. module_exit(led_exit);
  198. MODULE_LICENSE("GPL");
  199. MODULE_AUTHOR("hanhan");

这边主要关注设备树数据读取的部分。代码中第 101 至 112 行,分别只是“象征性”地读取了 compatible 和 status 属性,并没有真正用到它们。第 114 至 122 行按数组形式读取了 reg 属性,然后在 125 至 129 行按照 (地址,长度) 对,使用 ioremap 函数进行寄存器映射。这边我们可以看到,就是原来寄存器地址的硬编码从代码中变到了设备树文件中,是那股“设计模式”的味道。

获取 reg 属性的内容再用 ioremap 函数进行映射的操作,可以直接用 of_iomap 函数进行替代,更加方便。对应内容为下述代码中的 132 至 136 行。

  1. #include <linux/module.h>
  2. #include <linux/cdev.h>
  3. #include <linux/device.h>
  4. #include <linux/fs.h>
  5. #include <asm/io.h>
  6. #include <asm/uaccess.h>
  7. #include <linux/of.h>
  8. #include <linux/slab.h>
  9. #include <linux/of_address.h>
  10.  
  11. #define LED_CNT 1
  12. #define LED_NAME "dtsled"
  13.  
  14. void __iomem* CCM_CCGR1;
  15. void __iomem* SW_MUX_GPIO1_IO03;
  16. void __iomem* SW_PAD_GPIO1_IO03;
  17. void __iomem* GPIO1_GDIR;
  18. void __iomem* GPIO1_DR;
  19.  
  20. struct LedDev
  21. {
  22.     /* 设备号相关 */
  23.     dev_t devId;
  24.     int major;
  25.     int minor;
  26.     /* cdev */
  27.     struct cdev cdev;
  28.     /* mdev */
  29.     struct class* class;
  30.     struct device* device;
  31. };
  32.  
  33. struct LedDev ledDev;
  34.  
  35. int led_open(struct inode* indoe, struct file* filp)
  36. {
  37.     printk("led_open\n");
  38.     return 0;
  39. }
  40.  
  41. int led_release(struct inode* indoe, struct file* filp)
  42. {
  43.     printk("led_release\n");
  44.     return 0;
  45. }
  46.  
  47. void turn_on_led(void)
  48. {
  49.     int val = readl(GPIO1_DR);
  50.     val &= ~(1 << 3);
  51.     writel(val, GPIO1_DR);
  52. }
  53.  
  54. void turn_off_led(void)
  55. {
  56.     int val = readl(GPIO1_DR);
  57.     val |= 1 << 3;
  58.     writel(val, GPIO1_DR); 
  59. }
  60.  
  61. ssize_t led_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
  62. {
  63.     int res;
  64.     char status;
  65.  
  66.     res = copy_from_user(&status, buf, sizeof(status));
  67.     if (res < 0)
  68.     {
  69.         printk("copy_from_user error\n");
  70.         return -EFAULT;
  71.     }
  72.  
  73.     if (status == 1)
  74.         turn_on_led();
  75.     else
  76.         turn_off_led();
  77.  
  78.     return 0;
  79. }
  80.  
  81. const struct file_operations led_fops =
  82. {
  83.     .owner = THIS_MODULE,
  84.     .open = led_open,
  85.     .release = led_release,
  86.     .write = led_write,
  87. };
  88.  
  89. static int led_init(void)
  90. {
  91.     /* 获取设备树数据 */
  92.     int res, i, val;
  93.     const char* str;
  94.     u32 regData[10];
  95.     struct device_node* nd = of_find_node_by_path("/hanhanled");
  96.     if (nd == NULL)
  97.     {
  98.         printk("/hanhanled node find failed\n");
  99.         return -EINVAL;
  100.     }
  101.     // compatible
  102.     res = of_property_read_string(nd, "compatible", &str);
  103.     if (res >= 0)
  104.     {
  105.         printk("compatible = %s\n", str);
  106.     }
  107.     // status
  108.     res = of_property_read_string(nd, "status", &str);
  109.     if (res >= 0)
  110.     {
  111.         printk("status = %s\n", str);
  112.     }
  113.     // reg
  114.     res = of_property_read_u32_array(nd, "reg", regData, 10);
  115.     if (res >= 0)
  116.     {
  117.         for (i = 0; i < 10; i++)
  118.         {
  119.             printk("%d ", regData[i]);
  120.         }
  121.         printk("\n");
  122.     }
  123.  
  124.     /* 寄存器映射 */
  125. #if 0
  126.     CCM_CCGR1 = ioremap(regData[0], regData[1]);
  127.     SW_MUX_GPIO1_IO03 = ioremap(regData[2], regData[3]);
  128.     SW_PAD_GPIO1_IO03 = ioremap(regData[4], regData[5]);
  129.     GPIO1_GDIR = ioremap(regData[6], regData[7]);
  130.     GPIO1_DR = ioremap(regData[8], regData[9]);
  131. #else
  132.     CCM_CCGR1 = of_iomap(nd, 0);
  133.     SW_MUX_GPIO1_IO03 = of_iomap(nd, 1);
  134.     SW_PAD_GPIO1_IO03 = of_iomap(nd, 2);
  135.     GPIO1_GDIR = of_iomap(nd, 3);
  136.     GPIO1_DR = of_iomap(nd, 4);
  137. #endif
  138.  
  139.     // 使能 GPIO1 时钟
  140.     val = readl(CCM_CCGR1);
  141.     val |= (3 << 26);
  142.     writel(val, CCM_CCGR1);
  143.  
  144.     // 复用为 GPIO
  145.     writel(5, SW_MUX_GPIO1_IO03);
  146.  
  147.     // IO 属性
  148.     writel(0x10b0, SW_PAD_GPIO1_IO03);
  149.  
  150.     // GPIO 设置为输出
  151.     val = readl(GPIO1_GDIR);
  152.     val |= (1 << 3);
  153.     writel(val, GPIO1_GDIR);
  154.  
  155.     // led 默认关闭
  156.     val = readl(GPIO1_DR);
  157.     val |= (1 << 3);
  158.     writel(val, GPIO1_DR);
  159.  
  160.     /* 注册设备号 */
  161.     if (ledDev.major)
  162.     {
  163.         ledDev.devId = MKDEV(ledDev.major, ledDev.minor);
  164.         register_chrdev_region(ledDev.devId, LED_CNTLED_NAME);
  165.     }
  166.     else
  167.     {
  168.         alloc_chrdev_region(&ledDev.devId, 0LED_CNTLED_NAME);
  169.         ledDev.major = MAJOR(ledDev.devId);
  170.         ledDev.minor = MINOR(ledDev.devId);
  171.     }
  172.     printk("major = %d, minor = %d\n", ledDev.major, ledDev.minor);
  173.  
  174.     /* 设置 cdev */
  175.     ledDev.cdev.owner = THIS_MODULE;
  176.     cdev_init(&ledDev.cdev, &led_fops);
  177.     cdev_add(&ledDev.cdev, ledDev.devId, LED_CNT);
  178.  
  179.     /* 设置 mdev */
  180.     ledDev.class = class_create(THIS_MODULELED_NAME);
  181.     if (IS_ERR(ledDev.class))
  182.     {
  183.         return PTR_ERR(ledDev.class);
  184.     }
  185.  
  186.     ledDev.device = device_create(ledDev.class, NULL, ledDev.devId, NULLLED_NAME);
  187.     if (IS_ERR(ledDev.device))
  188.     {
  189.         return PTR_ERR(ledDev.device);
  190.     }
  191.  
  192.     return 0;
  193. }
  194.  
  195. static void __exit led_exit(void)
  196. {
  197.     cdev_del(&ledDev.cdev);
  198.     unregister_chrdev_region(ledDev.devId, LED_CNT);
  199.  
  200.     device_destroy(ledDev.class, ledDev.devId);
  201.     class_destroy(ledDev.class);
  202. }
  203.  
  204. module_init(led_init);
  205. module_exit(led_exit);
  206. MODULE_LICENSE("GPL");
  207. MODULE_AUTHOR("hanhan");