#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) 2、int 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()读取端口配置值,以查看引脚处于何种工作模式
浙公网安备 33010602011771号