[PATCH 11/33] add tsc2301 keypad driver

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

 



From: Felipe Balbi <felipe.balbi@xxxxxxxxx>

Signed-off-by: Felipe Balbi <felipe.balbi@xxxxxxxxx>
---
 drivers/input/keyboard/Kconfig      |    6 +
 drivers/input/keyboard/Makefile     |    1 +
 drivers/input/keyboard/tsc2301_kp.c |  475 +++++++++++++++++++++++++++++++++++
 3 files changed, 482 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/tsc2301_kp.c

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 04e6c77..e4d0436 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -279,6 +279,12 @@ config OMAP_PS2
 	  To compile this driver as a module, choose M here: the
 	  module will be called innovator_ps2.
 
+config KEYBOARD_TSC2301
+	tristate "TSC2301 keypad support"
+	depends on SPI_TSC2301
+	help
+	  Say Y here for if you are using the keypad features of TSC2301.
+
 config KEYBOARD_LM8323
 	tristate "LM8323 keypad chip"
 	depends on I2C
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index a19e04b..ae47fff 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_KEYBOARD_HIL)		+= hil_kbd.o
 obj-$(CONFIG_KEYBOARD_HIL_OLD)		+= hilkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
 obj-$(CONFIG_OMAP_PS2)			+= innovator_ps2.o
+obj-$(CONFIG_KEYBOARD_TSC2301)		+= tsc2301_kp.o
 obj-$(CONFIG_KEYBOARD_LM8323)		+= lm8323.o
 obj-$(CONFIG_KEYBOARD_TWL4030)		+= omap-twl4030keypad.o
 obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
diff --git a/drivers/input/keyboard/tsc2301_kp.c b/drivers/input/keyboard/tsc2301_kp.c
new file mode 100644
index 0000000..a974a5f
--- /dev/null
+++ b/drivers/input/keyboard/tsc2301_kp.c
@@ -0,0 +1,475 @@
+/*
+ * TSC2301 keypad driver
+ *
+ * Copyright (C) 2005-2006 Nokia Corporation
+ *
+ * Written by Jarkko Oikarinen
+ * Rewritten by Juha Yrjola <juha.yrjola@xxxxxxxxx>
+ *
+ * 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/input.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+
+#include <linux/spi/tsc2301.h>
+
+#define TSC2301_KEYBOARD_PRODUCT_ID      0x0051
+#define TSC2301_KEYBOARD_PRODUCT_VERSION 0x0001
+#define TSC2301_DEBOUNCE_TIME_2MS        0x0000
+#define TSC2301_DEBOUNCE_TIME_10MS       0x0800
+#define TSC2301_DEBOUNCE_TIME_20MS       0x1000
+#define TSC2301_DEBOUNCE_TIME_50MS       0x1800
+#define TSC2301_DEBOUNCE_TIME_60MS       0x2000
+#define TSC2301_DEBOUNCE_TIME_80MS       0x2800
+#define TSC2301_DEBOUNCE_TIME_100MS      0x3000
+#define TSC2301_DEBOUNCE_TIME_120MS      0x3800
+
+#define TSC2301_DEBOUNCE_TIME		TSC2301_DEBOUNCE_TIME_20MS
+
+#define TSC2301_RELEASE_TIMEOUT		50
+
+struct tsc2301_kp {
+	struct input_dev	*idev;
+	char			phys[32];
+	spinlock_t		lock;
+	struct mutex		mutex;
+	struct timer_list	timer;
+	u16			keys_pressed;
+	unsigned		pending:1;
+	unsigned		user_disabled:1;
+	unsigned		disable_depth;
+
+	struct spi_transfer	read_xfer[4];
+	struct spi_message	read_msg;
+
+	u16			data;
+	u16			mask;
+
+	int			irq;
+	s16			keymap[16];
+};
+
+static inline int tsc2301_kp_disabled(struct tsc2301 *tsc)
+{
+	return tsc->kp->disable_depth != 0;
+}
+
+static void tsc2301_kp_send_key_events(struct tsc2301 *tsc,
+				       u16 prev_state,
+				       u16 new_state)
+{
+	struct tsc2301_kp *kp = tsc->kp;
+	u16 common, released, pressed;
+	int i;
+
+	common = prev_state & new_state;
+	released = common ^ prev_state;
+	pressed = common ^ new_state;
+	if (!released && !pressed)
+		return;
+	for (i = 0; i < 16 && (released || pressed); i++) {
+		if (released & 1) {
+			dev_dbg(&tsc->spi->dev, "key %d released\n", i);
+			input_report_key(kp->idev, kp->keymap[i], 0);
+		}
+		released >>= 1;
+		if (pressed & 1) {
+			dev_dbg(&tsc->spi->dev, "key %d pressed\n", i);
+			input_report_key(kp->idev, kp->keymap[i], 1);
+		}
+		pressed >>= 1;
+	}
+	input_sync(kp->idev);
+}
+
+static inline void _filter_out(struct tsc2301 *tsc, u16 prev_state,
+			       u16 *new_state, int row1, int row2, u8 rect_pat)
+{
+	u16 mask;
+
+	mask = (rect_pat << (row1 * 4)) | (rect_pat << (row2 * 4));
+	mask &= ~prev_state;
+	*new_state &= ~mask;
+	dev_dbg(&tsc->spi->dev, "filtering ghost keys %02x\n", mask);
+}
+
+static void tsc2301_filter_ghost_keys(struct tsc2301 *tsc, u16 prev_state,
+				      u16 *new_state)
+{
+	int row1, row2;
+	u16 key_map;
+	u16 row1_map;
+	static const u8 rect_pat[] = {
+		0x3, 0x5, 0x9, 0x6, 0xa, 0xc, 0,
+	};
+
+	key_map = *new_state;
+	for (row1 = 0; row1 < 4; row1++) {
+		row1_map = (key_map >> (row1 * 4)) & 0xf;
+		if (!row1_map)
+			continue;
+		for (row2 = row1 + 1; row2 < 4; row2++) {
+			u16 rect_map = (key_map >> (row2 * 4)) & 0xf;
+			const u8 *rp;
+
+			rect_map &= row1_map;
+			if (!rect_map)
+				continue;
+			for (rp = rect_pat; *rp; rp++)
+				if ((rect_map & *rp) == *rp)
+					_filter_out(tsc, prev_state, new_state,
+						    row1, row2, *rp);
+		}
+	}
+}
+
+static void tsc2301_kp_timer(unsigned long arg)
+{
+	struct tsc2301 *tsc = (void *) arg;
+	struct tsc2301_kp *kp = tsc->kp;
+	unsigned long flags;
+
+	tsc2301_kp_send_key_events(tsc, kp->keys_pressed, 0);
+	spin_lock_irqsave(&kp->lock, flags);
+	kp->keys_pressed = 0;
+	spin_unlock_irqrestore(&kp->lock, flags);
+}
+
+static void tsc2301_kp_rx(void *arg)
+{
+	struct tsc2301 *tsc = arg;
+	struct tsc2301_kp *kp = tsc->kp;
+	unsigned long flags;
+	u16 kp_data;
+
+	kp_data = kp->data;
+	dev_dbg(&tsc->spi->dev, "KP data %04x\n", kp_data);
+
+	tsc2301_filter_ghost_keys(tsc, kp->keys_pressed, &kp_data);
+	tsc2301_kp_send_key_events(tsc, kp->keys_pressed, kp_data);
+	spin_lock_irqsave(&kp->lock, flags);
+	kp->keys_pressed = kp_data;
+	kp->pending = 0;
+	spin_unlock_irqrestore(&kp->lock, flags);
+}
+
+static irqreturn_t tsc2301_kp_irq_handler(int irq, void *dev_id)
+{
+	struct tsc2301 *tsc = dev_id;
+	struct tsc2301_kp *kp = tsc->kp;
+	unsigned long flags;
+	int r;
+
+	spin_lock_irqsave(&kp->lock, flags);
+	if (tsc2301_kp_disabled(tsc)) {
+		spin_unlock_irqrestore(&kp->lock, flags);
+		return IRQ_HANDLED;
+	}
+	kp->pending = 1;
+	spin_unlock_irqrestore(&kp->lock, flags);
+	mod_timer(&kp->timer,
+		 jiffies + msecs_to_jiffies(TSC2301_RELEASE_TIMEOUT));
+	r = spi_async(tsc->spi, &tsc->kp->read_msg);
+	if (r)
+		dev_err(&tsc->spi->dev, "kp: spi_async() failed");
+	return IRQ_HANDLED;
+}
+
+static void tsc2301_kp_start_scan(struct tsc2301 *tsc)
+{
+	tsc2301_write_reg(tsc, TSC2301_REG_KPMASK, tsc->kp->mask);
+	tsc2301_write_reg(tsc, TSC2301_REG_KEY, TSC2301_DEBOUNCE_TIME);
+}
+
+static void tsc2301_kp_stop_scan(struct tsc2301 *tsc)
+{
+	tsc2301_write_reg(tsc, TSC2301_REG_KEY, 1 << 14);
+}
+
+/* Must be called with the mutex held */
+static void tsc2301_kp_enable(struct tsc2301 *tsc)
+{
+	struct tsc2301_kp *kp = tsc->kp;
+	unsigned long flags;
+
+	spin_lock_irqsave(&kp->lock, flags);
+	BUG_ON(!tsc2301_kp_disabled(tsc));
+	if (--kp->disable_depth != 0) {
+		spin_unlock_irqrestore(&kp->lock, flags);
+		return;
+	}
+	spin_unlock_irqrestore(&kp->lock, flags);
+
+	set_irq_type(kp->irq, IRQ_TYPE_EDGE_FALLING);
+	tsc2301_kp_start_scan(tsc);
+	enable_irq(kp->irq);
+}
+
+/* Must be called with the mutex held */
+static int tsc2301_kp_disable(struct tsc2301 *tsc, int release_keys)
+{
+	struct tsc2301_kp *kp = tsc->kp;
+	unsigned long flags;
+
+	spin_lock_irqsave(&kp->lock, flags);
+	if (kp->disable_depth++ != 0) {
+		spin_unlock_irqrestore(&kp->lock, flags);
+		goto out;
+	}
+	disable_irq_nosync(kp->irq);
+	set_irq_type(kp->irq, IRQ_TYPE_NONE);
+	spin_unlock_irqrestore(&kp->lock, flags);
+
+	while (kp->pending) {
+		msleep(1);
+	}
+
+	tsc2301_kp_stop_scan(tsc);
+out:
+	if (!release_keys)
+		del_timer(&kp->timer); /* let timeout release keys */
+
+	return 0;
+}
+
+/* The following workaround is needed for a HW bug triggered by the
+ * following:
+ * 1. keep any key pressed
+ * 2. disable keypad
+ * 3. release all keys
+ * 4. reenable keypad
+ * 5. disable touch screen controller
+ *
+ * After this the keypad scanner will get stuck in busy state and won't
+ * report any interrupts for further keypresses. One way to recover is to
+ * restart the keypad scanner whenever we enable / disable the
+ * touchscreen controller.
+ */
+void tsc2301_kp_restart(struct tsc2301 *tsc)
+{
+	if (!tsc2301_kp_disabled(tsc)) {
+		tsc2301_kp_start_scan(tsc);
+	}
+}
+
+static ssize_t tsc2301_kp_disable_show(struct device *dev,
+				       struct device_attribute *attr, char *buf)
+{
+	struct tsc2301		*tsc = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", tsc2301_kp_disabled(tsc) ? 1 : 0);
+}
+
+static ssize_t tsc2301_kp_disable_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	struct tsc2301		*tsc = dev_get_drvdata(dev);
+	struct tsc2301_kp	*kp = tsc->kp;
+	char *endp;
+	int i;
+
+	i = simple_strtoul(buf, &endp, 10);
+	i = i ? 1 : 0;
+
+	mutex_lock(&kp->mutex);
+	if (i == kp->user_disabled) {
+		mutex_unlock(&kp->mutex);
+		return count;
+	}
+	kp->user_disabled = i;
+
+	if (i)
+		tsc2301_kp_disable(tsc, 1);
+	else
+		tsc2301_kp_enable(tsc);
+	mutex_unlock(&kp->mutex);
+
+	return count;
+}
+
+static DEVICE_ATTR(disable_kp, 0664, tsc2301_kp_disable_show,
+		   tsc2301_kp_disable_store);
+
+static const u16 tsc2301_kp_read_data = 0x8000 | TSC2301_REG_KPDATA;
+
+static void tsc2301_kp_setup_spi_xfer(struct tsc2301 *tsc)
+{
+	struct tsc2301_kp *kp = tsc->kp;
+	struct spi_message *m = &kp->read_msg;
+	struct spi_transfer *x = &kp->read_xfer[0];
+
+	spi_message_init(&kp->read_msg);
+
+	x->tx_buf = &tsc2301_kp_read_data;
+	x->len = 2;
+	spi_message_add_tail(x, m);
+	x++;
+
+	x->rx_buf = &kp->data;
+	x->len = 2;
+	spi_message_add_tail(x, m);
+
+	m->complete = tsc2301_kp_rx;
+	m->context = tsc;
+}
+
+#ifdef CONFIG_PM
+int tsc2301_kp_suspend(struct tsc2301 *tsc)
+{
+	struct tsc2301_kp *kp = tsc->kp;
+
+	mutex_lock(&kp->mutex);
+	tsc2301_kp_disable(tsc, 1);
+	mutex_unlock(&kp->mutex);
+	return 0;
+}
+
+void tsc2301_kp_resume(struct tsc2301 *tsc)
+{
+	struct tsc2301_kp *kp = tsc->kp;
+
+	mutex_lock(&kp->mutex);
+	tsc2301_kp_enable(tsc);
+	mutex_unlock(&kp->mutex);
+}
+#endif
+
+int __devinit tsc2301_kp_init(struct tsc2301 *tsc,
+			      struct tsc2301_platform_data *pdata)
+{
+	struct input_dev *idev;
+	struct tsc2301_kp *kp;
+	int r, i;
+	u16 mask;
+
+	if (pdata->keyb_int < 0) {
+		dev_err(&tsc->spi->dev, "need kbirq");
+		return -EINVAL;
+	}
+
+	kp = kzalloc(sizeof(*kp), GFP_KERNEL);
+	if (kp == NULL)
+		return -ENOMEM;
+	tsc->kp = kp;
+
+	kp->irq = pdata->keyb_int;
+	spin_lock_init(&kp->lock);
+	mutex_init(&kp->mutex);
+
+	init_timer(&kp->timer);
+	kp->timer.data = (unsigned long) tsc;
+	kp->timer.function = tsc2301_kp_timer;
+
+	idev = input_allocate_device();
+	if (idev == NULL) {
+		r = -ENOMEM;
+		goto err1;
+	}
+	if (pdata->keyb_name)
+		idev->name = pdata->keyb_name;
+	else
+		idev->name = "TSC2301 keypad";
+	snprintf(kp->phys, sizeof(kp->phys), "%s/input-kp", tsc->spi->dev.bus_id);
+	idev->phys = kp->phys;
+
+	mask = 0;
+	idev->evbit[0] = BIT(EV_KEY);
+	for (i = 0; i < 16; i++) {
+		if (pdata->keymap[i] > 0) {
+			set_bit(pdata->keymap[i], idev->keybit);
+			kp->keymap[i] = pdata->keymap[i];
+		} else {
+			kp->keymap[i] = -1;
+			mask |= 1 << i;
+		}
+	}
+
+	if (pdata->kp_rep)
+		set_bit(EV_REP, idev->evbit);
+
+	kp->idev = idev;
+
+	tsc2301_kp_setup_spi_xfer(tsc);
+
+	r = device_create_file(&tsc->spi->dev, &dev_attr_disable_kp);
+	if (r < 0)
+		goto err2;
+
+	tsc2301_kp_start_scan(tsc);
+
+	/* IRQ mode 0 is faulty, it can cause the KBIRQ to get stuck.
+	 * Mode 2 deasserts the IRQ at:
+	 * - HW or SW reset
+	 * - Setting SCS flag in REG_KEY register
+	 * - Releasing all keys
+	 * - Reading the REG_KPDATA
+	 */
+	tsc2301_write_kbc(tsc, 2);
+
+	tsc2301_write_reg(tsc, TSC2301_REG_KPMASK, mask);
+	kp->mask = mask;
+
+	set_irq_type(kp->irq, IRQ_TYPE_EDGE_FALLING);
+
+	r = request_irq(kp->irq, tsc2301_kp_irq_handler, IRQF_SAMPLE_RANDOM,
+			"tsc2301-kp", tsc);
+	if (r < 0) {
+		dev_err(&tsc->spi->dev, "unable to get kbirq IRQ");
+		goto err3;
+	}
+	set_irq_wake(kp->irq, 1);
+
+	/* We need to read the register once..? */
+	tsc2301_read_reg(tsc, TSC2301_REG_KPDATA);
+
+	r = input_register_device(idev);
+	if (r < 0) {
+		dev_err(&tsc->spi->dev, "can't register keypad device\n");
+		goto err4;
+	}
+
+	return 0;
+
+err4:
+	free_irq(kp->irq, tsc);
+err3:
+	tsc2301_kp_stop_scan(tsc);
+	device_remove_file(&tsc->spi->dev, &dev_attr_disable_kp);
+err2:
+	input_free_device(kp->idev);
+err1:
+	kfree(kp);
+	return r;
+}
+
+void __devexit tsc2301_kp_exit(struct tsc2301 *tsc)
+{
+	struct tsc2301_kp *kp = tsc->kp;
+
+	tsc2301_kp_disable(tsc, 1);
+	input_unregister_device(kp->idev);
+	free_irq(kp->irq, tsc);
+	device_remove_file(&tsc->spi->dev, &dev_attr_disable_kp);
+
+	kfree(kp);
+}
-- 
1.6.0.1.141.g445ca

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Arm (vger)]     [ARM Kernel]     [ARM MSM]     [Linux Tegra]     [Linux WPAN Networking]     [Linux Wireless Networking]     [Maemo Users]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux