Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3048754
  • 博文数量: 396
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 4209
  • 用 户 组: 普通用户
  • 注册时间: 2016-07-04 13:04
文章分类

全部博文(396)

文章存档

2022年(1)

2021年(2)

2020年(8)

2019年(24)

2018年(135)

2017年(158)

2016年(68)

我的朋友

分类: 嵌入式

2019-04-01 17:44:35

文章目录

    前言
    测试平台
    架构
        DTS配置
        基本数据结构
        设备注册
        设备probe流程
        设备资源解析
        按键注册
        中断处理
            中断处理-top level
            中断处理-bottom level
    应用测试
        设备DTS配置
        gpio-keys驱动使能
        按键事件应用测试
    总结

前言

Linux内核中的gpio-keys.c(driver/input/keyboard/gpio-keys.c)统一了所有关于按键的驱动实现方式。其良好的代码架构可以兼容几乎所有平台的关于按键的处理流程。如果需要在目标平台实现关于按键的驱动程序,完全可以直接使用该驱动,几乎不用自己实现任何代码。
测试平台

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

    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>;
    };   
};    

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

基本数据结构

/* 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];
};

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51

设备注册

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", },
    { },
};

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

设备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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68

    (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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75

    (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",
                button->gpio, error);
            goto fail;
        }
        bdata->irq = irq;

        INIT_WORK(&bdata->work, gpio_keys_gpio_work_func);------------------------------>(4)
        setup_timer(&bdata->timer,
                gpio_keys_gpio_timer, (unsigned long)bdata);

        isr = gpio_keys_gpio_isr;
        irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;

    } else {
        ... ...
    }

    input_set_capability(input, button->type ?: EV_KEY, button->code);

    /*
     * 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_any_context_irq(bdata->irq, isr, irqflags, desc, bdata);------------>(5)
    if (error < 0) {
        dev_err(dev, "Unable to claim irq %d; error %d\n",
            bdata->irq, error);
        goto fail;
    }

    ... ...
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64

    (1)申请gpio资源,注意gpio_request_one的参数,由于key/button都为GPIO属性信号所以其第二个参数为GPIOF_IN。
    (2)初始化key/button去抖所需要的定时器,注意gpio_set_debounce可能会失败,如果失败的话(4)的setup_timer会完成key/button的去抖功能。
    (3)获取gpio所对应的irq,该irq为系统维护该gpio中断相关的所有操作的句柄参数。
    (4)初始化key/button中断处理的bottom level处理workqueue,初始化key/button去抖定时器,gpio_keys_gpio_timer为定时器的超时处理函数,该函数十分的简单的,其调用schedule_work(&bdata->work);来调度中断的workqueue。初始化中断触发方式为:IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,即边沿触发。
    (5)申请中断。request/_any/_context/_irq申请中断处理所需的资源,并激活该interrupt line。注意该函数会根据中断描述的配置选择hartirq或者threaded方式的中断top level处理。

中断处理
中断处理-top level

gpio-keys驱动的上半部处理十分的简单,处理过程如下

static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
    struct gpio_button_data *bdata = dev_id;

    BUG_ON(irq != bdata->irq);

    if (bdata->button->wakeup)------------------------------------------------>(1)
        pm_stay_awake(bdata->input->dev.parent);
    if (bdata->timer_debounce)
        mod_timer(&bdata->timer,
            jiffies + msecs_to_jiffies(bdata->timer_debounce));--------------->(2)
    else
        schedule_work(&bdata->work);

    return IRQ_HANDLED;
}

static void gpio_keys_gpio_timer(unsigned long _data)
{
    struct gpio_button_data *bdata = (struct gpio_button_data *)_data;

    schedule_work(&bdata->work);------------------------------------------->(3)
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    (1)如果key/button具有系统唤醒功能,调用电源相关的处理过程。
    (2)key/button的timer_debounce肯定为大于0,所以,调用mod_timer启动去抖处理定时器。
    (3)去抖定时器超时后会调用gpio_keys_gpio_timer定时器超时处理函数,该函数的实现十分的简单,其就做一件事,即调度key/button的workqueue。

中断处理-bottom level

上文提到过gpio-keys中断下半部的处理方式为workqueue,中断上半部的去抖定时器如果超时的话,会触发workqueue调度,workqueue会在合适的时间点执行。下面为workqueue的处理流程。

static void gpio_keys_gpio_work_func(struct work_struct *work)
{
    struct gpio_button_data *bdata =
        container_of(work, struct gpio_button_data, work);

    gpio_keys_gpio_report_event(bdata);-------------------------------------------------->(1)

    if (bdata->button->wakeup)
        pm_relax(bdata->input->dev.parent);
}

static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata)
{
    const struct gpio_keys_button *button = bdata->button;
    struct input_dev *input = bdata->input;
    unsigned int type = button->type ?: EV_KEY;
    int state = (gpio_get_value_cansleep(button->gpio) ? 1 : 0) ^ button->active_low;--->(2)

    if (type == EV_ABS) {
        if (state)
            input_event(input, type, button->code, button->value);
    } else {
        input_event(input, type, button->code, !!state);--------------------------------->(3)
    }
    input_sync(input);
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    (1)上报key/button的gpio状态。
    (2)读取gpio的I/O状态,并根据key/button的active_low状态将其转换为key/button的state。
    (3)通过input子系统上报key/button的按键事件。

应用测试

下面举一个例子,讲解如何通过DTS配置gpio-keys驱动,以及如何通过应用程序监测key/button的按键事件。
设备DTS配置

` 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>;
    };   
};`

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

上面的DTS配置一个gpio0 GPIO_A5为一个按键,配置按键事件,启用wake-up功能。
gpio-keys驱动使能

使能gpio-keys驱动的驱动配置路径如下:

Device Driver--->
    Input device support--->
        Keyboards------------->
          GPIO buttons

    1
    2
    3
    4

保存内核配置,重新编译内核,将DTB和zImage文件下载到开发板。
按键事件应用测试

经过上述的配置之后,系统启动之后,我们会在/dev/input目录下看到对应于设备的设备文件,本例为event2。通过下面的应用程序就可以读取设备的按键事件了,应用程序如下:

#include                                                                                                                                                                                      
#include
#include
#include
#include
#include
#include

#define INPUT_DEV "/dev/input/event2"

int main(int argc, char * const argv[])
{
    int fd = 0;

    struct input_event event;

    int ret = 0;

    fd = open(INPUT_DEV, O_RDONLY);

    while(1){
        ret = read(fd, &event, sizeof(event));
        if(ret == -1) {
            perror("Failed to read.\n");
            exit(1);
        }

        if(event.type != EV_SYN) {
            printf("type:%d, code:%d, value:%d\n", event.type, event.code, event.value);
        }
    }   

    return 0;
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34

程序的输出结果如下:

type:1, code:116, value:1//type:EV_KEY, code:116:power key, value:1,按键按下
type:1, code:116, value:0
type:1, code:116, value:1
type:1, code:116, value:0
type:1, code:116, value:1
type:1, code:116, value:0

    1
    2
    3
    4
    5
    6

总结

gpio-keys驱动基本统一了Linux系统所有按键相关的驱动模式,我们开发按键驱动时可以直接配置使用该驱动。另外,该驱动借助input子系统与用户空间的应用程序进行交互,省去了编写文件系统相关的接口(省去了file_operations结构的配置,input子系统已经做了这部分工作)的工作。可以使驱动专注于key/button按键事件的处理,简化了驱动的处理流程。
---------------------  
作者:飞翔de刺猬  
来源:CSDN  
原文:https://blog.csdn.net/lhl_blog/article/details/82892809  
版权声明:本文为博主原创文章,转载请附上博文链接!
阅读(4503) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~