fl2440按键中断驱动

时间:2022-08-20 17:29:54
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/timer.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/uaccess.h>    //copy_to_user
#include <linux/jiffies.h>    //内核时钟
#include <asm/atomic.h>    //atomic_t
#include <mach/regs-gpio.h>    //S3C2410_GPF0等的定义
#include <mach/hardware.h>    //s3c2410_gpio_getpin等的定义
#include <mach/irqs.h>    //IRQ_EINT0等的定义
#define DEV_NAME "fl2440_button_driver"
#define KEY_COUNT    4    //按键的个数
#define MAX_KEY_BUF    16    //缓冲区的长度
#define KEY_UP    0    //按键的状态 抬起
#define KEY_DOWN    1    //按下
#define KEY_DOWN_INT    2    //按下,来自中断
#define DELAY_1    HZ/50
#define DELAY_2    HZ/20
#define IS_KEY_DOWN(x)    !s3c2410_gpio_getpin(key_infos[x].gpio)
typedef unsigned char KEY_RET;
struct KEY_DEV
{
    unsigned int key_status[KEY_COUNT];//四个键的状态,应当被初始化为抬起
    KEY_RET    key_buffer[MAX_KEY_BUF];//按键缓冲区,大小为16个字符
    unsigned int head,tail;//按键缓冲区的头和尾
    wait_queue_head_t wq;//等待队列
    struct cdev cdev;//cdev结构体
}key_dev;
static struct timer_list key_timers[KEY_COUNT];//去抖动定时器
//按键的硬件结构体
struct KEY_INFO
{
    int irq;//中断号
    unsigned long gpio;//gpio引脚地址
    char key_value;//键值
    char key_name[6];
}key_infos[KEY_COUNT]=
{
    {IRQ_EINT0,S3C2410_GPF0,'A',"key_a"},
    {IRQ_EINT2,S3C2410_GPF2,'B',"key_b"},
    {IRQ_EINT3,S3C2410_GPF3,'C',"key_c"},
    {IRQ_EINT4,S3C2410_GPF4,'D',"key_d"},
};
   
int dev_major=0;    //主设备号
atomic_t need_request_irq=ATOMIC_INIT(1);
irqreturn_t interrupt_handler(int irq,void* dev_id)
{
    int key=(int)dev_id;
    disable_irq(key_infos[key].irq);
    key_dev.key_status[key]=KEY_DOWN_INT;
    key_timers[key].expires=jiffies+DELAY_1;
    add_timer(&key_timers[key]);
    return IRQ_HANDLED;
}
static int dev_open(struct inode* inode,struct file *filp)
{
    int i=0;
    printk("this is %s /n",__FUNCTION__);
    filp->private_data=container_of(inode->i_cdev,struct KEY_DEV,cdev);
    if(atomic_dec_and_test(&need_request_irq))    //如果是第一次打开,就注册中断
    {
        atomic_add(2,&need_request_irq);
        for(i=0;i<KEY_COUNT;i++)
        {
            if(request_irq(key_infos[i].irq,interrupt_handler,IRQF_TRIGGER_LOW|IRQF_DISABLED,key_infos[i].key_name,(void*)i))
                goto error0;
        }
    }
    key_dev.head=key_dev.tail=0;
    return 0;
error0:
    printk("request_irq error,%d!/n",i);
    for(--i;i>=0;i--)
        free_irq(key_infos[i].irq,(void*)i);
    return -EBUSY;
}
static int dev_release(struct inode *inode,struct file *filp)
{
    int i=0;
    printk("this is %s /n",__FUNCTION__);
    atomic_dec(&need_request_irq);
    for(i=0;i<KEY_COUNT;i++)
        free_irq(key_infos[i].irq,(void*)i);   
    return 0;
}
ssize_t dev_read(struct file *filp,char __user *userp,size_t count,loff_t *offp)
{
    int c,i;
    if(key_dev.tail==key_dev.head)//没有数据
    {
        if(filp->f_flags & O_NONBLOCK)//非阻塞的
            return -EAGAIN;
        if(wait_event_interruptible(key_dev.wq,key_dev.tail!=key_dev.head))
            return -ERESTARTSYS;
    }
    c=min(key_dev.tail-key_dev.head,count);
    copy_to_user((void*)userp,key_dev.key_buffer,c);
    //移动剩下的按键值到前面去   
    for(i=c;i<=key_dev.tail;i++)
    {
        key_dev.key_buffer[i-c]=key_dev.key_buffer[i];
    }
    key_dev.tail-=c;
    return 0;
}
   
struct file_operations fops=
{
    .owner=THIS_MODULE,
    .open=dev_open,
    .release=dev_release,
    .read=dev_read,
};
//记录按键值,唤醒等待队列
void key_event(int key)
{
    if(key_dev.tail-key_dev.head>=MAX_KEY_BUF)//缓冲区已满
        return ;
    key_dev.key_buffer[key_dev.tail++]=key_infos[key].key_value;
    wake_up_interruptible(&key_dev.wq);    //唤醒等待队列
}
//定时器处理函数
void key_timer_function(unsigned long arg)
{
    int key=arg;
    if(IS_KEY_DOWN(key))
    {
        if(key_dev.key_status[key]==KEY_DOWN_INT)
        {
            key_dev.key_status[key]=KEY_DOWN;
            key_timers[key].expires=jiffies+DELAY_2;
            key_event(key);//记录按键值,唤醒等待队列
            add_timer(&key_timers[key]);
        }
        else
        {
            key_timers[key].expires=jiffies+DELAY_2;
            add_timer(&key_timers[key]);
        }
    }
    else    //按键抬起
    {
        key_dev.key_status[key]=KEY_UP;
        enable_irq(key_infos[key].irq);
    }
}
static int __init dev_init(void)
{
    dev_t dev_id;
    int err,i;
    err=alloc_chrdev_region(&dev_id,0,KEY_COUNT,DEV_NAME);
    if(err)
        goto error0;
    dev_major=MAJOR(dev_id);
    cdev_init(&key_dev.cdev,&fops);
    key_dev.cdev.owner=THIS_MODULE;
    err=cdev_add(&key_dev.cdev,dev_id,KEY_COUNT);
    if(err)
        goto error2;
    for(i=0;i<KEY_COUNT;i++)
    {
        init_timer(&key_timers[i]);
        key_timers[i].data=i;
        key_timers[i].function=key_timer_function;
        key_dev.key_status[i]=KEY_UP;
    }
    init_waitqueue_head(&key_dev.wq);
    printk("init module successful!/n");
    return 0;
error2:
    cdev_del(&key_dev.cdev);
    unregister_chrdev_region(dev_id,KEY_COUNT);
error0:
    return err;
}
static void __exit dev_exit(void)
{
    cdev_del(&key_dev.cdev);
    unregister_chrdev_region(MKDEV(dev_major,0),KEY_COUNT);
    printk("exit module!/n");
}
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");