超级版主
- 积分
- 4666
- 金钱
- 4666
- 注册时间
- 2019-5-8
- 在线时间
- 1224 小时
|
1)实验平台:正点原子STM32MP157开发板
2) 章节摘自【正点原子】《STM32MP157嵌入式Linux驱动开发指南》
3)购买链接:https://item.taobao.com/item.htm?&id=629270721801
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/arm-linux/zdyzmp157.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子STM32MP157技术交流群:691905614
第二十四章 设备树下的LED驱动实验
上一章我们详细的讲解了设备树语法以及在驱动开发中常用的OF函数,本章我们就开始第一个基于设备树的Linux驱动实验。本章在第二十二章实验的基础上完成,只是将其驱动开发改为设备树形式而已。
24.1 设备树LED驱动原理
在《第二十二章 新字符设备驱动实验》中,我们直接在驱动文件newchrled.c中定义有关寄存器物理地址,然后使用io_remap函数进行内存映射,得到对应的虚拟地址,最后操作寄存器对应的虚拟地址完成对GPIO的初始化。本章我们在第四十二章实验基础上完成,本章我们使用设备树来向Linux内核传递相关的寄存器物理地址,Linux驱动文件使用上一章讲解的OF函数从设备树中获取所需的属性值,然后使用获取到的属性值来初始化相关的IO。本章实验还是比较简单的,本章实验重点内容如下:
①、在stm32mp157d-atk.dts文件中创建相应的设备节点。
②、编写驱动程序(在第二十二章实验基础上完成),获取设备树中的相关属性值。
③、使用获取到的有关属性值来初始化LED所使用的GPIO。
24.2 硬件原理图分析
本实验的硬件原理参考21.2小节即可。
24.3 实验程序编写
24.3.1 修改设备树文件
在根节点“/”下创建一个名为“stm32mp1_led”的子节点,打开stm32mp157d-atk.dts文件,在根节点“/”最后面输入如下所示内容:
示例代码44.3.1.1 stm32mp1_led节点
- 1 stm32mp1_led {
- 2 compatible = "atkstm32mp1-led";
- 3 status = "okay";
- 4 reg = <0X50000A28 0X04 /* RCC_MP_AHB4ENSETR */
- 5 0X5000A000 0X04 /* GPIOI_MODER */
- 6 0X5000A004 0X04 /* GPIOI_OTYPER */
- 7 0X5000A008 0X04 /* GPIOI_OSPEEDR */
- 8 0X5000A00C 0X04 /* GPIOI_PUPDR */
- 9 0X5000A018 0X04 >; /* GPIOI_BSRR */
- 10 };
复制代码
第2行,属性compatible设置stm32mp1_led节点兼容为“atkstm32mp1-led”。
第3行,属性status设置状态为“okay”。
第4~9行,reg属性,非常重要!reg属性设置了驱动里面所要使用的寄存器物理地址,比如第4行的“0X50000A28 0X04”表示STM32MP1的RCC_MP_AHB4ENSETR寄存器,其中寄存器地址为0X50000A28,长度为4个字节。
设备树修改完成以后输入如下命令重新编译一下stm32mp157d-atk.dts:
编译完成以后得到stm32mp157d-atk.dtb,使用新的stm32mp157d-atk.dtb启动Linux内核。Linux启动成功以后进入到/proc/device-tree/目录中查看是否有“stm32mp1_led”这个节点,结果如图24.3.1.1所示:
图24.3.1.1 stm32mp1_led节点
如果没有“stm32mp1_led”节点的话请重点检查下面两点:
①、检查设备树修改是否成功,也就是stm32mp1_led节点是否为根节点“/”的子节点。
②、检查是否使用新的设备树启动的Linux内核。
可以进入到图24.3.1.1中的stm32mpl_led目录中,查看一下都有哪些属性文件,结果如图24.3.1.2所示:
图24.3.1.2 stm32mp1_led节点文件
大家可以用cat命令查看一下compatible、status等属性值是否和我们设置的一致。
24.3.2 LED灯驱动程序编写
设备树准备好以后就可以编写驱动程序了,本章实验在第二十二章实验驱动文件newchrled.c的基础上修改而来。新建名为“4_dtsled”文件夹,然后在4_dtsled文件夹里面创建vscode工程,工作区命名为“dtsled”。工程创建好以后新建dtsled.c文件,在dtsled.c里面输入如下内容:
示例代码24.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 /***************************************************************
- 18 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
- 19 文件名 : dtsled.c
- 20 作者 : 正点原子Linux团队
- 21 版本 : V1.0
- 22 描述 : LED驱动文件。
- 23 其他 : 无
- 24 论坛 : <a href="www.openedv.com" target="_blank">www.openedv.com</a>
- 25 日志 : 初版V1.0 2020/12/19 正点原子Linux团队创建
- 26 ***************************************************************/
- 27 #define DTSLED_CNT 1 /* 设备号个数 */
- 28 #define DTSLED_NAME "dtsled" /* 名字 */
- 29 #define LEDOFF 0 /* 关灯 */
- 30 #define LEDON 1 /* 开灯 */
- 31
- 32 /* 映射后的寄存器虚拟地址指针 */
- 33 static void __iomem *MPU_AHB4_PERIPH_RCC_PI;
- 34 static void __iomem *GPIOI_MODER_PI;
- 35 static void __iomem *GPIOI_OTYPER_PI;
- 36 static void __iomem *GPIOI_OSPEEDR_PI;
- 37 static void __iomem *GPIOI_PUPDR_PI;
- 38 static void __iomem *GPIOI_BSRR_PI;
- 39
- 40 /* dtsled设备结构体 */
- 41 struct dtsled_dev{
- 42 dev_t devid; /* 设备号 */
- 43 struct cdev cdev; /* cdev */
- 44 struct class *class; /* 类 */
- 45 struct device *device; /* 设备 */
- 46 int major; /* 主设备号 */
- 47 int minor; /* 次设备号 */
- 48 struct device_node *nd; /* 设备节点 */
- 49 };
- 50
- 51 struct dtsled_dev dtsled; /* led设备 */
- 52
- 53 /*
- 54 * @description : LED打开/关闭
- 55 * [url=home.php?mod=space&uid=271674]@param[/url] - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED
- 56 * @return : 无
- 57 */
- 58 void led_switch(u8 sta)
- 59 {
- 60 u32 val = 0;
- 61 if(sta == LEDON) {
- 62 val = readl(GPIOI_BSRR_PI);
- 63 val |= (1 << 16);
- 64 writel(val, GPIOI_BSRR_PI);
- 65 }else if(sta == LEDOFF) {
- 66 val = readl(GPIOI_BSRR_PI);
- 67 val|= (1 << 0);
- 68 writel(val, GPIOI_BSRR_PI);
- 69 }
- 70 }
- 71
- 72 /*
- 73 * @description : 取消映射
- 74 * @return : 无
- 75 */
- 76 void led_unmap(void)
- 77 {
- 78 /* 取消映射 */
- 79 iounmap(MPU_AHB4_PERIPH_RCC_PI);
- 80 iounmap(GPIOI_MODER_PI);
- 81 iounmap(GPIOI_OTYPER_PI);
- 82 iounmap(GPIOI_OSPEEDR_PI);
- 83 iounmap(GPIOI_PUPDR_PI);
- 84 iounmap(GPIOI_BSRR_PI);
- 85 }
- 86
- 87 /*
- 88 * @description : 打开设备
- 89 * @param – inode : 传递给驱动的inode
- 90 * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
- 91 * 一般在open的时候将private_data指向设备结构体。
- 92 * @return : 0 成功;其他 失败
- 93 */
- 94 static int led_open(struct inode *inode, struct file *filp)
- 95 {
- 96 filp->private_data = &dtsled; /* 设置私有数据 */
- 97 return 0;
- 98 }
- 99
- 100 /*
- 101 * @description : 从设备读取数据
- 102 * @param - filp : 要打开的设备文件(文件描述符)
- 103 * @param - buf : 返回给用户空间的数据缓冲区
- 104 * @param - cnt : 要读取的数据长度
- 105 * @param - offt : 相对于文件首地址的偏移
- 106 * @return : 读取的字节数,如果为负值,表示读取失败
- 107 */
- 108 static ssize_t led_read(struct file *filp, char __user *buf,
- size_t cnt, loff_t *offt)
- 109 {
- 110 return 0;
- 111 }
- 112
- 113 /*
- 114 * @description : 向设备写数据
- 115 * @param - filp : 设备文件,表示打开的文件描述符
- 116 * @param - buf : 要写给设备写入的数据
- 117 * @param - cnt : 要写入的数据长度
- 118 * @param - offt : 相对于文件首地址的偏移
- 119 * @return : 写入的字节数,如果为负值,表示写入失败
- 120 */
- 121 static ssize_t led_write(struct file *filp, const char __user *buf,
- size_t cnt, loff_t *offt)
- 122 {
- 123 int retvalue;
- 124 unsigned char databuf[1];
- 125 unsigned char ledstat;
- 126
- 127 retvalue = copy_from_user(databuf, buf, cnt);
- 128 if(retvalue < 0) {
- 129 printk("kernel write failed!\r\n");
- 130 return -EFAULT;
- 131 }
- 132
- 133 ledstat = databuf[0]; /* 获取状态值 */
- 134
- 135 if(ledstat == LEDON) {
- 136 led_switch(LEDON); /* 打开LED灯 */
- 137 } else if(ledstat == LEDOFF) {
- 138 led_switch(LEDOFF); /* 关闭LED灯 */
- 139 }
- 140 return 0;
- 141 }
- 142
- 143 /*
- 144 * @description : 关闭/释放设备
- 145 * @param – filp : 要关闭的设备文件(文件描述符)
- 146 * @return : 0 成功;其他 失败
- 147 */
- 148 static int led_release(struct inode *inode, struct file *filp)
- 149 {
- 150 return 0;
- 151 }
- 152
- 153 /* 设备操作函数 */
- 154 static struct file_operations dtsled_fops = {
- 155 .owner = THIS_MODULE,
- 156 .open = led_open,
- 157 .read = led_read,
- 158 .write = led_write,
- 159 .release = led_release,
- 160 };
- 161
- 162 /*
- 163 * @description : 驱动出口函数
- 164 * @param : 无
- 165 * @return : 无
- 166 */
- 167 static int __init led_init(void)
- 168 {
- 169 u32 val = 0;
- 170 int ret;
- 171 u32 regdata[12];
- 172 const char *str;
- 173 struct property *proper;
- 174
- 175 /* 获取设备树中的属性数据 */
- 176 /* 1、获取设备节点:stm32mp1_led */
- 177 dtsled.nd = of_find_node_by_path("/stm32mp1_led");
- 178 if(dtsled.nd == NULL) {
- 179 printk("stm32mp1_led node nost find!\r\n");
- 180 return -EINVAL;
- 181 } else {
- 182 printk("stm32mp1_lcd node find!\r\n");
- 183 }
- 184
- 185 /* 2、获取compatible属性内容 */
- 186 proper = of_find_property(dtsled.nd, "compatible", NULL);
- 187 if(proper == NULL) {
- 188 printk("compatible property find failed\r\n");
- 189 } else {
- 190 printk("compatible = %s\r\n", (char*)proper->value);
- 191 }
- 192
- 193 /* 3、获取status属性内容 */
- 194 ret = of_property_read_string(dtsled.nd, "status", &str);
- 195 if(ret < 0){
- 196 printk("status read failed!\r\n");
- 197 } else {
- 198 printk("status = %s\r\n",str);
- 199 }
- 200
- 201 /* 4、获取reg属性内容 */
- 202 ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 12);
- 203 if(ret < 0) {
- 204 printk("reg property read failed!\r\n");
- 205 } else {
- 206 u8 i = 0;
- 207 printk("reg data:\r\n");
- 208 for(i = 0; i < 12; i++)
- 209 printk("%#X ", regdata<i>);
- 210 printk("\r\n");
- 211 }
- 212
- 213 /* 初始化LED */
- 214 /* 1、寄存器地址映射 */
- 215 MPU_AHB4_PERIPH_RCC_PI = of_iomap(dtsled.nd, 0);
- 216 GPIOI_MODER_PI = of_iomap(dtsled.nd, 1);
- 217 GPIOI_OTYPER_PI = of_iomap(dtsled.nd, 2);
- 218 GPIOI_OSPEEDR_PI = of_iomap(dtsled.nd, 3);
- 219 GPIOI_PUPDR_PI = of_iomap(dtsled.nd, 4);
- 220 GPIOI_BSRR_PI = of_iomap(dtsled.nd, 5);
- 221
- 222 /* 2、使能PI时钟 */
- 223 val = readl(MPU_AHB4_PERIPH_RCC_PI);
- 224 val &= ~(0X1 << 8); /* 清除以前的设置 */
- 225 val |= (0X1 << 8); /* 设置新值 */
- 226 writel(val, MPU_AHB4_PERIPH_RCC_PI);
- 227
- 228 /* 3、设置PI0通用的输出模式。*/
- 229 val = readl(GPIOI_MODER_PI);
- 230 val &= ~(0X3 << 0); /* bit0:1清零 */
- 231 val |= (0X1 << 0); /* bit0:1设置01 */
- 232 writel(val, GPIOI_MODER_PI);
- 233
- 234 /* 3、设置PI0为推挽模式。*/
- 235 val = readl(GPIOI_OTYPER_PI);
- 236 val &= ~(0X1 << 0); /* bit0清零,设置为上拉*/
- 237 writel(val, GPIOI_OTYPER_PI);
- 238
- 239 /* 4、设置PI0为高速。*/
- 240 val = readl(GPIOI_OSPEEDR_PI);
- 241 val &= ~(0X3 << 0); /* bit0:1 清零 */
- 242 val |= (0x2 << 0); /* bit0:1 设置为10 */
- 243 writel(val, GPIOI_OSPEEDR_PI);
- 244
- 245 /* 5、设置PI0为上拉。*/
- 246 val = readl(GPIOI_PUPDR_PI);
- 247 val &= ~(0X3 << 0); /* bit0:1 清零 */
- 248 val |= (0x1 << 0); /* bit0:1 设置为01 */
- 249 writel(val,GPIOI_PUPDR_PI);
- 250
- 251 /* 6、默认关闭LED */
- 252 val = readl(GPIOI_BSRR_PI);
- 253 val |= (0x1 << 0);
- 254 writel(val, GPIOI_BSRR_PI);
- 255
- 256 /* 注册字符设备驱动 */
- 257 /* 1、创建设备号 */
- 258 if (dtsled.major) { /* 定义了设备号 */
- 259 dtsled.devid = MKDEV(dtsled.major, 0);
- 260 ret = register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);
- 261 if(ret < 0) {
- 262 pr_err("cannot register %s char driver [ret=%d]\n",DTSLED_NAME, DTSLED_CNT);
- 263 goto fail_map;
- 264 }
- 265 } else { /* 没有定义设备号 */
- 266 ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT,
- DTSLED_NAME); /* 申请设备号 */
- 267 if(ret < 0) {
- 268 pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n",
- DTSLED_NAME, ret);
- 269 goto fail_map;
- 270 }
- 271 dtsled.major = MAJOR(dtsled.devid); /* 获取分配号的主设备号 */
- 272 dtsled.minor = MINOR(dtsled.devid); /* 获取分配号的次设备号 */
- 273
- 274 }
- 275 printk("dtsled major=%d,minor=%d\r\n",dtsled.major,
- dtsled.minor);
- 276
- 277 /* 2、初始化cdev */
- 278 dtsled.cdev.owner = THIS_MODULE;
- 279 cdev_init(&dtsled.cdev, &dtsled_fops);
- 280
- 281 /* 3、添加一个cdev */
- 282 ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);
- 283 if(ret < 0)
- 284 goto del_unregister;
- 285
- 286 /* 4、创建类 */
- 287 dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
- 288 if (IS_ERR(dtsled.class)) {
- 289 goto del_cdev;
- 290 }
- 291
- 292 /* 5、创建设备 */
- 293 dtsled.device = device_create(dtsled.class, NULL, dtsled.devid,
- NULL, DTSLED_NAME);
- 294 if (IS_ERR(dtsled.device)) {
- 295 goto destroy_class;
- 296 }
- 297
- 298 return 0;
- 299
- 300 destroy_class:
- 301 class_destroy(dtsled.class);
- 302 del_cdev:
- 303 cdev_del(&dtsled.cdev);
- 304 del_unregister:
- 305 unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
- 306 fail_map:
- 307 led_unmap();
- 308 return -EIO;
- 309 }
- 310
- 311 /*
- 312 * @description : 驱动出口函数
- 313 * @param : 无
- 314 * @return : 无
- 315 */
- 316 static void __exit led_exit(void)
- 317 {
- 318 /* 取消映射 */
- 319 led_unmap();
- 320
- 321 /* 注销字符设备驱动 */
- 322 cdev_del(&dtsled.cdev); /* 删除cdev */
- 323 unregister_chrdev_region(dtsled.devid, DTSLED_CNT); /*注销*/
- 324
- 325 device_destroy(dtsled.class, dtsled.devid);
- 326 class_destroy(dtsled.class);
- 327 }
- 328
- 329 module_init(led_init);
- 330 module_exit(led_exit);
- 331 MODULE_LICENSE("GPL");
- 332 MODULE_AUTHOR("ALIENTEK");
- 333 MODULE_INFO(intree, "Y");</i>
复制代码
dtsled.c文件中的内容和第二十二章的newchrled.c文件中的内容基本一样,只是dtsled.c中包含了处理设备树的代码,我们重点来看一下这部分代码。
第48行,在设备结构体dtsled_dev中添加了成员变量nd,nd是device_node结构体类型指针,表示设备节点。如果我们要读取设备树某个节点的属性值,首先要先得到这个节点,一般在设备结构体中添加device_node指针变量来存放这个节点。
第177~183行,通过of_find_node_by_path函数得到stm32mp1_led节点,后续其他的OF函数要使用device_node。
第186~191行,通过of_find_property函数获取stm32mp1_led节点的compatible属性,返回值为property结构体类型指针变量,property的成员变量value表示属性值。
第194~199行,通过of_property_read_string函数获取stm32mp1_led节点的status属性值。
第202~211行,通过of_property_read_u32_array函数获取stm32mp1_led节点的reg属性所有值,并且将获取到的值都存放到regdata数组中。第209行将获取到的reg属性值依次输出到终端上。
第215~220行,使用of_iomap函数一次性完成读取reg属性以及内存映射,of_iomap函数是设备树推荐使用的OF函数。
24.3.3 编写测试APP
本章直接使用第二十二章的测试APP,将上一章的ledApp.c文件复制到本章实验工程下即可。
24.4.1 编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,本章实验的Makefile文件和第二十章实验基本一样,只是将obj-m变量的值改为dtsled.o,Makefile内容如下所示:
示例代码24.4.1.1 Makefile文件
- 1 KERNELDIR := /home/zuozhongkai/linux/my_linux/linux-5.4.31
- ......
- 4 obj-m := dtsled.o
- ......
- 11 clean:
- 12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
复制代码
第4行,设置obj-m变量的值为dtsled.o。
输入如下命令编译出驱动模块文件:
编译成功以后就会生成一个名为“dtsled.ko”的驱动模块文件。
2、编译测试APP
输入如下命令编译测试ledApp.c这个测试程序:
- arm-none-linux-gnueabihf-gcc ledApp.c -o ledApp
复制代码
编译成功以后就会生成ledApp这个应用程序。
24.4.2 运行测试
将上一小节编译出来的dtsled.ko和ledApp这两个文件拷贝到rootfs/lib/modules/5.4.31目录中,重启开发板,进入到目录lib/modules/5.4.31中,输入如下命令加载dtsled.ko驱动模块:
- depmod //第一次加载驱动的时候需要运行此命令
- modprobe dtsled //加载驱动
复制代码
驱动加载成功以后会在终端中输出一些信息,如图24.4.2.1所示:
图24.4.2.1 驱动加载成功以后输出的信息
从图24.4.2.1可以看出,stm32mp1_led这个节点找到了,并且compatible属性值为“atkstm32mp1-led”,status属性值为“okay”,reg属性的值为“0X50000A28 0X4 0X5000A000 0X4 0X5000A004 0X4 0X5000A008 0X4 0X5000A00C 0X4 0X5000A018 0X4”,这些都和我们设置的设备树一致。
驱动加载成功以后就可以使用ledApp软件来测试驱动是否工作正常,输入如下命令打开LED灯:
- ./ledApp /dev/dtsled 1 //打开LED灯
复制代码
输入上述命令以后观察开发板上的红色LED灯是否点亮,如果点亮的话说明驱动工作正常。在输入如下命令关闭LED灯:
- ./ledApp /dev/dtsled 0 //关闭LED灯
复制代码
输入上述命令以后观察开发板上的红色LED灯是否熄灭。如果要卸载驱动的话输入如下命令即可:
|
|