📄 44b0-key.c
字号:
/* last modify 2007.5.13, *//* by guanlan */#include<linux/module.h>#include<linux/init.h>#include<linux/sched.h>#include<linux/kernel.h>#include<linux/fs.h>#include<linux/types.h>#include<asm/uaccess.h>#include<asm/irq.h>#define PDATG (*(volatile unsigned int *)0x01D20044) #define PCONG (*(volatile unsigned int *)0x01D20040) /* macro of registers */#define PUPG (*(volatile unsigned int *)0x01D20048)#define EXTINT (*(volatile unsigned int *)0x01D20050)#define EXTINTPND (*(volatile unsigned int *)0x01D20054)#define INTMSK (*(volatile unsigned int *)0x01E0000C)#define I_ISPC (*(volatile unsigned int *)0x01E00024)#define DEVICE_NAME "s3c44b0-keyboard"#define KBD_MAJOR 61 /* the major number of kbd dev */#define IRQ_KBD 21#define KEYSTATUS_UP 0 /* the status of kbd */#define KEYSTATUS_DOWNX 1 /* some key generate interrupt,but not sure whether it is really pushed */#define KEYSTATUS_DOWN 2 /* it is sure that the key is pushed *//* * we make use of low-level trigger interrupt,due to it is more dependable than rising-edge trigger * interrupt.wo use so-called "qv dou" and timer in linux. * 1."qv dou" : if a interrupt generates,we not sure whether some key is really pushed.so we delay * 10ms,and read the register again to sure that key is pushed really! * 2.it is a waste that just delay some time without doing other things.so we use timer in lunux, * and during the time we can do other things. */#define KBD_TIMER_DELAY (HZ/10) #define KBD_TIMER_DELAY1 (HZ/100)/* * if user's process can't deal the interrupt in time,the kbd drive can save the key number * in a cycle buffer,user can get the key number in BUF_TAIL when BUF_HEAD!=BUF_TAIL */#define MAX_KBD_BUF 16#define BUF_HEAD (kbd_dev.buf[kbd_dev.head])#define BUF_TAIL (kbd_dev.buf[kbd_dev.tail])#define INCBUF(x,mod) ((++(x))&((mod)-1)) /* mod = MAX_KBD_BUF *//* * there are something confusing me.At first i want to disable the IRQ_KEY by using register INTMSK, * but it doesn't work.second, i want to change the I/O port G's work mode from INT to INPUT,it is * effect,but i think it's not a good way.At last,i use the system API disable_irq and enable_irq, * it works well.And it cost me one day to finish it. *//*************************************************** #define KBD_OPEN_INT (INTMSK &= 0xffdfffff) #define KBD_CLOSE_INT (INTMSK |= 0x200000) #define KBD_OPEN_INT (PCONG |= 0xff00) #define KBD_CLOSE_INT (PCONG &= 0xffff00ff)***************************************************/typedef unsigned char KBD_RET;typedef struct { unsigned int status; unsigned int head; unsigned int tail; KBD_RET buf[MAX_KBD_BUF]; wait_queue_head_t wq;}KBD_DEV;static KBD_DEV kbd_dev;static void(*kbdEvent)(void);/* a function point,it is used to call kbdEvent_dummy and kbdEvent_raw */static struct timer_list kbd_timer;void kbdEvent_dummy(void){}static int key = 0;int read_ExINT_key(void);int ISKBD_DOWN(void);void KBD_REG_INIT(void);static void kbdEvent_raw(void);static void kbd_int_isr(int irq,void *dev_id,struct pt_regs *reg);static void kbd_timer_handler(unsigned long data); static int kbd_open(struct inode *inode,struct file *filp){ kbd_dev.head = kbd_dev.tail = 0; kbdEvent = kbdEvent_raw; MOD_INC_USE_COUNT; return 0;}static int kbd_release(struct inode *inode,struct file *filp){ kbdEvent = kbdEvent_dummy; MOD_DEC_USE_COUNT; return 0;}static KBD_RET kbdRead(void){ KBD_RET kbd_ret; kbd_ret = BUF_TAIL; kbd_dev.tail = INCBUF(kbd_dev.tail,MAX_KBD_BUF); return kbd_ret;}/* * if kbd_dev.head!=kbd_dev.tail shows that there are keys saved in buffer,so we can read the * key number and do something according the number. * if user read at NOBLOCK mode,return immedicatly,if read at BLOCK mode ,then sleep on current * process,wait the interrupt to wake up it.*/static ssize_t kbd_read(struct file *filp,char *buffer,size_t count,\ loff_t *ppos){ KBD_RET kbd_ret;retry: if(kbd_dev.head != kbd_dev.tail) { kbd_ret = kbdRead(); copy_to_user(buffer,(char *)&kbd_ret,sizeof(KBD_RET)); return sizeof(KBD_RET); } else { if(filp->f_flags&O_NONBLOCK) return -EAGAIN; interruptible_sleep_on(&(kbd_dev.wq)); if(signal_pending(current)) return -ERESTARTSYS; goto retry; } return sizeof(KBD_RET);}static struct file_operations kbd_ops={ owner: THIS_MODULE, open: kbd_open, read: kbd_read, release: kbd_release,};static int __init kbd_init(void){ int ret; //set_external_irq(IRQ(4),EXT_LOWLEVEL,GPIO_PULLUP_EN); //set_external_irq(IRQ(5),EXT_LOWLEVEL,GPIO_PULLUP_EN); //set_external_irq(IRQ(6),EXT_LOWLEVEL,GPIO_PULLUP_EN); KBD_REG_INIT(); kbdEvent = kbdEvent_dummy; ret = register_chrdev(KBD_MAJOR,DEVICE_NAME,&kbd_ops); if(ret<0) { printk(DEVICE_NAME"can't get major number\n"); return ret; } ret = request_irq(IRQ_KBD,kbd_int_isr,SA_INTERRUPT,DEVICE_NAME,kbd_int_isr); if(ret) return ret; kbd_dev.head = kbd_dev.tail = 0; kbd_dev.status = KEYSTATUS_UP; init_waitqueue_head(&(kbd_dev.wq)); init_timer(&kbd_timer); kbd_timer.function = kbd_timer_handler; printk(DEVICE_NAME "initialized\n"); return 0;}void KBD_REG_INIT(void){ PCONG |= 0xff00; PUPG |= 0xf0; EXTINT &= 0x8888ffff; //KBD_OPEN_INT;}static void __exit kbd_exit(void){ int ret; ret = unregister_chrdev(KBD_MAJOR,DEVICE_NAME); if(ret<0) printk("unable to release kbd device!");}/* * the following interrupt service routine is the most important part of this driver. * we must clear the corresponding bits of EXTINTPND (by write 1 to EXTINTPND) and INTPND * (by write 1 to I_ISPC),otherwise it will generate interrupt all the time,never stop,and * generate the error like "The IRQ 21 is locking the system,disabled."*/static void kbd_int_isr(int irq,void *dev_id,struct pt_regs *reg){ if((kbd_dev.status == KEYSTATUS_UP)) { //KBD_CLOSE_INT; disable_irq(IRQ_KBD); kbd_dev.status = KEYSTATUS_DOWNX; EXTINTPND |= 0x0f; I_ISPC |= 0x200000; kbd_timer.expires = jiffies + KBD_TIMER_DELAY1; add_timer(&kbd_timer); printk("Occured key board Interrupt,irq = %d\n",irq); } else { disable_irq(IRQ_KBD); kbd_dev.status == KEYSTATUS_UP; EXTINTPND |= 0x0f; I_ISPC |= 0x200000; printk("Something disturb the key value,but it has been settled\n"); enable_irq(IRQ_KBD); }}static void kbd_timer_handler(unsigned long data){ if(ISKBD_DOWN()) { if(kbd_dev.status == KEYSTATUS_DOWNX) { kbd_dev.status = KEYSTATUS_DOWN; kbd_timer.expires = jiffies + KBD_TIMER_DELAY; kbdEvent(); printk("KEY DOWN,code= %x\n",key); } else { kbd_timer.expires = jiffies + KBD_TIMER_DELAY; } add_timer(&kbd_timer); } else { kbd_dev.status = KEYSTATUS_UP; del_timer(&kbd_timer); printk("KEY UP,code = %x\n",key); //kbdEvent(); //KBD_OPEN_INT; enable_irq(IRQ_KBD); } EXTINTPND |= 0x0f; I_ISPC |= 0x200000;}int ISKBD_DOWN(void){ int temp; temp = EXTINTPND; //temp = PDATG; if((temp&0x0f)==0) //if((temp&0x0f)==0x1111) temp = 0; else temp = 1; return temp;}/* the corresponding bit will always be set 1 if the key was pushed,though the INT was masked. */int read_ExINT_key(void){ int temp; temp = EXTINTPND;// temp = PDATG; if(temp&0x01)// if(~temp&0x01) temp = 1; else if(temp&0x02)// else if(~temp&0x02) temp = 2; else if(temp&0x04)// else if(~temp&0x04) temp = 3; else if(temp&0x08)// else if(~temp&0x08) temp = 4; else temp = 0; return temp;}static void kbdEvent_raw(void){ key = read_ExINT_key(); BUF_HEAD = key; kbd_dev.head = INCBUF(kbd_dev.head,MAX_KBD_BUF); wake_up_interruptible(&(kbd_dev.wq));}module_init(kbd_init);module_exit(kbd_exit);MODULE_LICENSE("LOJO");MODULE_AUTHOR("GUANLAN");MODULE_DESCRIPTION("kbd driver for S3C44B0");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -