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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM