Linux按键驱动示例
如果要将该程序应用于具体工程中,建议将中断处理函数“button_irq()”中的“__udelay(50000)“需要改为使用内核定时器。
按键原理图
//------------------------------------------驱动程序-----------------------------------------------------
//#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);//卸载设备驱动程序的入口