【正点原子Linux连载】 第九章 设备树下的LED驱动实验 摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南

1)实验平台:正点原子ATK-DLRK3568开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=731866264428
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban

第九章 设备树下的LED驱动实验

上一章我们详细的讲解了设备树语法以及在驱动开发中常用的OF函数,本章我们就开始第一个基于设备树的Linux驱动实验。本章在第七章实验的基础上完成,只是将其驱动开发改为设备树形式而已。

9.1 设备树LED驱动原理
在《第七章 新字符设备驱动实验》中,我们直接在驱动文件newchrled.c中定义有关寄存器物理地址,然后使用io_remap函数进行内存映射,得到对应的虚拟地址,最后操作寄存器对应的虚拟地址完成对GPIO的初始化。本章我们在第七章实验基础上完成,本章我们使用设备树来向Linux内核传递相关的寄存器物理地址,Linux驱动文件使用上一章讲解的OF函数从设备树中获取所需的属性值,然后使用获取到的属性值来初始化相关的IO。本章实验还是比较简单的,本章实验重点内容如下:
①、在rk3568-atk-evb1-ddr4-v10.dtsi文件中创建相应的设备节点。
②、编写驱动程序(在第七章实验基础上完成),获取设备树中的相关属性值。
③、使用获取到的有关属性值来初始化LED所使用的GPIO。
9.2 硬件原理图分析
本实验的硬件原理参考6.2小节即可。
9.3 实验程序编写
9.3.1 修改设备树文件
在根节点“/”下创建一个名为“rk3568_led”的子节点,打开rk3568-atk-evb1-ddr4-v10.dtsi文件,在根节点“/”最后面输入如下所示内容:
示例代码9.3.1.1 rk3568_led节点

1   rk3568_led {
2        compatible = "atkrk3568-led";
3        status = "okay";
4        reg = <0x0 0xFDC20010 0x0 0x08 /* PMU_GRF_GPIO0C_IOMUX_L       */
5               0x0 0xFDC20090 0x0 0x08   /* PMU_GRF_GPIO0C_DS_0          */
6               0x0  0xFDD60004 0x0 0x08  /* GPIO0_SWPORT_DR_H            */
7               0x0  0xFDD6000C 0x0 0x08 >;/* GPIO0_SWPORT_DDR_H         */
8   };
第2行,属性compatible设置rk3568_led节点兼容为“atkrk3568-led”。
第3行,属性status设置状态为“okay”。
第4~7行,reg属性,非常重要!reg属性设置了驱动里面所要使用的寄存器物理地址,比如第4行的“0x0 0xFDC20010 0x0 0x08”表示RK3568的PMU_GRF_GPIO0C_IOMUX_L寄存器,其中寄存器地址为0xFDC20010,长度为8个字节。

设备树修改完成以后在SDK顶层目录输入如下命令重新编译一下内核:
./build.sh kernel
编译完成以后得到boot.img,如图9.3.1.1所示:
在这里插入图片描述

图9.3.1.1 内核编译出来的zboot.img
图9.3.1.1中zboot.img就是编译出来的内核+设备树打包在一起的文件。
烧写方法很简单,分两步:
②、开发板进入LOADER模式,前提是开发板已经烧写了uboot,并且uboot正常运行。
③、打开RKDevTool工具,导入配置文件,然后将图9.3.1.1中的boot.img放到要烧写的目录,然后只烧写boot.img,详细请看《【正点原子】 ATK-DLRK3568嵌入式Linux系统开发手册》第5.2.3小节-烧录。
烧写完成以后启动开发板。Linux启动成功以后进入到/proc/device-tree/目录中查看是否有“rk3568_led”这个节点,结果如图9.3.1.3所示:
在这里插入图片描述

图9.3.1.2 rk3568_led节点
如果没有“rk3568_led”节点的话请重点检查下面两点:
①、检查设备树修改是否成功,也就是rk3568_led节点是否为根节点“/”的子节点。
②、检查是否使用新的设备树启动的Linux内核。
可以进入到图9.3.1.3中的rk3568_led目录中,查看一下都有哪些属性文件,结果如图9.3.1.4所示:
在这里插入图片描述

图9.3.1.4 rk3568_led节点文件
大家可以用cat命令查看一下compatible、status等属性值是否和我们设置的一致。
9.3.2 LED灯驱动程序编写
设备树准备好以后就可以编写驱动程序了,本章实验在第七章实验驱动文件newchrled.c的基础上修改而来。新建名为“04_dtsled”文件夹,然后在04_dtsled文件夹里面创建vscode工程,工作区命名为“dtsled”。工程创建好以后新建dtsled.c文件,在dtsled.c里面输入如下内容:
示例代码9.3.2.1 dtsled.c文件内容

1   #include <linux/types.h>
2   #include <linux/kernel.h>
3   #include <linux/delay.h>
4   #include <linux/ide.h>
5   #include <linux/init.h>
6   #include <linux/module.h>
7   #include <linux/errno.h>
8   #include <linux/gpio.h>
9   #include <linux/cdev.h>
10  #include <linux/device.h>
11  #include <linux/of.h>
12  #include <linux/of_address.h>
13  //#include <asm/mach/map.h>
14  #include <asm/uaccess.h>
15  #include <asm/io.h>
16  /***************************************************************
17  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
18  文件名       : dtsled.c
19  作者      : 正点原子
20  版本      : V1.0
21  描述      : LED驱动文件。
22  其他      : 无
23  论坛      : www.openedv.com
24  日志      : 初版V1.0 2022/12/06 正点原子团队创建
25  ***************************************************************/
26  #define DTSLED_CNT          1           /* 设备号个数 */
27  #define DTSLED_NAME         "dtsled"    /* 名字   */
28  #define LEDOFF              0           /* 关灯   */
29  #define LEDON               1           /* 开灯   */
30 
31  /* 映射后的寄存器虚拟地址指针 */
32  static void __iomem *PMU_GRF_GPIO0C_IOMUX_L_PI;
33  static void __iomem *PMU_GRF_GPIO0C_DS_0_PI;
34  static void __iomem *GPIO0_SWPORT_DR_H_PI;
35  static void __iomem *GPIO0_SWPORT_DDR_H_PI;
36 
37  /* dtsled设备结构体 */
38  struct dtsled_dev{
39      dev_t devid;            /* 设备号     */
40      struct cdev cdev;       /* cdev     */
41      struct class *class;        /* 类      */
42      struct device *device;  /* 设备    */
43      int major;              /* 主设备号   */
44      int minor;              /* 次设备号   */
45      struct device_node  *nd; /* 设备节点 */
46  };
47 
48  struct dtsled_dev dtsled;   /* led设备 */
49 
50  /*
51   * @description     : LED打开/关闭
52   * @param - sta     : LEDON(0) 打开LED,LEDOFF(1) 关闭LED
53   * @return          : 无
54   */
55  void led_switch(u8 sta)
56  {
57      u32 val = 0;
58      if(sta == LEDON) {
59          val = readl(GPIO0_SWPORT_DR_H_PI);
60          val &= ~(0X1 << 0); /* bit0 清零*/
61          val |= ((0X1 << 16) | (0X1 << 0));  /* bit16 置1,允许写bit0,
62                                             bit0,高电平*/
63          writel(val, GPIO0_SWPORT_DR_H_PI);
64      }else if(sta == LEDOFF) { 
65          val = readl(GPIO0_SWPORT_DR_H_PI);
66          val &= ~(0X1 << 0); /* bit0 清零*/
67          val |= ((0X1 << 16) | (0X0 << 0));  /* bit16 置1,允许写bit0,
68                                             bit0,低电平 */
69          writel(val, GPIO0_SWPORT_DR_H_PI);
70      } 
71  }
72 
73  /*
74   * @description     : 取消映射
75   * @return          : 无
76   */
77  void led_unmap(void)
78  {
79      /* 取消映射 */
80      iounmap(PMU_GRF_GPIO0C_IOMUX_L_PI);
81      iounmap(PMU_GRF_GPIO0C_DS_0_PI);
82      iounmap(GPIO0_SWPORT_DR_H_PI);
83      iounmap(GPIO0_SWPORT_DDR_H_PI);
84  }
85 
86  /*
87   * @description     : 打开设备
88   * @param - inode   : 传递给驱动的inode
89   * @param - filp    : 设备文件,file结构体有个叫做private_data的成员变量
90   *                    一般在open的时候将private_data指向设备结构体。
91   * @return          : 0 成功;其他 失败
92   */
93  static int led_open(struct inode *inode, struct file *filp)
94  {
95      filp->private_data = &dtsled; /* 设置私有数据 */
96      return 0;
97  }
98 
99  /*
100  * @description     : 从设备读取数据 
101  * @param - filp    : 要打开的设备文件(文件描述符)
102  * @param - buf     : 返回给用户空间的数据缓冲区
103  * @param - cnt     : 要读取的数据长度
104  * @param - offt    : 相对于文件首地址的偏移
105  * @return          : 读取的字节数,如果为负值,表示读取失败
106  */
107 static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
108 {
109     return 0;
110 }
111
112 /*
113  * @description     : 向设备写数据 
114  * @param - filp    : 设备文件,表示打开的文件描述符
115  * @param - buf     : 要写给设备写入的数据
116  * @param - cnt     : 要写入的数据长度
117  * @param - offt    : 相对于文件首地址的偏移
118  * @return          : 写入的字节数,如果为负值,表示写入失败
119  */
120 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
121 {
122     int retvalue;
123     unsigned char databuf[1];
124     unsigned char ledstat;
125
126     retvalue = copy_from_user(databuf, buf, cnt);
127     if(retvalue < 0) {
128         printk("kernel write failed!\r\n");
129         return -EFAULT;
130     }
131
132     ledstat = databuf[0];       /* 获取状态值 */
133
134     if(ledstat == LEDON) {  
135         led_switch(LEDON);      /* 打开LED灯 */
136     } else if(ledstat == LEDOFF) {
137         led_switch(LEDOFF);     /* 关闭LED灯 */
138     }
139     return 0;
140 }
141
142 /*
143  * @description     : 关闭/释放设备
144  * @param - filp    : 要关闭的设备文件(文件描述符)
145  * @return          : 0 成功;其他 失败
146  */
147 static int led_release(struct inode *inode, struct file *filp)
148 {
149     return 0;
150 }
151
152 /* 设备操作函数 */
153 static struct file_operations dtsled_fops = {
154     .owner = THIS_MODULE,
155     .open = led_open,
156     .read = led_read,
157     .write = led_write,
158     .release =  led_release,
159 };
160
161 /*
162  * @description : 驱动出口函数
163  * @param       : 无
164  * @return      : 无
165  */
166 static int __init led_init(void)
167 {
168     u32 val = 0;
169     int ret;
170     u32 regdata[16];
171     const char *str;
172     struct property *proper;
173
174     /* 获取设备树中的属性数据 */
175     /* 1、获取设备节点:rk3568_led */
176     dtsled.nd = of_find_node_by_path("/rk3568_led");
177     if(dtsled.nd == NULL) {
178         printk("rk3568_led node not find!\r\n");
179         goto fail_find_node;
180     } else {
181         printk("rk3568_led node find!\r\n");
182     }
183
184     /* 2、获取compatible属性内容 */
185     proper = of_find_property(dtsled.nd, "compatible", NULL);
186     if(proper == NULL) {
187         printk("compatible property find failed\r\n");
188     } else {
189         printk("compatible = %s\r\n", (char*)proper->value);
190     }
191
192     /* 3、获取status属性内容 */
193     ret = of_property_read_string(dtsled.nd, "status", &str);
194     if(ret < 0){
195         printk("status read failed!\r\n");
196     } else {
197         printk("status = %s\r\n",str);
198     }
199
200     /* 4、获取reg属性内容 */
201     ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 16);
202     if(ret < 0) {
203         printk("reg property read failed!\r\n");
204     } else {
205         u8 i = 0;
206         printk("reg data:\r\n");
207         for(i = 0; i < 16; i++)
208             printk("%#X ", regdata[i]);
209         printk("\r\n");
210     }
211
212     /* 初始化LED */
213     /* 1、寄存器地址映射 */
214     PMU_GRF_GPIO0C_IOMUX_L_PI = of_iomap(dtsled.nd, 0);
215     PMU_GRF_GPIO0C_DS_0_PI = of_iomap(dtsled.nd, 1);
216     GPIO0_SWPORT_DR_H_PI = of_iomap(dtsled.nd, 2);
217     GPIO0_SWPORT_DDR_H_PI = of_iomap(dtsled.nd, 3);
218
219     /* 2、设置GPIO0_C0为GPIO功能。*/
220     val = readl(PMU_GRF_GPIO0C_IOMUX_L_PI);
221     val &= ~(0X7 << 0); /* bit2:0,清零 */
222     val |= ((0X7 << 16) | (0X0 << 0));  /* bit18:16 置1,允许写bit2:0,
223                                            bit2:0:0,用作GPIO0_C0  */
224     writel(val, PMU_GRF_GPIO0C_IOMUX_L_PI);
225
226     /* 3、设置GPIO0_C0驱动能力为level5 */
227     val = readl(PMU_GRF_GPIO0C_DS_0_PI);
228     val &= ~(0X3F << 0);    /* bit5:0清零*/
229     val |= ((0X3F << 16) | (0X3F << 0));    /* bit21:16 置1,允许写bit5:0,
230                                            bit5:0:0,用作GPIO0_C0  */
231     writel(val, PMU_GRF_GPIO0C_DS_0_PI);
232
233     /* 4、设置GPIO0_C0为输出 */
234     val = readl(GPIO0_SWPORT_DDR_H_PI);
235     val &= ~(0X1 << 0); /* bit0 清零*/
236     val |= ((0X1 << 16) | (0X1 << 0));  /* bit16 置1,允许写bit0,
237                                            bit0,高电平 */
238     writel(val, GPIO0_SWPORT_DDR_H_PI);
239
240     /* 5、设置GPIO0_C0为低电平,关闭LED灯。*/
241     val = readl(GPIO0_SWPORT_DR_H_PI);
242     val &= ~(0X1 << 0); /* bit0 清零*/
243     val |= ((0X1 << 16) | (0X0 << 0));  /* bit16 置1,允许写bit0,
244                                            bit0,低电平 */
245     writel(val, GPIO0_SWPORT_DR_H_PI);
246
247         /* 注册字符设备驱动 */
248     /* 1、创建设备号 */
249     if (dtsled.major) {     /*  定义了设备号 */
250         dtsled.devid = MKDEV(dtsled.major, 0);
251         ret = register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);
252         if(ret < 0) {
253             pr_err("cannot register %s char driver [ret=%d]\n",DTSLED_NAME, DTSLED_CNT);
254             goto fail_devid;
255         }
256     } else {                        /* 没有定义设备号 */
257         ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);   /* 申请设备号 */
258         if(ret < 0) {
259             pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", DTSLED_NAME, ret);
260             goto fail_devid;
261         }
262         dtsled.major = MAJOR(dtsled.devid); /* 获取分配号的主设备号 */
263         dtsled.minor = MINOR(dtsled.devid); /* 获取分配号的次设备号 */
264
265     }
266
267     printk("dtsled major=%d,minor=%d\r\n",dtsled.major, dtsled.minor);  
268     
269     /* 2、初始化cdev */
270     dtsled.cdev.owner = THIS_MODULE;
271     cdev_init(&dtsled.cdev, &dtsled_fops);
272     
273     /* 3、添加一个cdev */
274     ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);
275     if(ret < 0)
276         goto del_unregister;
277
278     /* 4、创建类 */
279     dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
280     if (IS_ERR(dtsled.class)) {
281         goto del_cdev;
282     }
283
284     /* 5、创建设备 */
285     dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
286     if (IS_ERR(dtsled.device)) {
287         goto destroy_class;
288     }
289
290     return 0;
291
292 destroy_class:
293     class_destroy(dtsled.class);
294 del_cdev:
295     cdev_del(&dtsled.cdev);
296 del_unregister:
297     unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
298 fail_devid:
299     led_unmap();
300 fail_find_node:
301     return -EIO;
302 }
303
304 /*
305  * @description : 驱动出口函数
306  * @param       : 无
307  * @return      : 无
308  */
309 static void __exit led_exit(void)
310 {
311     /* 取消映射 */
312     led_unmap();
313
314     /* 注销字符设备驱动 */
315     cdev_del(&dtsled.cdev);/*  删除cdev */
316     unregister_chrdev_region(dtsled.devid, DTSLED_CNT); /* 注销设备号 */
317
318     device_destroy(dtsled.class, dtsled.devid);
319     class_destroy(dtsled.class);
320 }
321
322 module_init(led_init);
323 module_exit(led_exit);
324 MODULE_LICENSE("GPL");
325 MODULE_AUTHOR("ALIENTEK");
326 MODULE_INFO(intree, "Y");

dtsled.c文件中的内容和第七章的newchrled.c文件中的内容基本一样,只是dtsled.c中包含了处理设备树的代码,我们重点来看一下这部分代码。
第45行,在设备结构体dtsled_dev中添加了成员变量nd,nd是device_node结构体类型指针,表示设备节点。如果我们要读取设备树某个节点的属性值,首先要先得到这个节点,一般在设备结构体中添加device_node指针变量来存放这个节点。
第176~182行,通过of_find_node_by_path函数得到rk3568_led节点,后续其他的OF函数要使用device_node。
第185~190行,通过of_find_property函数获取rk3568_led节点的compatible属性,返回值为property结构体类型指针变量,property的成员变量value表示属性值。
第193~198行,通过of_property_read_string函数获取rk3568_led节点的status属性值。
第201~210行,通过of_property_read_u32_array函数获取rk3568_led节点的reg属性所有值,并且将获取到的值都存放到regdata数组中。第208行将获取到的reg属性值依次输出到终端上。
第214~217行,使用of_iomap函数一次性完成读取reg属性以及内存映射,of_iomap函数是设备树推荐使用的OF函数。
9.3.3 编写测试APP
本章直接使用第七章的测试APP,将上一章的ledApp.c文件复制到本章实验工程下即可。
9.4.1 编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,本章实验的Makefile文件和第五章实验基本一样,只是将obj-m变量的值改为dtsled.o,Makefile内容如下所示:
示例代码9.4.1.1 Makefile文件

1  KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel
...... 
4  obj-m := dtsled.o
......
11 clean:
12  $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

第4行,设置obj-m变量的值为dtsled.o。
输入如下命令编译出驱动模块文件:
make ARCH=arm64 //ARCH=arm64必须指定,否则编译会失败
编译成功以后就会生成一个名为“dtsled.ko”的驱动模块文件。
2、编译测试APP
输入如下命令编译测试ledApp.c这个测试程序:
/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc ledApp.c -o ledApp
编译成功以后就会生成ledApp这个应用程序。
9.4.2 运行测试
先在开发板中输入如下命令关闭LED的心跳灯功能:
echo none > /sys/class/leds/work/trigger
在Ubuntu中将上一小节编译出来的dtsled.ko和ledApp这两个文件通过adb命令发送到开发板的/lib/modules/4.19.232目录下,命令如下:
adb push dtsled.ko ledApp /lib/modules/4.19.232
发送成功以后进入到开发板目录lib/modules/4.19.232中,输入如下命令加载dtsled.ko驱动模块:
depmod //第一次加载驱动的时候需要运行此命令
modprobe dtsled //加载驱动
驱动加载成功以后会在终端中输出一些信息,如图9.4.2.1所示:
在这里插入图片描述

图9.4.2.1 驱动加载成功以后输出的信息
从图9.4.2.1可以看出,rk3568_led这个节点找到了,并且compatible属性值为“atkrk3568-led”,status属性值为“okay”,reg属性的值为“0x0 0xFDC20010 0x0 0x08 0x0 0xFDC20090 0x0 0x08 0x0 0xFDD60004 0x0 0x08 0x0 0xFDD6000C 0x0 0x08”,这些都和我们设置的设备树一致。
驱动加载成功以后就可以使用ledApp软件来测试驱动是否工作正常,输入如下命令打开LED灯:
./ledApp /dev/dtsled 1 //打开LED灯
输入上述命令以后观察开发板上的红色LED灯是否点亮,如果点亮的话说明驱动工作正常。在输入如下命令关闭LED灯:
./ledApp /dev/dtsled 0 //关闭LED灯
输入上述命令以后观察开发板上的红色LED灯是否熄灭。如果要卸载驱动的话输入如下命令即可:
rmmod dtsled.ko

  • 13
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值