inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
unsigned int irq:为要注册中断服务函数的中断号,定义在mach/irqs.h
irq_handler_t handler:为要注册的中断服务函数
unsigned long irqflags: 触发中断的参数,比如边沿触发, 定义在linux/interrupt.h。
const char *devname:中断程序的名字,使用cat /proc/interrupt 可以查看中断程序名字
void *dev_id:传入中断处理程序的参数,注册共享中断时不能为NULL,因为卸载时需要这个做参数,避免卸载其它中断服务函数
对于共享中断,就是几个中断共享一个中断函数的情况。request_irq 函数最后一个参数void *dev_id不能为NULL,中断函数用这个参数来区分是哪个中断在执行。
void free_irq(unsigned int irq, void *dev_id)
卸载中断的时候,这个void *dev_id 也不能为NULL,系统通过这个参数来判断具体卸载哪一个中断。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h> /*delay*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h> /*kmalloc*/
#include <linux/vmalloc.h> /*vmalloc*/
#include <linux/types.h> /*ssize_t*/
#include <linux/fs.h> /*file_operaiotns*/
#include <linux/gpio_keys.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm-generic/ioctl.h>
#include <asm-generic/errno-base.h>
#include <mach/iomux-mx6dl.h>
/**主设备号和次设备号**/
int device_major = 0;
int device_minor = 0;
static struct class *gpio_class; /*在/sys目录创造一个类*/
static struct cdev *gpio_class_dev; /*在这个类下,创造一个设备节点*/
#define SABRESD_POWER_OFF IMX_GPIO_NR(3, 29) //MX6DL_PAD_EIM_D29__GPIO_3_29
#define SABRESD_VOLUME_UP IMX_GPIO_NR(1, 4) //MX6DL_PAD_GPIO_4__GPIO_1_4
#define SABRESD_VOLUME_DN IMX_GPIO_NR(1, 5) //MX6DL_PAD_GPIO_5__GPIO_1_5
//引脚描述符
struct pin_desc
{
int pin;
unsigned long long pinconfig;
int intnumber;
int key_val;
};
//引脚描述符,按引脚的不同定义不同的值,在中断中进行处理
struct pin_desc pin_desc[3] =
{
{SABRESD_POWER_OFF, MX6DL_PAD_EIM_D29__GPIO_3_29, gpio_to_irq(SABRESD_POWER_OFF), 0x1},
{SABRESD_VOLUME_UP, MX6DL_PAD_GPIO_4__GPIO_1_4, gpio_to_irq(SABRESD_VOLUME_UP), 0x2},
{SABRESD_VOLUME_DN, MX6DL_PAD_GPIO_5__GPIO_1_5, gpio_to_irq(SABRESD_VOLUME_DN), 0x3},
};
static DECLARE_WAIT_QUEUE_HEAD(Key_Status_Read_Wait);
static int wait_flag = 0;
/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
/* 主要是为了区分是按下还是松开 */
static unsigned char key_val = 0;
irqreturn_t button_irq(int irq, void *devid)
{
int pin_val;
struct pin_desc *pin_readed;
pin_readed = (struct pin_desc *)devid;
/* 1 获取当前引脚的电平值 */
pin_val = gpio_get_value(pin_readed->pin);
/* 2 根据电平值判断当前是按下还是松开 */
if (pin_val) /* 松开为高电平,返回0x8x */
{
key_val = 0x80 | pin_readed->key_val;
}
else /* 按下为低电平,返回0x0x */
{
//把当前按键的键值给一个全局静态变量,在read函数里给用户
key_val = pin_readed->key_val;
}
printk("%02x ", key_val);
wake_up(&Key_Status_Read_Wait);
wait_flag = 1;
/* 3 标记中断已经触发 */
return IRQ_RETVAL(IRQ_HANDLED);
}
/************************************************************************
中断程序的返回值是一个特殊类型—irqreturn_t。
但是中断程序的返回值却只有两个—IRQ_NONE和IRQ_HANDLED。
* IRQ_NONE means we didn't handle it.
* 中断程序接收到中断信号后发现这并不是注册时指定的中断原发出的中断信号.
* IRQ_HANDLED means that we did have a valid interrupt and handled it.
* 接收到了准确的中断信号,并且作了相应正确的处理
也可以使用宏IRQ_RETVAL(x),如果x非0值,那么该宏返回IRQ_HANDLED,否则,返回IRQ_NONE.
*************************************************************************/
/*open函数的实现*/
static int gpio_open(struct inode *inode, struct file *file)
{
printk(KERN_ALERT "OPEN\n");
return 0;
}
static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
printk(KERN_ALERT "ioctl\n");
return 0;
}
/*release函数的实现*/
static int gpio_close(struct inode *inode, struct file *file)
{
printk(KERN_ALERT "close\n");
return 0;
}
ssize_t gpio_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
printk(KERN_ALERT "read\n");
/* 将在中断中保存的数据返回给用户,并将标志位清零,表示已无数据 */
int ret;
wait_event_interruptible(Key_Status_Read_Wait, wait_flag);
wait_flag = 0;
//拷贝数据到用户空间
ret = copy_to_user(buf, &key_val, sizeof(key_val));
key_val = 0;
if (ret)
{
return 1;
}
//读取数据完毕后需要将标志位清零,表示暂时无数据可读
return 0;
}
/*具体的文件操作集合*/
static const struct file_operations gpio_fops =
{
/*这是拥有者*/
.owner = THIS_MODULE,
.open = gpio_open,
.unlocked_ioctl = gpio_ioctl,
.release = gpio_close,
.read = gpio_read,
};
/* 3.1 实现button_open函数 */
static int Button_Initial(void)
{
/* 根据4个引脚的中断号,注册4个外部中断(用来感知不同的引脚),并实现中断处理函数 */
int ret;
#if 1
gpio_free(pin_desc[0].pin);
mxc_iomux_v3_setup_pad(pin_desc[0].pinconfig);
ret = gpio_request(pin_desc[0].pin, "S1");
if (ret == 0)
{
printk("gpio_request S1 success\n");
}
else
{
printk("gpio_request S1 fail\n");
}
ret = gpio_direction_input(pin_desc[0].pin);
if (ret == 0)
{
printk("gpio_direction_input S1 success\n");
}
else
{
printk("gpio_direction_input S1 fail\n");
}
ret = request_irq(pin_desc[0].intnumber, button_irq, IRQ_TYPE_EDGE_FALLING, "S1",
(void *)&pin_desc[0]);
if (ret)
{
printk("request_irq S1 error\n");
goto errout;
}
else
{
printk("request_irq S1 =%d\n", pin_desc[0].intnumber);
}
#endif
gpio_free(pin_desc[1].pin);
mxc_iomux_v3_setup_pad(pin_desc[1].pinconfig);
ret = gpio_request(pin_desc[1].pin, "S2");
if (ret == 0)
{
printk("gpio_request S2 success\n");
}
else
{
printk("gpio_request S2 fail\n");
}
ret = gpio_direction_input(pin_desc[1].pin);
if (ret == 0)
{
printk("gpio_direction_input S2 success\n");
}
else
{
printk("gpio_direction_input S2 fail\n");
}
ret = request_irq(pin_desc[1].intnumber, button_irq, IRQ_TYPE_EDGE_FALLING, "S2",
(void *)&pin_desc[1]);
if (ret)
{
printk("request_irq S2 error\n");
goto errout;
}
else
{
printk("request_irq S2 =%d\n", pin_desc[1].intnumber);
}
gpio_free(pin_desc[2].pin);
mxc_iomux_v3_setup_pad(pin_desc[2].pinconfig);
ret = gpio_request(pin_desc[2].pin, "S3");
if (ret == 0)
{
printk("gpio_request S3 success\n");
}
else
{
printk("gpio_request S3 fail\n");
}
ret = gpio_direction_input(pin_desc[2].pin);
if (ret == 0)
{
printk("gpio_direction_input S3 success\n");
}
else
{
printk("gpio_direction_input S3 fail\n");
}
ret = request_irq(pin_desc[2].intnumber, button_irq, IRQ_TYPE_EDGE_FALLING, "S3",
(void *)&pin_desc[2]);
if (ret)
{
printk("request_irq S3 error\n");
goto errout;
}
else
{
printk("request_irq S3 =%d\n", pin_desc[2].intnumber);
}
printk("button open\n");
return 0;
errout:
return ret;
}
/*驱动的初始化函数*/
static int ButtonDriver_Initial(void)
{
int ret;
dev_t gpio_dev_no; //设备号
if (device_major)
{
gpio_dev_no = MKDEV(device_major, device_minor);
register_chrdev_region(gpio_dev_no, 1, "button-chardev");
}
else
{
ret = alloc_chrdev_region(&gpio_dev_no, 0, 1, "button-chardev");
if (ret)
{
printk(KERN_ALERT "alloc_chrdev_region failed\n");
}
device_major = MAJOR(gpio_dev_no);
device_minor = MINOR(gpio_dev_no);
printk(KERN_ALERT "major=%d minor=%d\n", MAJOR(gpio_dev_no), MINOR(gpio_dev_no));
}
gpio_class_dev = cdev_alloc(); //分配空间
cdev_init(gpio_class_dev, &gpio_fops); /*字符设备初始化,绑定相关操作到设备*/
gpio_class_dev->owner = THIS_MODULE; /*设备的拥有者*/
cdev_add(gpio_class_dev, gpio_dev_no, 1); /*添加设备到内核*/
gpio_class = class_create(THIS_MODULE, "button-chardev-class"); /*创建设备类,用于自动创建设备文件*/
device_create(gpio_class, NULL, gpio_dev_no, NULL, "button-chardev-device"); /*依据以前创建的设备类,创建设备*/
Button_Initial();
return 0;
}
/************************************************************
在设备驱动中cdev_add将struct file_operations和设备号注册到系统后,
为了能够自动产生驱动对应的设备文件,需要调用class_create和device_create,
并通过uevent机制调用mdev(嵌入式linux由busybox提供)来调用mknod创建设备文件。
当然也可以不调用这两个接口,那就手工通过命令行mknod来创建设备文件。
1. 在驱动通过cdev_add将struct file_operations接口集和设备注册到系统后,
即利用class_create接口来创建设备类目录文件。
默认在/sysfs/创建class目录。
1.利用class_create接口来创建设备类目录文件后,再利用device_create
接口来创建具体设备目录和设备属性文件。
代表默认在/sysfs/创建devices目录
轮到mdev出场了,以上描述都是在sysfs文件系统中创建目录或者文件,
而应用程序访问的设备文件则需要创建在/dev/目录下。该项工作由mdev完成。
*************************************************************/
/*退出函数*/
static void ButtonDriver_Exit(void)
{
free_irq(pin_desc[0].intnumber, (void *)&pin_desc[0]);
free_irq(pin_desc[1].intnumber, (void *)&pin_desc[1]);
free_irq(pin_desc[2].intnumber, (void *)&pin_desc[2]);
/*设备卸载*/
cdev_del(gpio_class_dev); //注销设备
unregister_chrdev_region(MKDEV(device_major, device_minor), 1); //释放设备号
device_destroy(gpio_class, MKDEV(device_major, device_minor));
class_destroy(gpio_class);
/* 释放注册的4个中断 */
/*********************************************************
free_irq(unsigned int irq, void *dev_id);
参数说明:
unsigned int irq:要卸载的中断号
void *dev_id:这个是要卸载的中断action下的哪个服务函数,
***********************************************************/
}
/*LICENSE信息*/
MODULE_LICENSE("GPL");
/*卸载和加载*/
module_init(ButtonDriver_Initial);
module_exit(ButtonDriver_Exit);
/**************************************************************************
static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
unsigned int irq:为要注册中断服务函数的中断号,定义在mach/irqs.h
irq_handler_t handler:为要注册的中断服务函数
unsigned long irqflags: 触发中断的参数,比如边沿触发, 定义在linux/interrupt.h。
const char *devname:中断程序的名字,使用cat /proc/interrupt 可以查看中断程序名字
void *dev_id:传入中断处理程序的参数,注册共享中断时不能为NULL,因为卸载时需要这个做参数,避免卸载其它中断服务函数
void free_irq(unsigned int irq, void *dev_id)
{
struct irq_desc *desc = irq_to_desc(irq);
if (!desc)
return;
chip_bus_lock(irq, desc);
kfree(__free_irq(irq, dev_id));
chip_bus_sync_unlock(irq, desc);
}
unsigned int irq:要卸载的中断号
void *dev_id:这个是要卸载的中断action下的哪个服务函数
编程注意:
1.如果是采用非共享方式注册中断,则request_irq和free的最后一个参数都要为NULL。
2.如果采用共享中断方式,所有使用request_irq注册的中断时flags都要加上IRQF_SHARED这个共享参数,表明其实共享中断。
3.对于共享中断,每一个申请共享的中断,申请和释放时都要给request_irq和free_irq的最后一个参数dev和id_dev传递一个指针,
将来来中断的时候,将会传递这个指针到每个中断函数中,而中断函数就可以用来区分到底是不是它的中断,是则执行,
不是则判断后直接退出中断处理函数即可。同时在free_irq时也会使用这个指针,查找这个贡献中断链表上了所有注册的irq,
只有在这个指针能对的上的时候,才会删除它所在的链表节点(如果是最后一个节点还要释放该中断)。所在在编写中断处理
函数时该指针必须是唯一的,通常传的这个指针是该设备结构体的地址,这个每个设备不一样所以肯定是唯一的。
中断申请函数request_irq()与中断释放函数free_irq()的最后一个参数(void *dev 设备结构体)要保持一致,
必须是同一个指针,参数传递过来的都不算。
***************************************************************************/