Linux按键驱动示例

downtown 2010-12-20

如果要将该程序应用于具体工程中,建议将中断处理函数“button_irq()”中的“__udelay(50000)“需要改为使用内核定时器。

按键原理图

Linux按键驱动示例


//------------------------------------------驱动程序-----------------------------------------------------

//#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/time.h>
#include <linux/spinlock.h>
#include <linux/irq.h>

#include <asm/hardware.h>
#include <asm/delay.h>
#include <asm/uaccess.h>
#include <asm-arm/arch-s3c2410/regs-gpio.h>

#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm-arm/arch-s3c2410/irqs.h>
#include <asm-arm/irq.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <asm/uaccess.h>
#include <asm/hardware.h>
#include <asm/arch/regs-gpio.h>
#define DEVICE_NAME "button"
#define    MAX_KEY_COUNT  32                                   
#define EXTINT0 *(volatile unsigned int *)S3C2410_EXTINT0
#define EXTINT1 *(volatile unsigned int *)S3C2410_EXTINT1
#define EXTINT2 *(volatile unsigned int *)S3C2410_EXTINT2


MODULE_LICENSE("GPL");//模块应该指定代码所使用的许可证

typedef struct
{
    unsigned long jiffy[MAX_KEY_COUNT];        //按键时间, 如果读键时, 5秒钟以前的铵键作废
    unsigned char buf[MAX_KEY_COUNT];        //按键缓冲区
    unsigned int head,tail;                    //按键缓冲区头和尾
}KEY_BUFFER;

static KEY_BUFFER g_keyBuffer;        //键盘缓冲区
static spinlock_t buffer_lock;        //缓冲区锁

static int   button_major = 255;                //Define device major add by yoyo

static void *gpecon;
static void *gpedat;
static void *gpfcon;
static void *gpfdat;
static void *gpgcon;
static void *gpgdat;

/*
 *功能: 获取当前的毫秒数(从系统启动开始)
 *入口:   
 */
static unsigned long GetTickCount(void)
{
    struct timeval currTick;
    unsigned long ulRet;

    do_gettimeofday(&currTick);
    ulRet = currTick.tv_sec;
    ulRet *= 1000;
    ulRet += (currTick.tv_usec + 500) / 1000;
    return ulRet;
}

/*
 *功能: 初始化键盘缓冲区
 *入口:   
 */
static void init_keybuffer(void)
{
    int i;
    spin_lock_irq(&buffer_lock); //获得一个自旋锁具有不会受中断的干扰
    g_keyBuffer.head = 0;
    g_keyBuffer.tail = 0;
    for(i = 0; i < MAX_KEY_COUNT; i++)
    {
        g_keyBuffer.buf[i] = 0;
        g_keyBuffer.jiffy[i] = 0;
    }
    spin_unlock_irq(&buffer_lock);//释放自旋锁
}

/*
 *功能: 删除过时(5秒前的按键值)
 *入口:   
 */
static void remove_timeoutkey(void)
{
    unsigned long ulTick;

    spin_lock_irq(&buffer_lock); //获得一个自旋锁具有不会受中断的干扰
    while(g_keyBuffer.head != g_keyBuffer.tail)
    {
        ulTick = GetTickCount() - g_keyBuffer.jiffy[g_keyBuffer.head];
        if (ulTick  < 5000)    //5秒
            break;
        g_keyBuffer.buf[g_keyBuffer.head] = 0;
        g_keyBuffer.jiffy[g_keyBuffer.head] = 0;
        g_keyBuffer.head ++;
        g_keyBuffer.head &= (MAX_KEY_COUNT -1);
    }
    spin_unlock_irq(&buffer_lock);//释放自旋锁
}

/*
 *功能: 初始化GPIO, 设置中断0, 2, 11, 19为下降沿中断
 *入口:   
 */
static void init_gpio(void)
{
    //将GPE13 11 设置低位
    writel((readl(gpecon) | ((3<<26)|(3<<22))) & (~((1<<27)|(1<<23))), gpecon);    //GPE13,11 设置为输出
    writel(readl(gpedat) & 0xffffd7ff, gpedat);                                    //GPE13,11 输出为0

    //将GPG6, 2 设置低位
    writel((readl(gpgcon) | 0x3030) & 0xffffdfdf, gpgcon);    //GPG6,2 设置为输出
    writel(readl(gpgdat) & 0xffffffbb, gpgdat);                //GPG6,2 输出为0

    writel((readl(gpfcon) | 0x33) & 0xffffffee, gpfcon);                        //GPF2, 0 设置为中断
    writel((readl(gpgcon) | (3<<22) | (3<<6)) & (~((1<<22) | (1<<6))), gpgcon);    //GPG11,3 设置为中断

    set_irq_type(IRQ_EINT0, IRQT_FALLING);
      // printk("dddddddddddd=%x\n",EXTINT0);
        EXTINT0=(EXTINT0&(~0x07))+0x02;
    set_irq_type(IRQ_EINT2, IRQT_FALLING);
         EXTINT0=(EXTINT0&(~(0x07<<8)))+(0x02<<8);
 
    set_irq_type(IRQ_EINT11, IRQT_FALLING);
         EXTINT1=(EXTINT1&(~(0x07<<12)))+(0x02<<12);
    set_irq_type(IRQ_EINT19, IRQT_FALLING);
         EXTINT2=(EXTINT2&(~(0x07<<12)))+(0x02<<12);

}

/*
 *功能: 激活中断
 *入口:   
 */
static __inline void enable_irqs(void)
{
    enable_irq(IRQ_EINT0);
    enable_irq(IRQ_EINT2);
    enable_irq(IRQ_EINT11);
    enable_irq(IRQ_EINT19);
}

/*
 *功能: 屏蔽中断
 *入口:   
 */
static __inline void disable_irqs(void)
{
    disable_irq(IRQ_EINT0);
    disable_irq(IRQ_EINT2);
    disable_irq(IRQ_EINT11);
    disable_irq(IRQ_EINT19);
}

/*
 *功能: 进入中断后, 扫描铵键码
 *入口:   
 *返回: 按键码(1-16), 0xff表示错误
 */
static __inline unsigned char button_scan(int irq)
{
    long lGPF, lGPG;        
        writel((readl(gpfcon) | 0x33) & 0xffffffcc, gpfcon);                            //GPF2,0 input
        writel((readl(gpgcon) | (3<<22) | (3<<6)) & (~((3<<22) | (3<<6))), gpgcon);     //GPG11,3 input


        //不利用irq号, 直接扫描键盘
    //设置G2低位, G6, E11, E13高位
    writel((readl(gpgdat) | (1<<6)) & (~(1<<2)), gpgdat);
    writel(readl(gpedat) | (1<<11) | (1<<13), gpedat);
    //取GPF0, GPF2, GPG3, GPG11的值
    lGPF = readl(gpfdat);
    lGPG = readl(gpgdat);
    //判断按键
    if ((lGPF & (1<<0)) == 0)        return 16;
    else if((lGPF & (1<<2)) == 0)    return 15;
    else if((lGPG & (1<<3)) == 0)    return 14;
    else if((lGPG & (1<<11)) == 0)    return 13;

    //设置G6低位, G2, E11, E13高位
    writel((readl(gpgdat) | (1<<2)) & (~(1<<6)), gpgdat);
    lGPF = readl(gpfdat);
    lGPG = readl(gpgdat);
    if ((lGPF & (1<<0)) == 0)        return 11;
    else if((lGPF & (1<<2)) == 0)    return 8;
    else if((lGPG & (1<<3)) == 0)    return 5;
    else if((lGPG & (1<<11)) == 0)    return 2;

    //设置E11低位, G2, G6, E13高位
    writel(readl(gpgdat) | (1<<6) | (1<<2), gpgdat);
    writel((readl(gpedat) | (1<<13)) & (~(1<<11)), gpedat);
    lGPF = readl(gpfdat);
    lGPG = readl(gpgdat);
    if ((lGPF & (1<<0)) == 0)        return 10;
    else if((lGPF & (1<<2)) == 0)    return 7;
    else if((lGPG & (1<<3)) == 0)    return 4;
    else if((lGPG & (1<<11)) == 0)    return 1;

    //设置E13低位, G2, G6, E11高位
    //writel(readl(gpgdat) | (1<<6) | (1<<2), gpgdat);
    writel((readl(gpedat) | (1<<11)) & (~(1<<13)), gpedat);
    lGPF = readl(gpfdat);
    lGPG = readl(gpgdat);
    if ((lGPF & (1<<0)) == 0)        return 12;
    else if((lGPF & (1<<2)) == 0)    return 9;
    else if((lGPG & (1<<3)) == 0)    return 6;
    else if((lGPG & (1<<11)) == 0)    return 3;

   
    return 0xff ;
}

/*
 *功能: 中断函数,
 *入口:    irq 中断号
 *
 */
static irqreturn_t button_irq(int irq, void *dev_id, struct pt_regs *regs)
{
    unsigned char ucKey;
    disable_irqs();
        printk("in irq\n");
    //延迟50毫秒, 屏蔽按键毛刺
    __udelay(50000);
    ucKey = button_scan(irq);
    if ((ucKey >= 1) && (ucKey <= 16))
    {
        //如果缓冲区已满, 则不添加
        if (((g_keyBuffer.head + 1) & (MAX_KEY_COUNT - 1)) != g_keyBuffer.tail)
        {
            spin_lock_irq(&buffer_lock);
            g_keyBuffer.buf[g_keyBuffer.tail] = ucKey;
            g_keyBuffer.jiffy[g_keyBuffer.tail] = GetTickCount();
            g_keyBuffer.tail ++;
            g_keyBuffer.tail &= (MAX_KEY_COUNT -1);
            spin_unlock_irq(&buffer_lock);
        }
    }
    init_gpio();
    enable_irqs();
 //printk("in irq! %x\n",EXTINT0);

    return IRQ_HANDLED;//2.6内核返回值一般是这个宏。
}

/*
 *功能: 申请中断
 *入口:   
 *
 */
static  int request_irqs()
{
    int ret;
    ret = request_irq(IRQ_EINT0, button_irq, SA_INTERRUPT, DEVICE_NAME, NULL);
    if (ret < 0)
        return ret;
    ret = request_irq(IRQ_EINT2, button_irq, SA_INTERRUPT, DEVICE_NAME, NULL);
    if (ret >= 0)
    {
        ret = request_irq(IRQ_EINT11, button_irq, SA_INTERRUPT, DEVICE_NAME, NULL);
        if (ret >= 0)
        {
            ret = request_irq(IRQ_EINT19, button_irq, SA_INTERRUPT, DEVICE_NAME, NULL);
            if (ret >= 0)
                return ret;
            free_irq(IRQ_EINT11, button_irq);
        }
        free_irq(IRQ_EINT2, button_irq);
    }
    free_irq(IRQ_EINT0, button_irq);
    return ret;
}

/*
 *功能: 释放中断
 *入口:   
 *
 */
static __inline void free_irqs()
{
    free_irq(IRQ_EINT0, NULL);//button_irq);
    free_irq(IRQ_EINT2, NULL);//button_irq);
    free_irq(IRQ_EINT11, NULL);//button_irq);
    free_irq(IRQ_EINT19, NULL);//button_irq);
}

/*
 *功能: 打开文件, 开始中断
 *入口:   
 *
 */
static int button_open(struct inode *inode,struct file *filp)
{
    int ret = nonseekable_open(inode, filp);
    if (ret >= 0)
    {
        init_keybuffer();
        enable_irqs();
    }
    return ret;
}

/*
 *功能: 关闭文件, 屏蔽中断
 *入口:   
 *
 */
static int button_release(struct inode *inode,struct file *filp)
{
    disable_irqs();
    return 0;
}

/*
 *功能: 读键盘
 *入口:   
 *
 */
static ssize_t button_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
    ssize_t ret = 0;

    remove_timeoutkey();
    spin_lock_irq(&buffer_lock);
    while((g_keyBuffer.head != g_keyBuffer.tail) && (((size_t)ret) < count) )
    {
        buffer[ret] = (char)(g_keyBuffer.buf[g_keyBuffer.head]);
        g_keyBuffer.buf[g_keyBuffer.head] = 0;
        g_keyBuffer.jiffy[g_keyBuffer.head] = 0;
        g_keyBuffer.head ++;
        g_keyBuffer.head &= (MAX_KEY_COUNT -1);
        ret ++;
    }
    spin_unlock_irq(&buffer_lock);
    return ret;
}

/*
 *功能: 清空键盘缓冲区
 *入口:   
 *
 */
static int button_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
    init_keybuffer();
    return 1;
}

/*
 *初始化并添加结构提struct cdev到系统之中
 */
static void led_setup_cdev(struct cdev *dev,int minor,struct file_operations *fops)
{
    int err;
    int devno=MKDEV(button_major,minor);
    cdev_init(dev,fops);//初始化结构体struct cdev
    dev->owner=THIS_MODULE;
    dev->ops=fops;//给结构体里的ops成员赋初值,这里是对设备操作的具体的实现函数
    err=cdev_add(dev,devno,1);//将结构提struct cdev添加到系统之中
    if(err)
    printk(KERN_INFO"Error %d adding button %d\n",err,minor);
}
/*
 *定义一个file_operations结构体,来实现对设备的具体操作的功能
 */
static struct file_operations button_fops =
{
    .owner = THIS_MODULE,
    .ioctl = button_ioctl,
    .open = button_open,
    .read = button_read,
    .release = button_release,
};

static struct cdev SimpleDevs;  //add by yoyo

/*
 *功能: 驱动初始化
 *入口:   
 *
 */

static int  button_init(void)
{
    int ret;
  int result; //add by yoyo
    gpecon = ioremap(0x56000040, 0x04);//得到相应IO口的虚拟地址,下同
    gpedat = ioremap(0x56000044, 0x04);
    gpfcon = ioremap(0x56000050, 0x04);
    gpfdat = ioremap(0x56000054, 0x04);
    gpgcon = ioremap(0x56000060, 0x04);
    gpgdat = ioremap(0x56000064, 0x04);

    init_gpio();
    ret = request_irqs();
    if (ret < 0) return ret;

 

    disable_irqs();
   
    //add by yoyo
   
    dev_t dev=MKDEV(button_major,0);//将主设备号和次设备号定义到一个dev_t数据类型的结构体之中
    if(button_major)
     result=register_chrdev_region(dev,1,"button");//静态注册一个设备,设备号先前指定好,并得到一个设备名,cat /proc/device来查看信息
    else
     {
        result=alloc_chrdev_region(&dev,0,1,"button");//如果主设备号被占用,则由系统提供一个主设备号给设备驱动程序
        button_major=MAJOR(dev);//得到主设备号
     }
    if(result<0)
     {
      printk(KERN_WARNING"button:unable to get major %d\n",button_major);       
      return result;
     }
    if(button_major==0)
    button_major=result;//如果静态分配失败。把动态非配的设备号给设备驱动程序
    printk(KERN_INFO"button register ok!!!!!!!!!!\n");
   
    led_setup_cdev(&SimpleDevs,0,&button_fops);//初始化和添加结构体struct cdev到系统之中
    //return 0;
    printk("button initialized.\n");
    return 0;
}

/*
 *功能: 驱动释放
 *入口:   
 *
 */
static void __exit button_exit(void)
{
    disable_irqs();
    free_irqs();

    iounmap(gpecon);
    iounmap( gpedat);
    iounmap(gpfcon);
    iounmap(gpfdat);
    iounmap(gpgcon);
        iounmap(gpgdat);

 

    cdev_del(&SimpleDevs);//删除结构体struct cdev
          printk("button_major=%d\n",button_major);
    unregister_chrdev_region(MKDEV(button_major,0),1);//卸载设备驱动所占有的资源
    printk("button device uninstalled\n");

}

module_init(button_init);//初始化设备驱动程序的入口
module_exit(button_exit);//卸载设备驱动程序的入口

相关推荐