GPIO按键处理流程

GPIO按键属于是系统的一种设备,挂载到platform总线上,其原理请参考Android学习之platform开发流程,以下直接介绍其在imx53qsb上的应用。

1       Button设备的定义和注册

1.1 Button设备的定义

Kernel-imx\arch\arm\mach-mx5\Mx53_loco.c#L690

#define GPIO_BUTTON(gpio_num, ev_code, act_low, descr, wake)      \

{                                                       \

       .gpio            = gpio_num,                          \

       .type             = EV_KEY,                      \

       .code           = ev_code,                       \

       .active_low = act_low,                        \

       .desc            = "btn " descr,                         \

       .wakeup             = wake,                                   \

}

 

static struct gpio_keys_button loco_buttons[] = {

       GPIO_BUTTON(MX53_nONKEY, KEY_POWER, 1, "power", 0),

       GPIO_BUTTON(USER_UI1, KEY_BACK, 1, "back", 0),

       //GPIO_BUTTON(USER_UI2, KEY_HOME, 1, "home", 0),   //edit by havery for menu key

       GPIO_BUTTON(USER_UI2, KEY_HOME, 1, "menu", 0),   //key 与。code有关,与。desc无关

};

static struct gpio_keys_platform_data loco_button_data = {

       .buttons       = loco_buttons,

       .nbuttons    = ARRAY_SIZE(loco_buttons),

};

 

static struct platform_device loco_button_device = {

       .name          = "gpio-keys",

       .id          = -1,

       .num_resources  = 0,

       .dev              = {

              .platform_data = &loco_button_data,

       }

};

1.2 Button设备的注册

Kernel-imx\arch\arm\mach-mx5\Mx53_loco.c#L713

static void __init loco_add_device_buttons(void)

{

       platform_device_register(&loco_button_device);

}

loco_add_device_buttons()其在mxc_board_init()调用。

2       Button 驱动的定义和注册

2.1 Button驱动的定义

Kernel-imx\drivers\input\keyboard\Gpio_keys.c#L589

static struct platform_driver gpio_keys_device_driver = {

       .probe          = gpio_keys_probe,

       .remove              = __devexit_p(gpio_keys_remove),

       .driver          = {

              .name   = "gpio-keys",

              .owner  = THIS_MODULE,

#ifdef CONFIG_PM

              .pm       = &gpio_keys_pm_ops,

#endif

       }

};

2.2 Button驱动的注册

Kernel-imx\drivers\input\keyboard\Gpio_keys.c#L601

static int __init gpio_keys_init(void)

{

       return platform_driver_register(&gpio_keys_device_driver);

}

module_init(gpio_keys_init);

 

platform_driver_register之后,将调用.probe函数:

Kernel-imx\drivers\input\keyboard\Gpio_keys.c#L417

static int __devinit gpio_keys_probe(struct platform_device *pdev)

{

       struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;

       struct gpio_keys_drvdata *ddata;

       struct device *dev = &pdev->dev;

       struct input_dev *input;

       int i, error;

       int wakeup = 0;

 

       ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +

                     pdata->nbuttons * sizeof(struct gpio_button_data),

                     GFP_KERNEL);

       input = input_allocate_device();

       if (!ddata || !input) {

              dev_err(dev, "failed to allocate state\n");

              error = -ENOMEM;

              goto fail1;

       }

 

       ddata->input = input;

       ddata->n_buttons = pdata->nbuttons;

       mutex_init(&ddata->disable_lock);

 

       platform_set_drvdata(pdev, ddata);

 

       input->name = pdev->name;

       input->phys = "gpio-keys/input0";

       input->dev.parent = &pdev->dev;

 

       input->id.bustype = BUS_HOST;

       input->id.vendor = 0x0001;

       input->id.product = 0x0001;

       input->id.version = 0x0100;

 

       /* Enable auto repeat feature of Linux input subsystem */

       if (pdata->rep)

              __set_bit(EV_REP, input->evbit);

 

       for (i = 0; i < pdata->nbuttons; i++) {

              struct gpio_keys_button *button = &pdata->buttons[i];

              struct gpio_button_data *bdata = &ddata->data[i];

              unsigned int type = button->type ?: EV_KEY;

 

              bdata->input = input;

              bdata->button = button;

 

              error = gpio_keys_setup_key(pdev, bdata, button);

              if (error)

                     goto fail2;

 

              if (button->wakeup)

                     wakeup = 1;

 

              input_set_capability(input, type, button->code);

       }

 

       error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);

       if (error) {

              dev_err(dev, "Unable to export keys/switches, error: %d\n",

                     error);

              goto fail2;

       }

 

       error = input_register_device(input);

       if (error) {

              dev_err(dev, "Unable to register input device, error: %d\n",

                     error);

              goto fail3;

       }

 

       /* get current state of buttons */

       for (i = 0; i < pdata->nbuttons; i++)

              gpio_keys_report_event(&ddata->data[i]);

       input_sync(input);

 

       device_init_wakeup(&pdev->dev, wakeup);

 

       return 0;

 

 fail3:

       sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);

 fail2:

       while (--i >= 0) {

              free_irq(gpio_to_irq(pdata->buttons[i].gpio), &ddata->data[i]);

              if (pdata->buttons[i].debounce_interval)

                     del_timer_sync(&ddata->data[i].timer);

              cancel_work_sync(&ddata->data[i].work);

              gpio_free(pdata->buttons[i].gpio);

       }

 

       platform_set_drvdata(pdev, NULL);

 fail1:

       input_free_device(input);

       kfree(ddata);

 

       return error;

}

 

3       Linux中断与时钟

Kernel-imx\drivers\input\keyboard\Gpio_keys.c#L359

static int __devinit gpio_keys_setup_key(struct platform_device *pdev,

                                    struct gpio_button_data *bdata,

                                    struct gpio_keys_button *button)

{

       char *desc = button->desc ? button->desc : "gpio_keys";

       struct device *dev = &pdev->dev;

       unsigned long irqflags;

       int irq, error;

 

 

setup_timer(&bdata->timer, gpio_keys_timer, (unsigned long)bdata);

       INIT_WORK(&bdata->work, gpio_keys_work_func);

 

       error = gpio_request(button->gpio, desc);

       if (error < 0) {

              dev_err(dev, "failed to request GPIO %d, error %d\n",

                     button->gpio, error);

              goto fail2;

       }

 

       error = gpio_direction_input(button->gpio);

       if (error < 0) {

              dev_err(dev, "failed to configure"

                     " direction for GPIO %d, error %d\n",

                     button->gpio, error);

              goto fail3;

       }

 

       irq = gpio_to_irq(button->gpio);

       if (irq < 0) {

              error = irq;

              dev_err(dev, "Unable to get irq number for GPIO %d, error %d\n",

                     button->gpio, error);

              goto fail3;

       }

 

       irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;

       /*

        * If platform has specified that the button can be disabled,

        * we don't want it to share the interrupt line.

        */

       if (!button->can_disable)

              irqflags |= IRQF_SHARED;

 

       error = request_irq(irq, gpio_keys_isr, irqflags, desc, bdata);

       if (error) {

              dev_err(dev, "Unable to claim irq %d; error %d\n",

                     irq, error);

              goto fail3;

       }

 

       return 0;

 

fail3:

       gpio_free(button->gpio);

fail2:

       return error;

}

gpio_keys_setup_key()gpio_keys_probe()调用,用来设置GPIO中断回调函以及中断号,下面进行简要分析

3.1 Linux时钟计时器

Linux 提供了一个简单的 API 来构造和管理计时器。它包含一些函数(和助手函数),用于创建、取消和管理计时器。

计时器通过 timer_list 结构定义,

Kernel-imx\include\linux\Timer.h #L12

struct timer_list {

       /*

        * All fields that change during normal runtime grouped to the

        * same cacheline

        */

       struct list_head entry;

       unsigned long expires;

       struct tvec_base *base;

 

       void (*function)(unsigned long);

       unsigned long data;

 

       int slack;

 

#ifdef CONFIG_TIMER_STATS

       void *start_site;

       char start_comm[16];

       int start_pid;

#endif

#ifdef CONFIG_LOCKDEP

       struct lockdep_map lockdep_map;

#endif

};

该结构包括实现一个计时器所需的所有数据(其中包括列表指针和在编译时配置的可选计时器统计数据)。从用户角度看,timer_list 包含一个过期时间,一个回调函数(当/如果计时器过期),以及一个用户提供的上下文。用户必须初始化计时器,可以采取几种方法,最简单的方法是调用 setup_timer,该函数初始化计时器并设置用户提供的回调函数和上下文。或者,用户可以设置计时器中的这些值(函数和数据)并简单地调用 init_timer。注意,init_timer setup_timer 内部调用。

Kernel-imx\include\linux\Timer.h #L104

#define init_timer(timer)\

                init_timer_key((timer), NULL, NULL)

#define setup_timer(timer, fn, data)\

setup_timer_key((timer), NULL, NULL, (fn), (data))

   

本章开始高亮显示有对其的引用:

setup_timer(&bdata->timer, gpio_keys_timer, (unsigned long)bdata);

       INIT_WORK(&bdata->work, gpio_keys_work_func);

 

Kernel-imx\drivers\input\keyboard\Gpio_keys.c#L336

static void gpio_keys_timer(unsigned long _data)

{

       struct gpio_button_data *data = (struct gpio_button_data *)_data;

       schedule_work(&data->work);

}

 

Kernel-imx\drivers\input\keyboard\Gpio_keys.c#L328

static void gpio_keys_work_func(struct work_struct *work)

{

       struct gpio_button_data *bdata =

              container_of(work, struct gpio_button_data, work);

 

       gpio_keys_report_event(bdata);//检测按键并上传

}

拥有上面经过初始化的bdata->timer之后,用户现在需要设置过期时间,这通过调用 mod_timer 来完成。由于用户通常提供一个未来的过期时间,他们通常在这里添加 jiffies 来从当前时间偏移。用户也可以通过调用 del_timer 来删除一个计时器(如果它还没有过期):

Kernel-imx\kernel \Timer.c#L797

int mod_timer(struct timer_list *timer, unsigned long expires)

{

       /*

        * This is a common optimization triggered by the

        * networking code - if the timer is re-modified

        * to be the same thing then just return:

        */

       if (timer_pending(timer) && timer->expires == expires)

              return 1;

 

       expires = apply_slack(timer, expires);

 

       return __mod_timer(timer, expires, false, TIMER_NOT_PINNED);

}

EXPORT_SYMBOL(mod_timer);

 

Kernel-imx\kernel \Timer.c#L901

int del_timer(struct timer_list *timer)

{

       struct tvec_base *base;

       unsigned long flags;

       int ret = 0;

 

       timer_stats_timer_clear_start_info(timer);

       if (timer_pending(timer)) {

              base = lock_timer_base(timer, &flags);

              if (timer_pending(timer)) {

                     detach_timer(timer, 1);

                     if (timer->expires == base->next_timer &&

                         !tbase_get_deferrable(timer->base))

                            base->next_timer = base->timer_jiffies;

                     ret = 1;

              }

              spin_unlock_irqrestore(&base->lock, flags);

       }

 

       return ret;

}

EXPORT_SYMBOL(del_timer); 

 

Kernel-imx\drivers\input\keyboard\Gpio_keys.c#L343

static irqreturn_t gpio_keys_isr(int irq, void *dev_id)

{

       struct gpio_button_data *bdata = dev_id;

       struct gpio_keys_button *button = bdata->button;

 

       BUG_ON(irq != gpio_to_irq(button->gpio));

 

       if (button->debounce_interval)

              mod_timer(&bdata->timer,

                     jiffies + msecs_to_jiffies(button->debounce_interval));

       else

              schedule_work(&bdata->work);

 

       return IRQ_HANDLED;

}

此处调用mod_timer来设置过期时间,此时间是为了防止按键抖动的一个时间。

3.2 Linux中断

申请IRQ
int request_irq(unsigned int irq, void (*handler)(int irq, void *dev_id, struct pt_regs *regs), unsigned long irqflags, const char *devname, void *dev_id);

释放IRQ
void free_irq(unsigned int irq, void *dev_id);
request_irq
返回0表示成功,返回-INVAL表示中断号无效或者中断处理函数为NULL。参数irq表示所要申请的中断号;handler为向系统登记的中断处理子程序,中断产生时由系统来调用;devicename为设备名,将会出现在/proc/interrupts文件里;dev_id为申请时告诉系统的设备标识;irq_flags是申请时的选项,它决定中断处理程序的一些特性,其中最重要的是中断处理程序是快速处理程序(flag里设置了SA_INTERRUPT)还是慢速处理程序(没有设置SA_INTERRUPT)。快速处理程序运行时,所有中断都被屏蔽,而慢速处理程序运行时,除了正在处理的中断外,其他中断都没有被屏蔽。在Linux系统中,中断可以被不同的中断处理程序共享,这要求每一个共享此中断的处理程序在申请中断时在flag里设置SA_SHIRQ,这些处理程序之间以dev_id来区分。如果中断由某个处理程序独占,则dev_id可以为NULLrequest_irq返回0时表示成功,返回-INVAL表示irq>15handler==NULL

 

另外,还提供了一组函数帮助我们控制中断。

void disable_irq(unsigned int irq)  

 void enable_irq(unsigned int irq)  

 void synchronize_irq(unsigned int irq)  

 void disable_irq_nosync(unsigned int irq) 

Kernel-imx\include\linux\Interrupt.h

static inline int __must_check

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,

           const char *name, void *dev)

{

       return request_threaded_irq(irq, handler, NULL, flags, name, dev);

}

Kernel-imx\kernel\irq\Manage.c

int request_threaded_irq(unsigned int irq, irq_handler_t handler,

                      irq_handler_t thread_fn, unsigned long irqflags,

                      const char *devname, void *dev_id)

{

       struct irqaction *action;

       struct irq_desc *desc;

       int retval;

 

       /*

        * Sanity-check: shared interrupts must pass in a real dev-ID,

        * otherwise we'll have trouble later trying to figure out

        * which interrupt is which (messes up the interrupt freeing

        * logic etc).

        */

       if ((irqflags & IRQF_SHARED) && !dev_id)

              return -EINVAL;

 

       desc = irq_to_desc(irq);

       if (!desc)

              return -EINVAL;

 

       if (desc->status & IRQ_NOREQUEST)

              return -EINVAL;

 

       if (!handler) {

              if (!thread_fn)

                     return -EINVAL;

              handler = irq_default_primary_handler;

       }

 

       action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);

       if (!action)

              return -ENOMEM;

 

       action->handler = handler;

       action->thread_fn = thread_fn;

       action->flags = irqflags;

       action->name = devname;

       action->dev_id = dev_id;

 

       chip_bus_lock(irq, desc);

       retval = __setup_irq(irq, desc, action);

       chip_bus_sync_unlock(irq, desc);

 

       if (retval)

              kfree(action);

 

#ifdef CONFIG_DEBUG_SHIRQ

       if (!retval && (irqflags & IRQF_SHARED)) {

              /*

               * It's a shared IRQ -- the driver ought to be prepared for it

               * to happen immediately, so let's make sure....

               * We disable the irq to make sure that a 'real' IRQ doesn't

               * run in parallel with our fake.

               */

              unsigned long flags;

 

              disable_irq(irq);

              local_irq_save(flags);

 

              handler(irq, dev_id);

 

              local_irq_restore(flags);

              enable_irq(irq);

       }

#endif

       return retval;

}

EXPORT_SYMBOL(request_threaded_irq);

本章开始高亮显示有对其的引用:

error = request_irq(irq, gpio_keys_isr, irqflags, desc, bdata);

 

Kernel-imx\drivers\input\keyboard\Gpio_keys.c#L343

static irqreturn_t gpio_keys_isr(int irq, void *dev_id)

{

       struct gpio_button_data *bdata = dev_id;

       struct gpio_keys_button *button = bdata->button;

 

       BUG_ON(irq != gpio_to_irq(button->gpio));

 

       if (button->debounce_interval)

              mod_timer(&bdata->timer,

                     jiffies + msecs_to_jiffies(button->debounce_interval));

       else

              schedule_work(&bdata->work);

 

       return IRQ_HANDLED;

}

你可能感兴趣的:(Android)