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完整代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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
查看类:
[root@zy:/]# ls /sys/class/dtsled -l total 0 lrwxrwxrwx 1 0 0 0 Mar 29 11:35 dtsled -> ../../devices/virtual/dtsled/dtsled [root@zy:/]# ls /sys/class/dtsled/dtsled/ -l total 0 -r--r--r-- 1 0 0 4096 Mar 29 11:21 dev drwxr-xr-x 2 0 0 0 Mar 29 11:35 power lrwxrwxrwx 1 0 0 0 Mar 29 11:35 subsystem -> ../../../../class/dtsled -rw-r--r-- 1 0 0 4096 Mar 29 11:35 uevent
在linux的/sys/firmware/devicetree/base 目录下可以查看到 led 节点,如下图所示:
[root@zy:/]# ls /sys/firmware/devicetree/base/ #address-cells memory@30000000 #size-cells model aliases name chosen nand@4e000000 clock-controller@4c000000 pinctrl@56000000 clocks rtc@57000000 compatible serial@50000000 cpus serial@50004000 i2c@54000000 serial@50008000 interrupt-controller@4a000000 srom-cs4@20000000 interrupt-parent timer@51000000 led watchdog@53000000
进入led节点的目录下可以查看到led节点的属性,如下图所示:
[root@zy:/]# ls /sys/firmware/devicetree/base/led -l total 0 -r--r--r-- 1 0 0 13 Mar 29 11:37 compatible -r--r--r-- 1 0 0 4 Mar 29 11:37 name -r--r--r-- 1 0 0 16 Mar 29 11:37 reg -r--r--r-- 1 0 0 5 Mar 29 11:37 status
通过过cat或 hexdump命令可以查看属性的值,如下图所示:
[root@zy:/sys/firmware/devicetree/base/led]# cat compatible mini2440-led
由于我当前使用的根文件系统不支持hexdump命令,因此无法查看reg文件的值。
4.4 编译测试应用程序
执行make命令编译测试应用程序,并将测试应用程序拷贝到nfs文件系统:
root@zhengyang:/work/sambashare/drivers/23.led_dev_dts# cd test/ root@zhengyang:/work/sambashare/drivers/23.led_dev_dts/test# make clean rm -rf *.o main root@zhengyang:/work/sambashare/drivers/23.led_dev_dts/test# make arm-linux-gcc -march=armv4t -o main main.c root@zhengyang:/work/sambashare/drivers/23.led_dev_dts/test# cp ./main /work/nfs_root/rootfs
运行应用程序:
[root@zy:/]# ./main /dev/dtsled on /dev/dtsled on! [root@zy:/]# ./main /dev/dtsled off /dev/dtsled off!
可以看到LED1~LED4同时点亮和同时熄灭。
如果你想单独控制每一个LED,那需要为每个LED编写对应的驱动程序,这里就不演示了。
4.5 卸载LED驱动
通过用lsmod可以查看当前安装了哪些驱动:
[root@zy:/]# lsmod led_dev 2476 0 - Live 0xbf000000 (O)
卸载时直接运行:
[root@zy:/]# rmmod led_dev
led driver exit
五、代码下载
Young / s3c2440_project[drivers]
参考文章