驱动做了三个多星期了,从helloworld到LED再到PlatformLED,现在终于到了按键驱动,对于驱动的理解深刻了不少,从完全看不懂到现在能够独立分析,进步很大,今天完成了按键驱动,做一个总结性的分享,给后面的自己看!加油!
按键驱动:
与之前不同,这次将按键驱动按照platform总线的理解,设备链表和驱动链表,做成了两个模块,一个kbd_driver.c 一个kbd_device.c 侧重点在理解按键的消抖和中断
中断:我个人理解为你正在看电影,有人叫你出去,你就得暂停出去处理,然后才能回来继续看电影,这就是一个生活中的中断。在开发板上,你也可以带入的理解。
上代码分析代码:
kbd_driver.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <mach/hardware.h>
#include <asm/gpio.h>
#include <asm/irq.h>
#include <linux/slab.h>
#include <mach/regs-gpio.h>
#include "kbd_driver.h"
/* 1HZ=100*jiffies 1*jiffies=10ms => 1HZ=100*10ms = 1s 这是在设置时钟/
#define CANCEL_DITHERING_DELAY (HZ/50) /* Remove button push down dithering timer delay 20ms 延时 */
typedef struct s3c_kbd_s
{
struct timer_list *timers; /* every key get a cancel dithering timer 消抖时间*/
struct input_dev *input_dev;
s3c_kbd_platform_data_t *pdata;
} s3c_kbd_t; /*--- end of struct s3c_kbd_s ---*/
s3c_kbd_t *s3c_kbd = NULL;
static irqreturn_t s3c_kbd_intterupt(int irq, void *dev_id) //见139行,一旦发生中断后,将中断号传过来
{
int i;
int found = 0;
struct platform_device *pdev = dev_id;
s3c_kbd_t *s3c_kbd = NULL;
s3c_kbd = platform_get_drvdata(pdev);
for(i=0; i<s3c_kbd->pdata->nkeys; i++) //寻找中断号
{
if(irq == s3c_kbd->pdata->keys[i].nIRQ)
{
found = 1;
break;
}
}
if(!found) /* An ERROR interrupt */
return IRQ_NONE;
mod_timer(&s3c_kbd->timers[i], jiffies+CANCEL_DITHERING_DELAY); //消抖定时器,延时 jiffies是当前时间由内核维护
//中断的处理涉及到上半部和下半部,上半部进行响应然后离开,下半部例如定时器会在这里执行,提高效率。
return IRQ_HANDLED;
}
static void cancel_dithering_timer_handler(unsigned long data)//消抖定时器处理方式:当62行延时结束后就来到这里调用这个函数,看看是否按键按下,这样就利用timer(定时器)来消抖
{
int which =(int)data;
unsigned int pinval;
pinval = s3c2410_gpio_getpin(s3c_kbd->pdata->keys[which].gpio); //获取按键引脚电平
if( pinval )
{
//printk("s3c_kbd key[%d] code[%d] released\n", which, s3c_kbd->pdata->keys[which].code);
input_event(s3c_kbd->input_dev, EV_KEY, s3c_kbd->pdata->keys[which].code, 0);
}
else
{
//printk("s3c_kbd key[%d] code[%d] pressed\n", which, s3c_kbd->pdata->keys[which].code);
input_event(s3c_kbd->input_dev, EV_KEY, s3c_kbd->pdata->keys[which].code, 1);
}
input_sync(s3c_kbd->input_dev);
}
static int s3c_kbd_probe(struct platform_device *pdev) //probe()函数传参调用platform_device相应的设备信息,在总线上device和driver name匹配时调用
{
int i = 0;
int rv = -ENOMEM;//还记得后面的if语句吗?这里是负值,如果不能正确执行就返回这个值哦!
struct input_dev *input_dev = NULL;
s3c_kbd_platform_data_t *pdata = pdev->dev.platform_data;//*pdata指向设备信息 从这里看出,驱动和设备的信息被隔离开
/* malloc s3c_kbd struct 给按键分配结构体 */
s3c_kbd = kmalloc(sizeof(s3c_kbd_t), GFP_KERNEL);
if( !s3c_kbd )
{
printk("error: s3c_kbd_probe kmalloc() for s3c_kbd failure\n");
goto fail;
}
memset(s3c_kbd, 0, sizeof(s3c_kbd_t));//void *memset(void *s,int c,size_t n) 总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。内存空间初始化
/* malloc cancel dithering timer for every key */
s3c_kbd->timers = (struct timer_list *) kmalloc(pdata->nkeys*sizeof(struct timer_list), GFP_KERNEL);//在linux/gfp.h中定义的一个宏,是分配内核空间的内存时的一个标志位。
if( !s3c_kbd->timers )
{
printk("error: s3c_kbd_probe kmalloc() for s3c_kbd timers failure\n");
goto fail;
}
memset(s3c_k