#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <mach/regs-gpio.h>
#include <linux/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <../mach-s3c2410/include/mach/irqs.h>
#include <linux/ioctl.h>
#define KEY_MAGIC ('o')
#define KEY_CLEAR_BUF _IO(KEY_MAGIC,0x04)
//*******************************
//some macro define
//*******************************
#define KEY_NUM 5
#define MAX_KEY_BUF 16 //for circle link
#define MINI2440_KEY_MAJOR 0
#define KEY_BUF_CLR 0x01
#define KEY_DELAY_20MS (HZ/50) //delay 20ms
#define KEY_DELAY_100MS (HZ/10) //delay 100ms
/* key define*/
#define KEY_UP S3C2410_GPF(0)
#define KEY_DOWN S3C2410_GPF(1)
#define KEY_LEFT S3C2410_GPF(2)
#define KEY_RIGHT S3C2410_GPG(3)
#define KEY_EXIT S3C2410_GPF(5)
#define KEY_ENTER S3C2410_GPF(7)
//buf循环链表:让x在mod进制下循环
//要求(mod-1)转换为二进制后必须是所有的"1"是连续在低位放置
//比如:mod=8; mod-1=7; 7 = 0x0000 0111(B)
//而不能是比如mod=9; mod-1=8; 8 = 0x0000 1000(B)
#define INC_BUF_POINTOR(x,mod) ((++(x))&((mod)-1))
//判断Key所在端口的状态
#define ISKEY_UP(key) (gpio_get_value(g_tkey_info[key].gpio_port) == 1)
#define ISKEY_HIGH(key) (gpio_get_value(g_tkey_info[key].gpio_port) == 1)
#define ISKEY_LOW(key) (gpio_get_value(g_tkey_info[key].gpio_port) == 0)
//*******************************
//key status define
//*******************************
#define KEYSTATUS_UP 0 //none action
#define KEYSTATUS_DOWN 1 //press key
#define KEYSTATUS_X 2 //unsure state
static int g_key_major = MINI2440_KEY_MAJOR;
struct KEY_DEV
{
unsigned int tkeystatus[KEY_NUM]; //6个按键的状态
unsigned char matrix_row[KEY_NUM]; //每个按键按下时对应的行号
unsigned char tbuf[MAX_KEY_BUF]; //按键缓冲区
unsigned int head,tail; //按键缓冲区头和尾
wait_queue_head_t wq; //等待队列
struct cdev cdev;
};
struct KEY_DEV *g_pkey_dev;
//设备类
static struct class *btn_cls;
static struct timer_list g_tkey_timer[KEY_NUM]; //6个按键去抖计时器
struct KEY_INFO
{
int irq_no; //irq number
unsigned int gpio_port; //gpio port
int key_no; //key number
unsigned char *name; //kye name
};
//定义按键所使用的资源
struct KEY_INFO g_tkey_info[KEY_NUM] =
{
//定义按键所使用的资源
{ IRQ_EINT0, KEY_UP, 1 ,"KEY_UP"}, //NO. 1 2 3 ... 与电路图匹配
{ IRQ_EINT2, KEY_LEFT, 2,"KEY_LEFT"},
{ IRQ_EINT11, KEY_RIGHT, 3,"KEY_RIGHT"},
{ IRQ_EINT5, KEY_EXIT, 4,"KEY_EXIT"},
{ IRQ_EINT7, KEY_ENTER, 5,"KEY_ENTER"},
};
static void keyEvent(int key_index)
{
g_pkey_dev->tbuf[g_pkey_dev->head] = g_tkey_info[key_index].key_no + g_pkey_dev->matrix_row[key_index]*KEY_NUM;
g_pkey_dev->head = INC_BUF_POINTOR(g_pkey_dev->head,MAX_KEY_BUF);
wake_up_interruptible(&g_pkey_dev->wq);
}
static void outputRow(unsigned char row,unsigned value)
{
gpio_set_value(S3C2410_GPE(row),value);
}
static int keyScan(int key_index)
{
unsigned char key_value;
int row = -1;
//行0输出1
outputRow(0,1);
outputRow(1,0);
outputRow(2,0);
outputRow(3,0);
outputRow(4,0);
outputRow(5,0);
if(ISKEY_HIGH(key_index)){
row = 0;
goto scanend;
}
//行1输出1
outputRow(0,0);
outputRow(1,1);
outputRow(2,0);
outputRow(3,0);
outputRow(4,0);
outputRow(5,0);
if(ISKEY_HIGH(key_index)){
row = 1;
goto scanend;
}
//行2输出1
outputRow(0,0);
outputRow(1,0);
outputRow(2,1);
outputRow(3,0);
outputRow(4,0);
outputRow(5,0);
if(ISKEY_HIGH(key_index)){
row = 2;
goto scanend;
}
//行3输出1
outputRow(0,0);
outputRow(1,0);
outputRow(2,0);
outputRow(3,1);
outputRow(4,0);
outputRow(5,0);
if(ISKEY_HIGH(key_index)){
row = 3;
goto scanend;
}
//行4输出1
outputRow(0,0);
outputRow(1,0);
outputRow(2,0);
outputRow(3,0);
outputRow(4,1);
outputRow(5,0);
if(ISKEY_HIGH(key_index)){
row = 4;
goto scanend;
}
//行5输出1
outputRow(0,0);
outputRow(1,0);
outputRow(2,0);
outputRow(3,0);
outputRow(4,0);
outputRow(5,1);
if(ISKEY_HIGH(key_index)){
row = 5;
goto scanend;
}
scanend:
if(row>=0){
g_pkey_dev->matrix_row[key_index] = row;
}
// printk("scan the row:%d \r\n",row);
outputRow(0,1);
outputRow(1,1);
outputRow(2,1);
outputRow(3,1);
outputRow(4,1);
outputRow(5,1);
return row;
}
static irqreturn_t key_eint_handler(int irq, void *dev_id)
{
int cnt,key_index;
key_index = 0;
for(cnt=0; cnt<KEY_NUM; cnt++) {
if(g_tkey_info[cnt].irq_no == irq){
key_index = cnt;
break;
}
}
if(key_index<0||key_index>=KEY_NUM){
printk("invalid key_index:%d \r\n",key_index);
return;
}
printk("Eint %d \r\n",key_index);
disable_irq_nosync(g_tkey_info[key_index].irq_no); //disable irq
g_pkey_dev->tkeystatus[key_index] = KEYSTATUS_X; //set key in unsure state
g_tkey_timer[key_index].expires = jiffies + KEY_DELAY_20MS; //set timer value
add_timer(&g_tkey_timer[key_index]); //start timer
return IRQ_HANDLED;
}
static void key_timer_handler(unsigned long data)
{
int key_index = data;
// printk("key_timer_handler,key_index:%d \r\n",key_index);
if (ISKEY_HIGH(key_index)){
if(g_pkey_dev->tkeystatus[key_index] == KEYSTATUS_X){
// printk("key down \r\n");
if(keyScan(key_index)>=0){
g_pkey_dev->tkeystatus[key_index] = KEYSTATUS_DOWN; //change key state
g_tkey_timer[key_index].expires = jiffies + KEY_DELAY_100MS; //re_initial timer
add_timer(&g_tkey_timer[key_index]); //restart timer
}else{
g_pkey_dev->tkeystatus[key_index] = KEYSTATUS_UP;
del_timer(&g_tkey_timer[key_index]);
enable_irq(g_tkey_info[key_index].irq_no);
}
}else {
//wait for user release the key
// printk("key waitting for release \r\n");
g_tkey_timer[key_index].expires = jiffies + KEY_DELAY_100MS;
add_timer(&g_tkey_timer[key_index]);
}
}else {
printk("key up \r\n");
if(g_pkey_dev->tkeystatus[key_index] == KEYSTATUS_DOWN){
// printk("keyEvent +++++++++++++\r\n");
keyEvent(key_index);
}
//user have released the key
g_pkey_dev->tkeystatus[key_index] = KEYSTATUS_UP;
del_timer(&g_tkey_timer[key_index]);
enable_irq(g_tkey_info[key_index].irq_no);
}
}
static int request_irqs(void)
{
//申请中断
struct KEY_INFO *key_info;
int i;
int err;
for(i=0; i<KEY_NUM; i++)
{
key_info = g_tkey_info + i;
// set_external_irq(key_info->irq_no, EXT_LOWLEVEL, GPIO_PULLUP_DIS); //set INT low voltage level target
// if(request_irq(key_info->irq_no, , SA_INTERRUPT, "Mini2440_Key", i))
// /include/linux/irq.h
// #define RQ_TYPE_NONE 0x00000000 /* Default, unspecified type */
// #define IRQ_TYPE_EDGE_RISING 0x00000001 /* Edge rising type */
// #define IRQ_TYPE_EDGE_FALLING 0x00000002 /* Edge falling type */
// #define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
// #define IRQ_TYPE_LEVEL_HIGH 0x00000004 /* Level high type */
// #define IRQ_TYPE_LEVEL_LOW 0x00000008 /* Level low type */
// #define IRQ_TYPE_SENSE_MASK 0x0000000f /* Mask of the above */
// #define IRQ_TYPE_PROBE 0x00000010 /* Probing in progress */
/* /include/linux/interrupt.h
* flags used only by the kernel as part of the irq handling routines.
* IRQF_DISABLED - keep irqs disabled when calling the action handler
* IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
* IRQF_SHARED - allow sharing the irq among several devices
* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
* IRQF_PERCPU - Interrupt is per cpu
* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
* registered first in an shared interrupt is considered for
* performance reasons)
*
* #define IRQF_DISABLED 0x00000020
* #define IRQF_SAMPLE_RANDOM 0x00000040
* #define IRQF_SHARED 0x00000080
* #define IRQF_PROBE_SHARED 0x00000100
* #define IRQF_TIMER 0x00000200
* #define IRQF_PERCPU 0x00000400
* #define IRQF_NOBALANCING 0x00000800
* #define IRQF_IRQPOLL 0x00001000
*/
printk("request_irq ==+=now,%s\r\n",key_info->name);
err = request_irq(key_info->irq_no, key_eint_handler, IRQ_TYPE_EDGE_RISING,
key_info->name, &key_info->key_no);
if(err){
printk("request_irq %s failed,error code:%d\r\n",key_info->name,err);
//return -1;
continue;
}
disable_irq(key_info->irq_no);
}
return 0;
}
void free_irqs(void)
{
struct KEY_INFO *key_info;
int i;
for(i=0; i<(sizeof(g_tkey_info)/sizeof(g_tkey_info[1])); i++)
{
key_info = g_tkey_info + i;
printk("free irq : %d \r\n",i);
free_irq(key_info->irq_no, &key_info->key_no);
}
}
static int key_open(struct inode *inode, struct file *filp)
{
int i;
printk(KERN_NOTICE "key opened\n");
g_pkey_dev->head = g_pkey_dev->tail = 0;
for(i=0;i<KEY_NUM;i++){
enable_irq(g_tkey_info[i].irq_no);
}
return 0;
}
static int key_release(struct inode *inode, struct file *filp)
{
int i = 0;
for(i=0;i<KEY_NUM;i++){
disable_irq(g_tkey_info[i].irq_no);
}
return 0;
}
static ssize_t key_read(struct file *filp, char *buf, size_t count, loff_t *ppos)
{
unsigned int ret,temp;
unsigned long flag;
retry:
if(g_pkey_dev->head != g_pkey_dev->tail){
local_irq_save(flag); //进入临界区,关闭中断
ret = g_pkey_dev->tbuf[g_pkey_dev->tail]; //读取尾部指针所指内容
\
g_pkey_dev->tail = INC_BUF_POINTOR(g_pkey_dev->tail, MAX_KEY_BUF);
local_irq_restore(flag); //退出临界区
// printk("driver key_read,key no:%d\n",ret);
temp = copy_to_user(buf, &ret, sizeof(unsigned int));
//printk(KERN_NOTICE "copy to user return %d\n", temp);
return (sizeof(unsigned int));
}else{
if(filp->f_flags & O_NONBLOCK){
return -EAGAIN;
}
//printk("E:test %d\n",s3c2410_gpio_getpin(g_tkey_info[0].gpio_port));
interruptible_sleep_on(&(g_pkey_dev->wq));
goto retry;
}
// return 0;
}
static int key_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
unsigned long flag;
if(_IOC_TYPE(cmd) != KEY_MAGIC) {
return - EINVAL;
}
switch(cmd){
case KEY_CLEAR_BUF:
local_irq_save(flag);
g_pkey_dev->head = g_pkey_dev->tail = 0;
local_irq_restore(flag);
printk(KERN_NOTICE "key buf is clear\n");
break;
default:
return - EINVAL;
}
return 0;
}
static struct file_operations g_tkey_fops =
{
.owner = THIS_MODULE,
.open = key_open, //打开设备
.release = key_release, //关闭设备
.read = key_read, //读取按键的键值
.unlocked_ioctl = key_ioctl, //清除缓冲区
};
static void key_setup_cdev(struct KEY_DEV *pdev, int index)
{
int err, devno;
devno = MKDEV(g_key_major, index);
cdev_init(&(g_pkey_dev->cdev), &g_tkey_fops);
pdev->cdev.owner = THIS_MODULE;
pdev->cdev.ops = &g_tkey_fops;
err = cdev_add(&pdev->cdev, devno, 1);
if(err){
printk(KERN_NOTICE "Error %d adding dev %d", err, index);
}
}
static int key_pin_setup(void)
{
int ret;
int reg;
// ret = gpio_request(S3C2410_GPG(3), "KEY_RIGHT");
// if (ret) {
// printk(KERN_ERR "failed to request KEY_RIGHT\n");
// return ret;
// }
s3c_gpio_cfgpin(S3C2410_GPF(0), S3C_GPIO_SFN(2));
s3c_gpio_cfgpin(S3C2410_GPF(1), S3C_GPIO_SFN(2));
s3c_gpio_cfgpin(S3C2410_GPF(2), S3C_GPIO_SFN(2));
s3c_gpio_cfgpin(S3C2410_GPG(3), S3C_GPIO_SFN(2));
s3c_gpio_cfgpin(S3C2410_GPF(5), S3C_GPIO_SFN(2));
s3c_gpio_cfgpin(S3C2410_GPF(7), S3C_GPIO_SFN(2));
// reg=s3c_gpio_getcfg(S3C2410_GPG(3));
// printk("S3C2410_GPG3 cfg:%x \r\n",reg);
// ret = gpio_request(S3C2410_GPE(0), "KEY");
// if (ret) {
// printk(KERN_ERR "failed to request KEY\n");
// return ret;
// }
//row 0
s3c_gpio_cfgpin(S3C2410_GPE(0), S3C_GPIO_SFN(1));
gpio_set_value(S3C2410_GPE(0),1);
// reg=s3c_gpio_getcfg(S3C2410_GPE(0));
// printk("S3C2410_GPE0 cfg:%x \r\n",reg);
// reg=gpio_get_value(S3C2410_GPE(0));
// printk("S3C2410_GPE0 value:%x \r\n",reg);
//row 1
s3c_gpio_cfgpin(S3C2410_GPE(1), S3C_GPIO_SFN(1));
gpio_set_value(S3C2410_GPE(1),1);
//row 2
s3c_gpio_cfgpin(S3C2410_GPE(2), S3C_GPIO_SFN(1));
gpio_set_value(S3C2410_GPE(2),1);
//row 3
s3c_gpio_cfgpin(S3C2410_GPE(3), S3C_GPIO_SFN(1));
gpio_set_value(S3C2410_GPE(3),1);
//row 4
s3c_gpio_cfgpin(S3C2410_GPE(4), S3C_GPIO_SFN(1));
gpio_set_value(S3C2410_GPE(4),1);
//row 5
s3c_gpio_cfgpin(S3C2410_GPE(5), S3C_GPIO_SFN(1));
gpio_set_value(S3C2410_GPE(5),1);
//row 6
s3c_gpio_cfgpin(S3C2410_GPE(6), S3C_GPIO_SFN(1));
gpio_set_value(S3C2410_GPE(6),1);
return 0;
}
static int kb_init(void)
{
//**********************************
//申请设备号,添加设备
//**********************************
int ret,i;
dev_t devno = MKDEV(g_key_major, 0);
if(g_key_major){
ret = register_chrdev_region(devno, 1, "kb");
}else{
ret = alloc_chrdev_region(&devno, 0, 1,"kb");
g_key_major = MAJOR(devno);
}
printk("key_init g_key_major:%d\r\n",g_key_major);
if(ret < 0){
printk("register_chrdev_region failed \r\n");
return ret;
}
g_pkey_dev = kmalloc(sizeof(struct KEY_DEV), GFP_KERNEL);
if(!g_pkey_dev){
printk("kmalloc g_pkey_dev failed\r\n");
ret = -ENOMEM;
goto fail_malloc;
}
memset(g_pkey_dev, 0, sizeof(struct KEY_DEV));
if(0!=key_pin_setup()){
goto fail_malloc;
}
key_setup_cdev(g_pkey_dev, 0);
btn_cls = class_create(THIS_MODULE, "kb");
device_create(btn_cls, NULL, devno, NULL, "kb");
//**********************************
//申请设备号,添加设备 完毕!
//下面初始化其他内容
//**********************************
for(i=0; i<KEY_NUM; i++){
g_tkey_timer[i].function = key_timer_handler;
g_tkey_timer[i].data = i;
init_timer(&g_tkey_timer[i]);
}
if(0<request_irqs()){
printk("request_irqs failed \r\n");
goto fail_malloc;
} //request all the key irq
g_pkey_dev->head = g_pkey_dev->tail = 0; //initial key_dev
for(i=0; i<KEY_NUM; i++){
g_pkey_dev->tkeystatus[i] = KEYSTATUS_UP;
}
init_waitqueue_head(&(g_pkey_dev->wq)); //initial wait queue
return 0;
fail_malloc:
unregister_chrdev_region(devno, 1);
return ret;
}
static void kb_exit(void)
{
dev_t devno = MKDEV(g_key_major, 0);
int i;
printk("kb_exit \r\n");
//free timer
for(i=0;i<KEY_NUM;i++){
del_timer(&g_tkey_timer[i]);
}
//free button irq
free_irqs(); //free irq
//destroy class
device_destroy(btn_cls, devno); //delete device node under /dev
class_destroy(btn_cls); //delete class created by us
//destroy cdev
cdev_del(&g_pkey_dev->cdev); //del cdev
//free memory
kfree(g_pkey_dev); //free memory
g_pkey_dev = NULL;
unregister_chrdev_region(devno, 1);
}
MODULE_AUTHOR("Tim");
MODULE_LICENSE("Dual BSD/GPL");
module_init(kb_init);
module_exit(kb_exit);</span>
应用测试:
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <stdlib.h>
#define KEY_MAGIC ('o')
#define KEY_CLEAR_BUF _IO(KEY_MAGIC,0x04)
int main()
{
int fd,ret,key_num;
fd = open("/dev/kb", 0);
if(fd < 0)
{
printf("open error! %d",errno);
return -1;
}
ret = ioctl(fd, KEY_CLEAR_BUF);
if(ret<0){
perror("ioctl");
close(fd);
exit(0);
}
while(1)
{
ret = read(fd, &key_num, sizeof(int));
printf("you press the key %d\n", key_num);
}
close(fd);
return 0;
}