欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

深入解析Linux GPIO-key驱动机制

最编程 2024-02-19 08:57:44
...

测试平台


本文介绍的代码在以下平台进行测试:


  • Host:Ubuntu14.04


  • Target:Firefly-rk3288


  • Compiler:arm-linux-android-gcc


架构


gpio-keys驱动基于Linux内核的input子系统实现,设备驱动以platform_device的方式注册到系统中。驱动对于按键基于中断的处理方式实现,并且通过input子系统将按键事件上报到应用层,供应用程序解析使用。


DTS配置


位于 Documentation/devicetree/bindings/gpio/gpio-keys.txt介绍了对于gpio-keys驱动程序的Device-Tree bingdings。其支持的属性定义如下,关于DTS基本语法的总结可以参见


Required properties


  • - compatible = "gpio-keys";


该属性定义了设备的兼容性。


Optional properties


  • -autorepeat: Boolean,启动input子系统的auto repeat特性。


Subnode properties


每一个button(key)都对应为gpio-keys的一个子节点,子节点的属性包括:


  • - gpios: device-tree gpio规格属性。


  • - label: key的描述性名称。


  • - linux,code: input子系统所定义的按键代码,参见:include/dt-bindings/input/input.h关于keys和buttons的code定义。


Optional subnode-properties


  • -linux,input-type:定义该key/button所依赖的event type(input子系统定义),默认为1 == EV_KEY。


  • -debounce-interval:定义该key/button的去抖间隔,默认为5ms。


  • -gpio-key,wakeup:Boolean,标识该key可以唤醒系统,例如,Android系统的power-key。


Example nodes:


gpio_keys_test { 
    compatible = "gpio-keys";
    #address-cells = <1>; 
    #size-cells = <0>; 
    autorepeat;
    powerkey { 
        label = "power key";
        linux,code = <116>;
        gpios = <&gpio0 GPIO_A5 GPIO_ACTIVE_LOW>;
        gpio-key,wakeup;
        debounce-interval = <5>; 
    };   
};    


基本数据结构


/* key/button的基本配置参数 */
struct gpio_keys_button {
  
  unsigned int code;  /* input event code (KEY_*, SW_*) */
  int gpio;   /* -1 if this key does not support gpio */
  int active_low;
  const char *desc;
  unsigned int type;  /* input event type (EV_KEY, EV_SW, EV_ABS) */
  int wakeup;   /* configure the button as a wake-up source */
  int debounce_interval;  /* debounce ticks interval in msecs */
  bool can_disable;
  int value;    /* axis value for EV_ABS */
  unsigned int irq; /* Irq number in case of interrupt keys */
};
/*key/button控制逻辑配置参数*/
struct gpio_button_data {
    const struct gpio_keys_button *button;
    struct input_dev *input;
    struct timer_list timer;
    struct work_struct work;
    unsigned int timer_debounce;  /* in msecs */
    unsigned int irq;
    spinlock_t lock;
    bool disabled;
    bool key_pressed;
};
/*key/button platform配置参数*/
struct gpio_keys_platform_data {
  struct gpio_keys_button *buttons;
  int nbuttons;
  unsigned int poll_interval; /* polling interval in msecs -
             for polling driver only */
  unsigned int rep:1;   /* enable input subsystem auto repeat */
  int (*enable)(struct device *dev);
  void (*disable)(struct device *dev);
  const char *name;   /* input device name */
};
/*key/button plaform_device data配置参数,该结构作为platform data注册到platform设备总线*/
struct gpio_keys_drvdata {
  const struct gpio_keys_platform_data *pdata;
  struct input_dev *input;
  struct mutex disable_lock;
  struct gpio_button_data data[0];
};


设备注册


gpio-keys驱动是以platform_driver的身份注册到系统中的,所以其需要定义platfrom_driver结构,如下:


static struct platform_driver gpio_keys_device_driver = {
  .probe    = gpio_keys_probe,//gpio-keys驱动初始化函数
  .remove   = gpio_keys_remove,//gpio-keys驱动卸载处理函数
  .driver   = {
    .name = "gpio-keys",
    .owner  = THIS_MODULE,
    .pm = &gpio_keys_pm_ops,
    .of_match_table = of_match_ptr(gpio_keys_of_match),//定义驱动的兼容属性,具体定义如下:
  }
};
static struct of_device_id gpio_keys_of_match[] = {
  { .compatible = "gpio-keys", },
  { },
};


设备probe流程


下面主要分析一下驱动的probe主要流程,较为细节的代码请参照内核代码。


static int gpio_keys_probe(struct platform_device *pdev)
{
  ... ...
  if (!pdata) {
    pdata = gpio_keys_get_devtree_pdata(dev);------------------------------------------->(1)
    if (IS_ERR(pdata))
      return PTR_ERR(pdata);
  }
  ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +
      pdata->nbuttons * sizeof(struct gpio_button_data),
      GFP_KERNEL);
  input = input_allocate_device();--------------------------------------------------------(2)
  if (!ddata || !input) {
    dev_err(dev, "failed to allocate state\n");
    error = -ENOMEM;
    goto fail1;
  }
  platform_set_drvdata(pdev, ddata);
  input_set_drvdata(input, ddata);
  input->name = pdata->name ? : pdev->name;
  input->phys = "gpio-keys/input0";
  input->dev.parent = &pdev->dev;
  input->open = gpio_keys_open;
  input->close = gpio_keys_close;
  ... ...
  /* Enable auto repeat feature of Linux input subsystem */
  if (pdata->rep)
    __set_bit(EV_REP, input->evbit);
  for (i = 0; i < pdata->nbuttons; i++) {--------------------------------------------(3)
    const struct gpio_keys_button *button = &pdata->buttons[i];
    struct gpio_button_data *bdata = &ddata->data[i];
    error = gpio_keys_setup_key(pdev, input, bdata, button);
    if (error)
      goto fail2;
    if (button->wakeup)
      wakeup = 1;
  }
  error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);----------------(4)
  if (error) {
    dev_err(dev, "Unable to export keys/switches, error: %d\n",
      error);
    goto fail2;
  }
  error = input_register_device(input);---------------------------------------------(5)
  if (error) {
    dev_err(dev, "Unable to register input device, error: %d\n",
      error);
    goto fail3;
  }
  device_init_wakeup(&pdev->dev, wakeup);
  return 0;
  ... ... 
}


  • (1)解析DTS关于gpio-keys的属性定义,创建、初始化gpio_keys_platform_data。


  • (2)分配、初始化input设备。


  • (3)遍历所有key/button,注册key/buton所需的资源(gpio、irq等)。


  • (4)注册gpio-keys在sys文件系统下的访问接口属性,gpio-keys设备在sys文件系统路径为:/sys/devices/gpio_keys_test.32,其中gpio_keys_test为DTS中设备设备节点名称。


  • (5)注册input设备。


设备资源解析


gpio_keys_get_devtree_pdata函数完成将DTS节点的设备属性翻译成gpio_keys_platform_data结构,具体执行流程如下。


gpio_keys_get_devtree_pdata(struct device *dev)
{
  ... ...
  nbuttons = of_get_child_count(node);-----------------------------------------------(1)
  if (nbuttons == 0) {
    error = -ENODEV;
    goto err_out;
  }
  pdata = kzalloc(sizeof(*pdata) + nbuttons * (sizeof *button),
      GFP_KERNEL);
  if (!pdata) {
    error = -ENOMEM;
    goto err_out;
  }
  pdata->buttons = (struct gpio_keys_button *)(pdata + 1);
  pdata->nbuttons = nbuttons;
  pdata->rep = !!of_get_property(node, "autorepeat", NULL);
  i = 0;
  for_each_child_of_node(node, pp) {------------------------------------------------(2)
    int gpio;
    enum of_gpio_flags flags;
    if (!of_find_property(pp, "gpios", NULL)) {
      pdata->nbuttons--;
      dev_warn(dev, "Found button without gpios\n");
      continue;
    }
    gpio = of_get_gpio_flags(pp, 0, &flags);
    if (gpio < 0) {
      error = gpio;
      if (error != -EPROBE_DEFER)
        dev_err(dev,
          "Failed to get gpio flags, error: %d\n",
          error);
      goto err_free_pdata;
    }
    button = &pdata->buttons[i++];
    button->gpio = gpio;
    button->active_low = flags & OF_GPIO_ACTIVE_LOW;
    if (of_property_read_u32(pp, "linux,code", &button->code)) {
      dev_err(dev, "Button without keycode: 0x%x\n",
        button->gpio);
      error = -EINVAL;
      goto err_free_pdata;
    }
    button->desc = of_get_property(pp, "label", NULL);
    if (of_property_read_u32(pp, "linux,input-type", &button->type))
      button->type = EV_KEY;
    button->wakeup = !!of_get_property(pp, "gpio-key,wakeup", NULL);
    if (of_property_read_u32(pp, "debounce-interval",
           &button->debounce_interval))
      button->debounce_interval = 5;
  }
  if (pdata->nbuttons == 0) {
    error = -EINVAL;
    goto err_free_pdata;
  }
  return pdata;
}


  • (1)获取keys/button的节点数量,初始化input系统的autorepeat属性。


  • (2)遍历DTS所有子节点,依次读取key/button的gpios、flags、linux,code、linux,input-type、gpio-key,wakeup、debounce-interval属性字段。


按键注册


gpio_keys_setup_key主要完成gpio的申请、配置以及gpio所关联的irq的申请、初始化配置功能,具体执行流程如下。


static int gpio_keys_setup_key(struct platform_device *pdev,
      struct input_dev *input,
      struct gpio_button_data *bdata,
      const struct gpio_keys_button *button)
{
  ......
  if (gpio_is_valid(button->gpio)) {
    error = gpio_request_one(button->gpio, GPIOF_IN, desc);----------------------------->(1)
    if (error < 0) {
      dev_err(dev, "Failed to request GPIO %d, error %d\n",
        button->gpio, error);
      return error;
    }
    if (button->debounce_interval) {---------------------------------------------------->(2)
      error = gpio_set_debounce(button->gpio,
          button->debounce_interval * 1000);
      /* use timer if gpiolib doesn't provide debounce */
      if (error < 0)
        bdata->timer_debounce =
            button->debounce_interval;
    }
    irq = gpio_to_irq(button->gpio);--------------------------------------------------->(3)
    if (irq < 0) {
      error = irq;
      dev_err(dev,
        "Unable to get irq number for GPIO %d, error %d\n",

						

上一篇: SS和Netstat查询得到的结果有差异

下一篇: 深入探讨UVC驱动中的ioctl控制 - __uvc_query_ctrl详解