* [PATCH] Input: introduce medfield keypad driver
@ 2010-07-08 16:41 Alan Cox
2010-07-08 17:42 ` Ville Syrjälä
2010-07-09 16:21 ` Dmitry Torokhov
0 siblings, 2 replies; 4+ messages in thread
From: Alan Cox @ 2010-07-08 16:41 UTC (permalink / raw)
To: linux-input, dmitry.torokhov
From: Zheng Ba <zheng.ba@intel.com>
An i2c bus driver for the Intel Medfield keypad interface.
Signed-off-by: Zheng Ba <zheng.ba@intel.com>
Signed-off-by: Alan Cox <alan@linux.intel.com>
---
drivers/input/keyboard/Kconfig | 10 +
drivers/input/keyboard/Makefile | 1
drivers/input/keyboard/mfld_keypad.c | 613 ++++++++++++++++++++++++++++++++++
drivers/input/keyboard/mfld_keypad.h | 56 +++
4 files changed, 680 insertions(+), 0 deletions(-)
create mode 100644 drivers/input/keyboard/mfld_keypad.c
create mode 100644 drivers/input/keyboard/mfld_keypad.h
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 950f6d1..10cc686 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -309,6 +309,16 @@ config KEYBOARD_MCS
To compile this driver as a module, choose M here: the
module will be called mcs_touchkey.
+config KEYBOARD_MFLD
+ tristate "Medfield I2C keypad support"
+ depends on I2C && GPIOLIB && X86_MRST
+ help
+ Say Y here if you want to get support for the keypad controller
+ on the Medfield (MFLD) platform of Intel MID.
+
+ To compile this as a module, choose M here: the
+ module will be called mfld_keypad.
+
config KEYBOARD_IMX
tristate "IMX keypad support"
depends on ARCH_MXC
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index a1f5e9d..9672c3d 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_KEYBOARD_BFIN) += bf54x-keys.o
obj-$(CONFIG_KEYBOARD_DAVINCI) += davinci_keyscan.o
obj-$(CONFIG_KEYBOARD_EP93XX) += ep93xx_keypad.o
obj-$(CONFIG_KEYBOARD_INTEL_MID) += intel_mid_keypad.o
+obj-$(CONFIG_KEYBOARD_MFLD) += mfld_keypad.o
obj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o
obj-$(CONFIG_KEYBOARD_TCA6416) += tca6416-keypad.o
obj-$(CONFIG_KEYBOARD_HIL) += hil_kbd.o
diff --git a/drivers/input/keyboard/mfld_keypad.c b/drivers/input/keyboard/mfld_keypad.c
new file mode 100644
index 0000000..096349a
--- /dev/null
+++ b/drivers/input/keyboard/mfld_keypad.c
@@ -0,0 +1,613 @@
+/*
+ * drivers/input/keyboard/mfld_keypad.c
+ *
+ * Copyright (c) 2010 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+
+#include "mfld_keypad.h"
+
+/* commands to send to the chip */
+#define MFLD_KEYPAD_CMD_RSTCTRL 0x82 /* the reset register */
+#define MFLD_KEYPAD_CMD_EXTRSTN 0x83 /* external reset ball enable */
+#define MFLD_KEYPAD_CMD_RSTINTCLR 0x84 /* clear the intrrupt caused
+ by power-on */
+
+#define MFLD_KEYPAD_CMD_READ_INT 0x91 /* get interrupt status */
+
+#define MFLD_KEYPAD_CMD_AUTOSLPENA 0x8B /* auto sleep enable */
+#define MFLD_KEYPAD_CMD_AUTOSLPTIMER 0x8C /* min time to switch
+ into auto sleep */
+#define MFLD_KEYPAD_CMD_WAKEUPEN 0x8E /* wake-up automatically
+ when i2c access */
+#define MFLD_KEYPAD_CMD_CLKEN 0x8A /* enable internal clock */
+#define MFLD_KEYPAD_CMD_CLKCFG 0x89 /* clock configure,2x */
+#define MFLD_KEYPAD_CMD_IOCFG 0xA7 /* input/output configure */
+#define MFLD_KEYPAD_CMD_IOPCEXT 0xA8 /* pull up resistor
+ programming */
+#define MFLD_KEYPAD_CMD_IOPC0 0xAA
+#define MFLD_KEYPAD_CMD_IOPC1 0xAC
+#define MFLD_KEYPAD_CMD_IOPC2 0xAE
+
+/* keyboard read event */
+#define MFLD_KEYPAD_FIFO_LEN 8
+#define MFLD_KEYPAD_CMD_READ_FIFO 0x10 /* event buffer for key press,
+ EVTCODE reg */
+#define MFLD_KEYPAD_CMD_KBDRIS 0x06 /* kbd raw interrupt register */
+#define MFLD_KEYPAD_CMD_KBDMIS 0x07 /* kbd mask interrupt reg */
+#define MFLD_KEYPAD_CMD_KBDIC 0x08 /* clear the kbd interrupt &
+ event buffer */
+#define MFLD_KEYPAD_CMD_KBDMSK 0x09 /* KBD mask register */
+#define MFLD_KEYPAD_CMD_KBDMFS 0x8F /* KBD modified feature set */
+#define MFLD_KEYPAD_CMD_GPIODIR2 0xC8 /* gpio input/ output */
+#define MFLD_KEYPAD_CMD_GPIODIR1 0xC7 /* bit clear after reset */
+#define MFLD_KEYPAD_CMD_GPIODIR0 0xC6
+#define MFLD_KEYPAD_CMD_GPIODATA2 0xC4 /* MASK & Data bit */
+#define MFLD_KEYPAD_CMD_GPIODATA1 0xC2
+#define MFLD_KEYPAD_CMD_GPIODATA0 0xC0
+
+/* About the direct key function, maybe non-used here */
+#define MFLD_KEYPAD_CMD_DEVTCODE 0xE6 /* direct key event */
+#define MFLD_KEYPAD_CMD_DEBOUNCE 0xE8 /* direct key debounce */
+#define MFLD_KEYPAD_CMD_DIRECT0 0xEC /* direct key enable */
+#define MFLD_KEYPAD_CMD_DIRECT1 0xED
+#define MFLD_KEYPAD_CMD_DIRECT2 0xEE
+#define MFLD_KEYPAD_CMD_DIRECT3 0xEF
+#define MFLD_KEYPAD_CMD_DKBDRIS 0xF0 /* unmasked direct key intr */
+#define MFLD_KEYPAD_CMD_DKBDMIS 0xF1 /* key mask interrupt */
+#define MFLD_KEYPAD_CMD_DKBDIC 0xF2 /* direct key intr clear */
+#define MFLD_KEYPAD_CMD_DKBDMSK 0xF3 /* direct key mask register */
+
+/* keyboard setting registers */
+#define MFLD_KEYPAD_CMD_KBDSETTLE 0x01 /* setup of init wait period */
+#define MFLD_KEYPAD_CMD_KBDBOUNCE 0x02 /* setup of de-bouncing */
+#define MFLD_KEYPAD_CMD_KBDSIZE 0x03 /* keyboard matrix setup */
+#define MFLD_KEYPAD_CMD_KBDDEDCFG 0x04 /* dedicated key setup */
+
+/* For detected */
+#define MFLD_KEYPAD_CMD_MANUFACT 0x80 /* manufact.code */
+#define MFLD_KEYPAD_CMD_SWVERSION 0x81 /* SW version */
+
+/* interrupt status */
+#define INT_KEYPAD 0x40 /* kbd irq bit */
+#define INT_PORIRQ 0x80 /* when power on, the bit is set */
+#define INT_MELINT 0x08 /* more than 8 key events overflow */
+
+#define MFLD_KEYPAD_INT_GPIO 51
+#define MFLD_KEYPAD_RST_GPIO 173
+
+/* XXX backlight controls */
+#define PWM1CLKDIV0 0x64
+#define PWM2CLKDIV1 0x65
+#define PWM2DUTYCYCLE 0x69 /* brightness control */
+
+struct mfld_keypad_chip {
+ /* device lock */
+ struct mutex lock;
+ struct i2c_client *client;
+ struct work_struct work;
+ struct input_dev *idev;
+ bool kp_enabled;
+ bool pm_suspend;
+ char phys[32];
+ const unsigned short *keymap;
+ int size_x;
+ int size_y;
+ int shift_down;
+};
+
+#define work_to_mfld_keypad(w) container_of(w, struct mfld_keypad_chip, work)
+#define client_to_mfld_keypad(c) container_of(c, struct mfld_keypad_chip, \
+ client)
+#define dev_to_mfld_keypad(d) container_of(c, struct mfld_keypad_chip, \
+ client->dev)
+
+#define MFLD_KEYPAD_MAX_DATA 8
+
+/*
+ * To write, access the chip's address in write mode, and dump the
+ * command and data on the bus. The command and data are taken as
+ * sequential u8s out of varargs, to a maxinum of MFLD_KEYPAD_MAX_DATA
+ */
+static int mfld_keypad_write(struct mfld_keypad_chip *tc, int len, ...)
+{
+ int ret, i;
+ va_list ap;
+ u8 data[MFLD_KEYPAD_MAX_DATA];
+
+ va_start(ap, len);
+ if (len > MFLD_KEYPAD_MAX_DATA) {
+ WARN_ON(1);
+ dev_err(&tc->client->dev, "tried to send %d bytes\n", len);
+ va_end(ap);
+ return 0;
+ }
+
+ for (i = 0; i < len; i++)
+ data[i] = va_arg(ap, int);
+ va_end(ap);
+
+ /*
+ * If the host is asleep, send again when get NACK
+ */
+ ret = i2c_master_send(tc->client, data, len);
+ if (unlikely(ret == -EREMOTEIO))
+ ret = i2c_master_send(tc->client, data, len);
+
+ if (unlikely(ret != len))
+ dev_err(&tc->client->dev, "sent %d bytes of %d total\n",
+ len, ret);
+
+ return ret;
+}
+
+/*
+ * To read, first send the command byte and end the transaction.
+ * Then we can get the data in read mode.
+ */
+static int mfld_keypad_read(struct mfld_keypad_chip *tc,
+ u8 cmd, u8 *buf, int len)
+{
+ int ret;
+ /*
+ * In case of host's asleep, send again when get NACK
+ */
+ ret = i2c_master_send(tc->client, &cmd, 1);
+ if (unlikely(ret == -EREMOTEIO))
+ ret = i2c_master_send(tc->client, &cmd, 1);
+
+ if (unlikely(ret != 1)) {
+ dev_err(&tc->client->dev, "sending command 0x%2x failed.\n",
+ cmd);
+ return 0;
+ }
+
+ ret = i2c_master_recv(tc->client, buf, len);
+ if (unlikely(ret != len))
+ dev_err(&tc->client->dev, "want %d bytes, got %d\n", len, ret);
+
+ return ret;
+}
+
+static int mfld_keypad_configure(struct mfld_keypad_chip *tc)
+{
+ int keysize = (tc->size_x << 4) | tc->size_y;
+
+ mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_CLKEN, 0x01);
+ mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_KBDSIZE, keysize);
+ mfld_keypad_write(tc, 3, MFLD_KEYPAD_CMD_KBDDEDCFG, 0xFF, 0xFF);
+ mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_CLKCFG, 0x45);
+ /* maximum debounce time : 15.6ms */
+ mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_KBDBOUNCE, 0x02);
+
+ mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_KBDMSK, 0x0F);
+ mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_DKBDMSK, 0x03);
+ mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_KBDIC, 0x83);
+ mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_DKBDIC, 0x01);
+
+ mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_KBDMSK, 0x00);
+
+ return 0;
+}
+
+/*
+ * AT-style: low 7 bits are the keycode, and the top
+ * bit indicates the state( 1 for down, 0 for up)
+ */
+static inline u8 mfld_keypad_whichkey(u8 event)
+{
+ /* bit[7-4]:key row, bit[3-0]:key col */
+ u8 row, col;
+ u8 key;
+ row = (event & 0x70) >> 4;
+ col = (event & 0x0F);
+
+ key = row * 8 + col;
+
+ return key;
+}
+
+static inline int mfld_keypad_ispress(u8 event)
+{
+ /* 1: pressed, 0: released */
+ return (event & 0x80) ? 0 : 1;
+}
+
+/* Reset the keybit of input */
+static void set_keymap_bit(struct mfld_keypad_chip *tc)
+{
+ int i;
+ for (i = 0; i < KEYMAP_SIZE; i++)
+ __set_bit(tc->keymap[i], tc->idev->keybit);
+}
+
+/* Shift key report needed? */
+static int shift_key_needed(struct mfld_keypad_chip *tc, u8 key)
+{
+ if (tc->keymap == mfld_keymap_fn) {
+ switch (key) {
+ case 0: /* '~' */
+ case 1: /* '@' */
+ case 2: /* '#' */
+ case 3: /* '$' */
+ case 4: /* '%' */
+ case 5: /* '^' */
+ case 6: /* '&' */
+ case 8: /* '|' */
+ case 22: /* ':' */
+ case 29: /* '_' */
+ case 30: /* '!' */
+ case 31: /* '?' */
+ case 32: /* '(' */
+ case 42: /* '"' */
+ case 44: /* ')' */
+ return 1;
+ default:
+ return 0;
+ }
+ }
+ return 0;
+}
+
+/* Report the 'right shift' key */
+static void report_shift_key(struct mfld_keypad_chip *tc, u8 key, int isdown)
+{
+ if (tc->kp_enabled) {
+ /* The right shift key stored at last position:47 */
+ input_report_key(tc->idev, tc->keymap[RIGHT_SHIFT_KEY], isdown);
+ input_sync(tc->idev);
+ }
+}
+
+/* Report the key code */
+static void submit_key(struct mfld_keypad_chip *tc, u8 key,
+ unsigned short keycode, int isdown)
+{
+ /*
+ * Translate the non-exist keycode keys.
+ * when key press down, report the 'shift' key pressed ahead.
+ */
+
+ if (shift_key_needed(tc, key) && isdown)
+ report_shift_key(tc, key, isdown);
+
+ /* report the key */
+ if (tc->kp_enabled) {
+ input_report_key(tc->idev, keycode, isdown);
+ input_sync(tc->idev);
+ }
+
+ /*
+ * When key press up, report the 'shift' up followed.
+ */
+ if (shift_key_needed(tc, key) && !isdown)
+ report_shift_key(tc, key, isdown);
+
+ /*
+ * if no keys are pressed anymore, remove the 'shift'
+ */
+ if (i2c_smbus_read_byte_data(tc->client, 0x0B) == 0x7F) {
+ input_report_key(tc->idev, KEY_RIGHTSHIFT, 0);
+ input_report_key(tc->idev, KEY_LEFTSHIFT, 0);
+ input_sync(tc->idev);
+ }
+}
+
+/* Key event interrupt handler */
+static inline void process_keys(struct mfld_keypad_chip *tc)
+{
+ int ret;
+ u8 key_queue[MFLD_KEYPAD_FIFO_LEN];
+ int tail = 0;
+
+ u8 evtcode;
+
+ ret = mfld_keypad_read(tc, MFLD_KEYPAD_CMD_READ_FIFO, &evtcode, 1);
+ if (ret < 0) {
+ dev_err(&tc->client->dev, "Failed reading fifo\n");
+ return;
+ }
+
+ /* clear event buffer */
+ mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_KBDIC, 0x83);
+
+ if (evtcode != 0x7F && evtcode != 0xFF) {
+ u8 key = mfld_keypad_whichkey(evtcode);
+ int isdown = mfld_keypad_ispress(evtcode);
+ unsigned short keycode = tc->keymap[key];
+
+ /* The function key pressed */
+ if (key == FN_KEY && isdown) {
+ tc->keymap = mfld_keymap_fn;
+ set_keymap_bit(tc);
+ keycode = tc->keymap[key];
+ }
+
+ /* Function key press up */
+ if (key == FN_KEY && !isdown) {
+ /*
+ * dequeue the key_queue,
+ * where keys stored while FN is pressed
+ */
+ int j;
+ for (j = 0; j < tail; j++) /* keys up */
+ submit_key(tc, key_queue[j],
+ mfld_keymap_fn[key_queue[j]], 0);
+ tail = 0;
+
+ tc->keymap = mfld_keymap;
+ set_keymap_bit(tc);
+ }
+
+ if (tc->keymap == mfld_keymap_fn)
+ key_queue[tail++] = key;
+
+ submit_key(tc, key, keycode, isdown);
+ }
+}
+
+/*
+ * Bottom Half: handle the interrupt by posting key events, or dealing with
+ * errors appropriately
+ */
+static void mfld_keypad_work(struct work_struct *work)
+{
+ struct mfld_keypad_chip *tc = work_to_mfld_keypad(work);
+ u8 ints = 0;
+
+ mutex_lock(&tc->lock);
+ while ((mfld_keypad_read(tc, MFLD_KEYPAD_CMD_READ_INT, &ints, 1) == 1)
+ && ints) {
+ if (likely(ints & INT_KEYPAD))
+ process_keys(tc);
+ }
+
+ mutex_unlock(&tc->lock);
+}
+
+/*
+ * We cannot use I2c in interrupt context, so we just schedule work.
+ */
+static irqreturn_t mfld_keypad_irq(int irq, void *data)
+{
+ struct mfld_keypad_chip *tc = data;
+ schedule_work(&tc->work);
+ return IRQ_HANDLED;
+}
+
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static int mfld_keypad_detect(struct i2c_client *client,
+ struct i2c_board_info *info)
+{
+ struct i2c_adapter *adapter = client->adapter;
+ int manufact, sw_ver;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+
+ dev_err(&client->dev, "error checking function\n");
+ return -ENODEV;
+ }
+
+ /* validate vendor and revison */
+ manufact = i2c_smbus_read_byte_data(client,
+ MFLD_KEYPAD_CMD_MANUFACT);
+ sw_ver = i2c_smbus_read_byte_data(client,
+ MFLD_KEYPAD_CMD_SWVERSION);
+ if (manufact != 0x03 || sw_ver != 0xC0) {
+ dev_err(&client->dev, "error checking vendor and revision\n");
+ return -ENODEV;
+ }
+
+ i2c_smbus_write_byte_data(client, MFLD_KEYPAD_CMD_RSTINTCLR, 0x01);
+
+ dev_dbg(&client->dev, "Successfully detected the i2c keypad\n");
+ strlcpy(info->type, "mfld_keypad", I2C_NAME_SIZE);
+
+ return 0;
+}
+
+static int __devinit mfld_keypad_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct input_dev *idev;
+ struct mfld_keypad_chip *tc;
+ unsigned int irq;
+ int i, err;
+ int gpio;
+
+ tc = kzalloc(sizeof(*tc), GFP_KERNEL);
+ idev = input_allocate_device();
+ if (!tc || !idev) {
+ err = -ENOMEM;
+ goto fail1;
+ }
+
+ i2c_set_clientdata(client, tc);
+ tc->client = client;
+ tc->idev = idev;
+ tc->keymap = mfld_keymap;
+
+ mutex_init(&tc->lock);
+ INIT_WORK(&tc->work, mfld_keypad_work);
+
+ tc->size_x = SIZE_X;
+ tc->size_y = SIZE_Y;
+ dev_vdbg(&client->dev, "Keypad size: %d x %d\n",
+ tc->size_x, tc->size_y);
+
+ mfld_keypad_configure(tc);
+
+ tc->kp_enabled = true;
+
+ idev->name = "mfld_keypad";
+ idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) |
+ BIT_MASK(EV_REL);
+ idev->keycode = (unsigned short *)mfld_keymap;
+ idev->keycodesize = sizeof(unsigned short);
+ idev->keycodemax = sizeof(mfld_keymap);
+
+ for (i = 0; i < KEYMAP_SIZE; i++)
+ set_bit(mfld_keymap[i], idev->keybit);
+
+ err = input_register_device(idev);
+ if (err) {
+ dev_dbg(&client->dev, "error register input device\n");
+ goto fail1;
+ }
+
+ gpio = MFLD_KEYPAD_INT_GPIO;
+ irq = gpio_to_irq(gpio);
+ if (irq < 0) {
+ dev_dbg(&client->dev, "error acquire IRQ to GPIO %d\n", gpio);
+ err = irq;
+ goto fail2;
+ }
+ client->irq = irq;
+
+ err = request_irq(irq, mfld_keypad_irq,
+ IRQF_SHARED | IRQ_TYPE_EDGE_FALLING,
+ "mfld_keypad", tc);
+ if (err) {
+ dev_err(&client->dev, "could not get IRQ %d\n", irq);
+ goto fail2;
+ }
+
+ return 0;
+
+fail2:
+ input_unregister_device(idev);
+ idev = NULL;
+fail1:
+ input_free_device(idev);
+ kfree(tc);
+ return err;
+}
+
+static int __devexit mfld_keypad_remove(struct i2c_client *client)
+{
+ struct mfld_keypad_chip *tc = i2c_get_clientdata(client);
+
+ free_irq(client->irq, tc);
+ cancel_work_sync(&tc->work);
+
+ input_unregister_device(tc->idev);
+
+ kfree(tc);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * The chip can switch off when there is no activity, so
+ * explicit suspend is not needed
+ */
+
+static int mfld_keypad_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+ struct mfld_keypad_chip *tc = i2c_get_clientdata(client);
+
+ set_irq_wake(client->irq, 0);
+ disable_irq(client->irq);
+
+ mutex_lock(&tc->lock);
+ tc->pm_suspend = true;
+ mutex_unlock(&tc->lock);
+
+ return 0;
+}
+
+static int mfld_keypad_resume(struct i2c_client *client)
+{
+ struct mfld_keypad_chip *tc = i2c_get_clientdata(client);
+
+ mutex_lock(&tc->lock);
+ tc->pm_suspend = false;
+ mutex_unlock(&tc->lock);
+
+ enable_irq(client->irq);
+ set_irq_wake(client->irq, 1);
+
+ return 0;
+}
+
+#else
+#define mfld_keypad_suspend NULL
+#define mfld_keypad_resume NULL
+#endif
+
+/* Address to scan: 0x45-i2c slave addr*/
+static const unsigned short normal_i2c[] = { 0x45, I2C_CLIENT_END };
+
+static const struct i2c_device_id mfld_keypad_id[] = {
+ {"mfld_keypad", 0},
+ {}
+};
+
+static struct i2c_driver mfld_keypad_i2c_driver = {
+ .driver = {
+ .name = "mfld_keypad",
+ },
+ .probe = mfld_keypad_probe,
+ .remove = __devexit_p(mfld_keypad_remove),
+ .suspend = mfld_keypad_suspend,
+ .resume = mfld_keypad_resume,
+ .id_table = mfld_keypad_id,
+ .detect = mfld_keypad_detect,
+ .address_list = normal_i2c,
+};
+
+MODULE_DEVICE_TABLE(i2c, mfld_keypad_id);
+
+static int __init mfld_keypad_init(void)
+{
+ /*
+ * TODO
+ * Should add platform detect here before hardware reset, or if
+ * it loaded on a non-mfld system it might do unintended things.
+ */
+ int gpio = MFLD_KEYPAD_RST_GPIO;
+ gpio_request(gpio, "mfld_keypad");
+ /* hardware reset */
+ gpio_direction_output(gpio, 0);
+ gpio_direction_output(gpio, 1);
+ return i2c_add_driver(&mfld_keypad_i2c_driver);
+}
+
+static void __exit mfld_keypad_exit(void)
+{
+ i2c_del_driver(&mfld_keypad_i2c_driver);
+ gpio_free(MFLD_KEYPAD_RST_GPIO);
+}
+
+module_init(mfld_keypad_init);
+module_exit(mfld_keypad_exit);
+
+MODULE_AUTHOR("Zheng Ba <zheng.ba@intel.com>");
+MODULE_AUTHOR("Yang Liang");
+MODULE_DESCRIPTION("Medfield keypad driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/keyboard/mfld_keypad.h b/drivers/input/keyboard/mfld_keypad.h
new file mode 100644
index 0000000..dbbf149
--- /dev/null
+++ b/drivers/input/keyboard/mfld_keypad.h
@@ -0,0 +1,56 @@
+#ifndef __MFLD_KEYPAD_H
+#define __MFLD_KEYPAD_H
+
+#include <linux/types.h>
+#include <linux/input.h>
+
+#define KEYMAP_SIZE 49 /* 6 x 8 + right_shift */
+#define SIZE_X 6
+#define SIZE_Y 8
+
+/* two keys index in keymap */
+#define FN_KEY 33 /* function key position */
+#define RIGHT_SHIFT_KEY 48 /* right shift key position */
+
+const unsigned short mfld_keymap[KEYMAP_SIZE] = {
+ KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8,
+ KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I,
+ KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K,
+ KEY_LEFTSHIFT, KEY_Z, KEY_X,
+ KEY_C, KEY_V, KEY_B, KEY_N, KEY_M,
+ KEY_9, KEY_FN, KEY_LEFTALT, KEY_SPACE,
+ KEY_DELETE, KEY_LEFT, KEY_DOWN, KEY_RIGHT,
+ KEY_LEFTCTRL, KEY_O, KEY_L, KEY_UP,
+ KEY_0, KEY_P, KEY_BACKSPACE, KEY_ENTER, KEY_RIGHTSHIFT,
+};
+
+/* Some FN_KEYS have no direct keycode */
+#define KEY_EXCLAM KEY_1 /* '!' -> shift+1 */
+#define KEY_AT KEY_2 /* '@' -> shift+2 */
+#define KEY_NUMER_SIGN KEY_3 /* '#' -> shift+3 */
+#define KEY_NOR KEY_6 /* '^' -> shift+6 */
+#define KEY_PERCENT KEY_5 /* '%' -> shift+5 */
+#define KEY_AMPERSAND KEY_7 /* '&' -> shift+7 */
+
+#define KEY_BAR KEY_BACKSLASH /* '|' -> shift+\ */
+#define KEY_COLON KEY_SEMICOLON /* ':' -> shift+; */
+#define KEY_UNDERSCORE KEY_KPMINUS /* '_' -> shift+- */
+#define KEY_QUOTE_DBL KEY_APOSTROPHE /* '"' -> shift+' */
+#define KEY_QUES KEY_SLASH /* '/' -> shift+/ */
+
+const unsigned short mfld_keymap_fn[KEYMAP_SIZE] = {
+ KEY_GRAVE, KEY_AT, KEY_NUMER_SIGN, KEY_4,
+ KEY_PERCENT, KEY_NOR, KEY_AMPERSAND, KEY_KPASTERISK,
+ KEY_BAR, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED,
+ KEY_RESERVED, KEY_BACKSLASH, KEY_SLASH, KEY_KPPLUS,
+ KEY_TAB, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED,
+ KEY_APOSTROPHE, KEY_SEMICOLON, KEY_COLON, KEY_COMMA,
+ KEY_CAPSLOCK, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED,
+ KEY_DOT, KEY_UNDERSCORE, KEY_EXCLAM, KEY_QUES,
+ KEY_9, KEY_RESERVED, KEY_RESERVED, KEY_COMMA,
+ KEY_RESERVED, KEY_RESERVED, KEY_PAGEDOWN, KEY_RESERVED,
+ KEY_RESERVED, KEY_KPMINUS, KEY_QUOTE_DBL, KEY_PAGEUP,
+ KEY_0, KEY_EQUAL, KEY_CONFIG, KEY_RESERVED, KEY_RIGHTSHIFT,
+};
+
+#endif /* __MFLD_KEYPAD_H */
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH] Input: introduce medfield keypad driver
2010-07-08 16:41 [PATCH] Input: introduce medfield keypad driver Alan Cox
@ 2010-07-08 17:42 ` Ville Syrjälä
2010-07-09 16:21 ` Dmitry Torokhov
1 sibling, 0 replies; 4+ messages in thread
From: Ville Syrjälä @ 2010-07-08 17:42 UTC (permalink / raw)
To: Alan Cox; +Cc: linux-input, dmitry.torokhov
On Thu, Jul 08, 2010 at 05:41:33PM +0100, Alan Cox wrote:
> From: Zheng Ba <zheng.ba@intel.com>
>
> An i2c bus driver for the Intel Medfield keypad interface.
>
> Signed-off-by: Zheng Ba <zheng.ba@intel.com>
> Signed-off-by: Alan Cox <alan@linux.intel.com>
> ---
>
> drivers/input/keyboard/Kconfig | 10 +
> drivers/input/keyboard/Makefile | 1
> drivers/input/keyboard/mfld_keypad.c | 613 ++++++++++++++++++++++++++++++++++
> drivers/input/keyboard/mfld_keypad.h | 56 +++
> 4 files changed, 680 insertions(+), 0 deletions(-)
> create mode 100644 drivers/input/keyboard/mfld_keypad.c
> create mode 100644 drivers/input/keyboard/mfld_keypad.h
>
>
> diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
> index 950f6d1..10cc686 100644
> --- a/drivers/input/keyboard/Kconfig
> +++ b/drivers/input/keyboard/Kconfig
> @@ -309,6 +309,16 @@ config KEYBOARD_MCS
> To compile this driver as a module, choose M here: the
> module will be called mcs_touchkey.
>
> +config KEYBOARD_MFLD
> + tristate "Medfield I2C keypad support"
> + depends on I2C && GPIOLIB && X86_MRST
> + help
> + Say Y here if you want to get support for the keypad controller
> + on the Medfield (MFLD) platform of Intel MID.
> +
> + To compile this as a module, choose M here: the
> + module will be called mfld_keypad.
> +
> config KEYBOARD_IMX
> tristate "IMX keypad support"
> depends on ARCH_MXC
> diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
> index a1f5e9d..9672c3d 100644
> --- a/drivers/input/keyboard/Makefile
> +++ b/drivers/input/keyboard/Makefile
<snip>
> +/*
> + * We cannot use I2c in interrupt context, so we just schedule work.
> + */
> +static irqreturn_t mfld_keypad_irq(int irq, void *data)
> +{
> + struct mfld_keypad_chip *tc = data;
> + schedule_work(&tc->work);
Use threaded irq?
> + return IRQ_HANDLED;
> +}
--
Ville Syrjälä
syrjala@sci.fi
http://www.sci.fi/~syrjala/
--
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] 4+ messages in thread
* Re: [PATCH] Input: introduce medfield keypad driver
2010-07-08 16:41 [PATCH] Input: introduce medfield keypad driver Alan Cox
2010-07-08 17:42 ` Ville Syrjälä
@ 2010-07-09 16:21 ` Dmitry Torokhov
2010-07-09 16:56 ` Dmitry Torokhov
1 sibling, 1 reply; 4+ messages in thread
From: Dmitry Torokhov @ 2010-07-09 16:21 UTC (permalink / raw)
To: Alan Cox; +Cc: linux-input
On Thu, Jul 08, 2010 at 05:41:33PM +0100, Alan Cox wrote:
> From: Zheng Ba <zheng.ba@intel.com>
>
> An i2c bus driver for the Intel Medfield keypad interface.
>
> Signed-off-by: Zheng Ba <zheng.ba@intel.com>
> Signed-off-by: Alan Cox <alan@linux.intel.com>
> ---
>
> drivers/input/keyboard/Kconfig | 10 +
> drivers/input/keyboard/Makefile | 1
> drivers/input/keyboard/mfld_keypad.c | 613 ++++++++++++++++++++++++++++++++++
> drivers/input/keyboard/mfld_keypad.h | 56 +++
> 4 files changed, 680 insertions(+), 0 deletions(-)
> create mode 100644 drivers/input/keyboard/mfld_keypad.c
> create mode 100644 drivers/input/keyboard/mfld_keypad.h
>
>
> diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
> index 950f6d1..10cc686 100644
> --- a/drivers/input/keyboard/Kconfig
> +++ b/drivers/input/keyboard/Kconfig
> @@ -309,6 +309,16 @@ config KEYBOARD_MCS
> To compile this driver as a module, choose M here: the
> module will be called mcs_touchkey.
>
> +config KEYBOARD_MFLD
> + tristate "Medfield I2C keypad support"
> + depends on I2C && GPIOLIB && X86_MRST
> + help
> + Say Y here if you want to get support for the keypad controller
> + on the Medfield (MFLD) platform of Intel MID.
> +
> + To compile this as a module, choose M here: the
> + module will be called mfld_keypad.
> +
> config KEYBOARD_IMX
> tristate "IMX keypad support"
> depends on ARCH_MXC
> diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
> index a1f5e9d..9672c3d 100644
> --- a/drivers/input/keyboard/Makefile
> +++ b/drivers/input/keyboard/Makefile
> @@ -14,6 +14,7 @@ obj-$(CONFIG_KEYBOARD_BFIN) += bf54x-keys.o
> obj-$(CONFIG_KEYBOARD_DAVINCI) += davinci_keyscan.o
> obj-$(CONFIG_KEYBOARD_EP93XX) += ep93xx_keypad.o
> obj-$(CONFIG_KEYBOARD_INTEL_MID) += intel_mid_keypad.o
> +obj-$(CONFIG_KEYBOARD_MFLD) += mfld_keypad.o
> obj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o
> obj-$(CONFIG_KEYBOARD_TCA6416) += tca6416-keypad.o
> obj-$(CONFIG_KEYBOARD_HIL) += hil_kbd.o
> diff --git a/drivers/input/keyboard/mfld_keypad.c b/drivers/input/keyboard/mfld_keypad.c
> new file mode 100644
> index 0000000..096349a
> --- /dev/null
> +++ b/drivers/input/keyboard/mfld_keypad.c
> @@ -0,0 +1,613 @@
> +/*
> + * drivers/input/keyboard/mfld_keypad.c
> + *
> + * Copyright (c) 2010 Intel Corporation.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope 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.,
> + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/sched.h>
> +#include <linux/mutex.h>
> +#include <linux/delay.h>
> +#include <linux/input.h>
> +#include <linux/device.h>
> +#include <linux/gpio.h>
> +#include <linux/slab.h>
> +
> +#include "mfld_keypad.h"
> +
> +/* commands to send to the chip */
> +#define MFLD_KEYPAD_CMD_RSTCTRL 0x82 /* the reset register */
> +#define MFLD_KEYPAD_CMD_EXTRSTN 0x83 /* external reset ball enable */
> +#define MFLD_KEYPAD_CMD_RSTINTCLR 0x84 /* clear the intrrupt caused
> + by power-on */
> +
> +#define MFLD_KEYPAD_CMD_READ_INT 0x91 /* get interrupt status */
> +
> +#define MFLD_KEYPAD_CMD_AUTOSLPENA 0x8B /* auto sleep enable */
> +#define MFLD_KEYPAD_CMD_AUTOSLPTIMER 0x8C /* min time to switch
> + into auto sleep */
> +#define MFLD_KEYPAD_CMD_WAKEUPEN 0x8E /* wake-up automatically
> + when i2c access */
> +#define MFLD_KEYPAD_CMD_CLKEN 0x8A /* enable internal clock */
> +#define MFLD_KEYPAD_CMD_CLKCFG 0x89 /* clock configure,2x */
> +#define MFLD_KEYPAD_CMD_IOCFG 0xA7 /* input/output configure */
> +#define MFLD_KEYPAD_CMD_IOPCEXT 0xA8 /* pull up resistor
> + programming */
> +#define MFLD_KEYPAD_CMD_IOPC0 0xAA
> +#define MFLD_KEYPAD_CMD_IOPC1 0xAC
> +#define MFLD_KEYPAD_CMD_IOPC2 0xAE
> +
> +/* keyboard read event */
> +#define MFLD_KEYPAD_FIFO_LEN 8
> +#define MFLD_KEYPAD_CMD_READ_FIFO 0x10 /* event buffer for key press,
> + EVTCODE reg */
> +#define MFLD_KEYPAD_CMD_KBDRIS 0x06 /* kbd raw interrupt register */
> +#define MFLD_KEYPAD_CMD_KBDMIS 0x07 /* kbd mask interrupt reg */
> +#define MFLD_KEYPAD_CMD_KBDIC 0x08 /* clear the kbd interrupt &
> + event buffer */
> +#define MFLD_KEYPAD_CMD_KBDMSK 0x09 /* KBD mask register */
> +#define MFLD_KEYPAD_CMD_KBDMFS 0x8F /* KBD modified feature set */
> +#define MFLD_KEYPAD_CMD_GPIODIR2 0xC8 /* gpio input/ output */
> +#define MFLD_KEYPAD_CMD_GPIODIR1 0xC7 /* bit clear after reset */
> +#define MFLD_KEYPAD_CMD_GPIODIR0 0xC6
> +#define MFLD_KEYPAD_CMD_GPIODATA2 0xC4 /* MASK & Data bit */
> +#define MFLD_KEYPAD_CMD_GPIODATA1 0xC2
> +#define MFLD_KEYPAD_CMD_GPIODATA0 0xC0
> +
> +/* About the direct key function, maybe non-used here */
> +#define MFLD_KEYPAD_CMD_DEVTCODE 0xE6 /* direct key event */
> +#define MFLD_KEYPAD_CMD_DEBOUNCE 0xE8 /* direct key debounce */
> +#define MFLD_KEYPAD_CMD_DIRECT0 0xEC /* direct key enable */
> +#define MFLD_KEYPAD_CMD_DIRECT1 0xED
> +#define MFLD_KEYPAD_CMD_DIRECT2 0xEE
> +#define MFLD_KEYPAD_CMD_DIRECT3 0xEF
> +#define MFLD_KEYPAD_CMD_DKBDRIS 0xF0 /* unmasked direct key intr */
> +#define MFLD_KEYPAD_CMD_DKBDMIS 0xF1 /* key mask interrupt */
> +#define MFLD_KEYPAD_CMD_DKBDIC 0xF2 /* direct key intr clear */
> +#define MFLD_KEYPAD_CMD_DKBDMSK 0xF3 /* direct key mask register */
> +
> +/* keyboard setting registers */
> +#define MFLD_KEYPAD_CMD_KBDSETTLE 0x01 /* setup of init wait period */
> +#define MFLD_KEYPAD_CMD_KBDBOUNCE 0x02 /* setup of de-bouncing */
> +#define MFLD_KEYPAD_CMD_KBDSIZE 0x03 /* keyboard matrix setup */
> +#define MFLD_KEYPAD_CMD_KBDDEDCFG 0x04 /* dedicated key setup */
> +
> +/* For detected */
> +#define MFLD_KEYPAD_CMD_MANUFACT 0x80 /* manufact.code */
> +#define MFLD_KEYPAD_CMD_SWVERSION 0x81 /* SW version */
> +
> +/* interrupt status */
> +#define INT_KEYPAD 0x40 /* kbd irq bit */
> +#define INT_PORIRQ 0x80 /* when power on, the bit is set */
> +#define INT_MELINT 0x08 /* more than 8 key events overflow */
> +
> +#define MFLD_KEYPAD_INT_GPIO 51
> +#define MFLD_KEYPAD_RST_GPIO 173
> +
> +/* XXX backlight controls */
> +#define PWM1CLKDIV0 0x64
> +#define PWM2CLKDIV1 0x65
> +#define PWM2DUTYCYCLE 0x69 /* brightness control */
> +
> +struct mfld_keypad_chip {
> + /* device lock */
> + struct mutex lock;
> + struct i2c_client *client;
> + struct work_struct work;
> + struct input_dev *idev;
> + bool kp_enabled;
> + bool pm_suspend;
> + char phys[32];
> + const unsigned short *keymap;
> + int size_x;
> + int size_y;
> + int shift_down;
> +};
> +
> +#define work_to_mfld_keypad(w) container_of(w, struct mfld_keypad_chip, work)
> +#define client_to_mfld_keypad(c) container_of(c, struct mfld_keypad_chip, \
> + client)
> +#define dev_to_mfld_keypad(d) container_of(c, struct mfld_keypad_chip, \
> + client->dev)
> +
> +#define MFLD_KEYPAD_MAX_DATA 8
> +
> +/*
> + * To write, access the chip's address in write mode, and dump the
> + * command and data on the bus. The command and data are taken as
> + * sequential u8s out of varargs, to a maxinum of MFLD_KEYPAD_MAX_DATA
> + */
> +static int mfld_keypad_write(struct mfld_keypad_chip *tc, int len, ...)
> +{
> + int ret, i;
> + va_list ap;
> + u8 data[MFLD_KEYPAD_MAX_DATA];
> +
> + va_start(ap, len);
> + if (len > MFLD_KEYPAD_MAX_DATA) {
> + WARN_ON(1);
> + dev_err(&tc->client->dev, "tried to send %d bytes\n", len);
> + va_end(ap);
> + return 0;
> + }
> +
> + for (i = 0; i < len; i++)
> + data[i] = va_arg(ap, int);
> + va_end(ap);
> +
> + /*
> + * If the host is asleep, send again when get NACK
> + */
> + ret = i2c_master_send(tc->client, data, len);
> + if (unlikely(ret == -EREMOTEIO))
> + ret = i2c_master_send(tc->client, data, len);
> +
> + if (unlikely(ret != len))
> + dev_err(&tc->client->dev, "sent %d bytes of %d total\n",
> + len, ret);
> +
> + return ret;
> +}
> +
> +/*
> + * To read, first send the command byte and end the transaction.
> + * Then we can get the data in read mode.
> + */
> +static int mfld_keypad_read(struct mfld_keypad_chip *tc,
> + u8 cmd, u8 *buf, int len)
> +{
> + int ret;
> + /*
> + * In case of host's asleep, send again when get NACK
> + */
> + ret = i2c_master_send(tc->client, &cmd, 1);
> + if (unlikely(ret == -EREMOTEIO))
> + ret = i2c_master_send(tc->client, &cmd, 1);
> +
> + if (unlikely(ret != 1)) {
> + dev_err(&tc->client->dev, "sending command 0x%2x failed.\n",
> + cmd);
> + return 0;
> + }
> +
> + ret = i2c_master_recv(tc->client, buf, len);
> + if (unlikely(ret != len))
> + dev_err(&tc->client->dev, "want %d bytes, got %d\n", len, ret);
> +
Maybe use i2c_transfer()? Alan, could you please CC Jean for I2C bits review?
> + return ret;
> +}
> +
> +static int mfld_keypad_configure(struct mfld_keypad_chip *tc)
> +{
> + int keysize = (tc->size_x << 4) | tc->size_y;
> +
> + mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_CLKEN, 0x01);
> + mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_KBDSIZE, keysize);
> + mfld_keypad_write(tc, 3, MFLD_KEYPAD_CMD_KBDDEDCFG, 0xFF, 0xFF);
> + mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_CLKCFG, 0x45);
> + /* maximum debounce time : 15.6ms */
> + mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_KBDBOUNCE, 0x02);
> +
> + mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_KBDMSK, 0x0F);
> + mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_DKBDMSK, 0x03);
> + mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_KBDIC, 0x83);
> + mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_DKBDIC, 0x01);
> +
> + mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_KBDMSK, 0x00);
> +
> + return 0;
> +}
> +
> +/*
> + * AT-style: low 7 bits are the keycode, and the top
> + * bit indicates the state( 1 for down, 0 for up)
> + */
> +static inline u8 mfld_keypad_whichkey(u8 event)
> +{
> + /* bit[7-4]:key row, bit[3-0]:key col */
> + u8 row, col;
> + u8 key;
> + row = (event & 0x70) >> 4;
> + col = (event & 0x0F);
> +
> + key = row * 8 + col;
> +
> + return key;
> +}
> +
> +static inline int mfld_keypad_ispress(u8 event)
> +{
> + /* 1: pressed, 0: released */
> + return (event & 0x80) ? 0 : 1;
> +}
> +
> +/* Reset the keybit of input */
> +static void set_keymap_bit(struct mfld_keypad_chip *tc)
> +{
> + int i;
> + for (i = 0; i < KEYMAP_SIZE; i++)
> + __set_bit(tc->keymap[i], tc->idev->keybit);
> +}
> +
> +/* Shift key report needed? */
> +static int shift_key_needed(struct mfld_keypad_chip *tc, u8 key)
> +{
> + if (tc->keymap == mfld_keymap_fn) {
> + switch (key) {
> + case 0: /* '~' */
> + case 1: /* '@' */
> + case 2: /* '#' */
> + case 3: /* '$' */
> + case 4: /* '%' */
> + case 5: /* '^' */
> + case 6: /* '&' */
> + case 8: /* '|' */
> + case 22: /* ':' */
> + case 29: /* '_' */
> + case 30: /* '!' */
> + case 31: /* '?' */
> + case 32: /* '(' */
> + case 42: /* '"' */
> + case 44: /* ')' */
> + return 1;
> + default:
> + return 0;
> + }
> + }
> + return 0;
> +}
> +
> +/* Report the 'right shift' key */
> +static void report_shift_key(struct mfld_keypad_chip *tc, u8 key, int isdown)
> +{
> + if (tc->kp_enabled) {
> + /* The right shift key stored at last position:47 */
> + input_report_key(tc->idev, tc->keymap[RIGHT_SHIFT_KEY], isdown);
> + input_sync(tc->idev);
> + }
> +}
> +
> +/* Report the key code */
> +static void submit_key(struct mfld_keypad_chip *tc, u8 key,
> + unsigned short keycode, int isdown)
> +{
> + /*
> + * Translate the non-exist keycode keys.
> + * when key press down, report the 'shift' key pressed ahead.
> + */
This is cute, French, Czech and host of other nationalities will surely
appreciate.
Seriously, this stuff should be handled either by legacy keyboard driver
keymaps or in userspace by X or whatever replaces it, with proper
international keymaps, etc, etc, but not in kernel. Please remove it
from this driver as well as the other keyboard driver you posted.
> +
> + if (shift_key_needed(tc, key) && isdown)
> + report_shift_key(tc, key, isdown);
> +
> + /* report the key */
> + if (tc->kp_enabled) {
> + input_report_key(tc->idev, keycode, isdown);
> + input_sync(tc->idev);
> + }
> +
> + /*
> + * When key press up, report the 'shift' up followed.
> + */
> + if (shift_key_needed(tc, key) && !isdown)
> + report_shift_key(tc, key, isdown);
> +
> + /*
> + * if no keys are pressed anymore, remove the 'shift'
> + */
> + if (i2c_smbus_read_byte_data(tc->client, 0x0B) == 0x7F) {
> + input_report_key(tc->idev, KEY_RIGHTSHIFT, 0);
> + input_report_key(tc->idev, KEY_LEFTSHIFT, 0);
> + input_sync(tc->idev);
> + }
> +}
> +
> +/* Key event interrupt handler */
> +static inline void process_keys(struct mfld_keypad_chip *tc)
> +{
> + int ret;
> + u8 key_queue[MFLD_KEYPAD_FIFO_LEN];
> + int tail = 0;
> +
> + u8 evtcode;
> +
> + ret = mfld_keypad_read(tc, MFLD_KEYPAD_CMD_READ_FIFO, &evtcode, 1);
> + if (ret < 0) {
> + dev_err(&tc->client->dev, "Failed reading fifo\n");
> + return;
> + }
> +
> + /* clear event buffer */
> + mfld_keypad_write(tc, 2, MFLD_KEYPAD_CMD_KBDIC, 0x83);
> +
> + if (evtcode != 0x7F && evtcode != 0xFF) {
> + u8 key = mfld_keypad_whichkey(evtcode);
> + int isdown = mfld_keypad_ispress(evtcode);
> + unsigned short keycode = tc->keymap[key];
> +
> + /* The function key pressed */
> + if (key == FN_KEY && isdown) {
> + tc->keymap = mfld_keymap_fn;
> + set_keymap_bit(tc);
> + keycode = tc->keymap[key];
> + }
> +
> + /* Function key press up */
> + if (key == FN_KEY && !isdown) {
> + /*
> + * dequeue the key_queue,
> + * where keys stored while FN is pressed
> + */
> + int j;
> + for (j = 0; j < tail; j++) /* keys up */
> + submit_key(tc, key_queue[j],
> + mfld_keymap_fn[key_queue[j]], 0);
> + tail = 0;
> +
> + tc->keymap = mfld_keymap;
> + set_keymap_bit(tc);
> + }
> +
> + if (tc->keymap == mfld_keymap_fn)
> + key_queue[tail++] = key;
> +
> + submit_key(tc, key, keycode, isdown);
> + }
> +}
> +
> +/*
> + * Bottom Half: handle the interrupt by posting key events, or dealing with
> + * errors appropriately
> + */
> +static void mfld_keypad_work(struct work_struct *work)
> +{
> + struct mfld_keypad_chip *tc = work_to_mfld_keypad(work);
> + u8 ints = 0;
> +
> + mutex_lock(&tc->lock);
> + while ((mfld_keypad_read(tc, MFLD_KEYPAD_CMD_READ_INT, &ints, 1) == 1)
> + && ints) {
> + if (likely(ints & INT_KEYPAD))
> + process_keys(tc);
> + }
> +
> + mutex_unlock(&tc->lock);
> +}
> +
> +/*
> + * We cannot use I2c in interrupt context, so we just schedule work.
> + */
> +static irqreturn_t mfld_keypad_irq(int irq, void *data)
> +{
> + struct mfld_keypad_chip *tc = data;
> + schedule_work(&tc->work);
> + return IRQ_HANDLED;
> +}
> +
> +/* Return 0 if detection is successful, -ENODEV otherwise */
> +static int mfld_keypad_detect(struct i2c_client *client,
> + struct i2c_board_info *info)
> +{
> + struct i2c_adapter *adapter = client->adapter;
> + int manufact, sw_ver;
> +
> + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
> +
> + dev_err(&client->dev, "error checking function\n");
> + return -ENODEV;
> + }
> +
> + /* validate vendor and revison */
> + manufact = i2c_smbus_read_byte_data(client,
> + MFLD_KEYPAD_CMD_MANUFACT);
> + sw_ver = i2c_smbus_read_byte_data(client,
> + MFLD_KEYPAD_CMD_SWVERSION);
> + if (manufact != 0x03 || sw_ver != 0xC0) {
> + dev_err(&client->dev, "error checking vendor and revision\n");
> + return -ENODEV;
> + }
> +
> + i2c_smbus_write_byte_data(client, MFLD_KEYPAD_CMD_RSTINTCLR, 0x01);
> +
> + dev_dbg(&client->dev, "Successfully detected the i2c keypad\n");
> + strlcpy(info->type, "mfld_keypad", I2C_NAME_SIZE);
> +
> + return 0;
> +}
> +
> +static int __devinit mfld_keypad_probe(struct i2c_client *client,
> + const struct i2c_device_id *id)
> +{
> + struct input_dev *idev;
> + struct mfld_keypad_chip *tc;
> + unsigned int irq;
> + int i, err;
> + int gpio;
> +
> + tc = kzalloc(sizeof(*tc), GFP_KERNEL);
> + idev = input_allocate_device();
> + if (!tc || !idev) {
> + err = -ENOMEM;
> + goto fail1;
> + }
> +
> + i2c_set_clientdata(client, tc);
> + tc->client = client;
> + tc->idev = idev;
> + tc->keymap = mfld_keymap;
> +
> + mutex_init(&tc->lock);
> + INIT_WORK(&tc->work, mfld_keypad_work);
> +
> + tc->size_x = SIZE_X;
> + tc->size_y = SIZE_Y;
> + dev_vdbg(&client->dev, "Keypad size: %d x %d\n",
> + tc->size_x, tc->size_y);
> +
> + mfld_keypad_configure(tc);
> +
> + tc->kp_enabled = true;
Why do you need this flag?
> +
> + idev->name = "mfld_keypad";
Setting up phys and parent device would be nice.
> + idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) |
> + BIT_MASK(EV_REL);
> + idev->keycode = (unsigned short *)mfld_kesound;
This cast indicates that the driver is doing wrong thing. Keymaps can be
changed via EVIOCSKEYCODE. You need to allocate a copy of the keymap that
is private to the device.
> + idev->keycodesize = sizeof(unsigned short);
> + idev->keycodemax = sizeof(mfld_keymap);
> +
> + for (i = 0; i < KEYMAP_SIZE; i++)
> + set_bit(mfld_keymap[i], idev->keybit);
> +
> + err = input_register_device(idev);
> + if (err) {
> + dev_dbg(&client->dev, "error register input device\n");
> + goto fail1;
> + }
> +
> + gpio = MFLD_KEYPAD_INT_GPIO;
> + irq = gpio_to_irq(gpio);
> + if (irq < 0) {
> + dev_dbg(&client->dev, "error acquire IRQ to GPIO %d\n", gpio);
Could you please fix the error messages so they sound a bit better?
> + err = irq;
> + goto fail2;
> + }
> + client->irq = irq;
> +
> + err = request_irq(irq, mfld_keypad_irq,
> + IRQF_SHARED | IRQ_TYPE_EDGE_FALLING,
IRQ_TYPE_EDGE_FALLING is not supposed to be used here,
IRQF_TRIGGER_FALLING is I believe. Also, as Ville mentioned, this shoudl
be threaded IRQ.
> + "mfld_keypad", tc);
> + if (err) {
> + dev_err(&client->dev, "could not get IRQ %d\n", irq);
> + goto fail2;
> + }
> +
> + return 0;
> +
> +fail2:
> + input_unregister_device(idev);
> + idev = NULL;
> +fail1:
> + input_free_device(idev);
> + kfree(tc);
> + return err;
> +}
> +
> +static int __devexit mfld_keypad_remove(struct i2c_client *client)
> +{
> + struct mfld_keypad_chip *tc = i2c_get_clientdata(client);
> +
> + free_irq(client->irq, tc);
> + cancel_work_sync(&tc->work);
> +
> + input_unregister_device(tc->idev);
> +
> + kfree(tc);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +/*
> + * The chip can switch off when there is no activity, so
> + * explicit suspend is not needed
> + */
> +
> +static int mfld_keypad_suspend(struct i2c_client *client, pm_message_t mesg)
> +{
> + struct mfld_keypad_chip *tc = i2c_get_clientdata(client);
> +
> + set_irq_wake(client->irq, 0);
Hmm, I think you meant to enable wakeups here, no? Most drivers do:
if (device_may_wakeup(&client->dev))
enable_irq_wake(client->irq);
and do
device_init_wakeup(&pdev->dev, 1);
in probe().
> + disable_irq(client->irq);
> +
> + mutex_lock(&tc->lock);
> + tc->pm_suspend = true;
Useless locking here. And the flag itself.
> + mutex_unlock(&tc->lock);
> +
> + return 0;
> +}
> +
> +static int mfld_keypad_resume(struct i2c_client *client)
> +{
> + struct mfld_keypad_chip *tc = i2c_get_clientdata(client);
> +
> + mutex_lock(&tc->lock);
> + tc->pm_suspend = false;
> + mutex_unlock(&tc->lock);
> +
> + enable_irq(client->irq);
> + set_irq_wake(client->irq, 1);
> +
> + return 0;
> +}
> +
> +#else
> +#define mfld_keypad_suspend NULL
> +#define mfld_keypad_resume NULL
> +#endif
> +
> +/* Address to scan: 0x45-i2c slave addr*/
> +static const unsigned short normal_i2c[] = { 0x45, I2C_CLIENT_END };
> +
> +static const struct i2c_device_id mfld_keypad_id[] = {
> + {"mfld_keypad", 0},
> + {}
> +};
> +
> +static struct i2c_driver mfld_keypad_i2c_driver = {
> + .driver = {
> + .name = "mfld_keypad",
> + },
> + .probe = mfld_keypad_probe,
> + .remove = __devexit_p(mfld_keypad_remove),
> + .suspend = mfld_keypad_suspend,
> + .resume = mfld_keypad_resume,
> + .id_table = mfld_keypad_id,
> + .detect = mfld_keypad_detect,
> + .address_list = normal_i2c,
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, mfld_keypad_id);
> +
> +static int __init mfld_keypad_init(void)
> +{
> + /*
> + * TODO
> + * Should add platform detect here before hardware reset, or if
> + * it loaded on a non-mfld system it might do unintended things.
> + */
> + int gpio = MFLD_KEYPAD_RST_GPIO;
> + gpio_request(gpio, "mfld_keypad");
Error handling?
> + /* hardware reset */
> + gpio_direction_output(gpio, 0);
> + gpio_direction_output(gpio, 1);
Does this belong here and not in detect/probe?
> + return i2c_add_driver(&mfld_keypad_i2c_driver);
i2c_register_driver() is more common.
> +}
> +
> +static void __exit mfld_keypad_exit(void)
> +{
> + i2c_del_driver(&mfld_keypad_i2c_driver);
> + gpio_free(MFLD_KEYPAD_RST_GPIO);
> +}
> +
> +module_init(mfld_keypad_init);
> +module_exit(mfld_keypad_exit);
> +
> +MODULE_AUTHOR("Zheng Ba <zheng.ba@intel.com>");
> +MODULE_AUTHOR("Yang Liang");
> +MODULE_DESCRIPTION("Medfield keypad driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/input/keyboard/mfld_keypad.h b/drivers/input/keyboard/mfld_keypad.h
> new file mode 100644
> index 0000000..dbbf149
> --- /dev/null
> +++ b/drivers/input/keyboard/mfld_keypad.h
> @@ -0,0 +1,56 @@
> +#ifndef __MFLD_KEYPAD_H
> +#define __MFLD_KEYPAD_H
> +
> +#include <linux/types.h>
> +#include <linux/input.h>
> +
> +#define KEYMAP_SIZE 49 /* 6 x 8 + right_shift */
> +#define SIZE_X 6
> +#define SIZE_Y 8
> +
> +/* two keys index in keymap */
> +#define FN_KEY 33 /* function key position */
> +#define RIGHT_SHIFT_KEY 48 /* right shift key position */
> +
> +const unsigned short mfld_keymap[KEYMAP_SIZE] = {
> + KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8,
> + KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I,
> + KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K,
> + KEY_LEFTSHIFT, KEY_Z, KEY_X,
> + KEY_C, KEY_V, KEY_B, KEY_N, KEY_M,
> + KEY_9, KEY_FN, KEY_LEFTALT, KEY_SPACE,
> + KEY_DELETE, KEY_LEFT, KEY_DOWN, KEY_RIGHT,
> + KEY_LEFTCTRL, KEY_O, KEY_L, KEY_UP,
> + KEY_0, KEY_P, KEY_BACKSPACE, KEY_ENTER, KEY_RIGHTSHIFT,
> +};
> +
> +/* Some FN_KEYS have no direct keycode */
> +#define KEY_EXCLAM KEY_1 /* '!' -> shift+1 */
> +#define KEY_AT KEY_2 /* '@' -> shift+2 */
> +#define KEY_NUMER_SIGN KEY_3 /* '#' -> shift+3 */
> +#define KEY_NOR KEY_6 /* '^' -> shift+6 */
> +#define KEY_PERCENT KEY_5 /* '%' -> shift+5 */
> +#define KEY_AMPERSAND KEY_7 /* '&' -> shift+7 */
> +
> +#define KEY_BAR KEY_BACKSLASH /* '|' -> shift+\ */
> +#define KEY_COLON KEY_SEMICOLON /* ':' -> shift+; */
> +#define KEY_UNDERSCORE KEY_KPMINUS /* '_' -> shift+- */
> +#define KEY_QUOTE_DBL KEY_APOSTROPHE /* '"' -> shift+' */
> +#define KEY_QUES KEY_SLASH /* '/' -> shift+/ */
> +
> +const unsigned short mfld_keymap_fn[KEYMAP_SIZE] = {
> + KEY_GRAVE, KEY_AT, KEY_NUMER_SIGN, KEY_4,
> + KEY_PERCENT, KEY_NOR, KEY_AMPERSAND, KEY_KPASTERISK,
> + KEY_BAR, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED,
> + KEY_RESERVED, KEY_BACKSLASH, KEY_SLASH, KEY_KPPLUS,
> + KEY_TAB, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED,
> + KEY_APOSTROPHE, KEY_SEMICOLON, KEY_COLON, KEY_COMMA,
> + KEY_CAPSLOCK, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED,
> + KEY_DOT, KEY_UNDERSCORE, KEY_EXCLAM, KEY_QUES,
> + KEY_9, KEY_RESERVED, KEY_RESERVED, KEY_COMMA,
> + KEY_RESERVED, KEY_RESERVED, KEY_PAGEDOWN, KEY_RESERVED,
> + KEY_RESERVED, KEY_KPMINUS, KEY_QUOTE_DBL, KEY_PAGEUP,
> + KEY_0, KEY_EQUAL, KEY_CONFIG, KEY_RESERVED, KEY_RIGHTSHIFT,
> +};
> +
> +#endif /* __MFLD_KEYPAD_H */
>
I do not see the reason for a separate file.
Thanks.
--
Dmitry
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] Input: introduce medfield keypad driver
2010-07-09 16:21 ` Dmitry Torokhov
@ 2010-07-09 16:56 ` Dmitry Torokhov
0 siblings, 0 replies; 4+ messages in thread
From: Dmitry Torokhov @ 2010-07-09 16:56 UTC (permalink / raw)
To: Alan Cox; +Cc: linux-input
On Fri, Jul 09, 2010 at 09:21:13AM -0700, Dmitry Torokhov wrote:
> On Thu, Jul 08, 2010 at 05:41:33PM +0100, Alan Cox wrote:
>
> > + return i2c_add_driver(&mfld_keypad_i2c_driver);
>
> i2c_register_driver() is more common.
>
Ignore me here, I am mistaken.
--
Dmitry
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2010-07-09 16:56 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-07-08 16:41 [PATCH] Input: introduce medfield keypad driver Alan Cox
2010-07-08 17:42 ` Ville Syrjälä
2010-07-09 16:21 ` Dmitry Torokhov
2010-07-09 16:56 ` Dmitry Torokhov
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).