Chinaunix首页 | 论坛 | 博客
  • 博客访问: 218729
  • 博文数量: 48
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 258
  • 用 户 组: 普通用户
  • 注册时间: 2013-05-22 23:45
文章分类

全部博文(48)

文章存档

2015年(4)

2014年(34)

2013年(10)

分类: LINUX

2014-06-28 15:40:44

原文地址:GPIO按键驱动 作者:haoran9175

内核提供了一个通用的CPIO驱动,定义在drivers/input/keyboard/gpio-keys.c中

GPIO按键驱动是基于平台设备的,这里分析s3c6410的GPIO按键驱动

一、GPIO按键使用的资源定义

点击(此处)折叠或打开

  1. //定义在arch/arm/mach-s3c64xx/mach-smdk6410.c中
  2. //按键定义
  3. static struct gpio_keys_button gpio_buttons[] = {
  4.     {
  5.         .gpio        = S3C64XX_GPN(0),//使用的GPIO口
  6.         .code        = KEY_UP,//按键产生的事件编码
  7.         .desc        = "BUTTON1",//描述
  8.         .active_low    = 1,//低电平触发
  9.         .wakeup        = 0,
  10.     },
  11.     {
  12.         .gpio        = S3C64XX_GPN(1),
  13.         .code        = KEY_DOWN,
  14.         .desc        = "BUTTON2",
  15.         .active_low    = 1,
  16.         .wakeup        = 0,
  17.     },
  18.     {
  19.         .gpio        = S3C64XX_GPN(2),
  20.         .code        = KEY_LEFT,
  21.         .desc        = "BUTTON3",
  22.         .active_low    = 1,
  23.         .wakeup        = 0,
  24.     },
  25.     {
  26.         .gpio        = S3C64XX_GPN(3),
  27.         .code        = KEY_RIGHT,
  28.         .desc        = "BUTTON4",
  29.         .active_low    = 1,
  30.         .wakeup        = 0,
  31.     },
  32.     {
  33.         .gpio        = S3C64XX_GPN(4),
  34.         .code        = KEY_ENTER,
  35.         .desc        = "BUTTON5",
  36.         .active_low    = 1,
  37.         .wakeup        = 0,
  38.     },
  39.     {
  40.         .gpio        = S3C64XX_GPN(5),
  41.         .code        = KEY_ESC,
  42.         .desc        = "BUTTON6",
  43.         .active_low    = 1,
  44.         .wakeup        = 0,
  45.     }
  46. };
  47. //私有数据结构
  48. static struct gpio_keys_platform_data gpio_button_data = {
  49.     .buttons    = gpio_buttons,//按键列表
  50.     .nbuttons    = ARRAY_SIZE(gpio_buttons),//按键数
  51. };
  52. //定义GPIO按键的平台设备结构
  53. static struct platform_device gpio_button_device = {
  54.     .name        = "gpio-keys",//设备名
  55.     .id        = -1,//设备ID
  56.     .num_resources    = 0,//使用的资源大小
  57.     .dev        = {
  58.         .platform_data    = &gpio_button_data,//私有数据
  59.     }
  60. };
二、基于平台设备驱动的结构

点击(此处)折叠或打开

  1. static struct platform_driver gpio_keys_device_driver = {
  2.     .probe        = gpio_keys_probe,    //探测方法
  3.     .remove        = __devexit_p(gpio_keys_remove),//移除方法
  4.     .driver        = {
  5.         .name    = "gpio-keys",//驱动名,这里的name要和platform_device结构中的name相同
  6.         .owner    = THIS_MODULE,//驱动所属的模块
  7. #ifdef CONFIG_PM
  8.         .pm    = &gpio_keys_pm_ops,//电源管理函数集合
  9. #endif
  10.     }
  11. };
三、平台设备驱动的注册和卸载方法

点击(此处)折叠或打开

  1. //驱动加载方法
  2. static int __init gpio_keys_init(void)
  3. {
  4.     //注册平台设备驱动
  5.     return platform_driver_register(&gpio_keys_device_driver);
  6. }
  7. //驱动卸载方法
  8. static void __exit gpio_keys_exit(void)
  9. {
  10.     //注销平台设备驱动
  11.     platform_driver_unregister(&gpio_keys_device_driver);
  12. }
四、探测方法

点击(此处)折叠或打开

  1. static int __devinit gpio_keys_probe(struct platform_device *pdev)
  2. {
  3.     //得到平台设备私有数据
  4.     struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
  5.     struct gpio_keys_drvdata *ddata;//gpio键私有数据
  6.     struct device *dev = &pdev->dev;//得到平台设备
  7.     struct input_dev *input;
  8.     int i, error;
  9.     int wakeup = 0;
  10.     //分配空间,gpio_keys_drvdata结构中有struct gpio_button_data data[0];成员,
  11.     //这里分配pdata->nbuttons个gpio_button_data大小的空间
  12.     ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +
  13.             pdata->nbuttons * sizeof(struct gpio_button_data),
  14.             GFP_KERNEL);
  15.     //分配input_dev输入设备结构
  16.     input = input_allocate_device();
  17.     if (!ddata || !input) {
  18.         dev_err(dev, "failed to allocate state\n");
  19.         error = -ENOMEM;
  20.         goto fail1;
  21.     }
  22.     //为struct gpio_keys_drvdata *ddata;赋值
  23.     ddata->input = input;//input_dev输入设备结构
  24.     ddata->n_buttons = pdata->nbuttons;//按键数
  25.     ddata->enable = pdata->enable;//使用系统默认的方法
  26.     ddata->disable = pdata->disable;
  27.     mutex_init(&ddata->disable_lock);//初始化互斥体
  28.     //将ddata作为平台设备的私有数据
  29.     platform_set_drvdata(pdev, ddata);
  30.     //将ddata作为input设备的私有数据
  31.     input_set_drvdata(input, ddata);
  32.     //为input_dev结构体赋值
  33.     input->name = pdev->name;
  34.     input->phys = "gpio-keys/input0";
  35.     input->dev.parent = &pdev->dev;
  36.     input->open = gpio_keys_open;//打开方法
  37.     input->close = gpio_keys_close;//关闭方法

  38.     input->id.bustype = BUS_HOST;
  39.     input->id.vendor = 0x0001;
  40.     input->id.product = 0x0001;
  41.     input->id.version = 0x0100;

  42.     /* 启用自动重复特性的Linux输入子系统*/
  43.     if (pdata->rep)
  44.         __set_bit(EV_REP, input->evbit);
  45.     //依次设置各gpio_keys_button
  46.     for (i = 0; i < pdata->nbuttons; i++) {
  47.         struct gpio_keys_button *button = &pdata->buttons[i];//得到gpio_keys_button
  48.         struct gpio_button_data *bdata = &ddata->data[i];//得到gpio_button_data
  49.         unsigned int type = button->type ?: EV_KEY;

  50.         bdata->input = input;
  51.         bdata->button = button;
  52.         /*
  53.         该函数完成的功能为
  54.         1、设置定时器处理方法
  55.         2、设置工作队列处理方法
  56.         3、申请并设置GPIO口
  57.         4、申请中断
  58.         */
  59.         //配置GPIO口
  60.         error = gpio_keys_setup_key(pdev, bdata, button);
  61.         if (error)
  62.             goto fail2;

  63.         if (button->wakeup)
  64.             wakeup = 1;
  65.         //标记按键处理某一事件
  66.         input_set_capability(input, type, button->code);
  67.     }
  68.     //在sys下创建组
  69.     error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);
  70.     if (error) {
  71.         dev_err(dev, "Unable to export keys/switches, error: %d\n",
  72.             error);
  73.         goto fail2;
  74.     }
  75.     //注册input设备
  76.     error = input_register_device(input);
  77.     if (error) {
  78.         dev_err(dev, "Unable to register input device, error: %d\n",
  79.             error);
  80.         goto fail3;
  81.     }

  82.     /* 得到当前按钮的状态*/
  83.     for (i = 0; i < pdata->nbuttons; i++)
  84.         gpio_keys_report_event(&ddata->data[i]);
  85.     input_sync(input);//同步input
  86.     //唤醒设备初始化
  87.     device_init_wakeup(&pdev->dev, wakeup);

  88.     return 0;

  89.  fail3:
  90.     sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
  91.  fail2:
  92.     while (--i >= 0) {
  93.         free_irq(gpio_to_irq(pdata->buttons[i].gpio), &ddata->data[i]);
  94.         if (ddata->data[i].timer_debounce)
  95.             del_timer_sync(&ddata->data[i].timer);
  96.         cancel_work_sync(&ddata->data[i].work);
  97.         gpio_free(pdata->buttons[i].gpio);
  98.     }

  99.     platform_set_drvdata(pdev, NULL);
  100.  fail1:
  101.     input_free_device(input);
  102.     kfree(ddata);

  103.     return error;
  104. }
设置GPIO口方法:用于申请GPIO资源,配置GPIO口功能,申请中断

点击(此处)折叠或打开

  1. static int __devinit gpio_keys_setup_key(struct platform_device *pdev,
  2.                      struct gpio_button_data *bdata,
  3.                      struct gpio_keys_button *button)
  4. {
  5.     char *desc = button->desc ? button->desc : "gpio_keys";
  6.     struct device *dev = &pdev->dev;
  7.     unsigned long irqflags;
  8.     int irq, error;
  9.     //设置定时器处理方法
  10.     setup_timer(&bdata->timer, gpio_keys_timer, (unsigned long)bdata);
  11.     //设置工作队列处理方法
  12.     INIT_WORK(&bdata->work, gpio_keys_work_func);
  13.     //申请GPIO口
  14.     error = gpio_request(button->gpio, desc);
  15.     if (error < 0) {
  16.         dev_err(dev, "failed to request GPIO %d, error %d\n",
  17.             button->gpio, error);
  18.         goto fail2;
  19.     }
  20.     //设置GPIO口为输入
  21.     error = gpio_direction_input(button->gpio);
  22.     if (error < 0) {
  23.         dev_err(dev, "failed to configure"
  24.             " direction for GPIO %d, error %d\n",
  25.             button->gpio, error);
  26.         goto fail3;
  27.     }
  28.     //如果gpio_keys_button->debounce_interval设置了放抖动的时间,则设置GPIO口的去抖动时间
  29.     if (button->debounce_interval) {
  30.         error = gpio_set_debounce(button->gpio,
  31.                      button->debounce_interval * 1000);
  32.         /* use timer if gpiolib doesn't provide debounce */
  33.         if (error < 0)
  34.             bdata->timer_debounce = button->debounce_interval;
  35.     }
  36.     //根据GPIO得到中断号
  37.     irq = gpio_to_irq(button->gpio);
  38.     if (irq < 0) {
  39.         error = irq;
  40.         dev_err(dev, "Unable to get irq number for GPIO %d, error %d\n",
  41.             button->gpio, error);
  42.         goto fail3;
  43.     }
  44.     //中断的触发方向设置为上升沿和下降沿触发
  45.     irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
  46.     /*
  47.      * If platform has specified that the button can be disabled,
  48.      * we don't want it to share the interrupt line.
  49.      */
  50.      //若按钮是可用的,设置中断为共享中断
  51.     if (!button->can_disable)
  52.         irqflags |= IRQF_SHARED;
  53.     //申请中断
  54.     error = request_irq(irq, gpio_keys_isr, irqflags, desc, bdata);
  55.     if (error) {
  56.         dev_err(dev, "Unable to claim irq %d; error %d\n",
  57.             irq, error);
  58.         goto fail3;
  59.     }

  60.     return 0;

  61. fail3:
  62.     gpio_free(button->gpio);
  63. fail2:
  64.     return error;
  65. }
定时器处理方法

点击(此处)折叠或打开

  1. static void gpio_keys_timer(unsigned long _data)
  2. {
  3.     struct gpio_button_data *data = (struct gpio_button_data *)_data;
  4.     //调度其它任务
  5.     schedule_work(&data->work);
  6. }
处理任务的方法:用于处理任务队列中的任务

点击(此处)折叠或打开

  1. static void gpio_keys_work_func(struct work_struct *work)
  2. {
  3.     struct gpio_button_data *bdata =
  4.         container_of(work, struct gpio_button_data, work);
  5.     //调用gpio_keys_report_event传递事件
  6.     gpio_keys_report_event(bdata);
  7. }
传递事件方法:获得事件的类型、编码、值,调用input_event函数将事件传递给输入子系统核心

点击(此处)折叠或打开

  1. static void gpio_keys_report_event(struct gpio_button_data *bdata)
  2. {
  3.     struct gpio_keys_button *button = bdata->button;
  4.     struct input_dev *input = bdata->input;
  5.     unsigned int type = button->type ?: EV_KEY;
  6.     int state = (gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low;//得到按键状态
  7.     input_event(input, type, button->code, !!state);//向input核心传递事件
  8.     input_sync(input);//同步input
  9. }
中断处理方法:当按键被按下会产生中断

点击(此处)折叠或打开

  1. static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
  2. {
  3.     struct gpio_button_data *bdata = dev_id;
  4.     struct gpio_keys_button *button = bdata->button;

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

  6.     if (bdata->timer_debounce)
  7.         mod_timer(&bdata->timer,
  8.             jiffies + msecs_to_jiffies(bdata->timer_debounce));
  9.     else
  10.         schedule_work(&bdata->work);

  11.     return IRQ_HANDLED;
  12. }
五、移除方法

点击(此处)折叠或打开

  1. static int __devexit gpio_keys_remove(struct platform_device *pdev)
  2. {
  3.     struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
  4.     struct gpio_keys_drvdata *ddata = platform_get_drvdata(pdev);
  5.     struct input_dev *input = ddata->input;
  6.     int i;
  7.     //删除sys下的组
  8.     sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);

  9.     device_init_wakeup(&pdev->dev, 0);
  10.     //释放中断,释放GPIO资源
  11.     for (i = 0; i < pdata->nbuttons; i++) {
  12.         int irq = gpio_to_irq(pdata->buttons[i].gpio);
  13.         free_irq(irq, &ddata->data[i]);
  14.         if (ddata->data[i].timer_debounce)
  15.             del_timer_sync(&ddata->data[i].timer);
  16.         cancel_work_sync(&ddata->data[i].work);
  17.         gpio_free(pdata->buttons[i].gpio);
  18.     }
  19.     //注销输入设备
  20.     input_unregister_device(input);

  21.     return 0;
  22. }
六、总结
基于GPIO的按键驱动编写过程
1、定义GPIO按键平台设备使用的系统资源
2、注册GPIO按键平台设备
3、在探测方法中分配input_dev结构,读取前面定义GPIO按键平台设备使用的系统资源,初始化input_dev结构,
申请GPIO资源,配置GPIO口功能,通过GPIO口得到中断号,设置中断触发方式,申请中断。
4、向内核注册输入设备驱动;
5、当有中断产生时,获得事件类型、编码、值,调用input_event函数向输入子系统传递事件;
6、移除方法释放GPIO资源,释放中断号,注销输入设备驱动










阅读(3867) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~