Re: [PATCH] input: add support for generic GPIO-based matrix keypad

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Dmitry,

>
> Sounds good, patch updated again, with attachment.
>
> >From 76776dfc468dc35f0d8394a2331e1a91f390e642 Mon Sep 17 00:00:00 2001
> From: Marek Vasut <marek.vasut@xxxxxxxxx>
> Date: Thu, 7 May 2009 15:49:32 +0800
> Subject: [PATCH] input: add support for generic GPIO-based matrix keypad
>

Could you please review this patch and see if it can be acked as merge
window will open in couple of days? Thanks.


> Original patch by Marek Vasut, modified by Eric in:
>
> 1. use delayed work to simplify the debouncing process
>
> 2. build keycode array for fast lookup
>
> 3. combine col_polarity/row_polarity into a single active_low
>   field (are there some cases where the GPIOs are externally
>   connected with an inverter and thus causing two different
>   polarity requirement??)
>
> 4. use a generic bit array based XOR algorithm to detect key
>   press/release, which should make the column assertion time
>   shorter and code a bit cleaner
>
> 5. remove the ALT_FN handling, which is no way generic, ALT_FN
>   key should be treated as no different from other keys, and
>   translation can be done by commands like 'loadkeys'.
>
> 6. explicitly disable row IRQs and flush potential pending work,
>   and schedule an immediate scan after resuming as suggested
>   by Uli Luckas
>
> 7. incorporate review comments from many others
>
> Patch tested on Littleton/PXA310 (though PXA310 has a dedicate keypad
> controller, I have to configure those pins as generic GPIO to use this
> driver, works quite well, though ;-), and Sharp Zaurus model SL-C7x0
> and SL-C1000.
>
> Signed-off-by: Marek Vasut <marek.vasut@xxxxxxxxx>
> Reviewed-by: Trilok Soni <soni.trilok@xxxxxxxxx>
> Reviewed-by: Uli Luckas <u.luckas@xxxxxxx>
> Reviewed-by: Russell King <linux@xxxxxxxxxxxxxxxx>
> Reviewed-by: Robert Jarzmik <robert.jarzmik@xxxxxxx>
> Signed-off-by: Eric Miao <eric.miao@xxxxxxxxxxx>
> ---
>  drivers/input/keyboard/Kconfig         |   10 +
>  drivers/input/keyboard/Makefile        |    1 +
>  drivers/input/keyboard/matrix_keypad.c |  379 ++++++++++++++++++++++++++++++++
>  include/linux/input/matrix_keypad.h    |   34 +++
>  4 files changed, 424 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/input/keyboard/matrix_keypad.c
>  create mode 100644 include/linux/input/matrix_keypad.h
>
> diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
> index ea2638b..6b9f89c 100644
> --- a/drivers/input/keyboard/Kconfig
> +++ b/drivers/input/keyboard/Kconfig
> @@ -332,4 +332,14 @@ config KEYBOARD_SH_KEYSC
>
>          To compile this driver as a module, choose M here: the
>          module will be called sh_keysc.
> +
> +config KEYBOARD_MATRIX
> +       tristate "GPIO driven matrix keypad support"
> +       depends on GENERIC_GPIO
> +       help
> +         Enable support for GPIO driven matrix keypad
> +
> +         To compile this driver as a module, choose M here: the
> +         module will be called matrix_keypad.
> +
>  endif
> diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
> index 36351e1..1349408 100644
> --- a/drivers/input/keyboard/Makefile
> +++ b/drivers/input/keyboard/Makefile
> @@ -28,3 +28,4 @@ obj-$(CONFIG_KEYBOARD_HP7XX)          += jornada720_kbd.o
>  obj-$(CONFIG_KEYBOARD_MAPLE)           += maple_keyb.o
>  obj-$(CONFIG_KEYBOARD_BFIN)            += bf54x-keys.o
>  obj-$(CONFIG_KEYBOARD_SH_KEYSC)                += sh_keysc.o
> +obj-$(CONFIG_KEYBOARD_MATRIX)          += matrix_keypad.o
> diff --git a/drivers/input/keyboard/matrix_keypad.c
> b/drivers/input/keyboard/matrix_keypad.c
> new file mode 100644
> index 0000000..2078d64
> --- /dev/null
> +++ b/drivers/input/keyboard/matrix_keypad.c
> @@ -0,0 +1,379 @@
> +/*
> + * drivers/input/keyboard/matrix_keypad.c
> + *
> + *  GPIO driven matrix keyboard driver
> + *
> + *  Copyright (c) 2008 Marek Vasut <marek.vasut@xxxxxxxxx>
> + *
> + *  Based on corgikbd.c
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License version 2 as
> + *  published by the Free Software Foundation.
> + *
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/platform_device.h>
> +#include <linux/init.h>
> +#include <linux/input.h>
> +#include <linux/irq.h>
> +#include <linux/interrupt.h>
> +#include <linux/jiffies.h>
> +#include <linux/module.h>
> +#include <linux/gpio.h>
> +#include <linux/input/matrix_keypad.h>
> +
> +struct matrix_keypad {
> +       struct matrix_keypad_platform_data *pdata;
> +       struct input_dev *input_dev;
> +
> +       uint32_t last_key_state[MATRIX_MAX_COLS];
> +       uint32_t *keycodes;
> +       struct delayed_work work;
> +};
> +
> +static int __devinit build_keycodes(struct matrix_keypad *keypad)
> +{
> +       struct matrix_keypad_platform_data *pdata = keypad->pdata;
> +       struct input_dev *input_dev = keypad->input_dev;
> +       uint32_t *key;
> +       int i;
> +
> +       keypad->keycodes = kzalloc(MATRIX_MAX_KEYS * sizeof(int), GFP_KERNEL);
> +       if (keypad->keycodes == NULL)
> +               return -ENOMEM;
> +
> +       key = &pdata->key_map[0];
> +       for (i = 0; i < pdata->key_map_size; i++, key++) {
> +               keypad->keycodes[KEY_ROWCOL(*key)] = KEY_VAL(*key);
> +               set_bit(KEY_VAL(*key), input_dev->keybit);
> +       }
> +       return 0;
> +}
> +
> +static unsigned int lookup_keycode(struct matrix_keypad *keypad,
> +                                  int row, int col)
> +{
> +       return keypad->keycodes[(row << 4) + col];
> +}
> +
> +/* NOTE: normally the GPIO has to be put into HiZ when de-activated to cause
> + * minimum side effect when scanning other columns, here it is configured to
> + * be input, and it should work on most platforms.
> + */
> +static void __activate_col(struct matrix_keypad_platform_data *pdata,
> +                        int col, int on)
> +{
> +       int level_on = !pdata->active_low;
> +
> +       if (on)
> +               gpio_direction_output(pdata->col_gpios[col], level_on);
> +       else {
> +               gpio_set_value_cansleep(pdata->col_gpios[col], !level_on);
> +               gpio_direction_input(pdata->col_gpios[col]);
> +       }
> +}
> +
> +static void activate_all_cols(struct matrix_keypad_platform_data
> *pdata, int on)
> +{
> +       int col;
> +
> +       for (col = 0; col < pdata->num_col_gpios; col++)
> +               __activate_col(pdata, col, on);
> +}
> +
> +static void activate_col(struct matrix_keypad_platform_data *pdata,
> +                        int col, int on)
> +{
> +       __activate_col(pdata, col, on);
> +
> +       if (on && pdata->col_scan_delay_us)
> +               udelay(pdata->col_scan_delay_us);
> +}
> +
> +static int row_asserted(struct matrix_keypad_platform_data *pdata, int row)
> +{
> +       return gpio_get_value_cansleep(pdata->row_gpios[row]) ?
> +                       !pdata->active_low : pdata->active_low;
> +}
> +
> +static void enable_row_irqs(struct matrix_keypad *keypad)
> +{
> +       struct matrix_keypad_platform_data *pdata = keypad->pdata;
> +       int i;
> +
> +       for (i = 0; i < pdata->num_row_gpios; i++)
> +               enable_irq(gpio_to_irq(pdata->row_gpios[i]));
> +}
> +
> +static void disable_row_irqs(struct matrix_keypad *keypad)
> +{
> +       struct matrix_keypad_platform_data *pdata = keypad->pdata;
> +       int i;
> +
> +       for (i = 0; i < pdata->num_row_gpios; i++)
> +               disable_irq_nosync(gpio_to_irq(pdata->row_gpios[i]));
> +}
> +
> +/*
> + * This gets the keys from keyboard and reports it to input subsystem
> + */
> +static void matrix_keypad_scan(struct work_struct *work)
> +{
> +       struct matrix_keypad *keypad =
> +                       container_of(work, struct matrix_keypad, work.work);
> +       struct matrix_keypad_platform_data *pdata = keypad->pdata;
> +       uint32_t new_state[MATRIX_MAX_COLS];
> +       int row, col;
> +
> +       /* de-activate all columns for scanning */
> +       activate_all_cols(pdata, 0);
> +
> +       memset(new_state, 0, sizeof(new_state));
> +
> +       /* assert each column and read the row status out */
> +       for (col = 0; col < pdata->num_col_gpios; col++) {
> +
> +               activate_col(pdata, col, 1);
> +
> +               for (row = 0; row < pdata->num_row_gpios; row++)
> +                       new_state[col] |= row_asserted(pdata, row) ?
> +                                               (1 << row) : 0;
> +               activate_col(pdata, col, 0);
> +       }
> +
> +       for (col = 0; col < pdata->num_col_gpios; col++) {
> +               uint32_t bits_changed;
> +
> +               bits_changed = keypad->last_key_state[col] ^ new_state[col];
> +               if (bits_changed == 0)
> +                       continue;
> +
> +               for (row = 0; row < pdata->num_row_gpios; row++) {
> +                       if ((bits_changed & (1 << row)) == 0)
> +                               continue;
> +
> +                       input_report_key(keypad->input_dev,
> +                                       lookup_keycode(keypad, row, col),
> +                                       new_state[col] & (1 << row));
> +               }
> +       }
> +       input_sync(keypad->input_dev);
> +       memcpy(keypad->last_key_state, new_state, sizeof(new_state));
> +
> +       activate_all_cols(pdata, 1);
> +       enable_row_irqs(keypad);
> +}
> +
> +static irqreturn_t matrix_keypad_interrupt(int irq, void *id)
> +{
> +       struct matrix_keypad *keypad = id;
> +
> +       disable_row_irqs(keypad);
> +       schedule_delayed_work(&keypad->work,
> +                       msecs_to_jiffies(keypad->pdata->debounce_ms));
> +       return IRQ_HANDLED;
> +}
> +
> +#ifdef CONFIG_PM
> +static int matrix_keypad_suspend(struct platform_device *pdev,
> +                                pm_message_t state)
> +{
> +       struct matrix_keypad *keypad = platform_get_drvdata(pdev);
> +       struct matrix_keypad_platform_data *pdata = keypad->pdata;
> +       int i;
> +
> +       disable_row_irqs(keypad);
> +       flush_work(&keypad->work.work);
> +
> +       if (device_may_wakeup(&pdev->dev))
> +               for (i = 0; i < pdata->num_row_gpios; i++)
> +                       enable_irq_wake(gpio_to_irq(pdata->row_gpios[i]));
> +
> +       return 0;
> +}
> +
> +static int matrix_keypad_resume(struct platform_device *pdev)
> +{
> +       struct matrix_keypad *keypad = platform_get_drvdata(pdev);
> +       struct matrix_keypad_platform_data *pdata = keypad->pdata;
> +       int i;
> +
> +       if (device_may_wakeup(&pdev->dev))
> +               for (i = 0; i < pdata->num_row_gpios; i++)
> +                       disable_irq_wake(gpio_to_irq(pdata->row_gpios[i]));
> +
> +       /* Uli Luckas: schedule an immediate key scan as all key state changes
> +        * were lost while the device was suspended, columns will be activated
> +        * and IRQs be enabled after the scan.
> +        */
> +       schedule_delayed_work(&keypad->work, 0);
> +       return 0;
> +}
> +#else
> +#define matrix_keypad_suspend  NULL
> +#define matrix_keypad_resume   NULL
> +#endif
> +
> +static int __devinit init_matrix_gpio(struct matrix_keypad *keypad)
> +{
> +       struct matrix_keypad_platform_data *pdata = keypad->pdata;
> +       int i, err = -EINVAL;
> +
> +       /* initialized strobe lines as outputs, activated */
> +       for (i = 0; i < pdata->num_col_gpios; i++) {
> +               err = gpio_request(pdata->col_gpios[i], "matrix_kbd_col");
> +               if (err) {
> +                       pr_err("failed to request GPIO%d for COL%d\n",
> +                                       pdata->col_gpios[i], i);
> +                       goto err_free_cols;
> +               }
> +
> +               gpio_direction_output(pdata->col_gpios[i], !pdata->active_low);
> +       }
> +
> +       for (i = 0; i < pdata->num_row_gpios; i++) {
> +               err = gpio_request(pdata->row_gpios[i], "matrix_kbd_row");
> +               if (err) {
> +                       pr_err("failed to request GPIO%d for ROW%d\n",
> +                                       pdata->row_gpios[i], i);
> +                       goto err_free_rows;
> +               }
> +
> +               gpio_direction_input(pdata->row_gpios[i]);
> +       }
> +
> +       for (i = 0; i < pdata->num_row_gpios; i++) {
> +               err = request_irq(gpio_to_irq(pdata->row_gpios[i]),
> +                               matrix_keypad_interrupt, IRQF_DISABLED |
> +                               IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
> +                               "matrix-keypad", keypad);
> +               if (err) {
> +                       pr_err("Unable to acquire interrupt for GPIO line %i\n",
> +                               pdata->row_gpios[i]);
> +                       goto err_free_irqs;
> +               }
> +       }
> +       return 0;
> +
> +err_free_irqs:
> +       for (i = i - 1; i >= 0; i--)
> +               free_irq(gpio_to_irq(pdata->row_gpios[i]), keypad);
> +
> +err_free_rows:
> +       for (i = i - 1; i >= 0; i--)
> +               gpio_free(pdata->row_gpios[i]);
> +
> +err_free_cols:
> +       for (i = i - 1; i >= 0; i--)
> +               gpio_free(pdata->col_gpios[i]);
> +
> +       return err;
> +}
> +
> +static int __devinit matrix_keypad_probe(struct platform_device *pdev)
> +{
> +       struct matrix_keypad_platform_data *pdata;
> +       struct matrix_keypad *keypad;
> +       struct input_dev *input_dev;
> +       int err = -ENOMEM;
> +
> +       pdata = pdev->dev.platform_data;
> +       if (pdata == NULL) {
> +               dev_err(&pdev->dev, "no platform data defined\n");
> +               return -EINVAL;
> +       }
> +
> +       keypad = kzalloc(sizeof(struct matrix_keypad), GFP_KERNEL);
> +       if (keypad == NULL)
> +               return -ENOMEM;
> +
> +       input_dev = input_allocate_device();
> +       if (!input_dev)
> +               goto err_free_keypad;
> +
> +       platform_set_drvdata(pdev, keypad);
> +
> +       keypad->input_dev = input_dev;
> +       keypad->pdata = pdata;
> +       INIT_DELAYED_WORK(&keypad->work, matrix_keypad_scan);
> +
> +       input_dev->name         = pdev->name;
> +       input_dev->id.bustype   = BUS_HOST;
> +       input_dev->dev.parent   = &pdev->dev;
> +       input_dev->evbit[0]     = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
> +
> +       err = build_keycodes(keypad);
> +       if (err)
> +               goto err_free_input;
> +
> +       err = input_register_device(keypad->input_dev);
> +       if (err)
> +               goto err_free_keycodes;
> +
> +       err = init_matrix_gpio(keypad);
> +       if (err)
> +               goto err_unregister;
> +
> +       device_init_wakeup(&pdev->dev, 1);
> +       return 0;
> +
> +err_unregister:
> +       input_unregister_device(input_dev);
> +err_free_keycodes:
> +       kfree(keypad->keycodes);
> +err_free_input:
> +       input_free_device(input_dev);
> +err_free_keypad:
> +       kfree(keypad);
> +       return err;
> +}
> +
> +static int __devexit matrix_keypad_remove(struct platform_device *pdev)
> +{
> +       struct matrix_keypad *keypad = platform_get_drvdata(pdev);
> +       int i;
> +
> +       for (i = 0; i < keypad->pdata->num_row_gpios; i++) {
> +               free_irq(gpio_to_irq(keypad->pdata->row_gpios[i]), keypad);
> +               gpio_free(keypad->pdata->row_gpios[i]);
> +       }
> +
> +       for (i = 0; i < keypad->pdata->num_col_gpios; i++)
> +               gpio_free(keypad->pdata->col_gpios[i]);
> +
> +       input_unregister_device(keypad->input_dev);
> +       kfree(keypad->keycodes);
> +       kfree(keypad);
> +       return 0;
> +}
> +
> +static struct platform_driver matrix_keypad_driver = {
> +       .probe          = matrix_keypad_probe,
> +       .remove         = __devexit_p(matrix_keypad_remove),
> +       .suspend        = matrix_keypad_suspend,
> +       .resume         = matrix_keypad_resume,
> +       .driver         = {
> +               .name   = "matrix-keypad",
> +               .owner  = THIS_MODULE,
> +       },
> +};
> +
> +static int __init matrix_keypad_init(void)
> +{
> +       return platform_driver_register(&matrix_keypad_driver);
> +}
> +
> +static void __exit matrix_keypad_exit(void)
> +{
> +       platform_driver_unregister(&matrix_keypad_driver);
> +}
> +
> +module_init(matrix_keypad_init);
> +module_exit(matrix_keypad_exit);
> +
> +MODULE_AUTHOR("Marek Vasut <marek.vasut@xxxxxxxxx>");
> +MODULE_DESCRIPTION("GPIO Driven Matrix Keypad Driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:matrix-keypad");
> diff --git a/include/linux/input/matrix_keypad.h
> b/include/linux/input/matrix_keypad.h
> new file mode 100644
> index 0000000..8b661cb
> --- /dev/null
> +++ b/include/linux/input/matrix_keypad.h
> @@ -0,0 +1,34 @@
> +#ifndef _MATRIX_KEYPAD_H
> +#define _MATRIX_KEYPAD_H
> +
> +#include <linux/input.h>
> +
> +#define MATRIX_MAX_ROWS                16
> +#define MATRIX_MAX_COLS                16
> +#define MATRIX_MAX_KEYS                (MATRIX_MAX_ROWS * MATRIX_MAX_COLS)
> +
> +struct matrix_keypad_platform_data {
> +       /* scancode map for the matrix keys */
> +       uint32_t        *key_map;
> +       int             key_map_size;
> +
> +       unsigned        row_gpios[MATRIX_MAX_ROWS];
> +       unsigned        col_gpios[MATRIX_MAX_COLS];
> +       int             num_row_gpios;
> +       int             num_col_gpios;
> +
> +       unsigned int    active_low;
> +       unsigned int    col_scan_delay_us;
> +
> +       /* key debounce interval in milli-second */
> +       unsigned int    debounce_ms;
> +};
> +
> +#define KEY(row, col, val)     ((((row) & (MATRIX_MAX_ROWS - 1)) << 28) |\
> +                                (((col) & (MATRIX_MAX_COLS - 1)) << 24) |\
> +                                (val & 0xffffff))
> +
> +#define KEY_ROWCOL(k)          (((k) >> 24) & 0xff)
> +#define KEY_VAL(k)             ((k) & 0xffffff)
> +
> +#endif /* _MATRIX_KEYPAD_H */
> --
> 1.6.0.4
>




-- 
---Trilok Soni
http://triloksoni.wordpress.com
http://www.linkedin.com/in/triloksoni
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux