按键驱动分析
2010-03-24 09:55
197 查看
注释是自己的理解,可能会有写差错
#include <linux/module.h> #include <linux/init.h> #include <linux/input.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/timer.h> #include <linux/proc_fs.h> #include <asm/uaccess.h> #include <linux/errno.h> #include <linux/ttidef.h> #include <asm/sizes.h> #include <asm/io.h> #include <asm/arch/keyboard.h> #define SCAN_INTERVAL 5 //the interval time to scan the keyboard. 5 jiffer #define KEY_MAX_ROWS 6 #define KEY_MAX_COLS 5 #define FIRST_ROWS_POS 1 //the first row postion at gpio_0 #define FIRST_COLS_POS 11 //the first cloumn postion at gpio_0 #define KEY_NUM (KEY_MAX_ROWS * KEY_MAX_COLS) #if 1 static unsigned char za9l_keycode[30] = { [0] = KK_T, [1] = KK_S, [2] = KK_R, [3] = KK_Q, [4] = KK_8, [5] = KK_B, [6] = KK_A, [7] = KK_0, [8] = KK_K, [9] = KK_2, [10] = KK_H, [11] = KK_G, [12] = KK_F, [13] = KK_E, [14] = KK_9, [15] = KK_N, [16] = KK_M, [17] = KK_L, [18] = KK_3, [19] = KK_J, // [19] = KK_ENTR, [20] = KK_5, [21] = KK_4, [22] = KK_6, [23] = KK_1, [24] = KK_7, [25] = KK_P, [26] = KK_Q, [27] = KK_R, [28] = KK_C, [29] = KK_I, }; #endif #define KB_COL_PHYSICAL_ADDR 0x16000000 #define KB_ROW_PHYSICAL_ADDR 0x17000000 static void __iomem * kb_col_addr; // Input address for keyboard columns static void __iomem * kb_row_addr; // Output address for keyboard rows static unsigned int last_status_map; //the maxium number of key is 32 static unsigned int status_map_mask = 0xffffffff; #define COL_VALUE_MASK_R0 0x1f //the column mask when row is 0 #define COL_VALUE_MASK_R1 0x1f //the column mask when row is 1 #define COL_VALUE_MASK_R2 0x1f //the column mask when row is 2 #define COL_VALUE_MASK_R3 0x0f //the column mask when row is 3 #define COL_VALUE_MASK_R4 0x1f //the column mask when row is 4 #define COL_VALUE_MASK_R5 0x18 //the column mask when row is 5 #define COL_VALUE_MASK_DEFAULT 0x1f //when all the row output low,the valid column value static unsigned int row_value_mask[KEY_MAX_ROWS] = { COL_VALUE_MASK_R0, COL_VALUE_MASK_R1, COL_VALUE_MASK_R2, COL_VALUE_MASK_R3, COL_VALUE_MASK_R4, COL_VALUE_MASK_R5, }; //when no key pressed ,the value of last_status_map #define IDEAL_KEY_STATUS ( row_value_mask[0] | / ( (row_value_mask[1] ) << KEY_MAX_COLS ) | / ( (row_value_mask[2] ) << 2*KEY_MAX_COLS ) | / ( (row_value_mask[3] ) << 3*KEY_MAX_COLS ) | / ( (row_value_mask[4] ) << 4*KEY_MAX_COLS ) | / ( (row_value_mask[5] ) << 5*KEY_MAX_COLS )) static struct input_dev *za9lkb_dev; static struct timer_list my_timer; static enum keyStatus {key_up ,key_down}; static void raise_mytimer(int interval); /*设置某一行的电平状态 根据原理图很容易理解 都是操作IO口的*/ static inline void kb_set_row_val(int row, unsigned char val) { outb(val & 0x01, ((unsigned long)kb_row_addr + row)); } /*设置所有行的电平状态*/ static inline void kb_set_all_row(unsigned char val) { int row; for(row = 0; row < KEY_MAX_ROWS; row++){ kb_set_row_val(row, val); } } /*读取某一列的电平状态,没有按键发生时候,会是搞电平,因为有个电源直接为其供电,按下以后会被分流,变为低电平*/ static inline unsigned char kb_get_col_val(int col) { return inb(((unsigned long)kb_col_addr + col)); } /*读取所有的列的电平状态*/ static inline unsigned char kb_get_all_col(void) { int col; unsigned char val = 0; for(col = 0; col < KEY_MAX_COLS; col++){ val |= ( kb_get_col_val(col) & 0x01 ) << col; } return val; } /*获得所有列值,会自动左移 30--0*/ static inline unsigned int kb_get_all_col_row(int row) { unsigned int status; status = kb_get_all_col(); //获得所有列值 status <<= row * KEY_MAX_COLS; //列值左移row * KEY_MAX_COLS位 return status; } #define KB_TURN_OFF_LIGHT do { kb_set_row_val(7, 0); } while(0) #define KB_TURN_ON_LIGHT do { kb_set_row_val(7, 1); } while(0) static void scan_keyboard(void) { unsigned char val; unsigned int status_map = 0; unsigned int key_index, temp; int row; kb_set_all_row(0x00); //首先设置row全部输出0,来首次判断,是否有按键发生,没有则稍息等待下次扫描,有按键发生的话就进一步的找出具体键 val = kb_get_all_col() & COL_VALUE_MASK_DEFAULT; //col值占5bit,(4--0的顺序)如果有按键操作,(此时可以定位到某一列,但是行无法确定) if((last_status_map == IDEAL_KEY_STATUS) && (COL_VALUE_MASK_DEFAULT == val)) //判断是否依然全高电平空闲状态 goto out; //如果没有按键发生,咱就闪人 ; //如果不是空闲状态,ok,下一步逐行扫描揪出是哪个键 kb_set_all_row(0x01); //row全部置高输出1,使其无效 kb_set_row_val(0, 0x00); //设置0行值为0,逐行扫描开始,单独列出来,是因为它没有前一行 udelay(5); //不要小看此延迟,作用大着呢,就是为了防抖!!! status_map |= kb_get_all_col_row(0); //获得0行的所有列值 for(row = 1; row < KEY_MAX_ROWS; row++){ kb_set_row_val(row - 1, 0x01); //将前一行置高先,因为我们要逐行扫描,地毯式搜索 kb_set_row_val(row, 0x00); //置低扫描 udelay(5); status_map |= kb_get_all_col_row(row); //获得对应行的所有列值 } kb_set_row_val(row - 1, 0x01); //置高最后一行,使其无效 if(status_map == last_status_map) //说明这期间没有发生按键,前面那次的值只是抖动了一下,这点很重要阿~~~~~防抖的核心思想阿 goto out; for(key_index = 0; key_index < KEY_MAX_ROWS * KEY_MAX_COLS; key_index++){ temp = status_map & ( 1 << key_index ); //遍历逐行扫描获得的位图的每一位,揪出具体值 if( temp == ( last_status_map & ( 1 << key_index ))) //没有变化,表示未曾发生按键 continue; //忽略后面的语句继续下次循环 //否则为低的话 input_regs(za9lkb_dev, NULL); //注册发生的按键?za9lkb_dev->regs=null,不晓得有用不,貌似是中断 input_report_key(za9lkb_dev,za9l_keycode[key_index], (temp == 0) ? key_down : key_up);//报告按键事件,本质是填充intput-event结构体 input_sync(za9lkb_dev); //报告同步事件,它告知事件的接收者驱动已经发出了一个完整的报告 } last_status_map = status_map; out: raise_mytimer(SCAN_INTERVAL); //for the next scan of the keyboard } /*定义定时器*/ static void raise_mytimer(int interval) { init_timer(&my_timer); // 初始化定时器 my_timer.expires = jiffies + interval; //到点的 时间 my_timer.function = (void *)scan_keyboard; //关联函数,到点即扫描键盘 add_timer(&my_timer); // 正式添加定时器 } static const char str_on[4] = "on/n"; static const char str_off[5] = "off/n"; static struct proc_dir_entry *kb_proc; static char proc_buf[6]; static int proc_write_kb(struct file *file,const char *buffer, unsigned long count, void *data) { // printk("here0/n"); if(copy_from_user(kb_proc->data,buffer,4)) return -EFAULT; // printk("here1/n"); if (strncmp(str_on,kb_proc->data,3)==0) { KB_TURN_ON_LIGHT; return count; } if (strncmp(str_off,kb_proc->data,4)==0) { KB_TURN_OFF_LIGHT; return count; } return -1; } static int kb_proc_init(void) { kb_proc = create_proc_entry("tti/kb_light",0666,NULL); //用于与用户通讯,一般只读的,用户可以通过读取其文件信息,获知内核信息 if (kb_proc == NULL) return -ENOMEM; kb_proc->data = &proc_buf; kb_proc->write_proc = &proc_write_kb; kb_proc->owner = THIS_MODULE; return 0; } static void kb_proc_cleanup(void) { remove_proc_entry("tti/kb_light",NULL); } static int __init za9lkb_init(void) { int i; int ret; printk("ZA9L keyboard driver. version :1.0/n"); /* 内存重映射,因为我们不能直接操作物理地址,将其转换为虚拟地址,之后kb_row_addr 就是存储key行的虚拟地址, */ kb_row_addr = ioremap(KB_ROW_PHYSICAL_ADDR, SZ_1K); kb_col_addr = ioremap(KB_COL_PHYSICAL_ADDR, SZ_1K); if(!kb_row_addr || !kb_col_addr) { printk("ioremap for keyboard error/n"); goto map_err; } //创建即时系统信息 if ((ret = kb_proc_init()) != 0) { printk("register proc entry of keyboard failed/n"); goto map_err; } za9lkb_dev = input_allocate_device(); //申请input_dev结构,输入设备结构 if (!za9lkb_dev) { printk(KERN_ERR "za9lbd: not enough memory for input device/n"); goto alloc_err; } /*填充申请到的input_dev结构*/ za9lkb_dev->name = "za9l Keyboard"; za9lkb_dev->phys = "za9l/input2"; za9lkb_dev->id.bustype = BUS_AMIGA; za9lkb_dev->id.vendor = 0x0001; za9lkb_dev->id.product = 0x0001; za9lkb_dev->id.version = 0x0100; // za9lkb_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); za9lkb_dev->evbit[0] = BIT(EV_KEY) ; za9lkb_dev->keycode = za9l_keycode; za9lkb_dev->keycodesize = sizeof(unsigned char); za9lkb_dev->keycodemax = ARRAY_SIZE(za9l_keycode); //很经典很有技巧的一个宏 /* 填充按键扫描码*/ for (i = 0; i < KEY_NUM; i++) if (za9l_keycode[i]) set_bit(za9l_keycode[i], za9lkb_dev->keybit); /*对存储在地址(za9lkb_dev->keybit)中的数据变量执行置位(置一)操作指令, 相当与za9lkb_dev->keybit[za9l_keycode[i]] =1 ,其作用就是登记这个按键 告诉系统,这个按键我们用到了,系统可见,(keybit包含一整套的按键) 这其实是输入子系统的一个特性 */ last_status_map = status_map_mask = IDEAL_KEY_STATUS; //初始化按键状态 // printk("last_status_map :0x%x,status_map_mask :0x%x/n",last_status_map,status_map_mask); kb_set_all_row(0x01); //设置row端口全为1,即为端口输出1 KB_TURN_OFF_LIGHT; //奇怪,会有第七行吗? input_register_device(za9lkb_dev); //注册此结构体 raise_mytimer(SCAN_INTERVAL); //定义定时器,参数就是延迟值,就是起到延时一小会 然后调用键盘扫描函数 return 0; alloc_err: kb_proc_cleanup(); map_err: if(kb_col_addr) iounmap(kb_col_addr); if(kb_row_addr) iounmap(kb_row_addr); return -ENOMEM; } static void __exit za9lkb_exit(void) { kb_proc_cleanup(); input_unregister_device(za9lkb_dev); if(kb_col_addr) iounmap(kb_col_addr); if(kb_row_addr) iounmap(kb_row_addr); } module_init(za9lkb_init); module_exit(za9lkb_exit); MODULE_AUTHOR("key"); MODULE_DESCRIPTION("za9l keyboard driver"); MODULE_LICENSE("GPL");
相关文章推荐
- 按键驱动分析
- 交叉编译场景分析(arm-linux)(二)--编译tslib
- C#中使用反射的性能分析
- 需求分析之数据流图
- CoreData实例分析学习(2)
- 高斯背景建模程序分析(OpenCV)--转自 yimi (网易博客) .
- Android 根文件系统启动分析
- Obj文件分析与读取
- [RT-Thread 源码分析] 2. 内存管理2
- Mahout贝叶斯算法源码分析(2-3)
- oracle分析函数之rank学习记录
- (知其所以然 主题2)从底层分析OC中ARC和非ARC下深复制和浅复制
- mips汇编完整程序的分析
- Linux cgroup机制分析之框架分析 【转】
- gh0st源码分析与远控的编写(三)
- Linux Signal实现代码分析http://blog.csdn.net/suqin0802/article/details/8093004
- 基于ar9331 mips架构AP121 uboot分析(3) 启动流程
- Struts2 漏洞分析及如何提前预防
- linphone-KeepAliveReceiver.java文件分析
- Array的push与unshift方法性能比较分析