程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

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完整代码   

#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");
View Code

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]

参考文章

[1]linux驱动移植-按键中断驱动

[2]基于设备树的TQ2440的中断(2)

posted @ 2023-05-03 22:29  大奥特曼打小怪兽  阅读(188)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步