linux设备树-LED字符设备驱动
目录
----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------
在linux驱动移植-LED字符设备驱动中,我们直接使用ioremap函数进行内存映射,得到GPIO有关寄存器的虚拟地址,然后操作寄存器对应的虚拟地址控制GPIO的目的。
这一节,我们将采用设备树向LED驱动传递GPIO相关寄存器的物理地址,然后利用of函数从设备树中获取需要的属性值,用获取到的属性值来初始化GPIO。
具体步骤如下:
- 在dts文件中添加LED设备节点;
- 在LED字符设备驱动中利用of函数获取设备树中相关属性;
- 使用获取到的属性值初始化用到的GPIO;
一、修改设备树
1.1 硬件接线
查看Mini2440原理图、S3C2440数据手册,了解如何点亮LED。在Mini2440裸机开发之点亮LED中我们已经介绍了Mini2440 LED1~LED4的接线方式,以及寄存器的设置,这里简单说一下,就不具体介绍了;
- LED1~LED4对应引脚GPB5~GPB8,以点亮LED1为例;
- 配置控制寄存器GPBCON(0x56000010)的bit[11:10]=01,使GPB5引脚为输出模式;
- 配置数据寄存器GPBDAT(0x56000014)的bit5=0,使GPB5引脚输出低电平;
1.2 修改设备树s3c2440-smdk2440.dts
新增led节点,用于描述一种名为 "mini2440-led" 的LED设备
led{ compatible = "mini2440-led"; status = "okay"; reg = <0x56000010 0x04 /* GPBCON */ 0x56000014 0x04>; /* GPBDATA */ };
其中:
- compatible 属性表示该节点所描述的硬件设备与驱动程序的兼容性。mini2440-led 为自定义字符串,表示该设备所对应的驱动程序;
- status = "okay" 表示该设备节点状态正常,可以被系统所使用;
- reg 属性用于描述 LED 硬件寄存器的地址和长度信息,格式为 <起始地址 长度> <起始地址 长度> ...。在这里:
- 0x56000010 表示 GPBCON 寄存器的地址,0x04 表示它的长度(4 bytes);
- 0x56000014 表示 GPBDATA 寄存器的地址,同样是4 bytes;
通过这个 LED 节点的描述信息,设备树可以与相关的驱动程序进行匹配,从而实现对该LED硬件设备的初始化、控制等操作。
二、LED驱动程序
在/work/sambashare/drivers下创建23.led_dev_dts文件夹。用来保存LED驱动程序以及测试应用程序。
2.1 编写led_open、led_write函数
#define DTSLED_CNT 1 #define DTSLED_NAME "dtsled" /* 定义led结构体 */ struct led_dev { dev_t devid; /* 设备号 */ struct cdev cdev; /* 保存操作结构体的字符设备 */ struct class *class; /* class */ struct device *device; /* device */ struct device_node *nd; /* 设备节点 */ unsigned long __iomem *gpbcon; /* gpbcon寄存器 __iomem 表示该地址用于访问外设寄存器 */ unsigned long __iomem *gpbdat; /* gpbdat寄存器 */ }; /* 定义一个led设备 */ struct led_dev dts_led; /* GPB5~GPB8配置为输出 */ static int led_open(struct inode *inode, struct file *file) { u32 val; val = readl(dts_led.gpbcon); val &= ~((0x01 << 10) | (0x01 << 12) | (0x01 << 14) | (0x01 << 16)); val |= ((0x01 << 10) | (0x01 << 12) | (0x01 << 14) | (0x01 << 16)); writel(val,dts_led.gpbcon); return 0; } /* LED01~LED4点亮/熄灭 1:点亮 其他:熄灭 */ static void led_switch(int on_off) { u32 val = readl(dts_led.gpbdat); if(on_off == 1){ /* 点亮 */ val &= ~((0x01 << 5) | (0x01 << 6) | (0x01 << 7) | (0x01 << 8)); }else{ /* 熄灭 */ val |= ((0x01 << 5) | (0x01 << 6) | (0x01 << 7) | (0x01 << 8)); } writel(val, dts_led.gpbdat); } /* 点亮/熄灭 LED01~LED4 */ static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { int val; int ret = copy_from_user(&val, buf, count); /* 用户空间到内核空间传递数据 */ if(ret < 0){ printk("kernel write failed!\r\n"); return -EFAULT; } printk("value %d",val); led_switch(val); return 0; }
2.2 注册LED驱动程序
static int led_init(void) { int ret = 0; printk("%s enter.\n", __func__); /* 1. 动态分配字符设备号 */ ret = alloc_chrdev_region(&dts_led.devid, 0, 1,DTSLED_NAME); // ls /proc/devices看到的名字 /* 返回值为负数,表示操作失败 */ if (ret < 0) { printk("alloc char device region failed\n"); goto faile_devid; }else{ printk("alloc char device region success\n"); } /* 2.初始化字符设备,添加字符设备 */ cdev_init(&dts_led.cdev, &led_fops); ret = cdev_add(&dts_led.cdev, dts_led.devid, DTSLED_CNT); /* 返回值为负数,表示操作失败 */ if (ret < 0) { printk("char device add failed\n"); goto fail_cdev; }else{ printk("char device add success\n"); } /* 3.创建类,它会在sys目录下创建/sys/class/dtsled这个类 */ dts_led.class = class_create(THIS_MODULE, DTSLED_NAME); if(IS_ERR(dts_led.class)){ printk("create class failed\n"); ret = PTR_ERR(dts_led.class); goto fail_class; }else{ printk("create class success\n"); } /* 4. 在/sys/class/led下创建dtsled设备,然后mdev通过这个自动创建/dev/dtsled这个设备节点 */ dts_led.device = device_create(dts_led.class, NULL, dts_led.devid, NULL, DTSLED_NAME); if(IS_ERR(dts_led.device)){ printk("create device failed\n"); ret = PTR_ERR(dts_led.device); goto fail_device; }else{ printk("create device success\n"); } /* 5.通过路径来查找指定的led节点 */ dts_led.nd = of_find_node_by_path("/led"); if (dts_led.nd == NULL) { printk("led node can not found\n"); ret = -EINVAL; goto fail_findnd; }else{ printk("led node found\n"); } /*6.初始化硬件*/ dts_led.gpbcon = (unsigned int __iomem*)of_iomap(dts_led.nd,0); /* 寻找设备节点中指定索引号的内存资源,并将其映射到内核虚拟地址空间中 */ dts_led.gpbdat = (unsigned int __iomem*)of_iomap(dts_led.nd,1); return 0; fail_findnd: device_destroy(dts_led.class, dts_led.devid); fail_device: class_destroy(dts_led.class); fail_class: cdev_del(&dts_led.cdev); fail_cdev: unregister_chrdev_region(dts_led.devid, DTSLED_CNT); faile_devid: return ret; }
2.3 卸载LED驱动程序
static void __exit led_exit(void) { /* 注销虚拟地址 */ iounmap(dts_led.gpbcon); iounmap(dts_led.gpbdat); printk("led driver exit\n"); /* 注销类、以及类设备 */ device_destroy(dts_led.class, dts_led.devid); class_destroy(dts_led.class); cdev_del(&dts_led.cdev); unregister_chrdev_region(dts_led.devid, DTSLED_CNT); return; }
使用iounmap取消of_iomap所做的映射。
2.4 led_dev.c完整代码

#include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/io.h> #include <linux/uaccess.h> #include <linux/errno.h> #include <linux/of.h> #include <linux/of_address.h> #include <asm/io.h> #define DTSLED_CNT 1 #define DTSLED_NAME "dtsled" /* 定义led结构体 */ struct led_dev { dev_t devid; /* 设备号 */ struct cdev cdev; /* 保存操作结构体的字符设备 */ struct class *class; /* class */ struct device *device; /* device */ struct device_node *nd; /* 设备节点 */ unsigned long __iomem *gpbcon; /* gpbcon寄存器 __iomem 表示该地址用于访问外设寄存器 */ unsigned long __iomem *gpbdat; /* gpbdat寄存器 */ }; /* 定义一个led设备 */ struct led_dev dts_led; /* GPB5~GPB8配置为输出 */ static int led_open(struct inode *inode, struct file *file) { u32 val; val = readl(dts_led.gpbcon); val &= ~((0x01 << 10) | (0x01 << 12) | (0x01 << 14) | (0x01 << 16)); val |= ((0x01 << 10) | (0x01 << 12) | (0x01 << 14) | (0x01 << 16)); writel(val,dts_led.gpbcon); return 0; } /* LED01~LED4点亮/熄灭 1:点亮 其他:熄灭 */ static void led_switch(int on_off) { u32 val = readl(dts_led.gpbdat); if(on_off == 1){ /* 点亮 */ val &= ~((0x01 << 5) | (0x01 << 6) | (0x01 << 7) | (0x01 << 8)); }else{ /* 熄灭 */ val |= ((0x01 << 5) | (0x01 << 6) | (0x01 << 7) | (0x01 << 8)); } writel(val, dts_led.gpbdat); } /* 点亮/熄灭 LED01~LED4 */ static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { int val; int ret = copy_from_user(&val, buf, count); /* 用户空间到内核空间传递数据 */ if(ret < 0){ printk("kernel write failed!\r\n"); return -EFAULT; } printk("value %d",val); led_switch(val); return 0; } static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .write = led_write, }; static int led_init(void) { int ret = 0; printk("%s enter.\n", __func__); /* 1. 动态分配字符设备号 */ ret = alloc_chrdev_region(&dts_led.devid, 0, 1,DTSLED_NAME); // ls /proc/devices看到的名字 /* 返回值为负数,表示操作失败 */ if (ret < 0) { printk("alloc char device region failed\n"); goto faile_devid; }else{ printk("alloc char device region success\n"); } /* 2.初始化字符设备,添加字符设备 */ cdev_init(&dts_led.cdev, &led_fops); ret = cdev_add(&dts_led.cdev, dts_led.devid, DTSLED_CNT); /* 返回值为负数,表示操作失败 */ if (ret < 0) { printk("char device add failed\n"); goto fail_cdev; }else{ printk("char device add success\n"); } /* 3.创建类,它会在sys目录下创建/sys/class/dtsled这个类 */ dts_led.class = class_create(THIS_MODULE, DTSLED_NAME); if(IS_ERR(dts_led.class)){ printk("create class failed\n"); ret = PTR_ERR(dts_led.class); goto fail_class; }else{ printk("create class success\n"); } /* 4. 在/sys/class/led下创建dtsled设备,然后mdev通过这个自动创建/dev/dtsled这个设备节点 */ dts_led.device = device_create(dts_led.class, NULL, dts_led.devid, NULL, DTSLED_NAME); if(IS_ERR(dts_led.device)){ printk("create device failed\n"); ret = PTR_ERR(dts_led.device); goto fail_device; }else{ printk("create device success\n"); } /* 5.通过路径来查找指定的led节点 */ dts_led.nd = of_find_node_by_path("/led"); if (dts_led.nd == NULL) { printk("led node can not found\n"); ret = -EINVAL; goto fail_findnd; }else{ printk("led node found\n"); } /*6.初始化硬件*/ dts_led.gpbcon = (unsigned int __iomem*)of_iomap(dts_led.nd,0); /* 寻找设备节点中指定索引号的内存资源,并将其映射到内核虚拟地址空间中 */ dts_led.gpbdat = (unsigned int __iomem*)of_iomap(dts_led.nd,1); return 0; fail_findnd: device_destroy(dts_led.class, dts_led.devid); fail_device: class_destroy(dts_led.class); fail_class: cdev_del(&dts_led.cdev); fail_cdev: unregister_chrdev_region(dts_led.devid, DTSLED_CNT); faile_devid: return ret; } static void __exit led_exit(void) { /* 注销虚拟地址 */ iounmap(dts_led.gpbcon); iounmap(dts_led.gpbdat); printk("led driver exit\n"); /* 注销类、以及类设备 */ device_destroy(dts_led.class, dts_led.devid); class_destroy(dts_led.class); cdev_del(&dts_led.cdev); unregister_chrdev_region(dts_led.devid, DTSLED_CNT); return; } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL");
2.5 Makefile
KERN_DIR :=/work/sambashare/linux-5.2.8-dt all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += led_dev.o
三、LED驱动测试应用程序
在23.led_dev_dts下创建test文件夹,保存测试应用程序。
3.1 main.c
#include <sys/stat.h> #include <fcntl.h> #include <stdio.h> void print_usage(char *file) { printf("Usage:\n"); printf("%s <dev> <on|off>\n",file); printf("eg. \n"); printf("%s /dev/dtsled on\n", file); printf("%s /dev/dtsled off\n", file); } int main(int argc,char **argv) { int fd; int val; char *filename; if (argc != 3){ print_usage(argv[0]); return 0; } filename = argv[1]; fd = open(filename,O_RDWR); if(fd == -1){ printf("can't open %s!\n",filename); return 0; } if (!strcmp("on", argv[2])){ // 亮灯 val = 1; printf("%s on!\n",filename); write(fd, &val, 4); }else if (!strcmp("off", argv[2])){ // 灭灯 val = 0; printf("%s off!\n",filename); write(fd, &val, 4); }else{ print_usage(argv[0]); } return 0; }
3.2 Makefile
all: arm-linux-gcc -march=armv4t -o main main.c clean: rm -rf *.o main
四、烧录开发板测试
4.1 编译设备树
root@zhengyang:/work/sambashare/linux-5.2.8-dt# make dtbs DTC arch/arm/boot/dts/s3c2416-smdk2416.dtb DTC arch/arm/boot/dts/s3c2440-smdk2440.dtb
编译设备树文件,把前面配置过的arch/arm/boot/dts里的dts文件编译成dtb文件。
将s3c2440-smdk2440.dtb复制到tftp服务器路径下:
root@zhengyang:/work/sambashare/linux-5.2.8-dt# cp /work/sambashare/linux-5.2.8-dt/arch/arm/boot/dts/s3c2440-smdk2440.dtb /work/tftpboot/
4.2 编译驱动
执行make命令编译驱动,并将驱动程序拷贝到nfs文件系统:
root@zhengyang:/work/sambashare/linux-5.2.8-dt# cd /work/sambashare/drivers/23.led_dev_dts root@zhengyang:/work/sambashare/drivers/23.led_dev_dts# make root@zhengyang:/work/sambashare/drivers/23.led_dev_dts# cp /work/sambashare/drivers/23.led_dev_dts/led_dev.ko /work/nfs_root/rootfs
4.3 启动内核
uboot启动后,将dtb下载到内存地址0x30001000中:
SMDK2440 # tftp 0x30001000 s3c2440-smdk2440.dtb
然后将内核镜像加载到内存0x30008000地址:
nand read 0x30008000 kernel;
然后可以使用如下命令启动内核:
SMDK2440 # bootm 0x30008000 - 0x30001000 // 无设备树时,直接bootm 0x30008000 //bootm uImage地址 ramdisk地址 设备树镜像地址
安装驱动:
[root@zy:/]# insmod led_dev.ko alloc char device region success char device add success create class success create device success led node found
查看设备节点文件:
[root@zy:/]# ls /dev/dtsled -l crw-rw---- 1 0 0 249, 0 Mar 29 11:21 /dev/dtsled
查看类:
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2019-04-24 Java基础 -- 深入理解泛型