* [patch 2.6.29-rc3-git] input: twl4030_keypad driver
@ 2009-02-06 8:57 David Brownell
2009-02-06 9:16 ` Gadiyar, Anand
2009-04-21 6:22 ` David Brownell
0 siblings, 2 replies; 6+ messages in thread
From: David Brownell @ 2009-02-06 8:57 UTC (permalink / raw)
To: linux-input; +Cc: OMAP
From: David Brownell <dbrownell@users.sourceforge.net>
Add a driver for the keypad controller on TWL4030 family chips.
These support up to an 8x8 key matrix. The TWL4030 multifunction
chips are mostly used on OMAP3 (or OMAP 2430) based boards.
Reviewed-by: Trilok Soni <soni.trilok@gmail.com>"
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
---
Sometime later we may get a more generic/reusable solution
than the KEY() macros used here, which basically came from
the OMAP keypad support.
drivers/input/keyboard/Kconfig | 11
drivers/input/keyboard/Makefile | 1
drivers/input/keyboard/twl4030_keypad.c | 493 ++++++++++++++++++++++++++++++
include/linux/i2c/twl4030.h | 17 -
4 files changed, 517 insertions(+), 5 deletions(-)
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -259,6 +259,17 @@ config KEYBOARD_OMAP
To compile this driver as a module, choose M here: the
module will be called omap-keypad.
+config KEYBOARD_TWL4030
+ tristate "TI TWL4030/TWL5030/TPS659x0 keypad support"
+ depends on TWL4030_CORE
+ help
+ Say Y here if your board use the keypad controller on
+ TWL4030 family chips. It's safe to say enable this
+ even on boards that don't use the keypad controller.
+
+ To compile this driver as a module, choose M here: the
+ module will be called twl4030_keypad.
+
config KEYBOARD_PXA27x
tristate "PXA27x/PXA3xx keypad support"
depends on PXA27x || PXA3xx
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_KEYBOARD_TOSA) += tosakbd.
obj-$(CONFIG_KEYBOARD_HIL) += hil_kbd.o
obj-$(CONFIG_KEYBOARD_HIL_OLD) += hilkbd.o
obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o
+obj-$(CONFIG_KEYBOARD_TWL4030) += twl4030_keypad.o
obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keypad.o
obj-$(CONFIG_KEYBOARD_PXA930_ROTARY) += pxa930_rotary.o
obj-$(CONFIG_KEYBOARD_AAED2000) += aaed2000_kbd.o
--- /dev/null
+++ b/drivers/input/keyboard/twl4030_keypad.c
@@ -0,0 +1,493 @@
+/*
+ * twl4030_keypad.c - driver for 8x8 keypad controller in twl4030 chips
+ *
+ * Copyright (C) 2007 Texas Instruments, Inc.
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Code re-written for 2430SDP by:
+ * Syed Mohammed Khasim <x0khasim@ti.com>
+ *
+ * Initial Code:
+ * Manjunatha G K <manjugk@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/i2c/twl4030.h>
+
+
+/*
+ * The TWL4030 family chips include a keypad controller that supports
+ * up to an 8x8 switch matrix. The controller can issue system wakeup
+ * events, since it uses only the always-on 32KiHz oscillator, and has
+ * an internal state machine that decodes pressed keys, including
+ * multi-key combinations.
+ *
+ * This driver lets boards define what keycodes they wish to report for
+ * which scancodes, as part of the "struct twl4030_keypad_data" used in
+ * the probe() routine.
+ *
+ * See the TPS65950 documentation; that's the general availability
+ * version of the TWL5030 second generation part.
+ */
+#define MAX_ROWS 8 /* TWL4030 hard limit */
+
+struct twl4030_keypad {
+ unsigned *keymap;
+ unsigned int keymapsize;
+ u16 kp_state[MAX_ROWS];
+ unsigned n_rows;
+ unsigned n_cols;
+ unsigned irq;
+
+ struct device *dbg_dev;
+ struct input_dev *input;
+};
+
+#define ROWCOL_MASK KEY(0xf, 0xf, 0)
+#define KEYNUM_MASK ~PERSISTENT_KEY(0xf, 0xf)
+
+/*----------------------------------------------------------------------*/
+
+/* arbitrary prescaler value 0..7 */
+#define PTV_PRESCALER 4
+
+/* Register Offsets */
+#define KEYP_CTRL 0x00
+#define KEYP_DEB 0x01
+#define KEYP_LONG_KEY 0x02
+#define KEYP_LK_PTV 0x03
+#define KEYP_TIMEOUT_L 0x04
+#define KEYP_TIMEOUT_H 0x05
+#define KEYP_KBC 0x06
+#define KEYP_KBR 0x07
+#define KEYP_SMS 0x08
+#define KEYP_FULL_CODE_7_0 0x09 /* row 0 column status */
+#define KEYP_FULL_CODE_15_8 0x0a /* ... row 1 ... */
+#define KEYP_FULL_CODE_23_16 0x0b
+#define KEYP_FULL_CODE_31_24 0x0c
+#define KEYP_FULL_CODE_39_32 0x0d
+#define KEYP_FULL_CODE_47_40 0x0e
+#define KEYP_FULL_CODE_55_48 0x0f
+#define KEYP_FULL_CODE_63_56 0x10
+#define KEYP_ISR1 0x11
+#define KEYP_IMR1 0x12
+#define KEYP_ISR2 0x13
+#define KEYP_IMR2 0x14
+#define KEYP_SIR 0x15
+#define KEYP_EDR 0x16 /* edge triggers */
+#define KEYP_SIH_CTRL 0x17
+
+/* KEYP_CTRL_REG Fields */
+#define KEYP_CTRL_SOFT_NRST BIT(0)
+#define KEYP_CTRL_SOFTMODEN BIT(1)
+#define KEYP_CTRL_LK_EN BIT(2)
+#define KEYP_CTRL_TOE_EN BIT(3)
+#define KEYP_CTRL_TOLE_EN BIT(4)
+#define KEYP_CTRL_RP_EN BIT(5)
+#define KEYP_CTRL_KBD_ON BIT(6)
+
+/* KEYP_DEB, KEYP_LONG_KEY, KEYP_TIMEOUT_x*/
+#define KEYP_PERIOD_US(t, prescale) ((t) / (31 << (prescale + 1)) - 1)
+
+/* KEYP_LK_PTV_REG Fields */
+#define KEYP_LK_PTV_PTV_SHIFT 5
+
+/* KEYP_{IMR,ISR,SIR} Fields */
+#define KEYP_IMR1_MIS BIT(3)
+#define KEYP_IMR1_TO BIT(2)
+#define KEYP_IMR1_LK BIT(1)
+#define KEYP_IMR1_KP BIT(0)
+
+/* KEYP_EDR Fields */
+#define KEYP_EDR_KP_FALLING 0x01
+#define KEYP_EDR_KP_RISING 0x02
+#define KEYP_EDR_KP_BOTH 0x03
+#define KEYP_EDR_LK_FALLING 0x04
+#define KEYP_EDR_LK_RISING 0x08
+#define KEYP_EDR_TO_FALLING 0x10
+#define KEYP_EDR_TO_RISING 0x20
+#define KEYP_EDR_MIS_FALLING 0x40
+#define KEYP_EDR_MIS_RISING 0x80
+
+
+/*----------------------------------------------------------------------*/
+
+static int twl4030_kpread(struct twl4030_keypad *kp,
+ u8 *data, u32 reg, u8 num_bytes)
+{
+ int ret;
+
+ ret = twl4030_i2c_read(TWL4030_MODULE_KEYPAD, data, reg, num_bytes);
+ if (ret < 0) {
+ dev_warn(kp->dbg_dev,
+ "Couldn't read TWL4030: %X - ret %d[%x]\n",
+ reg, ret, ret);
+ return ret;
+ }
+ return ret;
+}
+
+static int twl4030_kpwrite_u8(struct twl4030_keypad *kp, u8 data, u32 reg)
+{
+ int ret;
+
+ ret = twl4030_i2c_write_u8(TWL4030_MODULE_KEYPAD, data, reg);
+ if (ret < 0) {
+ dev_warn(kp->dbg_dev,
+ "Could not write TWL4030: %X - ret %d[%x]\n",
+ reg, ret, ret);
+ return ret;
+ }
+ return ret;
+}
+
+static int twl4030_find_key(struct twl4030_keypad *kp, int col, int row)
+{
+ int i, rc;
+
+ rc = KEY(col, row, 0);
+ for (i = 0; i < kp->keymapsize; i++)
+ if ((kp->keymap[i] & ROWCOL_MASK) == rc)
+ return kp->keymap[i] & (KEYNUM_MASK | KEY_PERSISTENT);
+
+ return -EINVAL;
+}
+
+static inline u16 twl4030_col_xlate(struct twl4030_keypad *kp, u8 col)
+{
+ /* If all bits in a row are active for all coloumns then
+ * we have that row line connected to gnd. Mark this
+ * key on as if it was on matrix position n_cols (ie
+ * one higher than the size of the matrix).
+ */
+ if (col == 0xFF)
+ return 1 << kp->n_cols;
+ else
+ return col & ((1 << kp->n_cols) - 1);
+}
+
+static int twl4030_read_kp_matrix_state(struct twl4030_keypad *kp, u16 *state)
+{
+ u8 new_state[MAX_ROWS];
+ int row;
+ int ret = twl4030_kpread(kp,
+ new_state, KEYP_FULL_CODE_7_0, kp->n_rows);
+ if (ret >= 0) {
+ for (row = 0; row < kp->n_rows; row++)
+ state[row] = twl4030_col_xlate(kp, new_state[row]);
+ }
+ return ret;
+}
+
+static int twl4030_is_in_ghost_state(struct twl4030_keypad *kp, u16 *key_state)
+{
+ int i;
+ u16 check = 0;
+
+ for (i = 0; i < kp->n_rows; i++) {
+ u16 col = key_state[i];
+
+ if ((col & check) && hweight16(col) > 1)
+ return 1;
+ check |= col;
+ }
+
+ return 0;
+}
+
+static void twl4030_kp_scan(struct twl4030_keypad *kp, int release_all)
+{
+ u16 new_state[MAX_ROWS];
+ int col, row;
+
+ if (release_all)
+ memset(new_state, 0, sizeof(new_state));
+ else {
+ /* check for any changes */
+ int ret = twl4030_read_kp_matrix_state(kp, new_state);
+
+ if (ret < 0) /* panic ... */
+ return;
+ if (twl4030_is_in_ghost_state(kp, new_state))
+ return;
+ }
+
+ /* check for changes and print those */
+ for (row = 0; row < kp->n_rows; row++) {
+ int changed = new_state[row] ^ kp->kp_state[row];
+
+ if (!changed)
+ continue;
+
+ for (col = 0; col < kp->n_cols; col++) {
+ int key;
+
+ if (!(changed & (1 << col)))
+ continue;
+
+ dev_dbg(kp->dbg_dev, "key [%d:%d] %s\n", row, col,
+ (new_state[row] & (1 << col)) ?
+ "press" : "release");
+
+ key = twl4030_find_key(kp, col, row);
+ if (key < 0)
+ dev_warn(kp->dbg_dev,
+ "Spurious key event %d-%d\n",
+ col, row);
+ else if (key & KEY_PERSISTENT)
+ continue;
+ else
+ input_report_key(kp->input, key,
+ new_state[row] & (1 << col));
+ }
+ kp->kp_state[row] = new_state[row];
+ }
+ input_sync(kp->input);
+}
+
+/*
+ * Keypad interrupt handler
+ */
+static irqreturn_t do_kp_irq(int irq, void *_kp)
+{
+ struct twl4030_keypad *kp = _kp;
+ u8 reg;
+ int ret;
+
+#ifdef CONFIG_LOCKDEP
+ /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which
+ * we don't want and can't tolerate. Although it might be
+ * friendlier not to borrow this thread context...
+ */
+ local_irq_enable();
+#endif
+
+ /* Read & Clear TWL4030 pending interrupt */
+ ret = twl4030_kpread(kp, ®, KEYP_ISR1, 1);
+
+ /* Release all keys if I2C has gone bad or
+ * the KEYP has gone to idle state */
+ if ((ret >= 0) && (reg & KEYP_IMR1_KP))
+ twl4030_kp_scan(kp, 0);
+ else
+ twl4030_kp_scan(kp, 1);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Registers keypad device with input subsystem
+ * and configures TWL4030 keypad registers
+ */
+static int __devinit twl4030_kp_probe(struct platform_device *pdev)
+{
+ u8 reg;
+ int i;
+ int ret = 0;
+ struct twl4030_keypad *kp;
+ struct twl4030_keypad_data *pdata = pdev->dev.platform_data;
+
+ if (!pdata || !pdata->rows || !pdata->cols || !pdata->keymap
+ || pdata->rows > 8 || pdata->cols > 8) {
+ dev_err(&pdev->dev, "Invalid platform_data\n");
+ return -EINVAL;
+ }
+
+ kp = kzalloc(sizeof(*kp), GFP_KERNEL);
+ if (!kp)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, kp);
+
+ /* Get the debug Device */
+ kp->dbg_dev = &pdev->dev;
+
+ kp->input = input_allocate_device();
+ if (!kp->input) {
+ kfree(kp);
+ return -ENOMEM;
+ }
+
+ kp->keymap = pdata->keymap;
+ kp->keymapsize = pdata->keymapsize;
+ kp->n_rows = pdata->rows;
+ kp->n_cols = pdata->cols;
+ kp->irq = platform_get_irq(pdev, 0);
+
+ /* setup input device */
+ __set_bit(EV_KEY, kp->input->evbit);
+
+ /* Enable auto repeat feature of Linux input subsystem */
+ if (pdata->rep)
+ __set_bit(EV_REP, kp->input->evbit);
+
+ for (i = 0; i < kp->keymapsize; i++)
+ __set_bit(kp->keymap[i] & KEYNUM_MASK,
+ kp->input->keybit);
+
+ kp->input->name = "TWL4030 Keypad";
+ kp->input->phys = "twl4030_keypad/input0";
+ kp->input->dev.parent = &pdev->dev;
+
+ kp->input->id.bustype = BUS_HOST;
+ kp->input->id.vendor = 0x0001;
+ kp->input->id.product = 0x0001;
+ kp->input->id.version = 0x0003;
+
+ kp->input->keycode = kp->keymap;
+ kp->input->keycodesize = sizeof(unsigned int);
+ kp->input->keycodemax = kp->keymapsize;
+
+ ret = input_register_device(kp->input);
+ if (ret < 0) {
+ dev_err(kp->dbg_dev,
+ "Unable to register twl4030 keypad device\n");
+ goto err2;
+ }
+
+ /* Enable controller, with hardware decoding but not autorepeat */
+ reg = KEYP_CTRL_SOFT_NRST | KEYP_CTRL_SOFTMODEN
+ | KEYP_CTRL_TOE_EN | KEYP_CTRL_KBD_ON;
+ ret = twl4030_kpwrite_u8(kp, reg, KEYP_CTRL);
+ if (ret < 0)
+ goto err3;
+
+ /* NOTE: we could use sih_setup() here to package keypad
+ * event sources as four different IRQs ... but we don't.
+ */
+
+ /* Enable TO rising and KP rising and falling edge detection */
+ reg = KEYP_EDR_KP_BOTH | KEYP_EDR_TO_RISING;
+ ret = twl4030_kpwrite_u8(kp, reg, KEYP_EDR);
+ if (ret < 0)
+ goto err3;
+
+ /* Set PTV prescaler Field */
+ reg = (PTV_PRESCALER << KEYP_LK_PTV_PTV_SHIFT);
+ ret = twl4030_kpwrite_u8(kp, reg, KEYP_LK_PTV);
+ if (ret < 0)
+ goto err3;
+
+ /* Set key debounce time to 20 ms */
+ i = KEYP_PERIOD_US(20000, PTV_PRESCALER);
+ ret = twl4030_kpwrite_u8(kp, i, KEYP_DEB);
+ if (ret < 0)
+ goto err3;
+
+ /* Set timeout period to 100 ms */
+ i = KEYP_PERIOD_US(200000, PTV_PRESCALER);
+ ret = twl4030_kpwrite_u8(kp, (i & 0xFF), KEYP_TIMEOUT_L);
+ if (ret < 0)
+ goto err3;
+ ret = twl4030_kpwrite_u8(kp, (i >> 8), KEYP_TIMEOUT_H);
+ if (ret < 0)
+ goto err3;
+
+ /* Enable Clear-on-Read; disable remembering events that fire
+ * after the IRQ but before our handler acks (reads) them,
+ */
+ reg = TWL4030_SIH_CTRL_COR_MASK | TWL4030_SIH_CTRL_PENDDIS_MASK;
+ ret = twl4030_kpwrite_u8(kp, reg, KEYP_SIH_CTRL);
+ if (ret < 0)
+ goto err3;
+
+ /* initialize key state; irqs update it from here on */
+ ret = twl4030_read_kp_matrix_state(kp, kp->kp_state);
+ if (ret < 0)
+ goto err3;
+
+ /*
+ * This ISR will always execute in kernel thread context because of
+ * the need to access the TWL4030 over the I2C bus.
+ *
+ * NOTE: we assume this host is wired to TWL4040 INT1, not INT2 ...
+ */
+ ret = request_irq(kp->irq, do_kp_irq, 0, pdev->name, kp);
+ if (ret < 0) {
+ dev_info(kp->dbg_dev, "request_irq failed for irq no=%d\n",
+ kp->irq);
+ goto err3;
+ } else {
+ /* Enable KP and TO interrupts now. */
+ reg = (u8) ~(KEYP_IMR1_KP | KEYP_IMR1_TO);
+ ret = twl4030_kpwrite_u8(kp, reg, KEYP_IMR1);
+ if (ret < 0)
+ goto err5;
+ }
+
+ return ret;
+err5:
+ /* mask all events - we don't care about the result */
+ (void) twl4030_kpwrite_u8(kp, 0xff, KEYP_IMR1);
+ free_irq(kp->irq, NULL);
+err3:
+ input_unregister_device(kp->input);
+ kp->input = NULL;
+err2:
+ input_free_device(kp->input);
+ kfree(kp);
+ return -ENODEV;
+}
+
+static int __devexit twl4030_kp_remove(struct platform_device *pdev)
+{
+ struct twl4030_keypad *kp = platform_get_drvdata(pdev);
+
+ free_irq(kp->irq, kp);
+ input_unregister_device(kp->input);
+ kfree(kp);
+
+ return 0;
+}
+
+/*
+ * NOTE: twl4030 are multi-function devices connected via I2C.
+ * So this device is a child of an I2C parent, thus it needs to
+ * support unplug/replug (which most platform devices don't).
+ */
+
+MODULE_ALIAS("platform:twl4030_keypad");
+
+static struct platform_driver twl4030_kp_driver = {
+ .probe = twl4030_kp_probe,
+ .remove = __devexit_p(twl4030_kp_remove),
+ .driver = {
+ .name = "twl4030_keypad",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init twl4030_kp_init(void)
+{
+ return platform_driver_register(&twl4030_kp_driver);
+}
+module_init(twl4030_kp_init);
+
+static void __exit twl4030_kp_exit(void)
+{
+ platform_driver_unregister(&twl4030_kp_driver);
+}
+module_exit(twl4030_kp_exit);
+
+MODULE_AUTHOR("Texas Instruments");
+MODULE_DESCRIPTION("TWL4030 Keypad Driver");
+MODULE_LICENSE("GPL");
--- a/include/linux/i2c/twl4030.h
+++ b/include/linux/i2c/twl4030.h
@@ -255,12 +255,19 @@ struct twl4030_madc_platform_data {
int irq_line;
};
+/* Boards have uniqe mappings of {col, row} --> keycode.
+ * Column and row are 4 bits, but range only from 0..7;
+ * a PERSISTENT_KEY is "always on" and never reported.
+ */
+#define KEY_PERSISTENT 0x00800000
+#define KEY(col, row, keycode) (((col) << 28) | ((row) << 24) | (keycode))
+#define PERSISTENT_KEY(c, r) KEY((c), (r), KEY_PERSISTENT)
+
struct twl4030_keypad_data {
- int rows;
- int cols;
- int *keymap;
- int irq;
- unsigned int keymapsize;
+ unsigned rows;
+ unsigned cols;
+ unsigned *keymap;
+ unsigned short keymapsize;
unsigned int rep:1;
};
^ permalink raw reply [flat|nested] 6+ messages in thread
* RE: [patch 2.6.29-rc3-git] input: twl4030_keypad driver
2009-02-06 8:57 [patch 2.6.29-rc3-git] input: twl4030_keypad driver David Brownell
@ 2009-02-06 9:16 ` Gadiyar, Anand
2009-02-06 10:33 ` David Brownell
2009-04-21 6:22 ` David Brownell
1 sibling, 1 reply; 6+ messages in thread
From: Gadiyar, Anand @ 2009-02-06 9:16 UTC (permalink / raw)
To: David Brownell, linux-input@vger.kernel.org; +Cc: OMAP
> -----Original Message-----
> From: linux-omap-owner@vger.kernel.org
> [mailto:linux-omap-owner@vger.kernel.org] On Behalf Of David Brownell
> Sent: Friday, February 06, 2009 2:27 PM
> To: linux-input@vger.kernel.org
> Cc: OMAP
> Subject: [patch 2.6.29-rc3-git] input: twl4030_keypad driver
>
> From: David Brownell <dbrownell@users.sourceforge.net>
>
> Add a driver for the keypad controller on TWL4030 family chips.
> These support up to an 8x8 key matrix. The TWL4030 multifunction
> chips are mostly used on OMAP3 (or OMAP 2430) based boards.
>
> Reviewed-by: Trilok Soni <soni.trilok@gmail.com>"
^^^
Cut-paste error? Will it mess up git-shortlog accounting a little
or is it safe to ignore?
- Anand
<snip>
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [patch 2.6.29-rc3-git] input: twl4030_keypad driver
2009-02-06 9:16 ` Gadiyar, Anand
@ 2009-02-06 10:33 ` David Brownell
0 siblings, 0 replies; 6+ messages in thread
From: David Brownell @ 2009-02-06 10:33 UTC (permalink / raw)
To: Gadiyar, Anand; +Cc: linux-input@vger.kernel.org, OMAP
On Friday 06 February 2009, Gadiyar, Anand wrote:
>
> > Reviewed-by: Trilok Soni <soni.trilok@gmail.com>"
>
> ^^^
> Cut-paste error?
Yes.
> Will it mess up git-shortlog accounting a little
> or is it safe to ignore?
No idea; but that's trivial for whoever merges this
to address.
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [patch 2.6.29-rc3-git] input: twl4030_keypad driver
2009-02-06 8:57 [patch 2.6.29-rc3-git] input: twl4030_keypad driver David Brownell
2009-02-06 9:16 ` Gadiyar, Anand
@ 2009-04-21 6:22 ` David Brownell
2009-04-24 2:12 ` Dmitry Torokhov
1 sibling, 1 reply; 6+ messages in thread
From: David Brownell @ 2009-04-21 6:22 UTC (permalink / raw)
To: linux-input; +Cc: OMAP
On Friday 06 February 2009, David Brownell wrote:
> From: David Brownell <dbrownell@users.sourceforge.net>
>
> Add a driver for the keypad controller on TWL4030 family chips.
PING? I was told this was in the input queue, but it's not in
http://git.kernel.org/?p=linux/kernel/git/dtor/input.git
Here's a current version of the patch.
- Dave
============ CUT HERE
From: David Brownell <dbrownell@users.sourceforge.net>
Add a driver for the keypad controller on TWL4030 family chips.
These support up to an 8x8 key matrix. The TWL4030 multifunction
chips are mostly used on OMAP3 (or OMAP 2430) based boards.
Reviewed-by: Trilok Soni <soni.trilok@gmail.com>
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
---
Sometime later we may get a more generic/reusable solution
than the KEY() macros used here, which basically came from
the OMAP keypad support.
drivers/input/keyboard/Kconfig | 11
drivers/input/keyboard/Makefile | 1
drivers/input/keyboard/twl4030_keypad.c | 493 ++++++++++++++++++++++++++++++
include/linux/i2c/twl4030.h | 17 -
4 files changed, 517 insertions(+), 5 deletions(-)
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -259,6 +259,17 @@ config KEYBOARD_OMAP
To compile this driver as a module, choose M here: the
module will be called omap-keypad.
+config KEYBOARD_TWL4030
+ tristate "TI TWL4030/TWL5030/TPS659x0 keypad support"
+ depends on TWL4030_CORE
+ help
+ Say Y here if your board use the keypad controller on
+ TWL4030 family chips. It's safe to say enable this
+ even on boards that don't use the keypad controller.
+
+ To compile this driver as a module, choose M here: the
+ module will be called twl4030_keypad.
+
config KEYBOARD_PXA27x
tristate "PXA27x/PXA3xx keypad support"
depends on PXA27x || PXA3xx
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_KEYBOARD_TOSA) += tosakbd.
obj-$(CONFIG_KEYBOARD_HIL) += hil_kbd.o
obj-$(CONFIG_KEYBOARD_HIL_OLD) += hilkbd.o
obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o
+obj-$(CONFIG_KEYBOARD_TWL4030) += twl4030_keypad.o
obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keypad.o
obj-$(CONFIG_KEYBOARD_PXA930_ROTARY) += pxa930_rotary.o
obj-$(CONFIG_KEYBOARD_AAED2000) += aaed2000_kbd.o
--- /dev/null
+++ b/drivers/input/keyboard/twl4030_keypad.c
@@ -0,0 +1,493 @@
+/*
+ * twl4030_keypad.c - driver for 8x8 keypad controller in twl4030 chips
+ *
+ * Copyright (C) 2007 Texas Instruments, Inc.
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Code re-written for 2430SDP by:
+ * Syed Mohammed Khasim <x0khasim@ti.com>
+ *
+ * Initial Code:
+ * Manjunatha G K <manjugk@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/i2c/twl4030.h>
+
+
+/*
+ * The TWL4030 family chips include a keypad controller that supports
+ * up to an 8x8 switch matrix. The controller can issue system wakeup
+ * events, since it uses only the always-on 32KiHz oscillator, and has
+ * an internal state machine that decodes pressed keys, including
+ * multi-key combinations.
+ *
+ * This driver lets boards define what keycodes they wish to report for
+ * which scancodes, as part of the "struct twl4030_keypad_data" used in
+ * the probe() routine.
+ *
+ * See the TPS65950 documentation; that's the general availability
+ * version of the TWL5030 second generation part.
+ */
+#define MAX_ROWS 8 /* TWL4030 hard limit */
+
+struct twl4030_keypad {
+ unsigned *keymap;
+ unsigned int keymapsize;
+ u16 kp_state[MAX_ROWS];
+ unsigned n_rows;
+ unsigned n_cols;
+ unsigned irq;
+
+ struct device *dbg_dev;
+ struct input_dev *input;
+};
+
+#define ROWCOL_MASK KEY(0xf, 0xf, 0)
+#define KEYNUM_MASK ~PERSISTENT_KEY(0xf, 0xf)
+
+/*----------------------------------------------------------------------*/
+
+/* arbitrary prescaler value 0..7 */
+#define PTV_PRESCALER 4
+
+/* Register Offsets */
+#define KEYP_CTRL 0x00
+#define KEYP_DEB 0x01
+#define KEYP_LONG_KEY 0x02
+#define KEYP_LK_PTV 0x03
+#define KEYP_TIMEOUT_L 0x04
+#define KEYP_TIMEOUT_H 0x05
+#define KEYP_KBC 0x06
+#define KEYP_KBR 0x07
+#define KEYP_SMS 0x08
+#define KEYP_FULL_CODE_7_0 0x09 /* row 0 column status */
+#define KEYP_FULL_CODE_15_8 0x0a /* ... row 1 ... */
+#define KEYP_FULL_CODE_23_16 0x0b
+#define KEYP_FULL_CODE_31_24 0x0c
+#define KEYP_FULL_CODE_39_32 0x0d
+#define KEYP_FULL_CODE_47_40 0x0e
+#define KEYP_FULL_CODE_55_48 0x0f
+#define KEYP_FULL_CODE_63_56 0x10
+#define KEYP_ISR1 0x11
+#define KEYP_IMR1 0x12
+#define KEYP_ISR2 0x13
+#define KEYP_IMR2 0x14
+#define KEYP_SIR 0x15
+#define KEYP_EDR 0x16 /* edge triggers */
+#define KEYP_SIH_CTRL 0x17
+
+/* KEYP_CTRL_REG Fields */
+#define KEYP_CTRL_SOFT_NRST BIT(0)
+#define KEYP_CTRL_SOFTMODEN BIT(1)
+#define KEYP_CTRL_LK_EN BIT(2)
+#define KEYP_CTRL_TOE_EN BIT(3)
+#define KEYP_CTRL_TOLE_EN BIT(4)
+#define KEYP_CTRL_RP_EN BIT(5)
+#define KEYP_CTRL_KBD_ON BIT(6)
+
+/* KEYP_DEB, KEYP_LONG_KEY, KEYP_TIMEOUT_x*/
+#define KEYP_PERIOD_US(t, prescale) ((t) / (31 << (prescale + 1)) - 1)
+
+/* KEYP_LK_PTV_REG Fields */
+#define KEYP_LK_PTV_PTV_SHIFT 5
+
+/* KEYP_{IMR,ISR,SIR} Fields */
+#define KEYP_IMR1_MIS BIT(3)
+#define KEYP_IMR1_TO BIT(2)
+#define KEYP_IMR1_LK BIT(1)
+#define KEYP_IMR1_KP BIT(0)
+
+/* KEYP_EDR Fields */
+#define KEYP_EDR_KP_FALLING 0x01
+#define KEYP_EDR_KP_RISING 0x02
+#define KEYP_EDR_KP_BOTH 0x03
+#define KEYP_EDR_LK_FALLING 0x04
+#define KEYP_EDR_LK_RISING 0x08
+#define KEYP_EDR_TO_FALLING 0x10
+#define KEYP_EDR_TO_RISING 0x20
+#define KEYP_EDR_MIS_FALLING 0x40
+#define KEYP_EDR_MIS_RISING 0x80
+
+
+/*----------------------------------------------------------------------*/
+
+static int twl4030_kpread(struct twl4030_keypad *kp,
+ u8 *data, u32 reg, u8 num_bytes)
+{
+ int ret;
+
+ ret = twl4030_i2c_read(TWL4030_MODULE_KEYPAD, data, reg, num_bytes);
+ if (ret < 0) {
+ dev_warn(kp->dbg_dev,
+ "Couldn't read TWL4030: %X - ret %d[%x]\n",
+ reg, ret, ret);
+ return ret;
+ }
+ return ret;
+}
+
+static int twl4030_kpwrite_u8(struct twl4030_keypad *kp, u8 data, u32 reg)
+{
+ int ret;
+
+ ret = twl4030_i2c_write_u8(TWL4030_MODULE_KEYPAD, data, reg);
+ if (ret < 0) {
+ dev_warn(kp->dbg_dev,
+ "Could not write TWL4030: %X - ret %d[%x]\n",
+ reg, ret, ret);
+ return ret;
+ }
+ return ret;
+}
+
+static int twl4030_find_key(struct twl4030_keypad *kp, int col, int row)
+{
+ int i, rc;
+
+ rc = KEY(col, row, 0);
+ for (i = 0; i < kp->keymapsize; i++)
+ if ((kp->keymap[i] & ROWCOL_MASK) == rc)
+ return kp->keymap[i] & (KEYNUM_MASK | KEY_PERSISTENT);
+
+ return -EINVAL;
+}
+
+static inline u16 twl4030_col_xlate(struct twl4030_keypad *kp, u8 col)
+{
+ /* If all bits in a row are active for all coloumns then
+ * we have that row line connected to gnd. Mark this
+ * key on as if it was on matrix position n_cols (ie
+ * one higher than the size of the matrix).
+ */
+ if (col == 0xFF)
+ return 1 << kp->n_cols;
+ else
+ return col & ((1 << kp->n_cols) - 1);
+}
+
+static int twl4030_read_kp_matrix_state(struct twl4030_keypad *kp, u16 *state)
+{
+ u8 new_state[MAX_ROWS];
+ int row;
+ int ret = twl4030_kpread(kp,
+ new_state, KEYP_FULL_CODE_7_0, kp->n_rows);
+ if (ret >= 0) {
+ for (row = 0; row < kp->n_rows; row++)
+ state[row] = twl4030_col_xlate(kp, new_state[row]);
+ }
+ return ret;
+}
+
+static int twl4030_is_in_ghost_state(struct twl4030_keypad *kp, u16 *key_state)
+{
+ int i;
+ u16 check = 0;
+
+ for (i = 0; i < kp->n_rows; i++) {
+ u16 col = key_state[i];
+
+ if ((col & check) && hweight16(col) > 1)
+ return 1;
+ check |= col;
+ }
+
+ return 0;
+}
+
+static void twl4030_kp_scan(struct twl4030_keypad *kp, int release_all)
+{
+ u16 new_state[MAX_ROWS];
+ int col, row;
+
+ if (release_all)
+ memset(new_state, 0, sizeof(new_state));
+ else {
+ /* check for any changes */
+ int ret = twl4030_read_kp_matrix_state(kp, new_state);
+
+ if (ret < 0) /* panic ... */
+ return;
+ if (twl4030_is_in_ghost_state(kp, new_state))
+ return;
+ }
+
+ /* check for changes and print those */
+ for (row = 0; row < kp->n_rows; row++) {
+ int changed = new_state[row] ^ kp->kp_state[row];
+
+ if (!changed)
+ continue;
+
+ for (col = 0; col < kp->n_cols; col++) {
+ int key;
+
+ if (!(changed & (1 << col)))
+ continue;
+
+ dev_dbg(kp->dbg_dev, "key [%d:%d] %s\n", row, col,
+ (new_state[row] & (1 << col)) ?
+ "press" : "release");
+
+ key = twl4030_find_key(kp, col, row);
+ if (key < 0)
+ dev_warn(kp->dbg_dev,
+ "Spurious key event %d-%d\n",
+ col, row);
+ else if (key & KEY_PERSISTENT)
+ continue;
+ else
+ input_report_key(kp->input, key,
+ new_state[row] & (1 << col));
+ }
+ kp->kp_state[row] = new_state[row];
+ }
+ input_sync(kp->input);
+}
+
+/*
+ * Keypad interrupt handler
+ */
+static irqreturn_t do_kp_irq(int irq, void *_kp)
+{
+ struct twl4030_keypad *kp = _kp;
+ u8 reg;
+ int ret;
+
+#ifdef CONFIG_LOCKDEP
+ /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which
+ * we don't want and can't tolerate. Although it might be
+ * friendlier not to borrow this thread context...
+ */
+ local_irq_enable();
+#endif
+
+ /* Read & Clear TWL4030 pending interrupt */
+ ret = twl4030_kpread(kp, ®, KEYP_ISR1, 1);
+
+ /* Release all keys if I2C has gone bad or
+ * the KEYP has gone to idle state */
+ if ((ret >= 0) && (reg & KEYP_IMR1_KP))
+ twl4030_kp_scan(kp, 0);
+ else
+ twl4030_kp_scan(kp, 1);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Registers keypad device with input subsystem
+ * and configures TWL4030 keypad registers
+ */
+static int __devinit twl4030_kp_probe(struct platform_device *pdev)
+{
+ u8 reg;
+ int i;
+ int ret = 0;
+ struct twl4030_keypad *kp;
+ struct twl4030_keypad_data *pdata = pdev->dev.platform_data;
+
+ if (!pdata || !pdata->rows || !pdata->cols || !pdata->keymap
+ || pdata->rows > 8 || pdata->cols > 8) {
+ dev_err(&pdev->dev, "Invalid platform_data\n");
+ return -EINVAL;
+ }
+
+ kp = kzalloc(sizeof(*kp), GFP_KERNEL);
+ if (!kp)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, kp);
+
+ /* Get the debug Device */
+ kp->dbg_dev = &pdev->dev;
+
+ kp->input = input_allocate_device();
+ if (!kp->input) {
+ kfree(kp);
+ return -ENOMEM;
+ }
+
+ kp->keymap = pdata->keymap;
+ kp->keymapsize = pdata->keymapsize;
+ kp->n_rows = pdata->rows;
+ kp->n_cols = pdata->cols;
+ kp->irq = platform_get_irq(pdev, 0);
+
+ /* setup input device */
+ __set_bit(EV_KEY, kp->input->evbit);
+
+ /* Enable auto repeat feature of Linux input subsystem */
+ if (pdata->rep)
+ __set_bit(EV_REP, kp->input->evbit);
+
+ for (i = 0; i < kp->keymapsize; i++)
+ __set_bit(kp->keymap[i] & KEYNUM_MASK,
+ kp->input->keybit);
+
+ kp->input->name = "TWL4030 Keypad";
+ kp->input->phys = "twl4030_keypad/input0";
+ kp->input->dev.parent = &pdev->dev;
+
+ kp->input->id.bustype = BUS_HOST;
+ kp->input->id.vendor = 0x0001;
+ kp->input->id.product = 0x0001;
+ kp->input->id.version = 0x0003;
+
+ kp->input->keycode = kp->keymap;
+ kp->input->keycodesize = sizeof(unsigned int);
+ kp->input->keycodemax = kp->keymapsize;
+
+ ret = input_register_device(kp->input);
+ if (ret < 0) {
+ dev_err(kp->dbg_dev,
+ "Unable to register twl4030 keypad device\n");
+ goto err2;
+ }
+
+ /* Enable controller, with hardware decoding but not autorepeat */
+ reg = KEYP_CTRL_SOFT_NRST | KEYP_CTRL_SOFTMODEN
+ | KEYP_CTRL_TOE_EN | KEYP_CTRL_KBD_ON;
+ ret = twl4030_kpwrite_u8(kp, reg, KEYP_CTRL);
+ if (ret < 0)
+ goto err3;
+
+ /* NOTE: we could use sih_setup() here to package keypad
+ * event sources as four different IRQs ... but we don't.
+ */
+
+ /* Enable TO rising and KP rising and falling edge detection */
+ reg = KEYP_EDR_KP_BOTH | KEYP_EDR_TO_RISING;
+ ret = twl4030_kpwrite_u8(kp, reg, KEYP_EDR);
+ if (ret < 0)
+ goto err3;
+
+ /* Set PTV prescaler Field */
+ reg = (PTV_PRESCALER << KEYP_LK_PTV_PTV_SHIFT);
+ ret = twl4030_kpwrite_u8(kp, reg, KEYP_LK_PTV);
+ if (ret < 0)
+ goto err3;
+
+ /* Set key debounce time to 20 ms */
+ i = KEYP_PERIOD_US(20000, PTV_PRESCALER);
+ ret = twl4030_kpwrite_u8(kp, i, KEYP_DEB);
+ if (ret < 0)
+ goto err3;
+
+ /* Set timeout period to 100 ms */
+ i = KEYP_PERIOD_US(200000, PTV_PRESCALER);
+ ret = twl4030_kpwrite_u8(kp, (i & 0xFF), KEYP_TIMEOUT_L);
+ if (ret < 0)
+ goto err3;
+ ret = twl4030_kpwrite_u8(kp, (i >> 8), KEYP_TIMEOUT_H);
+ if (ret < 0)
+ goto err3;
+
+ /* Enable Clear-on-Read; disable remembering events that fire
+ * after the IRQ but before our handler acks (reads) them,
+ */
+ reg = TWL4030_SIH_CTRL_COR_MASK | TWL4030_SIH_CTRL_PENDDIS_MASK;
+ ret = twl4030_kpwrite_u8(kp, reg, KEYP_SIH_CTRL);
+ if (ret < 0)
+ goto err3;
+
+ /* initialize key state; irqs update it from here on */
+ ret = twl4030_read_kp_matrix_state(kp, kp->kp_state);
+ if (ret < 0)
+ goto err3;
+
+ /*
+ * This ISR will always execute in kernel thread context because of
+ * the need to access the TWL4030 over the I2C bus.
+ *
+ * NOTE: we assume this host is wired to TWL4040 INT1, not INT2 ...
+ */
+ ret = request_irq(kp->irq, do_kp_irq, 0, pdev->name, kp);
+ if (ret < 0) {
+ dev_info(kp->dbg_dev, "request_irq failed for irq no=%d\n",
+ kp->irq);
+ goto err3;
+ } else {
+ /* Enable KP and TO interrupts now. */
+ reg = (u8) ~(KEYP_IMR1_KP | KEYP_IMR1_TO);
+ ret = twl4030_kpwrite_u8(kp, reg, KEYP_IMR1);
+ if (ret < 0)
+ goto err5;
+ }
+
+ return ret;
+err5:
+ /* mask all events - we don't care about the result */
+ (void) twl4030_kpwrite_u8(kp, 0xff, KEYP_IMR1);
+ free_irq(kp->irq, NULL);
+err3:
+ input_unregister_device(kp->input);
+ kp->input = NULL;
+err2:
+ input_free_device(kp->input);
+ kfree(kp);
+ return -ENODEV;
+}
+
+static int __devexit twl4030_kp_remove(struct platform_device *pdev)
+{
+ struct twl4030_keypad *kp = platform_get_drvdata(pdev);
+
+ free_irq(kp->irq, kp);
+ input_unregister_device(kp->input);
+ kfree(kp);
+
+ return 0;
+}
+
+/*
+ * NOTE: twl4030 are multi-function devices connected via I2C.
+ * So this device is a child of an I2C parent, thus it needs to
+ * support unplug/replug (which most platform devices don't).
+ */
+
+MODULE_ALIAS("platform:twl4030_keypad");
+
+static struct platform_driver twl4030_kp_driver = {
+ .probe = twl4030_kp_probe,
+ .remove = __devexit_p(twl4030_kp_remove),
+ .driver = {
+ .name = "twl4030_keypad",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init twl4030_kp_init(void)
+{
+ return platform_driver_register(&twl4030_kp_driver);
+}
+module_init(twl4030_kp_init);
+
+static void __exit twl4030_kp_exit(void)
+{
+ platform_driver_unregister(&twl4030_kp_driver);
+}
+module_exit(twl4030_kp_exit);
+
+MODULE_AUTHOR("Texas Instruments");
+MODULE_DESCRIPTION("TWL4030 Keypad Driver");
+MODULE_LICENSE("GPL");
--- a/include/linux/i2c/twl4030.h
+++ b/include/linux/i2c/twl4030.h
@@ -302,12 +302,19 @@ struct twl4030_madc_platform_data {
int irq_line;
};
+/* Boards have uniqe mappings of {col, row} --> keycode.
+ * Column and row are 4 bits, but range only from 0..7;
+ * a PERSISTENT_KEY is "always on" and never reported.
+ */
+#define KEY_PERSISTENT 0x00800000
+#define KEY(col, row, keycode) (((col) << 28) | ((row) << 24) | (keycode))
+#define PERSISTENT_KEY(c, r) KEY((c), (r), KEY_PERSISTENT)
+
struct twl4030_keypad_data {
- int rows;
- int cols;
- int *keymap;
- int irq;
- unsigned int keymapsize;
+ unsigned rows;
+ unsigned cols;
+ unsigned *keymap;
+ unsigned short keymapsize;
unsigned int rep:1;
};
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [patch 2.6.29-rc3-git] input: twl4030_keypad driver
2009-04-21 6:22 ` David Brownell
@ 2009-04-24 2:12 ` Dmitry Torokhov
2009-04-24 9:09 ` David Brownell
0 siblings, 1 reply; 6+ messages in thread
From: Dmitry Torokhov @ 2009-04-24 2:12 UTC (permalink / raw)
To: David Brownell; +Cc: linux-input, OMAP
On Mon, Apr 20, 2009 at 11:22:41PM -0700, David Brownell wrote:
> On Friday 06 February 2009, David Brownell wrote:
> > From: David Brownell <dbrownell@users.sourceforge.net>
> >
> > Add a driver for the keypad controller on TWL4030 family chips.
>
> PING? I was told this was in the input queue, but it's not in
>
> http://git.kernel.org/?p=linux/kernel/git/dtor/input.git
>
> Here's a current version of the patch.
>
Dave,
It waqs sitting in my local queue, I had some concerns over the keymap
change as it was implemented in the version you sent me. The problem is
that you mangle key codes in your keymap table (encoding row/col data in
them) but input core is not aware of that and when you try using
EVIOCSETKEYCODE it will do wierd things. I was wondering what you would
think about the following patch that should rectify this issue.
Also, I don't think we need the special handling for "persistant" keys.
Just let these keys generate KEY_RESERVED and input core will not
propagate their events.
Thanks!
--
Dmitry
Input: twl4030_kepad fixups
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
---
drivers/input/keyboard/twl4030_keypad.c | 62 ++++++++++---------------------
include/linux/i2c/twl4030.h | 18 +++++++--
2 files changed, 32 insertions(+), 48 deletions(-)
diff --git a/drivers/input/keyboard/twl4030_keypad.c b/drivers/input/keyboard/twl4030_keypad.c
index 987f13c..b761cac 100644
--- a/drivers/input/keyboard/twl4030_keypad.c
+++ b/drivers/input/keyboard/twl4030_keypad.c
@@ -48,23 +48,18 @@
* See the TPS65950 documentation; that's the general availability
* version of the TWL5030 second generation part.
*/
-#define MAX_ROWS 8 /* TWL4030 hard limit */
struct twl4030_keypad {
- unsigned *keymap;
- unsigned int keymapsize;
- u16 kp_state[MAX_ROWS];
- unsigned n_rows;
- unsigned n_cols;
+ unsigned short keymap[TWL4030_KEYMAP_SIZE];
+ u16 kp_state[TWL4030_MAX_ROWS];
+ u8 n_rows;
+ u8 n_cols;
unsigned irq;
struct device *dbg_dev;
struct input_dev *input;
};
-#define ROWCOL_MASK KEY(0xf, 0xf, 0)
-#define KEYNUM_MASK ~PERSISTENT_KEY(0xf, 0xf)
-
/*----------------------------------------------------------------------*/
/* arbitrary prescaler value 0..7 */
@@ -156,18 +151,6 @@ static int twl4030_kpwrite_u8(struct twl4030_keypad *kp, u8 data, u32 reg)
return ret;
}
-static int twl4030_find_key(struct twl4030_keypad *kp, int col, int row)
-{
- int i, rc;
-
- rc = KEY(col, row, 0);
- for (i = 0; i < kp->keymapsize; i++)
- if ((kp->keymap[i] & ROWCOL_MASK) == rc)
- return kp->keymap[i] & (KEYNUM_MASK | KEY_PERSISTENT);
-
- return -EINVAL;
-}
-
static inline u16 twl4030_col_xlate(struct twl4030_keypad *kp, u8 col)
{
/* If all bits in a row are active for all coloumns then
@@ -183,7 +166,7 @@ static inline u16 twl4030_col_xlate(struct twl4030_keypad *kp, u8 col)
static int twl4030_read_kp_matrix_state(struct twl4030_keypad *kp, u16 *state)
{
- u8 new_state[MAX_ROWS];
+ u8 new_state[TWL4030_MAX_ROWS];
int row;
int ret = twl4030_kpread(kp,
new_state, KEYP_FULL_CODE_7_0, kp->n_rows);
@@ -213,7 +196,8 @@ static int twl4030_is_in_ghost_state(struct twl4030_keypad *kp, u16 *key_state)
static void twl4030_kp_scan(struct twl4030_keypad *kp, int release_all)
{
- u16 new_state[MAX_ROWS];
+ struct input_dev *input = kp->input;
+ u16 new_state[TWL4030_MAX_ROWS];
int col, row;
if (release_all)
@@ -246,20 +230,13 @@ static void twl4030_kp_scan(struct twl4030_keypad *kp, int release_all)
(new_state[row] & (1 << col)) ?
"press" : "release");
- key = twl4030_find_key(kp, col, row);
- if (key < 0)
- dev_warn(kp->dbg_dev,
- "Spurious key event %d-%d\n",
- col, row);
- else if (key & KEY_PERSISTENT)
- continue;
- else
- input_report_key(kp->input, key,
- new_state[row] & (1 << col));
+ key = kp->keymap[(row << 3) | col];
+ input_report_key(input, key,
+ new_state[row] & (1 << col));
}
kp->kp_state[row] = new_state[row];
}
- input_sync(kp->input);
+ input_sync(input);
}
/*
@@ -358,8 +335,8 @@ static int __devinit twl4030_kp_probe(struct platform_device *pdev)
int i;
int error;
- if (!pdata || !pdata->rows || !pdata->cols || !pdata->keymap
- || pdata->rows > 8 || pdata->cols > 8) {
+ if (!pdata || !pdata->rows || !pdata->cols ||
+ pdata->rows > TWL4030_MAX_ROWS || pdata->cols > TWL4030_MAX_COLS) {
dev_err(&pdev->dev, "Invalid platform_data\n");
return -EINVAL;
}
@@ -373,11 +350,9 @@ static int __devinit twl4030_kp_probe(struct platform_device *pdev)
/* Get the debug Device */
kp->dbg_dev = &pdev->dev;
-
kp->input = input;
- kp->keymap = pdata->keymap;
- kp->keymapsize = pdata->keymapsize;
+ memcpy(kp->keymap, pdata->keymap, sizeof(pdata->keymap));
kp->n_rows = pdata->rows;
kp->n_cols = pdata->cols;
kp->irq = platform_get_irq(pdev, 0);
@@ -389,8 +364,9 @@ static int __devinit twl4030_kp_probe(struct platform_device *pdev)
if (pdata->rep)
__set_bit(EV_REP, kp->input->evbit);
- for (i = 0; i < kp->keymapsize; i++)
- __set_bit(kp->keymap[i] & KEYNUM_MASK, input->keybit);
+ for (i = 0; i < ARRAY_SIZE(kp->keymap); i++)
+ __set_bit(kp->keymap[i], input->keybit);
+ __clear_bit(KEY_RESERVED, input->keybit);
input->name = "TWL4030 Keypad";
input->phys = "twl4030_keypad/input0";
@@ -402,8 +378,8 @@ static int __devinit twl4030_kp_probe(struct platform_device *pdev)
input->id.version = 0x0003;
input->keycode = kp->keymap;
- input->keycodesize = sizeof(unsigned int);
- input->keycodemax = kp->keymapsize;
+ input->keycodesize = sizeof(kp->keymap[0]);
+ input->keycodemax = ARRAY_SIZE(kp->keymap);
error = input_register_device(input);
if (error) {
diff --git a/include/linux/i2c/twl4030.h b/include/linux/i2c/twl4030.h
index 6b9722d..e493d2a 100644
--- a/include/linux/i2c/twl4030.h
+++ b/include/linux/i2c/twl4030.h
@@ -25,6 +25,8 @@
#ifndef __TWL4030_H_
#define __TWL4030_H_
+#include <linux/types.h>
+
/*
* Using the twl4030 core we address registers using a pair
* { module id, relative register offset }
@@ -306,16 +308,22 @@ struct twl4030_madc_platform_data {
* Column and row are 4 bits, but range only from 0..7;
* a PERSISTENT_KEY is "always on" and never reported.
*/
+
+#define TWL4030_MAX_ROWS 8
+#define TWL4030_MAX_COLS 8
+#define TWL4030_KEYMAP_SIZE (TWL4030_MAX_ROWS * TWL4030_MAX_COLS)
+
+/*
#define KEY_PERSISTENT 0x00800000
#define KEY(col, row, keycode) (((col) << 28) | ((row) << 24) | (keycode))
#define PERSISTENT_KEY(c, r) KEY((c), (r), KEY_PERSISTENT)
+*/
struct twl4030_keypad_data {
- unsigned rows;
- unsigned cols;
- unsigned *keymap;
- unsigned short keymapsize;
- unsigned int rep:1;
+ unsigned short keymap[TWL4030_KEYMAP_SIZE];
+ u8 rows;
+ u8 cols;
+ bool rep;
};
enum twl4030_usb_mode {
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [patch 2.6.29-rc3-git] input: twl4030_keypad driver
2009-04-24 2:12 ` Dmitry Torokhov
@ 2009-04-24 9:09 ` David Brownell
0 siblings, 0 replies; 6+ messages in thread
From: David Brownell @ 2009-04-24 9:09 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, OMAP
On Thursday 23 April 2009, Dmitry Torokhov wrote:
> >
>
> Dave,
>
> It waqs sitting in my local queue, I had some concerns over the keymap
> change as it was implemented in the version you sent me. The problem is
> that you mangle key codes in your keymap table (encoding row/col data in
Well, not really *me* I didn't write any of this. I just
cleaned it up and sent it along to help get this stuff out
of the OMAP tree, into mainline where it belongs.
> them) but input core is not aware of that and when you try using
> EVIOCSETKEYCODE it will do wierd things. I was wondering what you would
> think about the following patch that should rectify this issue.
It invalidates all the existing keypad tables, which have been
waiting for this driver to merge before they go upstream ... it'd
be simpler just to prevent EVIOCSETKEYCODE calls. Or provide
update methods that understand the structure of those entries;
conceptually they're a "struct { scancode; keycode; }" though
it's not coded that way
Needing to take 512 bytes per keytable -- vs the keypad-specific
sizes, typically much less even if using a qwerty -- is also a
minor issue. Keypads with 256 keys are *really* unusual! Most
current ones are smaller ... the biggest I've seen is 44.
> Also, I don't think we need the special handling for "persistant" keys.
> Just let these keys generate KEY_RESERVED and input core will not
> propagate their events.
I was never sure what to make of that, it seemed like a hack.
Only the "Labrador" boards (since renamed "Zoom1") seem to
need that mechanism.
So I'm not sure whether that would be appropriate. If it is,
then the keytable construction macros could just change. But
is the input core aware that it shouldn't remap such things?
- Dave
p.s. a few comments are below.
>
> Thanks!
>
> --
> Dmitry
>
>
> Input: twl4030_kepad fixups
>
> Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
> ---
>
> drivers/input/keyboard/twl4030_keypad.c | 62 ++++++++++---------------------
> include/linux/i2c/twl4030.h | 18 +++++++--
> 2 files changed, 32 insertions(+), 48 deletions(-)
>
>
> diff --git a/drivers/input/keyboard/twl4030_keypad.c b/drivers/input/keyboard/twl4030_keypad.c
> index 987f13c..b761cac 100644
> --- a/drivers/input/keyboard/twl4030_keypad.c
> +++ b/drivers/input/keyboard/twl4030_keypad.c
> @@ -48,23 +48,18 @@
> * See the TPS65950 documentation; that's the general availability
> * version of the TWL5030 second generation part.
> */
> -#define MAX_ROWS 8 /* TWL4030 hard limit */
>
> struct twl4030_keypad {
> - unsigned *keymap;
> - unsigned int keymapsize;
> - u16 kp_state[MAX_ROWS];
> - unsigned n_rows;
> - unsigned n_cols;
> + unsigned short keymap[TWL4030_KEYMAP_SIZE];
The keypad size is board-specific; not all possible switch
settings are used.
> + u16 kp_state[TWL4030_MAX_ROWS];
TWL4030_MAX_ROWS makes sense, although the same keypad macros
are used in some other OMAP boards that don't use TWL4030 family
chips, so it's not really TWL-specific. (OMAP1 boards often use
the "omap-keypad" driver.)
> + u8 n_rows;
> + u8 n_cols;
Didn't really need to change those. This is one of the cases
where the code to read a byte then zero-extend it uses more
space than using a 32-bit unsigned value instead of 8-bit. :)
> unsigned irq;
>
> struct device *dbg_dev;
> struct input_dev *input;
> };
>
> -#define ROWCOL_MASK KEY(0xf, 0xf, 0)
> -#define KEYNUM_MASK ~PERSISTENT_KEY(0xf, 0xf)
This being a side-effect of changing the key table encoding...
> -
> /*----------------------------------------------------------------------*/
>
> /* arbitrary prescaler value 0..7 */
> @@ -156,18 +151,6 @@ static int twl4030_kpwrite_u8(struct twl4030_keypad *kp, u8 data, u32 reg)
> return ret;
> }
>
> -static int twl4030_find_key(struct twl4030_keypad *kp, int col, int row)
> -{
> - int i, rc;
> -
> - rc = KEY(col, row, 0);
> - for (i = 0; i < kp->keymapsize; i++)
> - if ((kp->keymap[i] & ROWCOL_MASK) == rc)
> - return kp->keymap[i] & (KEYNUM_MASK | KEY_PERSISTENT);
> -
> - return -EINVAL;
> -}
> -
> static inline u16 twl4030_col_xlate(struct twl4030_keypad *kp, u8 col)
> {
> /* If all bits in a row are active for all coloumns then
> @@ -183,7 +166,7 @@ static inline u16 twl4030_col_xlate(struct twl4030_keypad *kp, u8 col)
>
> static int twl4030_read_kp_matrix_state(struct twl4030_keypad *kp, u16 *state)
> {
> - u8 new_state[MAX_ROWS];
> + u8 new_state[TWL4030_MAX_ROWS];
> int row;
> int ret = twl4030_kpread(kp,
> new_state, KEYP_FULL_CODE_7_0, kp->n_rows);
> @@ -213,7 +196,8 @@ static int twl4030_is_in_ghost_state(struct twl4030_keypad *kp, u16 *key_state)
>
> static void twl4030_kp_scan(struct twl4030_keypad *kp, int release_all)
> {
> - u16 new_state[MAX_ROWS];
> + struct input_dev *input = kp->input;
> + u16 new_state[TWL4030_MAX_ROWS];
> int col, row;
>
> if (release_all)
> @@ -246,20 +230,13 @@ static void twl4030_kp_scan(struct twl4030_keypad *kp, int release_all)
> (new_state[row] & (1 << col)) ?
> "press" : "release");
>
> - key = twl4030_find_key(kp, col, row);
> - if (key < 0)
> - dev_warn(kp->dbg_dev,
> - "Spurious key event %d-%d\n",
> - col, row);
> - else if (key & KEY_PERSISTENT)
> - continue;
> - else
> - input_report_key(kp->input, key,
> - new_state[row] & (1 << col));
> + key = kp->keymap[(row << 3) | col];
> + input_report_key(input, key,
> + new_state[row] & (1 << col));
That being the guts of this patchlet: using a flat table
lookup instead of a key/value search.
> }
> kp->kp_state[row] = new_state[row];
> }
> - input_sync(kp->input);
> + input_sync(input);
> }
>
> /*
> @@ -358,8 +335,8 @@ static int __devinit twl4030_kp_probe(struct platform_device *pdev)
> int i;
> int error;
>
> - if (!pdata || !pdata->rows || !pdata->cols || !pdata->keymap
> - || pdata->rows > 8 || pdata->cols > 8) {
> + if (!pdata || !pdata->rows || !pdata->cols ||
> + pdata->rows > TWL4030_MAX_ROWS || pdata->cols > TWL4030_MAX_COLS) {
> dev_err(&pdev->dev, "Invalid platform_data\n");
> return -EINVAL;
> }
> @@ -373,11 +350,9 @@ static int __devinit twl4030_kp_probe(struct platform_device *pdev)
>
> /* Get the debug Device */
> kp->dbg_dev = &pdev->dev;
> -
> kp->input = input;
>
> - kp->keymap = pdata->keymap;
> - kp->keymapsize = pdata->keymapsize;
> + memcpy(kp->keymap, pdata->keymap, sizeof(pdata->keymap));
Alternatively build a table of the "struct { scancode; keycode; }"
things here ... or update the table construction macros so that's
what they get in the first place (instead of integers with bitfields).
> kp->n_rows = pdata->rows;
> kp->n_cols = pdata->cols;
> kp->irq = platform_get_irq(pdev, 0);
> @@ -389,8 +364,9 @@ static int __devinit twl4030_kp_probe(struct platform_device *pdev)
> if (pdata->rep)
> __set_bit(EV_REP, kp->input->evbit);
>
> - for (i = 0; i < kp->keymapsize; i++)
> - __set_bit(kp->keymap[i] & KEYNUM_MASK, input->keybit);
> + for (i = 0; i < ARRAY_SIZE(kp->keymap); i++)
> + __set_bit(kp->keymap[i], input->keybit);
> + __clear_bit(KEY_RESERVED, input->keybit);
And I see KEY_RESERVED == 0, which is implicitly relied
on by the way most of that keymap is empty.
>
> input->name = "TWL4030 Keypad";
> input->phys = "twl4030_keypad/input0";
> @@ -402,8 +378,8 @@ static int __devinit twl4030_kp_probe(struct platform_device *pdev)
> input->id.version = 0x0003;
>
> input->keycode = kp->keymap;
> - input->keycodesize = sizeof(unsigned int);
> - input->keycodemax = kp->keymapsize;
> + input->keycodesize = sizeof(kp->keymap[0]);
> + input->keycodemax = ARRAY_SIZE(kp->keymap);
>
> error = input_register_device(input);
> if (error) {
> diff --git a/include/linux/i2c/twl4030.h b/include/linux/i2c/twl4030.h
> index 6b9722d..e493d2a 100644
> --- a/include/linux/i2c/twl4030.h
> +++ b/include/linux/i2c/twl4030.h
> @@ -25,6 +25,8 @@
> #ifndef __TWL4030_H_
> #define __TWL4030_H_
>
> +#include <linux/types.h>
> +
> /*
> * Using the twl4030 core we address registers using a pair
> * { module id, relative register offset }
> @@ -306,16 +308,22 @@ struct twl4030_madc_platform_data {
> * Column and row are 4 bits, but range only from 0..7;
> * a PERSISTENT_KEY is "always on" and never reported.
> */
> +
> +#define TWL4030_MAX_ROWS 8
> +#define TWL4030_MAX_COLS 8
> +#define TWL4030_KEYMAP_SIZE (TWL4030_MAX_ROWS * TWL4030_MAX_COLS)
> +
> +/*
> #define KEY_PERSISTENT 0x00800000
> #define KEY(col, row, keycode) (((col) << 28) | ((row) << 24) | (keycode))
> #define PERSISTENT_KEY(c, r) KEY((c), (r), KEY_PERSISTENT)
If your KEY_RESERVED thing checks out,
PERSISTENT_KEY(c,r) == KEY((c), (r), KEY_RESERVED)
Also, someone had commented that a bunch of other drivers need
basic scancode-to-keycode table support ... so maybe this kind
of stuff should become more standardized, instead of requiring
every driver to re-invent this roundy wheel-ish thing.
> +*/
>
> struct twl4030_keypad_data {
> - unsigned rows;
> - unsigned cols;
> - unsigned *keymap;
> - unsigned short keymapsize;
> - unsigned int rep:1;
> + unsigned short keymap[TWL4030_KEYMAP_SIZE];
> + u8 rows;
> + u8 cols;
> + bool rep;
> };
>
> enum twl4030_usb_mode {
>
>
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2009-04-24 9:09 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2009-02-06 8:57 [patch 2.6.29-rc3-git] input: twl4030_keypad driver David Brownell
2009-02-06 9:16 ` Gadiyar, Anand
2009-02-06 10:33 ` David Brownell
2009-04-21 6:22 ` David Brownell
2009-04-24 2:12 ` Dmitry Torokhov
2009-04-24 9:09 ` David Brownell
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).