#include <linux/config.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/arch/hardware.h>
#include <asm/arch/regs-gpio.h>
//#include </arch/arm/mach-s3c2410/gpio.h>
/* For id.version */
#define S3C2410KBDVERSION   0x0001
#define DRV_NAME            "s3c2410-maxtrixkb"

MODULE_AUTHOR("Arnaud Patard <>");
MODULE_DESCRIPTION("s3c2410 keyboard driver");
MODULE_LICENSE("GPL");

#define KEY_CODE_INVALID -1
//qsmstart2007-06-30
static void *gpgcon;

#define GPGCON 0x56000060

//qsm_end

//按键结构体,定义了每个按键的中断号,对应芯片管脚等属性
struct s3c2410kbd_button {
int irq; //管脚对应的中断号
int pin; //管脚的名称
int pin_setting;   //决定管脚的类型 输入、输出还是外部中断

int index;//行和列管脚的对应的行列信息
int keycode;//按键值
};

//键值表,通过读出的行列值访问该表,读取按键信息
static int s3c2410kbd_buttons[] = {
KEY_B,        KEY_3,          KEY_2,         KEY_1,
KEY_C,        KEY_6,          KEY_5,         KEY_4,
KEY_ENTER,    KEY_9,          KEY_8,         KEY_7,
KEY_P,        KEY_D,          KEY_0,         KEY_U,
};

键盘行扫描属性
static struct s3c2410kbd_button s3c2410kbd_x[] = {
{ IRQ_EINT0,S3C2410_GPF0,   S3C2410_GPF0_EINT0,   3<<2 ,KEY_CODE_INVALID},  

//S3C2410_GPF0_EINT0宏定义表示该管脚是多功能口 //3<<2即3*4为所在行的基址,在加列号即可得按键的序列号

{ IRQ_EINT11, S3C2410_GPG3,   S3C2410_GPG3_EINT11, 1<<2 ,KEY_CODE_INVALID},
{ IRQ_EINT19, S3C2410_GPG11, S3C2410_GPG11_EINT19, 0<<2 ,KEY_CODE_INVALID},
{ IRQ_EINT13, S3C2410_GPG5,   S3C2410_GPG5_EINT13, 2<<2 ,KEY_CODE_INVALID},
};


键盘列扫描属性
static struct s3c2410kbd_button s3c2410kbd_y[] = {
{ 0, S3C2410_GPB6,   S3C2410_GPB6_OUTP,   3 }, OUTP宏定义表示该管脚是输出
{ 0, S3C2410_GPB7,   S3C2410_GPB7_OUTP,   2 },未赋键值默认为零
{ 0, S3C2410_GPB5,   S3C2410_GPB5_OUTP,   1 },
{ 0, S3C2410_GPB10, S3C2410_GPB10_OUTP, 0 },
};

键盘属性结构体
struct s3c2410kbd {
struct input_dev dev;
spinlock_t        lock;
int               count;
int               shift;
char              phys[32];
};
static struct s3c2410kbd kbd;

按键处理函数
static irqreturn_t s3c2410kbd_keyevent(int irq, void *dev_id, struct pt_regs *regs)
{
struct s3c2410kbd_button *button = (struct s3c2410kbd_button *)dev_id;键盘的行地址
   unsigned int down,tmp;
int last_key = button->keycode; 最近一次按键信息

if (!button)当按键的行指针为空时,中断返回
   return IRQ_HANDLED;

mdelay(10);延时10ms后再进行行扫描电平判断
tmp = s3c2410_gpio_getpin(button->pin);读取引脚输入电平状态,当为按键中断时为低电平,当事件为按键释放中断时为高电平
down = !(tmp);             此时读出的是中断行的电平,而不是某个按键的电平

通过down来判断本次中断是按键中断还是释放键中断,从而决定是读取按键值还是对按键值初始化


if(last_key != KEY_CODE_INVALID)该事件为按键释放中断,键值归零,报告按键释放事件,并将所在行的键值进行归零
{
   input_report_key(&kbd.dev, last_key, 0);报告按键事件
   input_sync(&kbd.dev); 报告同步事件
   button->keycode = KEY_CODE_INVALID;

6. input_report_key
这个接口会向INPUT子系统上报按键,原型如下:
view plaincopy to clipboardprint?
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)  
{  
    input_event(dev, EV_KEY, code, !!value);  
}

参数:
dev:输入设备
code:要上报的keycode(从keymap中直接或间接得到的某个值)
value:0或1,0表示按键弹起,1表示按键按下
!!value的写法实际上是将int类型转换为bool类型,即最终传递进input_event中的参数要么为0要么为1.
7. input_sync表明时间已经完成
}
if(down)        //down=1,表示有键按下时,进行按键的相关处理
{
   int i;
  //lqm@ucdragon.net must set to input,or interrpt will trigger again
   for (i = 0; i < ARRAY_SIZE (s3c2410kbd_x); i++) //对中断行用三低一高电平进行列扫描,直到出现高电平
   {
    s3c2410_gpio_cfgpin(s3c2410kbd_x[i].pin,0);设置行引脚为输入,不再接受中断信号即实现关中断功能
   }
   for (i = 0; button->keycode == KEY_CODE_INVALID && i < ARRAY_SIZE (s3c2410kbd_y); i++)
   {
    s3c2410_gpio_setpin(s3c2410kbd_y[i].pin,1);设置列引脚为输出高电平
    mdelay(1);延时1ms
    if(s3c2410_gpio_getpin(button->pin))读取引脚输入值
    {
     button->keycode = s3c2410kbd_buttons[button->index + s3c2410kbd_y[i].index];
    }
    s3c2410_gpio_setpin(s3c2410kbd_y[i].pin,0);三低一高电平进行列扫描,直到出现高电平
   }
   if(button->keycode != KEY_CODE_INVALID && button->keycode != last_key)
   {
    input_report_key(&kbd.dev, button->keycode, down);
    input_sync(&kbd.dev);
    //printk(KERN_DEBUG "%x button %s\n",button->keycode, down ? "pressed" : "released");报告按键中断事件并同步事件 ,没有对button->keycode   进行初始化复位处理,复位在按键释放中断时处理
   }
   for (i = 0; i < ARRAY_SIZE (s3c2410kbd_x); i++)
   {
    s3c2410_gpio_cfgpin(s3c2410kbd_x[i].pin,s3c2410kbd_x[i].pin_setting);
   }
}将行扫描引脚重新切换为中断模式

return IRQ_HANDLED;中断返回
}

int __init s3c2410kbd_init(void)
{
int i;
printk(KERN_INFO "s3c2410kbd_init\n");

/* Initialise input stuff */
memset(&kbd, 0, sizeof(struct s3c2410kbd));
init_input_dev(&kbd.dev);
kbd.dev.evbit[0] = BIT(EV_KEY);
sprintf(kbd.phys, "input/s3c2410_kbd0");

kbd.dev.private = &kbd;
kbd.dev.name = DRV_NAME;
kbd.dev.phys = kbd.phys;
kbd.dev.id.bustype = BUS_HOST;
kbd.dev.id.vendor = 0xDEAD;
kbd.dev.id.product = 0xBEEF;
kbd.dev.id.version = S3C2410KBDVERSION;

gpgcon = ioremap_nocache(GPGCON,0x0000004); //qsm2007-06-30

for (i = 0; i < ARRAY_SIZE (s3c2410kbd_buttons); i++)
{
   set_bit(s3c2410kbd_buttons[i], kbd.dev.keybit);对键盘按键值进行置位
}
s3c2410_gpio_cfgpin(s3c2410kbd_x[0].pin,s3c2410kbd_x[0].pin_setting);
//qsmstat 对行扫描管脚进行类型设置???为何只有一行?此处是想只测试一行按键,如果想测试4*4键盘,可以将此行屏蔽掉,然后将下面粉红色的行打开
writel(readl(gpgcon)&(~(0x00c00cc0)),gpgcon);
writel(readl(gpgcon)|(0x00800880),gpgcon);
for (i = 0; i < ARRAY_SIZE (s3c2410kbd_x); i++)
{
//s3c2410_gpio_cfgpin(s3c2410kbd_x[i].pin,s3c2410kbd_x[i].pin_setting);
   request_irq (s3c2410kbd_x[i].irq, s3c2410kbd_keyevent, SA_INTERRUPT, DRV_NAME, &s3c2410kbd_x[i]);
   set_irq_type(s3c2410kbd_x[i].irq, IRQT_BOTHEDGE);上升下降沿触发,第一次触发键按下,第二次触发键释放
}申请中断并设置中断触发类型
for (i = 0; i < ARRAY_SIZE (s3c2410kbd_y); i++)
{
   s3c2410_gpio_cfgpin(s3c2410kbd_y[i].pin,s3c2410kbd_y[i].pin_setting);
//pin_setting用来决定列扫描管脚输入输出类型 此处为输出类型
   s3c2410_gpio_setpin(s3c2410kbd_y[i].pin,0);
//设置管脚的为低电平
}

printk(KERN_INFO "%s successfully loaded\n", DRV_NAME);

/* All went ok, so register to the input system */
input_register_device(&kbd.dev);键盘设备注册

return 0;
}

void __exit s3c2410kbd_remove(void)
{
int i;
for (i = 0; i < ARRAY_SIZE (s3c2410kbd_x); i++)
{
   disable_irq(s3c2410kbd_x[i].irq);          关中断,释放中断
   free_irq(s3c2410kbd_x[i].irq,&kbd.dev);
}
input_unregister_device(&kbd.dev);

}


module_init(s3c2410kbd_init);
module_exit(s3c2410kbd_remove); //分别对应init和exit函数

补充点:

1、引脚中断的触发方式的配置
set_irq_type(button_irqs[i].irq, IRQT_BOTHEDGE);
上面的函数调用具体中断号(button_irqs[i].irq)的desc->chip->type()
函数来完成对外中断的触发方式的配置。

外中断的触发方式有 高电平 低电平 上升沿 下降沿 上升/下降沿 五种触发方式,
这5种触发方式由 2440的gpio配置为中断的情况下,在由gpio寄存器组中的
EXTINTn (External Interrupt Control Register n)寄存器配置
为相应的5种触发方式之1来使用。

下面是2440中的 EINT7 触发方式的描述,其他的一样。
Setting the signaling method of the EINT7.
000 = Low level      001 = High level    01x = Falling edge triggered
10x = Rising edge triggered                11x = Both edge triggered

但是 IRQT_BOTHEDGE 这个预定义并不与上面的相对应

#define __IRQT_FALEDGE   (1 << 0)
#define __IRQT_RISEDGE   (1 << 1)
#define __IRQT_LOWLVL   (1 << 2)
#define __IRQT_HIGHLVL   (1 << 3)

#define IRQT_NOEDGE   (0)
#define IRQT_RISING   (__IRQT_RISEDGE)
#define IRQT_FALLING   (__IRQT_FALEDGE)
#define IRQT_BOTHEDGE   (__IRQT_RISEDGE|__IRQT_FALEDGE)
#define IRQT_LOW   (__IRQT_LOWLVL)
#define IRQT_HIGH   (__IRQT_HIGHLVL)
#define IRQT_PROBE   (1 << 4)

2int set_bit(int nr,long * addr)
{
int mask, retval;
addr += nr >> 5; //32位寻址的数组偏移量,也就是要搜索到的数组元素
mask = 1 << (nr & 0x1f); //取nr的低5位,并形成位掩码,nr的低5位的值代表需要置位的位置
retval = (mask & *addr) != 0; //如果该数组元素的nr的低5位该位为0,返回0,否则返回1
*addr |= mask; //对索引到的数组元素的第“nr的低5位”进行置位操作
return retval; //返回置位以前的该数组元素的位的值(0或者1)
}
总之函数的实现功能是:搜索addr中偏移量为nr的高27位的数组元素,并返回该元素的nr的低5
位的该位的值,如果该位是0,返回0,如果是1,返回1,最后再对该位进行置1操作。
addr 是一个长整型数组.nr分成两部分,低5位对应一个长整形数的第n位;nr的高位(5位以上)指示addr数组中的第m个数, set_bit完成的功能是,判断addr中第m个数的第n位是否为0,如果是0,返回0,否则返回一个非0的数。同时set_bit还将第m个数的第n位值1。

//s3c2410_gpio_cfgpin()的作用是规定io口的用途:如作为外部中断功能模块(10),还是普通输入口(00),还是普通输出口01

//s3c2410_gpio_pullup()该函数设置是否要内部电阻上拉:0不上拉,1上拉

//s3c2410_gpio_setpin()该函数用于设置端口输出电平0或1

//s3c2410_gpio_getcfg()读取端口配置值,以查看引脚处于何种工作模式

 

posted on 2013-02-17 18:35  爱哎唉  阅读(259)  评论(0)    收藏  举报