Linux Driver : gpio-keys

Linux Driver : gpio-keys的解析

背景

在阅读高通设备树配置一个按键的时候,没有找到按键是在什么时候进行处理的。因此根据仅有的线索gpio-key.c进行分析,发现根据之前的学习积累,很快就看懂了。

介绍

gpio-keys是基于platform来实现实现的一个通用的GPIO按键驱动,对上可以提供input子系统的event。

源码位置:drivers/input/keyboard/gpio_keys.c

这个文件是硬件无关的,而硬件有关的需要我们自己来注册。

整体流程:

0、指定硬件注册

1、初始化、解析硬件属性

2、注册中断、workqueue

3、处理中断、延迟消抖

过程解析

设备树

随便拿一段设备树来作为例子。

gpio_keys { 
    compatible = "gpio-keys";
    label = "gpio-keys";
    pinctrl-names = "default";
    pinctrl-0 = <&key_vol_up_default &key_sidekey1
        		&key_sidekey2 &key_sidekey3 &hall_dev_key>;

    vol_up { 
        label = "volume_up"; 
        gpios = <&pm6125_gpios 5 GPIO_ACTIVE_LOW>; 
        linux,input-type = <1>;
        linux,code = <KEY_VOLUMEUP>;
        linux,can-disable;
        debounce-interval = <15>;
        gpio-key,wakeup;
    };

    userkey1 {
        label = "userkey1";
        gpios = <&tlmm 107 GPIO_ACTIVE_HIGH>;
        linux,input-type = <1>;
        linux,code = <KEY_F22>;
        linux,can-disable;
        debounce-interval = <15>;
        gpio-key,wakeup;
    };
};

注册

第一步就是在初始化时注册platform_driver:

static const struct of_device_id gpio_keys_of_match[] = {
    { .compatible = "gpio-keys", },
    { }, 
};

MODULE_DEVICE_TABLE(of, gpio_keys_of_match);

static struct platform_driver gpio_keys_device_driver = {
    .probe      = gpio_keys_probe,
    .driver     = {
        .name   = "gpio-keys",
        .pm = &gpio_keys_pm_ops,
        .of_match_table = gpio_keys_of_match,
    }
};

static int __init gpio_keys_init(void)
{
	return platform_driver_register(&gpio_keys_device_driver);
}

当发现有设备匹配时(compatible = "gpio-keys"),执行gpio_keys_probe函数。

适配

在适配的时候,就会用到下面的对象。

struct gpio_keys_button_data {
    struct gpio_desc *gpiod;
    int last_state;
    int count;
    int threshold;
};

struct gpio_button_data {
    const struct gpio_keys_button *button;
    struct input_dev *input;
    struct gpio_desc *gpiod;

    unsigned short *code;

    struct timer_list release_timer;
    unsigned int release_delay; /* in msecs, for IRQ-only buttons */

    struct delayed_work work;
    unsigned int software_debounce; /* in msecs, for GPIO-driven buttons */

    unsigned int irq; 
    unsigned int wakeup_trigger_type;
    spinlock_t lock;
    bool disabled;
    bool key_pressed;
    bool suspended;
};

struct gpio_keys_drvdata {
    const struct gpio_keys_platform_data *pdata;
    struct input_dev *input;
    struct mutex disable_lock;
    unsigned short *keymap;
    struct gpio_button_data data[0];
};

在适配的时候,完成了设备数据的处理以及获取、中断的注册

static int gpio_keys_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
    struct fwnode_handle *child = NULL;
    struct gpio_keys_drvdata *ddata;
    struct input_dev *input;
    size_t size;
    int i, error;
    int wakeup = 0; 

    if (!pdata) {
        // 获取设备以及子节点的属性值,并保存下来,用于在后续的功能中使用
        pdata = gpio_keys_get_devtree_pdata(dev);
        if (IS_ERR(pdata))
            return PTR_ERR(pdata);
    }    

    size = sizeof(struct gpio_keys_drvdata) +
            pdata->nbuttons * sizeof(struct gpio_button_data);
    ddata = devm_kzalloc(dev, size, GFP_KERNEL);
    if (!ddata) {
        return -ENOMEM;
    }    

    ddata->keymap = devm_kcalloc(dev,
                     pdata->nbuttons, sizeof(ddata->keymap[0]),
                     GFP_KERNEL);
    if (!ddata->keymap)
        return -ENOMEM;

    input = devm_input_allocate_device(dev);
    if (!input) {
        return -ENOMEM;
    }    

    ddata->pdata = pdata;
    ddata->input = input;
    mutex_init(&ddata->disable_lock);

    platform_set_drvdata(pdev, ddata);
    input_set_drvdata(input, ddata);
    
	// 输入子系统有关
    input->name = pdata->name ? : pdev->name;
    input->phys = "gpio-keys/input0";
    input->dev.parent = dev; 
    input->open = gpio_keys_open;
    input->close = gpio_keys_close;

    input->id.bustype = BUS_HOST;
    input->id.vendor = 0x0001;
    input->id.product = 0x0001;
    input->id.version = 0x0100;

    input->keycode = ddata->keymap;
    input->keycodesize = sizeof(ddata->keymap[0]);
    input->keycodemax = pdata->nbuttons;

    /* Enable auto repeat feature of Linux input subsystem */
    if (pdata->rep)
        __set_bit(EV_REP, input->evbit);

    for (i = 0; i < pdata->nbuttons; i++) {
        const struct gpio_keys_button *button = &pdata->buttons[i];

        if (!dev_get_platdata(dev)) {
            child = device_get_next_child_node(dev, child);
            if (!child) {
                return -EINVAL;
            }
        }
		// 设置按键,注册中断(gpiod_to_irq)、workqueue、
        // 设置input子系统有关参数(input_set_capability),还设置了定时器(timer_setup,用于消抖)。
        error = gpio_keys_setup_key(pdev, input, ddata,
                        button, i, child);
        if (error) {
            fwnode_handle_put(child);
            return error;
        }

        if (button->wakeup)
            wakeup = 1;
    }

    fwnode_handle_put(child);

    error = devm_device_add_group(dev, &gpio_keys_attr_group);
    if (error) {
        return error;
    }
    
	// 设为唤醒属性(用于电源管理)
    device_init_wakeup(dev, wakeup);

    return 0;
}

中断响应

中断上半文:

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

    if (bdata->button->wakeup) {
        const struct gpio_keys_button *button = bdata->button;

        pm_stay_awake(bdata->input->dev.parent);
        if (bdata->suspended  &&
            (button->type == 0 || button->type == EV_KEY)) {
            /*
             * Simulate wakeup key press in case the key has
             * already released by the time we got interrupt
             * handler to run.
             */
            // 报告热键
            input_report_key(bdata->input, button->code, 1);
        }
    }
	// 延迟、消抖
    mod_delayed_work(system_wq,
             &bdata->work,
             msecs_to_jiffies(bdata->software_debounce));

    return IRQ_HANDLED;
}

中断下半文:

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;
    
	// 如果按键在超时时间内已经释放,则返回
    state = gpiod_get_value_cansleep(bdata->gpiod);
    if (state < 0) {
        return;
    }

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

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

    gpio_keys_gpio_report_event(bdata);

    if (bdata->button->wakeup)
        pm_relax(bdata->input->dev.parent);
}
原文地址:https://www.cnblogs.com/schips/p/linux_driver_gpio-key.html