Linux 驱动开发 四十:Linux 中断(二)

一、代码实现

1、原理图分析

在这里插入图片描述
通过原理图分析可以得到,当按键按下后 KEY0 为低电平,当按键释放后 KEY0 为高电平。通过原理图可以确定 KEY0 连接在 UART1_CTS 引脚上。UART1_CTS 引脚的 IO 编号为 GPIO1_IO18。

2、修改设备树

1、修改 SOC 级中断属性

根据《IMX6ULL参考手册》中 Chapter 3:Interrupts and DMA Events描述,具体描述如下:
在这里插入图片描述
在 imx6ull.dtsi 源码中已经存在 gpio1 中断相关描述,不需要我们修改,具体内容如下:

gpio1: gpio@0209c000 {
	compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
	reg = <0x0209c000 0x4000>;
	interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,	// GIC_SPI:代表中断类型、66:代表中断号、IRQ_TYPE_LEVEL_HIGH:代表触发类型
		     <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
	gpio-controller;
	#gpio-cells = <2>;
	interrupt-controller;
	#interrupt-cells = <2>;	// 代表子节点 interrupts 属性由 2 个字段描述(第一字段代表 GPIO 号,第二字段代表触发类型)
};

通过以上分析 GPIO1 中断相关属性已经由 NXP 完成,因此不需要修改。

总结:中断设备树定义了中断路由过程路径,子节点触发中断后向父节点路由中断信号。

2、修改设备级中断属性

在 imx6ull-lq-evk.dts 设备树源码中的 lq-key 节点添加中断属性,修改后节点信息如下:

lq-key {
	compatible = "lq-key";
	status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_key>;
	key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
	interrupt-parent = <&gpio1>;			// 指定中断父节点
	interrupts = <18 IRQ_TYPE_EDGE_BOTH>;	// 指定 GPIO 引脚和触发类型(双边沿触发)
};

对于触发类型详细描述见 include/linux/irq.h 文档,详细描述如下:

/*
 * IRQ line status.
 *
 * Bits 0-7 are the same as the IRQF_* bits in linux/interrupt.h
 *
 * IRQ_TYPE_NONE		- default, unspecified type
 * IRQ_TYPE_EDGE_RISING		- rising edge triggered
 * IRQ_TYPE_EDGE_FALLING	- falling edge triggered
 * IRQ_TYPE_EDGE_BOTH		- rising and falling edge triggered
 * IRQ_TYPE_LEVEL_HIGH		- high level triggered
 * IRQ_TYPE_LEVEL_LOW		- low level triggered
 * IRQ_TYPE_LEVEL_MASK		- Mask to filter out the level bits
 * IRQ_TYPE_SENSE_MASK		- Mask for all the above bits
 * IRQ_TYPE_DEFAULT		- For use by some PICs to ask irq_set_type
 *				  to setup the HW to a sane default (used
 *                                by irqdomain map() callbacks to synchronize
 *                                the HW state and SW flags for a newly
 *                                allocated descriptor).
 *
 * IRQ_TYPE_PROBE		- Special flag for probing in progress
 *
 * Bits which can be modified via irq_set/clear/modify_status_flags()
 * IRQ_LEVEL			- Interrupt is level type. Will be also
 *				  updated in the code when the above trigger
 *				  bits are modified via irq_set_irq_type()
 * IRQ_PER_CPU			- Mark an interrupt PER_CPU. Will protect
 *				  it from affinity setting
 * IRQ_NOPROBE			- Interrupt cannot be probed by autoprobing
 * IRQ_NOREQUEST		- Interrupt cannot be requested via
 *				  request_irq()
 * IRQ_NOTHREAD			- Interrupt cannot be threaded
 * IRQ_NOAUTOEN			- Interrupt is not automatically enabled in
 *				  request/setup_irq()
 * IRQ_NO_BALANCING		- Interrupt cannot be balanced (affinity set)
 * IRQ_MOVE_PCNTXT		- Interrupt can be migrated from process context
 * IRQ_NESTED_TRHEAD		- Interrupt nests into another thread
 * IRQ_PER_CPU_DEVID		- Dev_id is a per-cpu variable
 * IRQ_IS_POLLED		- Always polled by another interrupt. Exclude
 *				  it from the spurious interrupt detection
 *				  mechanism and from core side polling.
 */
enum {
	IRQ_TYPE_NONE		= 0x00000000,
	IRQ_TYPE_EDGE_RISING	= 0x00000001,
	IRQ_TYPE_EDGE_FALLING	= 0x00000002,
	IRQ_TYPE_EDGE_BOTH	= (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING),
	IRQ_TYPE_LEVEL_HIGH	= 0x00000004,
	IRQ_TYPE_LEVEL_LOW	= 0x00000008,
	IRQ_TYPE_LEVEL_MASK	= (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH),
	IRQ_TYPE_SENSE_MASK	= 0x0000000f,
	IRQ_TYPE_DEFAULT	= IRQ_TYPE_SENSE_MASK,

	IRQ_TYPE_PROBE		= 0x00000010,

	IRQ_LEVEL		= (1 <<  8),
	IRQ_PER_CPU		= (1 <<  9),
	IRQ_NOPROBE		= (1 << 10),
	IRQ_NOREQUEST		= (1 << 11),
	IRQ_NOAUTOEN		= (1 << 12),
	IRQ_NO_BALANCING	= (1 << 13),
	IRQ_MOVE_PCNTXT		= (1 << 14),
	IRQ_NESTED_THREAD	= (1 << 15),
	IRQ_NOTHREAD		= (1 << 16),
	IRQ_PER_CPU_DEVID	= (1 << 17),
	IRQ_IS_POLLED		= (1 << 18),
};

3、编译设备树

onlylove@ubuntu:~/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga$ make dtbs
  CHK     include/config/kernel.release
  CHK     include/generated/uapi/linux/version.h
  CHK     include/generated/utsrelease.h
make[1]: 'include/generated/mach-types.h' is up to date.
  CHK     include/generated/bounds.h
  CHK     include/generated/asm-offsets.h
  CALL    scripts/checksyscalls.sh
  DTC     arch/arm/boot/dts/imx6ull-lq-evk.dtb
onlylove@ubuntu:~/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga$

4、验证

/ # ls
bin       etc       linuxrc   proc      sbin      timer.ko  usr
dev       lib       mnt       root      sys       tmp
/ # cd /sys/firmware/devicetree/base/
/sys/firmware/devicetree/base # ls
#address-cells                 lq-led
#size-cells                    memory
aliases                        model
backlight                      name
chosen                         pxp_v4l2
clocks                         regulators
compatible                     reserved-memory
cpus                           soc
gpioled                        sound
interrupt-controller@00a01000  spi4
lq-key
/sys/firmware/devicetree/base # cd lq-key/
/sys/firmware/devicetree/base/lq-key # ls
compatible        interrupts        name              pinctrl-names
interrupt-parent  key-gpio          pinctrl-0         status
/sys/firmware/devicetree/base/lq-key # ls -al
total 0
drwxr-xr-x    2 0        0                0 Jan  1 00:01 .
drwxr-xr-x   18 0        0                0 Jan  1 00:01 ..
-r--r--r--    1 0        0                7 Jan  1 00:01 compatible
-r--r--r--    1 0        0                4 Jan  1 00:01 interrupt-parent
-r--r--r--    1 0        0                8 Jan  1 00:01 interrupts
-r--r--r--    1 0        0               12 Jan  1 00:01 key-gpio
-r--r--r--    1 0        0                7 Jan  1 00:01 name
-r--r--r--    1 0        0                4 Jan  1 00:01 pinctrl-0
-r--r--r--    1 0        0                8 Jan  1 00:01 pinctrl-names
-r--r--r--    1 0        0                5 Jan  1 00:01 status
/sys/firmware/devicetree/base/lq-key #

通过以上日志可以确定 key 中断属性添加成功。

3、Makefile

KERNELDIR := /home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := irq.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

4、搭建驱动框架

1、代码实现

#include "linux/init.h"
#include "linux/module.h"
#include "linux/kdev_t.h"
#include "linux/fs.h"
#include "linux/printk.h"
#include "linux/cdev.h"
#include "linux/device.h"

#define NEWCHRDEV_MAJOR 0   /* 主设备号(如果为0则让系统自动分配,如果大于0则使用指定设备号) */
#define NEWCHRDEV_MINOR 0   /* 次设备号 */
#define NEWCHRDEV_COUNT 1   /* 设备号个数 */
#define NEWCHRDEV_NAME  "newchrdev" /* 名子 */

typedef struct{
    struct cdev dev;        /* cdev 结构体 (声明在 "linux/cdev.h")*/
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    dev_t devid;            /* 设备号 */
    struct class *class;    /* 类 (声明在 "linux/device.h")*/
    struct device *device;  /* 设备 (声明在 "linux/device.h") */
}newchrdev_t;

newchrdev_t newchrdev;

static const struct file_operations newchrdevops = {
    .owner   = THIS_MODULE,
};

/*
 * MKDEV 声明在 "linux/kdev_t.h"
 * register_chrdev_region 声明在 "linux/fs.h"
 * printk 声明在 "linux/printk.h"
 * alloc_chrdev_region 声明在 "linux/fs.h"
 * cdev_init 声明在 "linux/cdev.h"
 * cdev_add 声明在 "linux/cdev.h"
 * class_create 声明在 "linux/device.h"
 */
/* 驱动入口函数 */
static int __init newchrdev_init(void)
{
    int ret;

    /* 1、字符设备号分配 */
    newchrdev.major = NEWCHRDEV_MAJOR;
    if(newchrdev.major){
        /* 指定设备号 */
        newchrdev.minor = NEWCHRDEV_MINOR;
        newchrdev.devid = MKDEV(newchrdev.major, newchrdev.minor);
        ret = register_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT,NEWCHRDEV_NAME);
        printk("newchrdev.major > 0!\r\n");
    }else{
        /* 系统分配设备号 */
        ret = alloc_chrdev_region(&newchrdev.devid,0,NEWCHRDEV_COUNT,NEWCHRDEV_NAME);
        newchrdev.major = MAJOR(newchrdev.devid);
        newchrdev.minor = MINOR(newchrdev.devid);
        printk("newchrdev.major = 0!\r\n");
    }
     if(ret < 0){
        printk("newchrdev xxx_chrdev_region failed!\r\n");
        goto newchrdev_chrdev_region_failed;
    }
    printk("newchrdev devid = %d newchrdev major=%d,minor=%d\r\n",newchrdev.devid,newchrdev.major,newchrdev.minor);

    /* 2、注册字符设备 */
    newchrdev.dev.owner = THIS_MODULE;
    cdev_init(&newchrdev.dev,&newchrdevops);
    ret = cdev_add(&newchrdev.dev,newchrdev.devid,NEWCHRDEV_COUNT);
    if(ret < 0){
        printk("newchrdev cdev_add failed!\r\n");
        goto newchrdev_cdev_add_failed;
    }

    /* 3、创建类 */
    newchrdev.class = class_create(THIS_MODULE,NEWCHRDEV_NAME);
    if(IS_ERR(newchrdev.class)) {
        printk("newchrdev class_create failed!\r\n");
        goto newchrdev_class_create_failed;
    }

    /* 4、创建设备 */
    newchrdev.device = device_create(newchrdev.class,NULL,newchrdev.devid,NULL,NEWCHRDEV_NAME);
    if(IS_ERR(newchrdev.device)){
        printk("newchrdev device_create failed!\r\n");
        goto neschrdev_device_creat_failed;
    }

    return 0;

//neschrdev_device_xxx_failed:    /* 其他操作失败(添加其他文件时,打开此选项) */
//    device_destroy(newchrdev.class,newchrdev.devid);
neschrdev_device_creat_failed:  /* 删除类 */
    class_destroy(newchrdev.class);
newchrdev_class_create_failed:  /*注销字符设备 */
    cdev_del(&newchrdev.dev);
newchrdev_cdev_add_failed:  /* 释放设备号 */
    unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT);
newchrdev_chrdev_region_failed:   /* 字符设备号分配失败处理函数(未分配资源,因此不做处理) */
    printk("failed!\r\n");
    return ret;
}

/*
 * device_destroy 声明在 "linux/device.h"
 * class_destroy 声明在 "linux/device.h"
 * cdev_del 声明在 "linux/cdev.h"
 * unregister_chrdev_region 声明在 "linux/fs.h"
 */
/* 驱动卸载函数 */
static void __exit newchrdev_exit(void)
{
    /* 4、删除设备 */
    device_destroy(newchrdev.class,newchrdev.devid);
    /* 3、删除类 */
    class_destroy(newchrdev.class);
    /* 2、注销字符设备 */
    cdev_del(&newchrdev.dev);
    /* 1、释放设备号 */
    unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT);
    printk("newchrdev_exit succed!\r\n");
}
/*
 * module_init 声明在 "linux/init.h"
 * module_exit 声明在 "linux/init.h"
 * MODULE_LICENSE 声明在 "linux/module.h"
 */
// 头文件 "linux/init.h"
module_init(newchrdev_init);
module_exit(newchrdev_exit);
MODULE_LICENSE("GPL");

2、编译

onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ ls -l
total 12
-rw-rw-r-- 1 onlylove onlylove 4367 Jan 18 07:09 irq.c
-rw-rw-r-- 1 onlylove onlylove  273 Jan 18 04:49 Makefile
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ make
make -C /home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/home/onlylove/linux/driver/linux_driver/8_irq modules
make[1]: Entering directory '/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga'
  CC [M]  /home/onlylove/linux/driver/linux_driver/8_irq/irq.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/onlylove/linux/driver/linux_driver/8_irq/irq.mod.o
  LD [M]  /home/onlylove/linux/driver/linux_driver/8_irq/irq.ko
make[1]: Leaving directory '/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga'
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ ls -l
total 36
-rw-rw-r-- 1 onlylove onlylove 4367 Jan 18 07:09 irq.c
-rw-rw-r-- 1 onlylove onlylove 5664 Jan 18 07:09 irq.ko
-rw-rw-r-- 1 onlylove onlylove 1266 Jan 18 07:09 irq.mod.c
-rw-rw-r-- 1 onlylove onlylove 2536 Jan 18 07:09 irq.mod.o
-rw-rw-r-- 1 onlylove onlylove 3844 Jan 18 07:09 irq.o
-rw-rw-r-- 1 onlylove onlylove  273 Jan 18 04:49 Makefile
-rw-rw-r-- 1 onlylove onlylove   61 Jan 18 07:09 modules.order
-rw-rw-r-- 1 onlylove onlylove    0 Jan 18 07:09 Module.symvers
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ 

3、测试

/ # ls
bin      etc      lib      mnt      root     sys      usr
dev      irq.ko   linuxrc  proc     sbin     tmp
/ # lsmod
Module                  Size  Used by    Not tainted
/ # insmod irq.ko
newchrdev.major = 0!
newchrdev devid = 260046848 newchrdev major=248,minor=0
/ # rmmod irq.ko
newchrdev_exit succed!
/ # insmod irq.ko
newchrdev.major = 0!
newchrdev devid = 260046848 newchrdev major=248,minor=0
/ # rmmod irq.ko
newchrdev_exit succed!
/ # insmod irq.ko
newchrdev.major = 0!
newchrdev devid = 260046848 newchrdev major=248,minor=0
/ # rmmod irq.ko
newchrdev_exit succed!
/ # insmod irq.ko
newchrdev.major = 0!
newchrdev devid = 260046848 newchrdev major=248,minor=0
/ # rmmod irq.ko
newchrdev_exit succed!
/ # insmod irq.ko
newchrdev.major = 0!
newchrdev devid = 260046848 newchrdev major=248,minor=0
/ # rmmod irq.ko
newchrdev_exit succed!
/ # insmod irq.ko
newchrdev.major = 0!
newchrdev devid = 260046848 newchrdev major=248,minor=0
/ # lsmod
Module                  Size  Used by    Tainted: G
irq                     1494  0
/ # ls /dev/newchrdev -l
crw-rw----    1 0        0         248,   0 Jan  1 02:29 /dev/newchrdev
/ #
/ #
/ # rmmod irq.ko
newchrdev_exit succed!
/ # lsmod
Module                  Size  Used by    Tainted: G
/ # ls /dev/newchrdev -l
ls: /dev/newchrdev: No such file or directory

5、key 引脚初始化

1、添加 key 数据类型

/* key 相关数据 */
typedef struct{
	int gpio;								/* gpio */
	int irqnum;								/* 中断号     */
	unsigned char value;					/* 按键对应的键值 */
	char name[10];							/* 名字 */
	irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
}key_t;

关于 key 使用的相关数据组织成结构体类型便于管理,多个按键可以定义多个变量

特变说明:按键相关设备树节点放在 newchrdev_t,因为它只有一个节点,不可能出现多个。

2、完善 newchrdev_t 类型

typedef struct{
    struct cdev dev;        /* cdev 结构体 (声明在 "linux/cdev.h")*/
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    dev_t devid;            /* 设备号 */
    struct class *class;    /* 类 (声明在 "linux/device.h")*/
    struct device *device;  /* 设备 (声明在 "linux/device.h") */

    struct device_node	*nd; /* 设备节点 (声明在 "linux/of.h") */
    key_t key_data;         /* 按键相关数据 */
}newchrdev_t;

新增加 nd 和 key_data 成员。

3、初始化 key 引脚

1、查找设备节点

2、从设备树中获取 GPIO 配置信息

3、申请一个 GPIO

4、设置 GPIO 为输入

5、获取中断号

6、申请中断

6、驱动代码:功能实现

1、源码

#include "linux/init.h"
#include "linux/module.h"
#include "linux/kdev_t.h"
#include "linux/fs.h"
#include "linux/printk.h"
#include "linux/cdev.h"
#include "linux/device.h"
#include "linux/of.h"
#include "linux/of_gpio.h"
#include "linux/gpio.h"
#include "linux/of_irq.h"
#include "linux/interrupt.h"

#define NEWCHRDEV_MAJOR 0   /* 主设备号(如果为0则让系统自动分配,如果大于0则使用指定设备号) */
#define NEWCHRDEV_MINOR 0   /* 次设备号 */
#define NEWCHRDEV_COUNT 1   /* 设备号个数 */
#define NEWCHRDEV_NAME  "newchrdev" /* 名子 */

int key_pin_init(void);
static irqreturn_t key_handler(int irq, void *dev_id);
void key_pin_exit(void);

/* key 相关数据 */
typedef struct{
	int gpio;								/* gpio */
	int irqnum;								/* 中断号     */
	unsigned char value;					/* 按键对应的键值 */
	char name[10];							/* 名字 */
	irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
}newkey_t;

typedef struct{
    struct cdev dev;        /* cdev 结构体 (声明在 "linux/cdev.h")*/
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    dev_t devid;            /* 设备号 */
    struct class *class;    /* 类 (声明在 "linux/device.h")*/
    struct device *device;  /* 设备 (声明在 "linux/device.h") */

    struct device_node	*nd; /* 设备节点 (声明在 "linux/of.h") */
    newkey_t key_data;         /* 按键相关数据 */
}newchrdev_t;

newchrdev_t newchrdev;

static const struct file_operations newchrdevops = {
    .owner   = THIS_MODULE,
};

/*
 * MKDEV 声明在 "linux/kdev_t.h"
 * register_chrdev_region 声明在 "linux/fs.h"
 * printk 声明在 "linux/printk.h"
 * alloc_chrdev_region 声明在 "linux/fs.h"
 * cdev_init 声明在 "linux/cdev.h"
 * cdev_add 声明在 "linux/cdev.h"
 * class_create 声明在 "linux/device.h"
 */
/* 驱动入口函数 */
static int __init newchrdev_init(void)
{
    int ret;

    /* 1、字符设备号分配 */
    newchrdev.major = NEWCHRDEV_MAJOR;
    if(newchrdev.major){
        /* 指定设备号 */
        newchrdev.minor = NEWCHRDEV_MINOR;
        newchrdev.devid = MKDEV(newchrdev.major, newchrdev.minor);
        ret = register_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT,NEWCHRDEV_NAME);
        printk("newchrdev.major > 0!\r\n");
    }else{
        /* 系统分配设备号 */
        ret = alloc_chrdev_region(&newchrdev.devid,0,NEWCHRDEV_COUNT,NEWCHRDEV_NAME);
        newchrdev.major = MAJOR(newchrdev.devid);
        newchrdev.minor = MINOR(newchrdev.devid);
        printk("newchrdev.major = 0!\r\n");
    }
     if(ret < 0){
        printk("newchrdev xxx_chrdev_region failed!\r\n");
        goto newchrdev_chrdev_region_failed;
    }
    printk("newchrdev devid = %d newchrdev major=%d,minor=%d\r\n",newchrdev.devid,newchrdev.major,newchrdev.minor);

    /* 2、注册字符设备 */
    newchrdev.dev.owner = THIS_MODULE;
    cdev_init(&newchrdev.dev,&newchrdevops);
    ret = cdev_add(&newchrdev.dev,newchrdev.devid,NEWCHRDEV_COUNT);
    if(ret < 0){
        printk("newchrdev cdev_add failed!\r\n");
        goto newchrdev_cdev_add_failed;
    }

    /* 3、创建类 */
    newchrdev.class = class_create(THIS_MODULE,NEWCHRDEV_NAME);
    if(IS_ERR(newchrdev.class)) {
        printk("newchrdev class_create failed!\r\n");
        goto newchrdev_class_create_failed;
    }

    /* 4、创建设备 */
    newchrdev.device = device_create(newchrdev.class,NULL,newchrdev.devid,NULL,NEWCHRDEV_NAME);
    if(IS_ERR(newchrdev.device)){
        printk("newchrdev device_create failed!\r\n");
        goto neschrdev_device_creat_failed;
    }

    /* 5、key 初始化 */
    ret = key_pin_init();
    if(ret < 0){
        printk("key_pin_init!\r\n");
        goto neschrdev_device_xxx_failed;
    }

    return 0;

neschrdev_device_xxx_failed:    /* 其他操作失败(添加其他文件时,打开此选项) */
    device_destroy(newchrdev.class,newchrdev.devid);
neschrdev_device_creat_failed:  /* 删除类 */
    class_destroy(newchrdev.class);
newchrdev_class_create_failed:  /*注销字符设备 */
    cdev_del(&newchrdev.dev);
newchrdev_cdev_add_failed:  /* 释放设备号 */
    unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT);
newchrdev_chrdev_region_failed:   /* 字符设备号分配失败处理函数(未分配资源,因此不做处理) */
    printk("failed!\r\n");
    return ret;
}

/*
 * device_destroy 声明在 "linux/device.h"
 * class_destroy 声明在 "linux/device.h"
 * cdev_del 声明在 "linux/cdev.h"
 * unregister_chrdev_region 声明在 "linux/fs.h"
 */
/* 驱动卸载函数 */
static void __exit newchrdev_exit(void)
{
    /* 5、key退出 */
    key_pin_exit();
    /* 4、删除设备 */
    device_destroy(newchrdev.class,newchrdev.devid);
    /* 3、删除类 */
    class_destroy(newchrdev.class);
    /* 2、注销字符设备 */
    cdev_del(&newchrdev.dev);
    /* 1、释放设备号 */
    unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT);
    printk("newchrdev_exit succed!\r\n");
}
/*
 * module_init 声明在 "linux/init.h"
 * module_exit 声明在 "linux/init.h"
 * MODULE_LICENSE 声明在 "linux/module.h"
 */
// 头文件 "linux/init.h"
module_init(newchrdev_init);
module_exit(newchrdev_exit);
MODULE_LICENSE("GPL");


/*
 * @description	: 按键 pin 初始化(从设备树中获取相关初始化数据)
 * @param 		: 无
 * @return 		: 无
 * 
 * of_find_node_by_path 声明在 "linux/of.h"
 * of_get_named_gpio 声明在 "linux/of_gpio.h"
 * memcpy 声明在 "asm/string.h"
 * gpio_request 声明在 "linux/gpio.h"
 * gpio_direction_input 声明在 "linux/gpio.h"
 * irq_of_parse_and_map 声明在 "linux/of_irq.h"
 * request_irq 声明在 "linux/interrupt.h"
 * IRQF_TRIGGER_FALLING 定义在 "linux/interrupt.h"
 * IRQF_TRIGGER_RISING 定义在 "linux/interrupt.h"
 */
int key_pin_init(void)
{
    int ret = 0;

    /* 1、查找设备节点 */
    newchrdev.nd = of_find_node_by_path("/lq-key");
    if (newchrdev.nd== NULL){
        printk("key node not find!\r\n");
        return -1;
    }

    /* 2、从设备树中获取 GPIO 配置信息 */
    newchrdev.key_data.gpio = of_get_named_gpio(newchrdev.nd,"key-gpio",0);
    if(newchrdev.key_data.gpio < 0){
        printk("can't get key-gpio\r\n");
        return -2;
    }

    /* 3、申请一个 GPIO */
    memcpy(newchrdev.key_data.name,"KEY",sizeof("KEY"));
    gpio_request(newchrdev.key_data.gpio,newchrdev.key_data.name);

    /* 4、设置 GPIO 为输入 */
    gpio_direction_input(newchrdev.key_data.gpio);

    /* 5、获取中断号 */
    newchrdev.key_data.irqnum = irq_of_parse_and_map(newchrdev.nd,0);
    printk("key:gpio=%d, irqnum=%d\r\n",newchrdev.key_data.gpio, newchrdev.key_data.irqnum);

    /* 6、申请中断 */
    newchrdev.key_data.handler = key_handler;
    ret = request_irq(newchrdev.key_data.irqnum,newchrdev.key_data.handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,newchrdev.key_data.name,&newchrdev);
    if(ret < 0){
        printk("can't request irq!\r\n");
        return -3;
    }

    return 0;
}

/* @description		: 中断服务函数,开启定时器,延时10ms,
 *				  	  定时器用于按键消抖。
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t key_handler(int irq, void *dev_id)
{
    printk("key0_handler\r\n");

    return IRQ_RETVAL(IRQ_HANDLED);
}

void key_pin_exit(void)
{
    free_irq(newchrdev.key_data.irqnum,&newchrdev);
}

2、编译

onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ ls
irq.c  Makefile
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ make
make -C /home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/home/onlylove/linux/driver/linux_driver/8_irq modules
make[1]: Entering directory '/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga'
  CC [M]  /home/onlylove/linux/driver/linux_driver/8_irq/irq.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/onlylove/linux/driver/linux_driver/8_irq/irq.mod.o
  LD [M]  /home/onlylove/linux/driver/linux_driver/8_irq/irq.ko
make[1]: Leaving directory '/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga'
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ ls
irq.c  irq.ko  irq.mod.c  irq.mod.o  irq.o  Makefile  modules.order  Module.symvers
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$

3、验证

/ # insmod irq.ko
newchrdev.major = 0!
newchrdev devid = 260046848 newchrdev major=248,minor=0
key:gpio=18, irqnum=49
/ # rmmod irq.ko
newchrdev_exit succed!
/ #
/ # ls
bin      etc      lib      mnt      root     sys      usr
dev      irq.ko   linuxrc  proc     sbin     tmp
/ # insmod irq.ko
newchrdev.major = 0!
newchrdev devid = 260046848 newchrdev major=248,minor=0
key:gpio=18, irqnum=49
/ # key0_handler
key0_handler
key0_handler
key0_handler
key0_handler
key0_handler
key0_handler
key0_handler

/ #

通过以上验证,功能基本实现,但按键消抖未实现

7、驱动代码:使用中断下半部

我们使用 tasklet 作为中断下半部。

1、源码

#include "linux/init.h"
#include "linux/module.h"
#include "linux/kdev_t.h"
#include "linux/fs.h"
#include "linux/printk.h"
#include "linux/cdev.h"
#include "linux/device.h"
#include "linux/of.h"
#include "linux/of_gpio.h"
#include "linux/gpio.h"
#include "linux/of_irq.h"
#include "linux/interrupt.h"

#define NEWCHRDEV_MAJOR 0   /* 主设备号(如果为0则让系统自动分配,如果大于0则使用指定设备号) */
#define NEWCHRDEV_MINOR 0   /* 次设备号 */
#define NEWCHRDEV_COUNT 1   /* 设备号个数 */
#define NEWCHRDEV_NAME  "newchrdev" /* 名子 */

int key_pin_init(void);
static irqreturn_t key_handler(int irq, void *dev_id);
void key_pin_exit(void);
static void key_tasklet(unsigned long data);

/* key 相关数据 */
typedef struct{
	int gpio;								/* gpio */
	int irqnum;								/* 中断号     */
	unsigned char value;					/* 按键对应的键值 */
	char name[10];							/* 名字 */
	irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */

    struct tasklet_struct tasklet;          /* tasklet 变量(中断下半部) */
    unsigned long tasklet_data;             /* 传递给 tasklet 处理函数的参数 */
}newkey_t;

typedef struct{
    struct cdev dev;        /* cdev 结构体 (声明在 "linux/cdev.h")*/
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    dev_t devid;            /* 设备号 */
    struct class *class;    /* 类 (声明在 "linux/device.h")*/
    struct device *device;  /* 设备 (声明在 "linux/device.h") */

    struct device_node	*nd; /* 设备节点 (声明在 "linux/of.h") */
    newkey_t key_data;         /* 按键相关数据 */
}newchrdev_t;

newchrdev_t newchrdev;

static const struct file_operations newchrdevops = {
    .owner   = THIS_MODULE,
};

/*
 * MKDEV 声明在 "linux/kdev_t.h"
 * register_chrdev_region 声明在 "linux/fs.h"
 * printk 声明在 "linux/printk.h"
 * alloc_chrdev_region 声明在 "linux/fs.h"
 * cdev_init 声明在 "linux/cdev.h"
 * cdev_add 声明在 "linux/cdev.h"
 * class_create 声明在 "linux/device.h"
 */
/* 驱动入口函数 */
static int __init newchrdev_init(void)
{
    int ret;

    /* 1、字符设备号分配 */
    newchrdev.major = NEWCHRDEV_MAJOR;
    if(newchrdev.major){
        /* 指定设备号 */
        newchrdev.minor = NEWCHRDEV_MINOR;
        newchrdev.devid = MKDEV(newchrdev.major, newchrdev.minor);
        ret = register_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT,NEWCHRDEV_NAME);
        printk("newchrdev.major > 0!\r\n");
    }else{
        /* 系统分配设备号 */
        ret = alloc_chrdev_region(&newchrdev.devid,0,NEWCHRDEV_COUNT,NEWCHRDEV_NAME);
        newchrdev.major = MAJOR(newchrdev.devid);
        newchrdev.minor = MINOR(newchrdev.devid);
        printk("newchrdev.major = 0!\r\n");
    }
     if(ret < 0){
        printk("newchrdev xxx_chrdev_region failed!\r\n");
        goto newchrdev_chrdev_region_failed;
    }
    printk("newchrdev devid = %d newchrdev major=%d,minor=%d\r\n",newchrdev.devid,newchrdev.major,newchrdev.minor);

    /* 2、注册字符设备 */
    newchrdev.dev.owner = THIS_MODULE;
    cdev_init(&newchrdev.dev,&newchrdevops);
    ret = cdev_add(&newchrdev.dev,newchrdev.devid,NEWCHRDEV_COUNT);
    if(ret < 0){
        printk("newchrdev cdev_add failed!\r\n");
        goto newchrdev_cdev_add_failed;
    }

    /* 3、创建类 */
    newchrdev.class = class_create(THIS_MODULE,NEWCHRDEV_NAME);
    if(IS_ERR(newchrdev.class)) {
        printk("newchrdev class_create failed!\r\n");
        goto newchrdev_class_create_failed;
    }

    /* 4、创建设备 */
    newchrdev.device = device_create(newchrdev.class,NULL,newchrdev.devid,NULL,NEWCHRDEV_NAME);
    if(IS_ERR(newchrdev.device)){
        printk("newchrdev device_create failed!\r\n");
        goto neschrdev_device_creat_failed;
    }

    /* 5、key 初始化 */
    ret = key_pin_init();
    if(ret < 0){
        printk("key_pin_init!\r\n");
        goto neschrdev_device_xxx_failed;
    }

    return 0;

neschrdev_device_xxx_failed:    /* 其他操作失败(添加其他文件时,打开此选项) */
    device_destroy(newchrdev.class,newchrdev.devid);
neschrdev_device_creat_failed:  /* 删除类 */
    class_destroy(newchrdev.class);
newchrdev_class_create_failed:  /*注销字符设备 */
    cdev_del(&newchrdev.dev);
newchrdev_cdev_add_failed:  /* 释放设备号 */
    unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT);
newchrdev_chrdev_region_failed:   /* 字符设备号分配失败处理函数(未分配资源,因此不做处理) */
    printk("failed!\r\n");
    return ret;
}

/*
 * device_destroy 声明在 "linux/device.h"
 * class_destroy 声明在 "linux/device.h"
 * cdev_del 声明在 "linux/cdev.h"
 * unregister_chrdev_region 声明在 "linux/fs.h"
 */
/* 驱动卸载函数 */
static void __exit newchrdev_exit(void)
{
    /* 5、key退出 */
    key_pin_exit();
    /* 4、删除设备 */
    device_destroy(newchrdev.class,newchrdev.devid);
    /* 3、删除类 */
    class_destroy(newchrdev.class);
    /* 2、注销字符设备 */
    cdev_del(&newchrdev.dev);
    /* 1、释放设备号 */
    unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT);
    printk("newchrdev_exit succed!\r\n");
}
/*
 * module_init 声明在 "linux/init.h"
 * module_exit 声明在 "linux/init.h"
 * MODULE_LICENSE 声明在 "linux/module.h"
 */
// 头文件 "linux/init.h"
module_init(newchrdev_init);
module_exit(newchrdev_exit);
MODULE_LICENSE("GPL");


/*
 * @description	: 按键 pin 初始化(从设备树中获取相关初始化数据)
 * @param 		: 无
 * @return 		: 无
 * 
 * of_find_node_by_path 声明在 "linux/of.h"
 * of_get_named_gpio 声明在 "linux/of_gpio.h"
 * memcpy 声明在 "asm/string.h"
 * gpio_request 声明在 "linux/gpio.h"
 * gpio_direction_input 声明在 "linux/gpio.h"
 * irq_of_parse_and_map 声明在 "linux/of_irq.h"
 * request_irq 声明在 "linux/interrupt.h"
 * IRQF_TRIGGER_FALLING 定义在 "linux/interrupt.h"
 * IRQF_TRIGGER_RISING 定义在 "linux/interrupt.h"
 * tasklet_init 声明在 "linux/interrupt.h"
 */
int key_pin_init(void)
{
    int ret = 0;

    /* 1、查找设备节点 */
    newchrdev.nd = of_find_node_by_path("/lq-key");
    if (newchrdev.nd== NULL){
        printk("key node not find!\r\n");
        return -1;
    }

    /* 2、从设备树中获取 GPIO 配置信息 */
    newchrdev.key_data.gpio = of_get_named_gpio(newchrdev.nd,"key-gpio",0);
    if(newchrdev.key_data.gpio < 0){
        printk("can't get key-gpio\r\n");
        return -2;
    }

    /* 3、申请一个 GPIO */
    memcpy(newchrdev.key_data.name,"KEY",sizeof("KEY"));
    gpio_request(newchrdev.key_data.gpio,newchrdev.key_data.name);

    /* 4、设置 GPIO 为输入 */
    gpio_direction_input(newchrdev.key_data.gpio);

    /* 5、获取中断号 */
    newchrdev.key_data.irqnum = irq_of_parse_and_map(newchrdev.nd,0);
    printk("key:gpio=%d, irqnum=%d\r\n",newchrdev.key_data.gpio, newchrdev.key_data.irqnum);

    /* 6、申请中断 */
    newchrdev.key_data.handler = key_handler;
    ret = request_irq(newchrdev.key_data.irqnum,newchrdev.key_data.handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,newchrdev.key_data.name,&newchrdev);
    if(ret < 0){
        printk("can't request irq!\r\n");
        return -3;
    }

    /* 7、初始化中断下半部(tasklet) */
    tasklet_init(&newchrdev.key_data.tasklet,key_tasklet,newchrdev.key_data.tasklet_data);

    return 0;
}

/* @description		: 中断服务函数,开启定时器,延时10ms,
 *				  	  定时器用于按键消抖。
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t key_handler(int irq, void *dev_id)
{
    printk("key0_handler\r\n");
    tasklet_schedule(&newchrdev.key_data.tasklet);
    return IRQ_RETVAL(IRQ_HANDLED);
}

void key_pin_exit(void)
{
    free_irq(newchrdev.key_data.irqnum,&newchrdev);
}

static void key_tasklet(unsigned long data)
{
    printk("key_tasklet\r\n");
}

以上源码为根据 Linux 中断设计思路二完善源码。

2、编译

onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ ls
irq.c  Makefile
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ make
make -C /home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/home/onlylove/linux/driver/linux_driver/8_irq modules
make[1]: Entering directory '/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga'
  CC [M]  /home/onlylove/linux/driver/linux_driver/8_irq/irq.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/onlylove/linux/driver/linux_driver/8_irq/irq.mod.o
  LD [M]  /home/onlylove/linux/driver/linux_driver/8_irq/irq.ko
make[1]: Leaving directory '/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga'
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ ls
irq.c  irq.ko  irq.mod.c  irq.mod.o  irq.o  Makefile  modules.order  Module.symvers
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ cp irq.ko ~/linux/nfs/rootfs-l
rootfs-l/        rootfs-l.tar.gz  
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ cp irq.ko ~/linux/nfs/rootfs-l/
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ ls
irq.c  irq.ko  irq.mod.c  irq.mod.o  irq.o  Makefile  modules.order  Module.symvers
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ 

3、验证

/ # ls
bin      etc      lib      mnt      root     sys      usr
dev      irq.ko   linuxrc  proc     sbin     tmp
/ # lsmod
Module                  Size  Used by    Not tainted
/ #
/ # insmod irq.ko
newchrdev.major = 0!
newchrdev devid = 260046848 newchrdev major=248,minor=0
key:gpio=18, irqnum=49
/ #
/ # lsmod
Module                  Size  Used by    Tainted: G
irq                     2563  0
/ # rmmod irq.ko
newchrdev_exit succed!
/ #
/ # lsmod
Module                  Size  Used by    Tainted: G
/ #
/ # insmod irq.ko
newchrdev.major = 0!
newchrdev devid = 260046848 newchrdev major=248,minor=0
key:gpio=18, irqnum=49
/ #
/ # key0_handler
key_tasklet
key0_handler
key_tasklet

/ # rmmod irq.ko
newchrdev_exit succed!
/ #

8、驱动代码:按键消抖

使用内核定时器完成按键消抖。

1、源码

#include "linux/init.h"
#include "linux/module.h"
#include "linux/kdev_t.h"
#include "linux/fs.h"
#include "linux/printk.h"
#include "linux/cdev.h"
#include "linux/device.h"
#include "linux/of.h"
#include "linux/of_gpio.h"
#include "linux/gpio.h"
#include "linux/of_irq.h"
#include "linux/interrupt.h"
#include "linux/jiffies.h"

#define NEWCHRDEV_MAJOR 0   /* 主设备号(如果为0则让系统自动分配,如果大于0则使用指定设备号) */
#define NEWCHRDEV_MINOR 0   /* 次设备号 */
#define NEWCHRDEV_COUNT 1   /* 设备号个数 */
#define NEWCHRDEV_NAME  "newchrdev" /* 名子 */

int key_pin_init(void);
static irqreturn_t key_handler(int irq, void *dev_id);
void key_pin_exit(void);
static void key_tasklet(unsigned long data);
void timer_function(unsigned long arg);
void key_timer_init(void);
void key_timer_exit(void);

/* key 相关数据 */
typedef struct{
	int gpio;								/* gpio */
	int irqnum;								/* 中断号     */
	unsigned char value;					/* 按键对应的键值 */
	char name[10];							/* 名字 */
	irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */

    struct tasklet_struct tasklet;          /* tasklet 变量(中断下半部) */
    unsigned long tasklet_data;             /* 传递给 tasklet 处理函数的参数 */
    struct timer_list key_timer;            /* 中断定时器,用于按键消抖 */
}newkey_t;

typedef struct{
    struct cdev dev;        /* cdev 结构体 (声明在 "linux/cdev.h")*/
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    dev_t devid;            /* 设备号 */
    struct class *class;    /* 类 (声明在 "linux/device.h")*/
    struct device *device;  /* 设备 (声明在 "linux/device.h") */

    struct device_node	*nd; /* 设备节点 (声明在 "linux/of.h") */
    newkey_t key_data;         /* 按键相关数据 */
}newchrdev_t;

newchrdev_t newchrdev;

static const struct file_operations newchrdevops = {
    .owner   = THIS_MODULE,
};

/*
 * MKDEV 声明在 "linux/kdev_t.h"
 * register_chrdev_region 声明在 "linux/fs.h"
 * printk 声明在 "linux/printk.h"
 * alloc_chrdev_region 声明在 "linux/fs.h"
 * cdev_init 声明在 "linux/cdev.h"
 * cdev_add 声明在 "linux/cdev.h"
 * class_create 声明在 "linux/device.h"
 */
/* 驱动入口函数 */
static int __init newchrdev_init(void)
{
    int ret;

    /* 1、字符设备号分配 */
    newchrdev.major = NEWCHRDEV_MAJOR;
    if(newchrdev.major){
        /* 指定设备号 */
        newchrdev.minor = NEWCHRDEV_MINOR;
        newchrdev.devid = MKDEV(newchrdev.major, newchrdev.minor);
        ret = register_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT,NEWCHRDEV_NAME);
        printk("newchrdev.major > 0!\r\n");
    }else{
        /* 系统分配设备号 */
        ret = alloc_chrdev_region(&newchrdev.devid,0,NEWCHRDEV_COUNT,NEWCHRDEV_NAME);
        newchrdev.major = MAJOR(newchrdev.devid);
        newchrdev.minor = MINOR(newchrdev.devid);
        printk("newchrdev.major = 0!\r\n");
    }
     if(ret < 0){
        printk("newchrdev xxx_chrdev_region failed!\r\n");
        goto newchrdev_chrdev_region_failed;
    }
    printk("newchrdev devid = %d newchrdev major=%d,minor=%d\r\n",newchrdev.devid,newchrdev.major,newchrdev.minor);

    /* 2、注册字符设备 */
    newchrdev.dev.owner = THIS_MODULE;
    cdev_init(&newchrdev.dev,&newchrdevops);
    ret = cdev_add(&newchrdev.dev,newchrdev.devid,NEWCHRDEV_COUNT);
    if(ret < 0){
        printk("newchrdev cdev_add failed!\r\n");
        goto newchrdev_cdev_add_failed;
    }

    /* 3、创建类 */
    newchrdev.class = class_create(THIS_MODULE,NEWCHRDEV_NAME);
    if(IS_ERR(newchrdev.class)) {
        printk("newchrdev class_create failed!\r\n");
        goto newchrdev_class_create_failed;
    }

    /* 4、创建设备 */
    newchrdev.device = device_create(newchrdev.class,NULL,newchrdev.devid,NULL,NEWCHRDEV_NAME);
    if(IS_ERR(newchrdev.device)){
        printk("newchrdev device_create failed!\r\n");
        goto neschrdev_device_creat_failed;
    }

    /* 5、key 初始化 */
    ret = key_pin_init();
    if(ret < 0){
        printk("key_pin_init!\r\n");
        goto neschrdev_device_xxx_failed;
    }

    return 0;

neschrdev_device_xxx_failed:    /* 其他操作失败(添加其他文件时,打开此选项) */
    device_destroy(newchrdev.class,newchrdev.devid);
neschrdev_device_creat_failed:  /* 删除类 */
    class_destroy(newchrdev.class);
newchrdev_class_create_failed:  /*注销字符设备 */
    cdev_del(&newchrdev.dev);
newchrdev_cdev_add_failed:  /* 释放设备号 */
    unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT);
newchrdev_chrdev_region_failed:   /* 字符设备号分配失败处理函数(未分配资源,因此不做处理) */
    printk("failed!\r\n");
    return ret;
}

/*
 * device_destroy 声明在 "linux/device.h"
 * class_destroy 声明在 "linux/device.h"
 * cdev_del 声明在 "linux/cdev.h"
 * unregister_chrdev_region 声明在 "linux/fs.h"
 */
/* 驱动卸载函数 */
static void __exit newchrdev_exit(void)
{
    /*6、删除内核定时器(用于按键消抖)*/
    key_timer_exit();
    /* 5、key退出 */
    key_pin_exit();
    /* 4、删除设备 */
    device_destroy(newchrdev.class,newchrdev.devid);
    /* 3、删除类 */
    class_destroy(newchrdev.class);
    /* 2、注销字符设备 */
    cdev_del(&newchrdev.dev);
    /* 1、释放设备号 */
    unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT);
    printk("newchrdev_exit succed!\r\n");
}
/*
 * module_init 声明在 "linux/init.h"
 * module_exit 声明在 "linux/init.h"
 * MODULE_LICENSE 声明在 "linux/module.h"
 */
// 头文件 "linux/init.h"
module_init(newchrdev_init);
module_exit(newchrdev_exit);
MODULE_LICENSE("GPL");


/*
 * @description	: 按键 pin 初始化(从设备树中获取相关初始化数据)
 * @param 		: 无
 * @return 		: 无
 * 
 * of_find_node_by_path 声明在 "linux/of.h"
 * of_get_named_gpio 声明在 "linux/of_gpio.h"
 * memcpy 声明在 "asm/string.h"
 * gpio_request 声明在 "linux/gpio.h"
 * gpio_direction_input 声明在 "linux/gpio.h"
 * irq_of_parse_and_map 声明在 "linux/of_irq.h"
 * request_irq 声明在 "linux/interrupt.h"
 * IRQF_TRIGGER_FALLING 定义在 "linux/interrupt.h"
 * IRQF_TRIGGER_RISING 定义在 "linux/interrupt.h"
 * tasklet_init 声明在 "linux/interrupt.h"
 * init_timer 声明在 "linux/timer.h"
 */
int key_pin_init(void)
{
    int ret = 0;

    /* 1、查找设备节点 */
    newchrdev.nd = of_find_node_by_path("/lq-key");
    if (newchrdev.nd== NULL){
        printk("key node not find!\r\n");
        return -1;
    }

    /* 2、从设备树中获取 GPIO 配置信息 */
    newchrdev.key_data.gpio = of_get_named_gpio(newchrdev.nd,"key-gpio",0);
    if(newchrdev.key_data.gpio < 0){
        printk("can't get key-gpio\r\n");
        return -2;
    }

    /* 3、申请一个 GPIO */
    memcpy(newchrdev.key_data.name,"KEY",sizeof("KEY"));
    gpio_request(newchrdev.key_data.gpio,newchrdev.key_data.name);

    /* 4、设置 GPIO 为输入 */
    gpio_direction_input(newchrdev.key_data.gpio);

    /* 5、获取中断号 */
    newchrdev.key_data.irqnum = irq_of_parse_and_map(newchrdev.nd,0);
    printk("key:gpio=%d, irqnum=%d\r\n",newchrdev.key_data.gpio, newchrdev.key_data.irqnum);

    /* 6、申请中断 */
    newchrdev.key_data.handler = key_handler;
    ret = request_irq(newchrdev.key_data.irqnum,newchrdev.key_data.handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,newchrdev.key_data.name,&newchrdev);
    if(ret < 0){
        printk("can't request irq!\r\n");
        return -3;
    }

    /* 7、初始化中断下半部(tasklet) */
    tasklet_init(&newchrdev.key_data.tasklet,key_tasklet,newchrdev.key_data.tasklet_data);

    /* 8、初始化定时器(用于按键消抖) */
    key_timer_init();

    return 0;
}

/* @description		: 中断服务函数,开启定时器,延时10ms,
 *				  	  定时器用于按键消抖。
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t key_handler(int irq, void *dev_id)
{
    printk("key0_handler\r\n");
    tasklet_schedule(&newchrdev.key_data.tasklet);
    return IRQ_RETVAL(IRQ_HANDLED);
}

void key_pin_exit(void)
{
    free_irq(newchrdev.key_data.irqnum,&newchrdev);
}

/*
 * jiffies 定义在 "linux/jiffies.h"
 * msecs_to_jiffies 声明在 "linux/jiffies.h"
 * 
 */
static void key_tasklet(unsigned long data)
{
    printk("key_tasklet\r\n");
    newchrdev.key_data.key_timer.data = (unsigned long)(&newchrdev.key_data.key_timer);
    mod_timer(&newchrdev.key_data.key_timer,jiffies + msecs_to_jiffies(2000));
}

void timer_function(unsigned long arg)
{
    printk("timer_function\r\n");
}

void key_timer_init(void)
{
    init_timer(&newchrdev.key_data.key_timer);
    newchrdev.key_data.key_timer.function = timer_function;
}

void key_timer_exit(void)
{
    del_timer(&newchrdev.key_data.key_timer);
}

2、编译

onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ ls
irq.c  Makefile
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ make
make -C /home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/home/onlylove/linux/driver/linux_driver/8_irq modules
make[1]: Entering directory '/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga'
  CC [M]  /home/onlylove/linux/driver/linux_driver/8_irq/irq.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/onlylove/linux/driver/linux_driver/8_irq/irq.mod.o
  LD [M]  /home/onlylove/linux/driver/linux_driver/8_irq/irq.ko
make[1]: Leaving directory '/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga'
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ ls
irq.c  irq.ko  irq.mod.c  irq.mod.o  irq.o  Makefile  modules.order  Module.symvers
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ cp irq.ko ~/linux/nfs/rootfs-l
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$ ls
irq.c  irq.ko  irq.mod.c  irq.mod.o  irq.o  Makefile  modules.order  Module.symvers
onlylove@ubuntu:~/linux/driver/linux_driver/8_irq$

3、验证

/ # ls
bin      etc      lib      mnt      root     sys      usr
dev      irq.ko   linuxrc  proc     sbin     tmp
/ # rm irq.ko
/ # random: nonblocking pool is initialized
ls
bin      etc      lib      mnt      root     sys      usr
dev      irq.ko   linuxrc  proc     sbin     tmp
/ # insmod irq.ko
newchrdev.major = 0!
newchrdev devid = 260046848 newchrdev major=248,minor=0
key:gpio=18, irqnum=49
/ # key0_handler
key_tasklet
key0_handler
key0_handler
key_tasklet
timer_function
key0_handler
key_tasklet
key0_handler
key_tasklet
timer_function
key0_handler
key_tasklet
key0_handler
key_tasklet
timer_function

/ # rmmod irq.ko
newchrdev_exit succed!
/ # ls
bin      etc      lib      mnt      root     sys      usr
dev      irq.ko   linuxrc  proc     sbin     tmp
/ #
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值