您的位置:首页 > 其它

按键驱动分析

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");
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: