700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 【正点原子MP157连载】第二十四章 设备树下的LED驱动实验-摘自【正点原子】STM32MP1

【正点原子MP157连载】第二十四章 设备树下的LED驱动实验-摘自【正点原子】STM32MP1

时间:2024-04-13 21:23:24

相关推荐

【正点原子MP157连载】第二十四章 设备树下的LED驱动实验-摘自【正点原子】STM32MP1

1)实验平台:正点原子STM32MP157开发板

2)购买链接:/item.htm?&id=629270721801

3)全套实验源码+手册+视频下载地址:/thread-318813-1-1.html

4)正点原子官方B站:/394620890

5)正点原子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 */50X5000A000 0X04 /* GPIOI_MODER*/60X5000A004 0X04 /* GPIOI_OTYPER */70X5000A008 0X04 /* GPIOI_OSPEEDR */80X5000A00C 0X04 /* GPIOI_PUPDR*/90X5000A018 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:

make dtbs

编译完成以后得到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.c20 作者: 正点原子Linux团队21 版本: V1.022 描述: LED驱动文件。23 其他: 无24 论坛: 25 日志: 初版V1.0 /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{42dev_t devid; /* 设备号*/43struct cdev cdev; /* cdev*/44struct class *class; /* 类*/45struct device *device; /* 设备 */46int major; /* 主设备号 */47int minor; /* 次设备号 */48struct device_node *nd; /* 设备节点 */49 };50 51 struct dtsled_dev dtsled; /* led设备 */52 53 /*54 * @description : LED打开/关闭55 * @param - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED56 * @return : 无57 */58 void led_switch(u8 sta)59 {60u32 val = 0;61if(sta == LEDON) {62val = readl(GPIOI_BSRR_PI);63val |= (1 << 16); 64writel(val, GPIOI_BSRR_PI);65}else if(sta == LEDOFF) {66val = readl(GPIOI_BSRR_PI);67val|= (1 << 0); 68writel(val, GPIOI_BSRR_PI);69} 70 }71 72 /*73 * @description : 取消映射74 * @return : 无75 */76 void led_unmap(void)77 {78 /* 取消映射 */79iounmap(MPU_AHB4_PERIPH_RCC_PI);80iounmap(GPIOI_MODER_PI);81iounmap(GPIOI_OTYPER_PI);82iounmap(GPIOI_OSPEEDR_PI);83iounmap(GPIOI_PUPDR_PI);84iounmap(GPIOI_BSRR_PI);85 }86 87 /*88 * @description : 打开设备89 * @param – inode: 传递给驱动的inode90 * @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 {96filp->private_data = &dtsled; /* 设置私有数据 */97return 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 {110return 0;111 }112113 /*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 {123int retvalue;124unsigned char databuf[1];125unsigned char ledstat;126127retvalue = copy_from_user(databuf, buf, cnt);128if(retvalue < 0) {129 printk("kernel write failed!\r\n");130 return -EFAULT;131}132133ledstat = databuf[0]; /* 获取状态值 */134135if(ledstat == LEDON) {136 led_switch(LEDON);/* 打开LED灯 */137} else if(ledstat == LEDOFF) {138 led_switch(LEDOFF); /* 关闭LED灯 */139}140return 0;141 }142143 /*144 * @description : 关闭/释放设备145 * @param – filp: 要关闭的设备文件(文件描述符)146 * @return : 0 成功;其他 失败147 */148 static int led_release(struct inode *inode, struct file *filp)149 {150return 0;151 }152153 /* 设备操作函数 */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 };161162 /*163 * @description : 驱动出口函数164 * @param : 无165 * @return: 无166 */167 static int __init led_init(void)168 {169u32 val = 0;170int ret;171u32 regdata[12];172const char *str;173struct property *proper;174175/* 获取设备树中的属性数据 */176/* 1、获取设备节点:stm32mp1_led */177dtsled.nd = of_find_node_by_path("/stm32mp1_led");178if(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}184185/* 2、获取compatible属性内容 */186proper = of_find_property(dtsled.nd, "compatible", NULL);187if(proper == NULL) {188 printk("compatible property find failed\r\n");189} else {190 printk("compatible = %s\r\n", (char*)proper->value);191}192193/* 3、获取status属性内容 */194ret = of_property_read_string(dtsled.nd, "status", &str);195if(ret < 0){196 printk("status read failed!\r\n");197} else {198 printk("status = %s\r\n",str);199}200201/* 4、获取reg属性内容 */202ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 12);203if(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}212213/* 初始化LED */214/* 1、寄存器地址映射 */215MPU_AHB4_PERIPH_RCC_PI = of_iomap(dtsled.nd, 0);216GPIOI_MODER_PI = of_iomap(dtsled.nd, 1);217GPIOI_OTYPER_PI = of_iomap(dtsled.nd, 2);218GPIOI_OSPEEDR_PI = of_iomap(dtsled.nd, 3);219GPIOI_PUPDR_PI = of_iomap(dtsled.nd, 4);220GPIOI_BSRR_PI = of_iomap(dtsled.nd, 5);221222/* 2、使能PI时钟 */223val = readl(MPU_AHB4_PERIPH_RCC_PI);224val &= ~(0X1 << 8); /* 清除以前的设置 */225val |= (0X1 << 8); /* 设置新值 */226writel(val, MPU_AHB4_PERIPH_RCC_PI);227228/* 3、设置PI0通用的输出模式。*/229val = readl(GPIOI_MODER_PI);230val &= ~(0X3 << 0); /* bit0:1清零 */231val |= (0X1 << 0); /* bit0:1设置01 */232writel(val, GPIOI_MODER_PI);233234/* 3、设置PI0为推挽模式。*/235val = readl(GPIOI_OTYPER_PI);236val &= ~(0X1 << 0); /* bit0清零,设置为上拉*/237writel(val, GPIOI_OTYPER_PI);238239/* 4、设置PI0为高速。*/240val = readl(GPIOI_OSPEEDR_PI);241val &= ~(0X3 << 0); /* bit0:1 清零 */242val |= (0x2 << 0); /* bit0:1 设置为10*/243writel(val, GPIOI_OSPEEDR_PI);244245/* 5、设置PI0为上拉。*/246val = readl(GPIOI_PUPDR_PI);247val &= ~(0X3 << 0); /* bit0:1 清零*/248val |= (0x1 << 0); /* bit0:1 设置为01*/249writel(val,GPIOI_PUPDR_PI);250251/* 6、默认关闭LED */252val = readl(GPIOI_BSRR_PI);253val |= (0x1 << 0);254writel(val, GPIOI_BSRR_PI);255256/* 注册字符设备驱动 */257/* 1、创建设备号 */258if (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); /* 获取分配号的次设备号 */273274}275printk("dtsled major=%d,minor=%d\r\n",dtsled.major, dtsled.minor); 276277/* 2、初始化cdev */278dtsled.cdev.owner = THIS_MODULE;279cdev_init(&dtsled.cdev, &dtsled_fops);280281/* 3、添加一个cdev */282ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);283if(ret < 0)284 goto del_unregister;285286/* 4、创建类 */287dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);288if (IS_ERR(dtsled.class)) {289 goto del_cdev;290}291292/* 5、创建设备 */293dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);294if (IS_ERR(dtsled.device)) {295 goto destroy_class;296}297298return 0;299300 destroy_class:301class_destroy(dtsled.class);302 del_cdev:303cdev_del(&dtsled.cdev);304 del_unregister:305unregister_chrdev_region(dtsled.devid, DTSLED_CNT);306 fail_map:307led_unmap();308return -EIO;309 }310311 /*312 * @description : 驱动出口函数313 * @param : 无314 * @return: 无315 */316 static void __exit led_exit(void)317 {318/* 取消映射 */319led_unmap();320321/* 注销字符设备驱动 */322cdev_del(&dtsled.cdev);/* 删除cdev */323unregister_chrdev_region(dtsled.devid, DTSLED_CNT); /*注销*/324325device_destroy(dtsled.class, dtsled.devid);326class_destroy(dtsled.class);327 }328329 module_init(led_init);330 module_exit(led_exit);331 MODULE_LICENSE("GPL");332 MODULE_AUTHOR("ALIENTEK");333 MODULE_INFO(intree, "Y");

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。

输入如下命令编译出驱动模块文件:

make -j32

编译成功以后就会生成一个名为“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灯是否熄灭。如果要卸载驱动的话输入如下命令即可:

rmmod dtsled.ko

【正点原子MP157连载】第二十四章 设备树下的LED驱动实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。