linux设备树-按键中断驱动
----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------
一、修改设备树
1.1 硬件接线
查看Mini2440原理图、S3C2440数据手册,了解如何读取按键的状态。这里粗略介绍一下Mini2440 K1~K6的接线方式:
- K1~K6依次对应引脚GPG0、GPG3、GPG5、GPG6、GPG7、GPG11;
- 按键按下引脚输入低电平、按键松开引脚输入高电平;
1.2 按键读取方式
试想一下,如果我们想判断按键K1有没有按下或者松开,采用中断的方式,按键按下时K1为低电平,松开时为高电平,采用双边沿触发方式;可以极大的提高CPU运行效率。
GPG0、GPG3、GPG5、GPG6、GPG7、GPG11对应的外部中断依次为EINT8、EINT11、EINT13、EINT14、EINT15、EINT19。
1.3 修改s3c2440-smdk2440.dts
在内核arch/arm/boot/dts/s3c2440-smdk2440.dts文件中添加mykey设备节点:
mykey: mykey { compatible = "mykey"; interrupt-parent = <&gpg>; interrupts = <0 IRQ_TYPE_EDGE_BOTH>, <3 IRQ_TYPE_EDGE_BOTH>, <5 IRQ_TYPE_EDGE_BOTH>, <6 IRQ_TYPE_EDGE_BOTH>, <7 IRQ_TYPE_EDGE_BOTH>, <11 IRQ_TYPE_EDGE_BOTH>; key_1 = <&gpg 0 GPIO_ACTIVE_HIGH>; key_2 = <&gpg 3 GPIO_ACTIVE_HIGH>; key_3 = <&gpg 5 GPIO_ACTIVE_HIGH>; key_4 = <&gpg 6 GPIO_ACTIVE_HIGH>; key_5 = <&gpg 7 GPIO_ACTIVE_HIGH>; key_6 = <&gpg 11 GPIO_ACTIVE_HIGH>; };
这里指定了中断控制器为gpg,同时指定了每个按键使用的外部中断硬件中断号(需要注意的是这个硬件中断号是从0开始计数的,这主要是因为gpg外部中断控制器有独立的中断域irq_domain),以及中断触发方式。
此外需要引入头文件:
#include <dt-bindings/gpio/gpio.h> /* 定义了GPIO_ACTIVE_HIGH */
在arch/arm/boot/dts/s3c2440-pinctrl.dtsi文件下有gpg设备节点配置信息:
gpg: gpg { gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; };
ggio-cells=2:表示使用这个bank的GPIO时,需要用两个32位数去描述;
- 第1位:表示使用的哪一个引脚;
- 第2位:表示有效电平;这里全局设置为高电平有效;
二、按键驱动程序
在/work/sambashare/drivers下创建25.button_dev_dts文件夹。用来保存按键驱动程序。
2.1 platform driver定义
这里我们采用platform设备驱动模型,因此需要定义platform_driver:
/* * 用于设备树匹配 */ static const struct of_device_id button_dt_match[] = { { .compatible = DTSKEY_NAME, }, {}, }; /* * platform驱动 */ static struct platform_driver button_driver = { .probe = button_probe, .remove = button_remove, .driver = { .name = DTSKEY_NAME, .of_match_table = button_dt_match, // 匹配列表 } };
2.2 button_probe
/* * 中断处理服务 */ static irqreturn_t button_irq(int irq, void *dev_id) { printk("%s enter, irq: %d, %s\n", __func__, irq, (char *)dev_id); return 0; } /* * 当驱动和硬件信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中 */ static int button_probe(struct platform_device *pdev) { int ret = 0, i = 0; int num_irq = 0; struct device *dev = &pdev->dev; char *key_name; printk("%s enter.\n", __func__); if (!dev->of_node) { dev_err(dev, "no device tree node\n"); return -EINVAL; } if(pdev->name != NULL){ printk("platform device name %s",pdev->name); // mykey } for(i = 0; i< IRQ_CNT; i ++){ // 1. 获取中断资源 ret = platform_get_irq(pdev, i); if (ret <= 0) { dev_err(&pdev->dev, "cannot find irq index %d\n",i); return ret; } key_name = kasprintf(GFP_KERNEL, "key-%d", i+1); // 2. 注册中断服务 中断触发类型设置为0,则使用设备树中的配置 ret = devm_request_irq(&pdev->dev, ret, button_irq, 0, key_name, key_name); if (ret != 0) { dev_err(&pdev->dev, "cannot claim irq %d\n", ret); return ret; } } return 0; }
这段代码主要就是获取每个按键对应的IRQ编号,然后注册中断服务,在中断服务子程序中,将当前按下的按键名称输出。
2.3 button_remove
/* * 硬件信息被移除了,或者驱动被卸载了,全部要释放,释放资源的操作就放在该函数中 */ static int button_remove(struct platform_device * pdev) { printk("button driver exit\n"); return 0; }
2.4 button_drv.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/errno.h> #include <linux/uaccess.h> #include <linux/platform_device.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_irq.h> #include <linux/interrupt.h> #define DTSKEY_CNT 1 #define DTSKEY_NAME "mykey" #define IRQ_CNT 6 /* * 中断处理服务 */ static irqreturn_t button_irq(int irq, void *dev_id) { printk("%s enter, irq: %d, %s\n", __func__, irq, (char *)dev_id); return 0; } /* * 当驱动和硬件信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中 */ static int button_probe(struct platform_device *pdev) { int ret = 0, i = 0; int num_irq = 0; struct device *dev = &pdev->dev; char *key_name; printk("%s enter.\n", __func__); if (!dev->of_node) { dev_err(dev, "no device tree node\n"); return -EINVAL; } if(pdev->name != NULL){ printk("platform device name %s",pdev->name); // mykey } for(i = 0; i< IRQ_CNT; i ++){ // 1. 获取中断资源 ret = platform_get_irq(pdev, i); if (ret <= 0) { dev_err(&pdev->dev, "cannot find irq index %d\n",i); return ret; } key_name = kasprintf(GFP_KERNEL, "key-%d", i+1); // 2. 注册中断服务 中断触发类型设置为0,则使用设备树中的配置 ret = devm_request_irq(&pdev->dev, ret, button_irq, 0, key_name, key_name); if (ret != 0) { dev_err(&pdev->dev, "cannot claim irq %d\n", ret); return ret; } } return 0; } /* * 硬件信息被移除了,或者驱动被卸载了,全部要释放,释放资源的操作就放在该函数中 */ static int button_remove(struct platform_device * pdev) { printk("button driver exit\n"); return 0; } /* * 用于设备树匹配 */ static const struct of_device_id button_dt_match[] = { { .compatible = DTSKEY_NAME, }, {}, }; /* * platform驱动 */ static struct platform_driver button_driver = { .probe = button_probe, .remove = button_remove, .driver = { .name = DTSKEY_NAME, .of_match_table = button_dt_match, // 匹配列表 } }; /* * platform驱动模块入口 */ static int button_drv_init(void) { // platform驱动注册 int err = platform_driver_register(&button_driver); if (err) { printk("platform driver registered failed\n"); } else { printk("platform driver registered successfully\n"); } return err; } /* * platform驱动模块出口 */ static void __exit button_drv_exit(void) { printk("platform driver unregistered\n"); // platform驱动卸载 platform_driver_unregister(&button_driver); } module_init(button_drv_init); module_exit(button_drv_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 += button_drv.o
三、烧录开发板测试
3.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/
3.2 编译驱动
执行make命令编译驱动,并将驱动程序拷贝到nfs文件系统:
root@zhengyang:/work/sambashare/drivers/25.button_dev_dts# cd /work/sambashare/drivers/25.button_dev_dts/ root@zhengyang:/work/sambashare/drivers/25.button_dev_dts# make root@zhengyang:/work/sambashare/drivers/25.button_dev_dts# cp button_drv.ko /work/nfs_root/rootfs/
3.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 button_drv.ko mykey mykey: no pinctrl handle OF: no dma-ranges found for node(/mykey) mykey mykey: device is not dma coherent mykey mykey: device is not behind an iommu button_probe enter. // 进入button_probe函数 platform device name mykey OF: of_irq_parse_one: dev=/mykey, index=0 // ① 从interrupts属性中解析到了第1个中断 OF: parent=/pinctrl@56000000/gpg, intsize=2 OF: intspec=0 of_irq_parse_raw: /pinctrl@56000000/gpg:00000000,00000003 OF: of_irq_parse_raw: ipar=/pinctrl@56000000/gpg, size=2 OF: -> addrsize=1 OF: -> got it ! OF: of_irq_parse_one: dev=/mykey, index=1 // ② 从interrupts属性中解析到了第2个中断 OF: parent=/pinctrl@56000000/gpg, intsize=2 OF: intspec=3 of_irq_parse_raw: /pinctrl@56000000/gpg:00000003,00000003 OF: of_irq_parse_raw: ipar=/pinctrl@56000000/gpg, size=2 OF: -> addrsize=1 OF: -> got it ! OF: of_irq_parse_one: dev=/mykey, index=2 // ③ 从interrupts属性中解析到了第3个中断 OF: parent=/pinctrl@56000000/gpg, intsize=2 OF: intspec=5 of_irq_parse_raw: /pinctrl@56000000/gpg:00000005,00000003 OF: of_irq_parse_raw: ipar=/pinctrl@56000000/gpg, size=2 OF: -> addrsize=1 OF: -> got it ! OF: of_irq_parse_one: dev=/mykey, index=3 // ④ 从interrupts属性中解析到了第4个中断 OF: parent=/pinctrl@56000000/gpg, intsize=2 OF: intspec=6 of_irq_parse_raw: /pinctrl@56000000/gpg:00000006,00000003 OF: of_irq_parse_raw: ipar=/pinctrl@56000000/gpg, size=2 OF: -> addrsize=1 OF: -> got it ! OF: of_irq_parse_one: dev=/mykey, index=4 // ⑤ 从interrupts属性中解析到了第5个中断 OF: parent=/pinctrl@56000000/gpg, intsize=2 OF: intspec=7 of_irq_parse_raw: /pinctrl@56000000/gpg:00000007,00000003 OF: of_irq_parse_raw: ipar=/pinctrl@56000000/gpg, size=2 OF: -> addrsize=1 OF: -> got it ! OF: of_irq_parse_one: dev=/mykey, index=5 // ⑥ 从interrupts属性中解析到了第6个中断 OF: parent=/pinctrl@56000000/gpg, intsize=2 OF: intspec=11 of_irq_parse_raw: /pinctrl@56000000/gpg:0000000b,00000003 OF: of_irq_parse_raw: ipar=/pinctrl@56000000/gpg, size=2 OF: -> addrsize=1 OF: -> got it ! platform driver registered successfully
这里输出了大量的调试信息,主要是因为我开启了调试日志。
在linux的/sys/firmware/devicetree/base目录下可以查看到 mykey节点,如下图所示:
[root@zy:/]# ls /sys/firmware/devicetree/base/ #address-cells mykey #size-cells myled 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 memory@30000000 watchdog@53000000
进入mykey节点的目录下可以查看到mykey节点的属性,如下图所示:
[root@zy:/]# ls /sys/firmware/devicetree/base/mykey/ -l total 0 -r--r--r-- 1 0 0 6 Jan 1 00:09 compatible -r--r--r-- 1 0 0 4 Jan 1 00:09 interrupt-parent -r--r--r-- 1 0 0 48 Jan 1 00:09 interrupts -r--r--r-- 1 0 0 12 Jan 1 00:09 key_1 -r--r--r-- 1 0 0 12 Jan 1 00:09 key_2 -r--r--r-- 1 0 0 12 Jan 1 00:09 key_3 -r--r--r-- 1 0 0 12 Jan 1 00:09 key_4 -r--r--r-- 1 0 0 12 Jan 1 00:09 key_5 -r--r--r-- 1 0 0 12 Jan 1 00:09 key_6 -r--r--r-- 1 0 0 6 Jan 1 00:09 name
3.4 中断查看
运行命令cat /proc/interrupts可以查看当前系统有哪些中断服务:
[root@zy:/]# cat /proc/interrupts CPU0 7: 1694 s3c-eint 7 Edge eth0 8: 0 s3c 8 Edge s3c2410-rtc tick 13: 63617 s3c 13 Edge samsung_time_irq 16: 0 s3c-eint 0 Edge key-1 // 按键1 17: 0 s3c-eint 3 Edge key-2 // 按键2 18: 0 s3c-eint 5 Edge key-3 // 按键3 19: 0 s3c-eint 6 Edge key-4 // 按键4 20: 0 s3c-eint 7 Edge key-5 // 按键5 21: 0 s3c-eint 11 Edge key-6 // 按键6 27: 0 s3c 27 Edge 54000000.i2c 30: 0 s3c 30 Edge s3c2410-rtc alarm 35: 95 s3c-level 35 Level 50004000.serial 36: 576 s3c-level 36 Level 50004000.serial 59: 0 s3c-level 59 Edge 53000000.watchdog Err: 0
从上图我们可以看到IRQ编号是全局唯一的,此外我们申请的6个外部中断:
- EINT8:IRQ编号为16,对应到中断域中的硬件中断号为硬件中断号为0;
- EINT11:IRQ编号为17,对应到中断域中的硬件中断号为硬件中断号为3;
- EINT13:IRQ编号为18,对应到中断域中的硬件中断号为硬件中断号为5;
- EINT14:IRQ编号为19,对应到中断域中的硬件中断号为硬件中断号为6;
- EINT15:IRQ编号为20,对应到中断域中的硬件中断号为硬件中断号为7;
- EINT19:IRQ编号为21,对应到中断域中的硬件中断号为硬件中断号为11;
s3c2440外部中断在初始化的时候,为gpf、gpg外部中断控制器各创建一个线性中断域;
- 对于gpf,其中断域支持8个中断,对应外部中断EINT0~7,对应到中断域中的硬件中断号为0~7;
- 对于gpg,其中断域支持16个中断,对应外部中断EINT8~23,对应到中断域中的硬件中断号为0~15;
3.5 按键按下测试
当我们随意按下K1~K6按键,控制台会输出如下信息:
[root@zy:/]# button_irq enter, irq: 20, key-5 button_irq enter, irq: 20, key-5 button_irq enter, irq: 16, key-1 button_irq enter, irq: 16, key-1 button_irq enter, irq: 19, key-4 button_irq enter, irq: 19, key-4 button_irq enter, irq: 19, key-4 button_irq enter, irq: 18, key-3 button_irq enter, irq: 18, key-3 button_irq enter, irq: 21, key-6 button_irq enter, irq: 21, key-6 button_irq enter, irq: 21, key-6 button_irq enter, irq: 17, key-2 button_irq enter, irq: 17, key-2
3.6 卸载驱动
通过用lsmod可以查看当前安装了哪些驱动:
[root@zy:/]# lsmod button_drv 2226 0 - Live 0xbf000000 (O)
卸载时直接运行:
[root@zy:/]# rmmod button_drv.ko
platform driver unregistered
button driver exit
四、代码下载
Young / s3c2440_project[drivers]
参考文章