All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/3] mfd: add STMPExxxx I/O Expander support
@ 2010-05-31 12:17 Rabin Vincent
  2010-05-31 12:17 ` [PATCH 2/3] gpio: add STMPExxxx GPIO driver Rabin Vincent
                   ` (2 more replies)
  0 siblings, 3 replies; 61+ messages in thread
From: Rabin Vincent @ 2010-05-31 12:17 UTC (permalink / raw)
  To: Samuel Ortiz
  Cc: linux-kernel, STEricsson_nomadik_linux, Rabin Vincent, Linus Walleij

Add support for the STMPExxxx family of I/O Expanders from
STMicroelectronics.  These devices include upto 24 gpios, a PWM
controller, and a keypad controller.  This patch adds the MFD core.

Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
---
 drivers/mfd/Kconfig       |   12 +
 drivers/mfd/Makefile      |    1 +
 drivers/mfd/stmpe.c       |  600 +++++++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/stmpe.h |  135 ++++++++++
 4 files changed, 748 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/stmpe.c
 create mode 100644 include/linux/mfd/stmpe.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 9da0e50..e4ee19d 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -177,6 +177,18 @@ config TWL4030_CODEC
 	select MFD_CORE
 	default n
 
+config MFD_STMPE
+	bool "Support STMicroelectronics STMPExxxx"
+	depends on I2C=y && GENERIC_HARDIRQS
+	select MFD_CORE
+	help
+	  Support for the STMPExxxx family of I/O Expanders from
+	  STMicroelectronics.
+
+	  This driver provides common support for accessing the device,
+	  additional drivers must be enabled in order to use the
+	  functionality of the device.
+
 config MFD_TC35892
 	bool "Support Toshiba TC35892"
 	depends on I2C=y && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index fb503e7..4410747 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_HTC_I2CPLD)	+= htc-i2cpld.o
 obj-$(CONFIG_MFD_DAVINCI_VOICECODEC)	+= davinci_voicecodec.o
 obj-$(CONFIG_MFD_DM355EVM_MSP)	+= dm355evm_msp.o
 
+obj-$(CONFIG_MFD_STMPE)		+= stmpe.o
 obj-$(CONFIG_MFD_TC35892)	+= tc35892.o
 obj-$(CONFIG_MFD_T7L66XB)	+= t7l66xb.o tmio_core.o
 obj-$(CONFIG_MFD_TC6387XB)	+= tc6387xb.o tmio_core.o
diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c
new file mode 100644
index 0000000..53e72b6
--- /dev/null
+++ b/drivers/mfd/stmpe.c
@@ -0,0 +1,600 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/stmpe.h>
+
+/* Interrupts */
+#define STMPE_INT_GPIOC		8
+#define STMPE1601_INT_PWM3	7
+#define STMPE1601_INT_PWM2	6
+#define STMPE1601_INT_PWM1	5
+#define STMPE1601_INT_PWM0	4
+#define STMPE24XX_INT_PWM2	7
+#define STMPE24XX_INT_PWM1	6
+#define STMPE24XX_INT_PWM0	5
+#define STMPE24XX_INT_ROT_OVER	4
+#define STMPE24XX_INT_ROT	3
+#define STMPE_INT_KEYPAD_OVER	2
+#define STMPE_INT_KEYPAD	1
+#define STMPE_INT_WAKEUP	0
+
+/* Core registers at same addresses on all variants */
+#define STMPE_ICR_LSB		0x11
+#define STMPE_IER_LSB		0x13
+#define STMPE_ISR_MSB		0x14
+#define STMPE_CHIP_ID		0x80
+
+#define STMPE_ICR_LSB_HIGH	(1 << 2)
+#define STMPE_ICR_LSB_EDGE	(1 << 1)
+#define STMPE_ICR_LSB_GIM	(1 << 0)
+
+/*
+ * The following registers are at different addresses on different variants.
+ * We provide a set of register indices and a translation table.
+ */
+
+#define STMPE1601_INT_EN_GPIO_MASK_LSB	0x17
+#define STMPE1601_INT_STA_GPIO_MSB	0x18
+#define STMPE1601_GPIO_MP_LSB		0x87
+#define STMPE1601_GPIO_SET_LSB		0x83
+#define STMPE1601_GPIO_CLR_LSB		0x85
+#define STMPE1601_GPIO_SET_DIR_LSB	0x89
+#define STMPE1601_GPIO_ED_MSB		0x8A
+#define STMPE1601_GPIO_RE_LSB		0x8D
+#define STMPE1601_GPIO_FE_LSB		0x8F
+#define STMPE1601_GPIO_AF_U_MSB		0x92
+
+#define STMPE24XX_IEGPIOR_LSB		0x18
+#define STMPE24XX_ISGPIOR_MSB		0x19
+#define STMPE24XX_GPMR_LSB		0xA5
+#define STMPE24XX_GPSR_LSB		0x85
+#define STMPE24XX_GPCR_LSB		0x88
+#define STMPE24XX_GPDR_LSB		0x8B
+#define STMPE24XX_GPEDR_MSB		0x8C
+#define STMPE24XX_GPRER_LSB		0x91
+#define STMPE24XX_GPFER_LSB		0x94
+#define STMPE24XX_GPAFR_U_MSB		0x9B
+
+static const u8 stmpe1601_regs[] = {
+	[STMPE_IDX_GPMR_LSB]	= STMPE1601_GPIO_MP_LSB,
+	[STMPE_IDX_GPSR_LSB]	= STMPE1601_GPIO_SET_LSB,
+	[STMPE_IDX_GPCR_LSB]	= STMPE1601_GPIO_CLR_LSB,
+	[STMPE_IDX_GPDR_LSB]	= STMPE1601_GPIO_SET_DIR_LSB,
+	[STMPE_IDX_GPRER_LSB]	= STMPE1601_GPIO_RE_LSB,
+	[STMPE_IDX_GPFER_LSB]	= STMPE1601_GPIO_FE_LSB,
+	[STMPE_IDX_GPAFR_U_MSB]	= STMPE1601_GPIO_AF_U_MSB,
+	[STMPE_IDX_IEGPIOR_LSB]	= STMPE1601_INT_EN_GPIO_MASK_LSB,
+	[STMPE_IDX_ISGPIOR_MSB]	= STMPE1601_INT_STA_GPIO_MSB,
+	[STMPE_IDX_GPEDR_MSB]	= STMPE1601_GPIO_ED_MSB,
+};
+
+static const u8 stmpe24xx_regs[] = {
+	[STMPE_IDX_GPMR_LSB]	= STMPE24XX_GPMR_LSB,
+	[STMPE_IDX_GPSR_LSB]	= STMPE24XX_GPSR_LSB,
+	[STMPE_IDX_GPCR_LSB]	= STMPE24XX_GPCR_LSB,
+	[STMPE_IDX_GPDR_LSB]	= STMPE24XX_GPDR_LSB,
+	[STMPE_IDX_GPRER_LSB]	= STMPE24XX_GPRER_LSB,
+	[STMPE_IDX_GPFER_LSB]	= STMPE24XX_GPFER_LSB,
+	[STMPE_IDX_GPAFR_U_MSB]	= STMPE24XX_GPAFR_U_MSB,
+	[STMPE_IDX_IEGPIOR_LSB]	= STMPE24XX_IEGPIOR_LSB,
+	[STMPE_IDX_ISGPIOR_MSB]	= STMPE24XX_ISGPIOR_MSB,
+	[STMPE_IDX_GPEDR_MSB]	= STMPE24XX_GPEDR_MSB,
+};
+
+/**
+ * stmpe_reg_read() - read a single STMPE register
+ * @stmpe:	Device to read from
+ * @reg:	Register to read
+ */
+int stmpe_reg_read(struct stmpe *stmpe, u8 reg)
+{
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(stmpe->i2c, reg);
+	if (ret < 0)
+		dev_err(stmpe->dev, "failed to read reg %#x: %d\n",
+			reg, ret);
+
+	dev_vdbg(stmpe->dev, "rd: reg %#x => data %#x\n", reg, ret);
+
+	return ret;
+}
+EXPORT_SYMBOL(stmpe_reg_read);
+
+/**
+ * stmpe_reg_write() - write a single STMPE register
+ * @stmpe:	Device to write to
+ * @reg:	Register to write
+ * @val:	Value to write
+ */
+int stmpe_reg_write(struct stmpe *stmpe, u8 reg, u8 val)
+{
+	int ret;
+
+	dev_vdbg(stmpe->dev, "wr: reg %#x <= %#x\n", reg, val);
+
+	ret = i2c_smbus_write_byte_data(stmpe->i2c, reg, val);
+	if (ret < 0)
+		dev_err(stmpe->dev, "failed to write reg %#x: %d\n",
+			reg, ret);
+
+	return ret;
+}
+EXPORT_SYMBOL(stmpe_reg_write);
+
+/**
+ * stmpe_set_bits() - set the value of a bitfield in a STMPE register
+ * @stmpe:	Device to write to
+ * @reg:	Register to write
+ * @mask:	Mask of bits to set
+ * @val:	Value to set
+ */
+int stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val)
+{
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+
+	ret = stmpe_reg_read(stmpe, reg);
+	if (ret < 0)
+		goto out;
+
+	ret &= ~mask;
+	ret |= val;
+
+	ret = stmpe_reg_write(stmpe, reg, ret);
+
+out:
+	mutex_unlock(&stmpe->lock);
+	return ret;
+}
+EXPORT_SYMBOL(stmpe_set_bits);
+
+/**
+ * stmpe_block_read() - read multiple STMPE registers
+ * @stmpe:	Device to read from
+ * @reg:	First register
+ * @length:	Number of registers
+ * @values:	Buffer to write to
+ */
+int stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length, u8 *values)
+{
+	int ret;
+
+	ret = i2c_smbus_read_i2c_block_data(stmpe->i2c, reg, length, values);
+	if (ret < 0)
+		dev_err(stmpe->dev, "failed to read regs %#x: %d\n",
+			reg, ret);
+
+	dev_vdbg(stmpe->dev, "rd: reg %#x (%d) => ret %#x\n", reg, length, ret);
+#ifdef VERBOSE_DEBUG
+	print_hex_dump_bytes("stmpe rd: ", DUMP_PREFIX_OFFSET, values, length);
+#endif
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_block_read);
+
+/**
+ * stmpe_block_write() - write multiple STMPE registers
+ * @stmpe:	Device to write to
+ * @reg:	First register
+ * @length:	Number of registers
+ * @values:	Values to write
+ */
+int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,
+			const u8 *values)
+{
+	int ret;
+
+	dev_vdbg(stmpe->dev, "wr: regs %#x (%d)\n", reg, length);
+#ifdef VERBOSE_DEBUG
+	print_hex_dump_bytes("stmpe wr: ", DUMP_PREFIX_OFFSET, values, length);
+#endif
+
+	ret = i2c_smbus_write_i2c_block_data(stmpe->i2c, reg, length,
+					     values);
+	if (ret < 0)
+		dev_err(stmpe->dev, "failed to write regs %#x: %d\n",
+			reg, ret);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_block_write);
+
+/**
+ * stmpe_set_altfunc: set the alternate function for STMPE pins
+ * @stmpe:	Device to configure
+ * @pins:	Bitmask of pins to affect
+ * @af:		Alternate function number (0 - 3)
+ *
+ * @pins is assumed to have a bit set for each of the bits whose alternate
+ * function is to be changed, numbered according to the GPIOXY numbers.
+ *
+ * If the GPIO module is not enabled, this function automatically enables it in
+ * order to perform the change.
+ */
+int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins, int af)
+{
+	u8 regaddr = stmpe->regs[STMPE_IDX_GPAFR_U_MSB];
+	int numregs = stmpe->num_gpios / 4;
+	u8 regs[numregs];
+	bool gpioon;
+	int syscon;
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+
+	syscon = stmpe_reg_read(stmpe, STMPE_SYSCON);
+	if (syscon < 0)
+		return syscon;
+
+	gpioon = syscon & STMPE_SYSCON_ENABLE_GPIO;
+	if (!gpioon) {
+		ret = stmpe_reg_write(stmpe, STMPE_SYSCON,
+				      syscon | STMPE_SYSCON_ENABLE_GPIO);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = stmpe_block_read(stmpe, regaddr, numregs, regs);
+	if (ret < 0)
+		goto out;
+
+	while (pins) {
+		int pin = __ffs(pins);
+		int regoffset = numregs - (pin / 4) - 1;
+		int pos = (pin % 4) * 2;
+
+		regs[regoffset] &= ~(0x3 << pos);
+		regs[regoffset] |= af << pos;
+
+		pins &= ~(1 << pin);
+	}
+
+	ret = stmpe_block_write(stmpe, regaddr, numregs, regs);
+
+	if (!gpioon)
+		stmpe_reg_write(stmpe, STMPE_SYSCON, syscon);
+
+out:
+	mutex_unlock(&stmpe->lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_set_altfunc);
+
+static struct resource gpio_resources[] = {
+	{
+		.start	= STMPE_INT_GPIOC,
+		.end	= STMPE_INT_GPIOC,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct resource keypad_resources[] = {
+	{
+		.start	= STMPE_INT_KEYPAD,
+		.end	= STMPE_INT_KEYPAD,
+		.flags	= IORESOURCE_IRQ,
+	},
+	{
+		.start	= STMPE_INT_KEYPAD_OVER,
+		.end	= STMPE_INT_KEYPAD_OVER,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct mfd_cell stmpe_devs[] = {
+	{
+		.name		= "stmpe-gpio",
+		.resources	= gpio_resources,
+		.num_resources	= ARRAY_SIZE(gpio_resources),
+	},
+	{
+		.name		= "stmpe-keypad",
+		.resources	= keypad_resources,
+		.num_resources	= ARRAY_SIZE(keypad_resources),
+	},
+};
+
+static irqreturn_t stmpe_irq(int irq, void *data)
+{
+	struct stmpe *stmpe = data;
+	int num = ARRAY_SIZE(stmpe->ier);
+	u8 isr[num];
+	int ret;
+	int i;
+
+	ret = stmpe_block_read(stmpe, STMPE_ISR_MSB, num, isr);
+	if (ret < 0)
+		return IRQ_NONE;
+
+	for (i = 0; i < num; i++) {
+		int bank = num - i - 1;
+		u8 status = isr[i];
+		u8 clear;
+
+		status &= stmpe->ier[bank];
+		if (!status)
+			continue;
+
+		clear = status;
+		while (status) {
+			int bit = __ffs(status);
+			int line = bank * 8 + bit;
+
+			handle_nested_irq(stmpe->irq_base + line);
+			status &= ~(1 << bit);
+		}
+
+		stmpe_reg_write(stmpe, STMPE_ISR_MSB + i, clear);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void stmpe_irq_lock(unsigned int irq)
+{
+	struct stmpe *stmpe = get_irq_chip_data(irq);
+
+	mutex_lock(&stmpe->irq_lock);
+}
+
+static void stmpe_irq_sync_unlock(unsigned int irq)
+{
+	struct stmpe *stmpe = get_irq_chip_data(irq);
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(stmpe->ier); i++) {
+		u8 new = stmpe->ier[i];
+		u8 old = stmpe->oldier[i];
+
+		if (new == old)
+			continue;
+
+		stmpe->oldier[i] = new;
+		stmpe_reg_write(stmpe, STMPE_IER_LSB - i, new);
+	}
+
+	mutex_unlock(&stmpe->irq_lock);
+}
+
+static void stmpe_irq_mask(unsigned int irq)
+{
+	struct stmpe *stmpe = get_irq_chip_data(irq);
+	int offset = irq - stmpe->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	stmpe->ier[regoffset] &= ~mask;
+}
+
+static void stmpe_irq_unmask(unsigned int irq)
+{
+	struct stmpe *stmpe = get_irq_chip_data(irq);
+	int offset = irq - stmpe->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	stmpe->ier[regoffset] |= mask;
+}
+
+static struct irq_chip stmpe_irq_chip = {
+	.name			= "stmpe",
+	.bus_lock		= stmpe_irq_lock,
+	.bus_sync_unlock	= stmpe_irq_sync_unlock,
+	.mask			= stmpe_irq_mask,
+	.unmask			= stmpe_irq_unmask,
+};
+
+static int __devinit stmpe_irq_init(struct stmpe *stmpe)
+{
+	int base = stmpe->irq_base;
+	int irq;
+
+	for (irq = base; irq < base + STMPE_NR_INTERNAL_IRQS; irq++) {
+		set_irq_chip_data(irq, stmpe);
+		set_irq_chip_and_handler(irq, &stmpe_irq_chip,
+					 handle_edge_irq);
+		set_irq_nested_thread(irq, 1);
+#ifdef CONFIG_ARM
+		set_irq_flags(irq, IRQF_VALID);
+#else
+		set_irq_noprobe(irq);
+#endif
+	}
+
+	return 0;
+}
+
+static void stmpe_irq_remove(struct stmpe *stmpe)
+{
+	int base = stmpe->irq_base;
+	int irq;
+
+	for (irq = base; irq < base + STMPE_NR_INTERNAL_IRQS; irq++) {
+#ifdef CONFIG_ARM
+		set_irq_flags(irq, 0);
+#endif
+		set_irq_chip_and_handler(irq, NULL, NULL);
+		set_irq_chip_data(irq, NULL);
+	}
+}
+
+static int __devinit stmpe_chip_init(struct stmpe *stmpe)
+{
+	unsigned int irq_trigger = stmpe->pdata->irq_trigger;
+	u8 icr = STMPE_ICR_LSB_GIM;
+	const char *name;
+	unsigned int id;
+	u8 data[2];
+	int ret;
+
+	ret = stmpe_block_read(stmpe, STMPE_CHIP_ID, ARRAY_SIZE(data), data);
+	if (ret < 0)
+		return ret;
+
+	id = (data[0] << 8) | data[1];
+
+	switch (id) {
+	case 0x0210:
+	case 0x0212:
+		name = "STMPE1601";
+		stmpe->variant = STMPE1601;
+		stmpe->regs = stmpe1601_regs;
+		stmpe->num_gpios = 16;
+		break;
+
+	case 0x0101:
+		name = "STMPE2401";
+		stmpe->variant = STMPE2401;
+		stmpe->regs = stmpe24xx_regs;
+		stmpe->num_gpios = 24;
+		break;
+
+	case 0x0120:
+		name = "STMPE2403";
+		stmpe->variant = STMPE2403;
+		stmpe->regs = stmpe24xx_regs;
+		stmpe->num_gpios = 24;
+		break;
+
+	default:
+		dev_err(stmpe->dev, "unknown id: %#x\n", id);
+		return -EINVAL;
+	}
+
+	dev_info(stmpe->dev, "%s detected\n", name);
+
+	/* Disable all modules -- subdrivers should enable what they need. */
+	ret = stmpe_set_bits(stmpe, STMPE_SYSCON, STMPE_SYSCON_ENABLE, 0);
+	if (ret)
+		return ret;
+
+	if (irq_trigger == IRQF_TRIGGER_FALLING ||
+	    irq_trigger == IRQF_TRIGGER_RISING)
+		icr |= STMPE_ICR_LSB_EDGE;
+
+	if (irq_trigger == IRQF_TRIGGER_RISING ||
+	    irq_trigger == IRQF_TRIGGER_HIGH)
+		icr |= STMPE_ICR_LSB_HIGH;
+
+	return stmpe_reg_write(stmpe, STMPE_ICR_LSB, icr);
+}
+
+static int __devinit stmpe_probe(struct i2c_client *i2c,
+				 const struct i2c_device_id *id)
+{
+	struct stmpe_platform_data *pdata = i2c->dev.platform_data;
+	struct stmpe *stmpe;
+	int ret;
+
+	if (!pdata)
+		return -EINVAL;
+
+	stmpe = kzalloc(sizeof(struct stmpe), GFP_KERNEL);
+	if (!stmpe)
+		return -ENOMEM;
+
+	mutex_init(&stmpe->irq_lock);
+	mutex_init(&stmpe->lock);
+
+	stmpe->dev = &i2c->dev;
+	stmpe->i2c = i2c;
+
+	stmpe->pdata = pdata;
+	stmpe->irq_base = pdata->irq_base;
+
+	i2c_set_clientdata(i2c, stmpe);
+
+	ret = stmpe_chip_init(stmpe);
+	if (ret)
+		goto out_free;
+
+	ret = stmpe_irq_init(stmpe);
+	if (ret)
+		goto out_free;
+
+	ret = request_threaded_irq(stmpe->i2c->irq, NULL, stmpe_irq,
+				   pdata->irq_trigger | IRQF_ONESHOT,
+				   "stmpe", stmpe);
+	if (ret) {
+		dev_err(stmpe->dev, "failed to request IRQ: %d\n", ret);
+		goto out_removeirq;
+	}
+
+	ret = mfd_add_devices(stmpe->dev, pdata->id, stmpe_devs,
+			      ARRAY_SIZE(stmpe_devs), NULL,
+			      stmpe->irq_base);
+	if (ret) {
+		dev_err(stmpe->dev, "failed to add children\n");
+		goto out_freeirq;
+	}
+
+	return 0;
+
+out_freeirq:
+	free_irq(stmpe->i2c->irq, stmpe);
+out_removeirq:
+	stmpe_irq_remove(stmpe);
+out_free:
+	i2c_set_clientdata(i2c, NULL);
+	kfree(stmpe);
+	return ret;
+}
+
+static int __devexit stmpe_remove(struct i2c_client *client)
+{
+	struct stmpe *stmpe = i2c_get_clientdata(client);
+
+	mfd_remove_devices(stmpe->dev);
+
+	free_irq(stmpe->i2c->irq, stmpe);
+	stmpe_irq_remove(stmpe);
+
+	i2c_set_clientdata(client, NULL);
+	kfree(stmpe);
+
+	return 0;
+}
+
+static const struct i2c_device_id stmpe_id[] = {
+	{ "stmpe", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, stmpe_id);
+
+static struct i2c_driver stmpe_driver = {
+	.driver.name	= "stmpe",
+	.driver.owner	= THIS_MODULE,
+	.probe		= stmpe_probe,
+	.remove		= __devexit_p(stmpe_remove),
+	.id_table	= stmpe_id,
+};
+
+static int __init stmpe_init(void)
+{
+	return i2c_add_driver(&stmpe_driver);
+}
+subsys_initcall(stmpe_init);
+
+static void __exit stmpe_exit(void)
+{
+	i2c_del_driver(&stmpe_driver);
+}
+module_exit(stmpe_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("STMPExxxx MFD core driver");
+MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
diff --git a/include/linux/mfd/stmpe.h b/include/linux/mfd/stmpe.h
new file mode 100644
index 0000000..7c6733b
--- /dev/null
+++ b/include/linux/mfd/stmpe.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#ifndef __LINUX_MFD_STMPE_H
+#define __LINUX_MFD_STMPE_H
+
+#include <linux/device.h>
+
+#define STMPE_SYSCON			0x02
+
+#define STMPE_SYSCON_ENABLE		(0xf << 0)
+#define STMPE_SYSCON_ENABLE_GPIO	(1 << 3)
+#define STMPE24XX_SYSCON_ENABLE_PWM	(1 << 2)
+#define STMPE_SYSCON_ENABLE_KPC		(1 << 1)
+#define STMPE16XX_SYSCON_ENABLE_SPWM	(1 << 0)
+#define STMPE24XX_SYSCON_ENABLE_ROT	(1 << 0)
+
+enum stmpe_variant {
+	STMPE1601,
+	STMPE2401,
+	STMPE2403,
+};
+
+/*
+ * For registers whose locations differ on variants,  the correct address is
+ * obtained by indexing stmpe->regs with one of the following.
+ */
+enum {
+	STMPE_IDX_GPMR_LSB,
+	STMPE_IDX_GPSR_LSB,
+	STMPE_IDX_GPCR_LSB,
+	STMPE_IDX_GPDR_LSB,
+	STMPE_IDX_GPEDR_MSB,
+	STMPE_IDX_GPRER_LSB,
+	STMPE_IDX_GPFER_LSB,
+	STMPE_IDX_GPAFR_U_MSB,
+	STMPE_IDX_IEGPIOR_LSB,
+	STMPE_IDX_ISGPIOR_MSB,
+};
+
+/**
+ * struct stmpe - STMPE MFD structure
+ * @lock: lock protecting I/O operations
+ * @irq_lock: IRQ bus lock
+ * @dev: device, mostly for dev_dbg()
+ * @i2c: i2c client
+ * @variant: the detected STMPExxxx model number
+ * @regs: list of addresses of registers which are at different addresses on
+ *	  different variants.  Indexed by one of STMPE_IDX_*.
+ * @irq_base: starting IRQ number for internal IRQs
+ * @num_gpios: number of gpios, differs for variants
+ * @ier: cache of IER registers for bus_lock
+ * @oldier: cache of IER registers for bus_lock
+ * @pdata: platform data
+ */
+struct stmpe {
+	struct mutex lock;
+	struct mutex irq_lock;
+	struct device *dev;
+	struct i2c_client *i2c;
+	enum stmpe_variant variant;
+	const u8 *regs;
+
+	int irq_base;
+	int num_gpios;
+	u8 ier[2];
+	u8 oldier[2];
+	struct stmpe_platform_data *pdata;
+};
+
+extern int stmpe_reg_write(struct stmpe *stmpe, u8 reg, u8 data);
+extern int stmpe_reg_read(struct stmpe *stmpe, u8 reg);
+extern int stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length,
+			    u8 *values);
+extern int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,
+			     const u8 *values);
+extern int stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val);
+extern int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins, int af);
+
+struct matrix_keymap_data;
+
+/**
+ * struct stmpe_keypad_platform_data - STMPE keypad platform data
+ * @keymap_data: key map table and size
+ * @debounce_ms: debounce interval, in ms.  Maximum is
+ *		 %STMPE_KEYPAD_MAX_DEBOUNCE.
+ * @scan_count: number of key scanning cycles to confirm key data.
+ *		Maximum is %STMPE_KEYPAD_MAX_SCAN_COUNT.
+ * @no_autorepeat: disable key autorepeat
+ */
+struct stmpe_keypad_platform_data {
+	struct matrix_keymap_data *keymap_data;
+	unsigned int debounce_ms;
+	unsigned int scan_count;
+	bool no_autorepeat;
+};
+
+/**
+ * struct stmpe_gpio_platform_data - STMPE GPIO platform data
+ * @gpio_base: first gpio number assigned.  A maximum of
+ *	       %STMPE_NR_GPIOS GPIOs will be allocated.
+ */
+struct stmpe_gpio_platform_data {
+	int gpio_base;
+};
+
+/**
+ * struct stmpe_platform_data - STMPE platform data
+ * @id: device id to distinguish between multiple STMPEs on the same board
+ * @irq_trigger: IRQ trigger to use for the interrupt to the host
+ * @irq_base: base IRQ number.  %STMPE_NR_IRQS irqs will be used, or
+ *	      %STMPE_NR_INTERNAL_IRQS if the GPIO driver is not used.
+ * @gpio: GPIO-specific platform data
+ * @keypad: keypad-specific platform data
+ */
+struct stmpe_platform_data {
+	int id;
+	int irq_base;
+	unsigned int irq_trigger;
+
+	struct stmpe_gpio_platform_data *gpio;
+	struct stmpe_keypad_platform_data *keypad;
+};
+
+#define STMPE_NR_INTERNAL_IRQS	9
+#define STMPE_INT_GPIO(x)	(STMPE_NR_INTERNAL_IRQS + (x))
+
+#define STMPE_NR_GPIOS		24
+#define STMPE_NR_IRQS		STMPE_INT_GPIO(STMPE_NR_GPIOS)
+
+#endif
-- 
1.7.0


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH 2/3] gpio: add STMPExxxx GPIO driver
  2010-05-31 12:17 [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Rabin Vincent
@ 2010-05-31 12:17 ` Rabin Vincent
  2010-05-31 12:17   ` Rabin Vincent
  2010-06-18 23:42 ` [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Samuel Ortiz
  2 siblings, 0 replies; 61+ messages in thread
From: Rabin Vincent @ 2010-05-31 12:17 UTC (permalink / raw)
  To: Samuel Ortiz
  Cc: linux-kernel, STEricsson_nomadik_linux, Rabin Vincent, Linus Walleij

Add support for the GPIOs on the STMPExxxx I/O Expanders.

Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
---
 drivers/gpio/Kconfig      |    7 +
 drivers/gpio/Makefile     |    1 +
 drivers/gpio/stmpe-gpio.c |  381 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 389 insertions(+), 0 deletions(-)
 create mode 100644 drivers/gpio/stmpe-gpio.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 724038d..14c6dfc 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -195,6 +195,13 @@ config GPIO_PCF857X
 	  This driver provides an in-kernel interface to those GPIOs using
 	  platform-neutral GPIO calls.
 
+config GPIO_STMPE
+	bool "STMPExxxx GPIOs"
+	depends on MFD_STMPE
+	help
+	  This enables support for the GPIOs found on the STMPExxxx
+	  I/O Expanders.
+
 config GPIO_TC35892
 	bool "TC35892 GPIOs"
 	depends on MFD_TC35892
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 51c3cdd..a9b93c3 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_GPIO_MCP23S08)	+= mcp23s08.o
 obj-$(CONFIG_GPIO_PCA953X)	+= pca953x.o
 obj-$(CONFIG_GPIO_PCF857X)	+= pcf857x.o
 obj-$(CONFIG_GPIO_PL061)	+= pl061.o
+obj-$(CONFIG_GPIO_STMPE)	+= stmpe-gpio.o
 obj-$(CONFIG_GPIO_TC35892)	+= tc35892-gpio.o
 obj-$(CONFIG_GPIO_TIMBERDALE)	+= timbgpio.o
 obj-$(CONFIG_GPIO_TWL4030)	+= twl4030-gpio.o
diff --git a/drivers/gpio/stmpe-gpio.c b/drivers/gpio/stmpe-gpio.c
new file mode 100644
index 0000000..857d3f5
--- /dev/null
+++ b/drivers/gpio/stmpe-gpio.c
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/stmpe.h>
+
+/*
+ * These registers are modified under the irq bus lock and cached to avoid
+ * unnecessary writes in bus_sync_unlock.
+ */
+enum { REG_RE, REG_FE, REG_IE };
+
+#define CACHE_NR_REGS	3
+#define CACHE_NR_BANKS	(STMPE_NR_GPIOS / 8)
+
+struct stmpe_gpio {
+	struct gpio_chip chip;
+	struct stmpe *stmpe;
+	struct device *dev;
+	struct mutex irq_lock;
+
+	int irq_base;
+
+	/* Caches of interrupt control registers for bus_lock */
+	u8 regs[CACHE_NR_REGS][CACHE_NR_BANKS];
+	u8 oldregs[CACHE_NR_REGS][CACHE_NR_BANKS];
+};
+
+static inline struct stmpe_gpio *to_stmpe_gpio(struct gpio_chip *chip)
+{
+	return container_of(chip, struct stmpe_gpio, chip);
+}
+
+static int stmpe_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	u8 reg = stmpe->regs[STMPE_IDX_GPMR_LSB] - (offset / 8);
+	u8 mask = 1 << (offset % 8);
+	int ret;
+
+	ret = stmpe_reg_read(stmpe, reg);
+	if (ret < 0)
+		return ret;
+
+	return ret & mask;
+}
+
+static void stmpe_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	int which = val ? STMPE_IDX_GPSR_LSB : STMPE_IDX_GPCR_LSB;
+	u8 reg = stmpe->regs[which] - (offset / 8);
+	u8 mask = 1 << (offset % 8);
+
+	stmpe_reg_write(stmpe, reg, mask);
+}
+
+static int stmpe_gpio_direction_output(struct gpio_chip *chip,
+					 unsigned offset, int val)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	u8 reg = stmpe->regs[STMPE_IDX_GPDR_LSB] - (offset / 8);
+	u8 mask = 1 << (offset % 8);
+
+	stmpe_gpio_set(chip, offset, val);
+
+	return stmpe_set_bits(stmpe, reg, mask, mask);
+}
+
+static int stmpe_gpio_direction_input(struct gpio_chip *chip,
+					unsigned offset)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	u8 reg = stmpe->regs[STMPE_IDX_GPDR_LSB] - (offset / 8);
+	u8 mask = 1 << (offset % 8);
+
+	return stmpe_set_bits(stmpe, reg, mask, mask);
+}
+
+static int stmpe_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+
+	return stmpe_gpio->irq_base + offset;
+}
+
+static struct gpio_chip template_chip = {
+	.label			= "stmpe",
+	.owner			= THIS_MODULE,
+	.direction_input	= stmpe_gpio_direction_input,
+	.get			= stmpe_gpio_get,
+	.direction_output	= stmpe_gpio_direction_output,
+	.set			= stmpe_gpio_set,
+	.to_irq			= stmpe_gpio_to_irq,
+	.can_sleep		= 1,
+};
+
+static int stmpe_gpio_irq_set_type(unsigned int irq, unsigned int type)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+	int offset = irq - stmpe_gpio->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH)
+		return -EINVAL;
+
+	if (type == IRQ_TYPE_EDGE_RISING)
+		stmpe_gpio->regs[REG_RE][regoffset] |= mask;
+	else
+		stmpe_gpio->regs[REG_RE][regoffset] &= ~mask;
+
+	if (type == IRQ_TYPE_EDGE_FALLING)
+		stmpe_gpio->regs[REG_FE][regoffset] |= mask;
+	else
+		stmpe_gpio->regs[REG_FE][regoffset] &= ~mask;
+
+	return 0;
+}
+
+static void stmpe_gpio_irq_lock(unsigned int irq)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+
+	mutex_lock(&stmpe_gpio->irq_lock);
+}
+
+static void stmpe_gpio_irq_sync_unlock(unsigned int irq)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	int num_banks = stmpe->num_gpios / 8;
+	static const u8 regmap[] = {
+		[REG_RE]	= STMPE_IDX_GPRER_LSB,
+		[REG_FE]	= STMPE_IDX_GPFER_LSB,
+		[REG_IE]	= STMPE_IDX_IEGPIOR_LSB,
+	};
+	int i, j;
+
+	for (i = 0; i < CACHE_NR_REGS; i++) {
+		for (j = 0; j < num_banks; j++) {
+			u8 old = stmpe_gpio->oldregs[i][j];
+			u8 new = stmpe_gpio->regs[i][j];
+
+			if (new == old)
+				continue;
+
+			stmpe_gpio->oldregs[i][j] = new;
+			stmpe_reg_write(stmpe, stmpe->regs[regmap[i]] - j, new);
+		}
+	}
+
+	mutex_unlock(&stmpe_gpio->irq_lock);
+}
+
+static void stmpe_gpio_irq_mask(unsigned int irq)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+	int offset = irq - stmpe_gpio->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	stmpe_gpio->regs[REG_IE][regoffset] &= ~mask;
+}
+
+static void stmpe_gpio_irq_unmask(unsigned int irq)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+	int offset = irq - stmpe_gpio->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	stmpe_gpio->regs[REG_IE][regoffset] |= mask;
+}
+
+static struct irq_chip stmpe_gpio_irq_chip = {
+	.name			= "stmpe-gpio",
+	.bus_lock		= stmpe_gpio_irq_lock,
+	.bus_sync_unlock	= stmpe_gpio_irq_sync_unlock,
+	.mask			= stmpe_gpio_irq_mask,
+	.unmask			= stmpe_gpio_irq_unmask,
+	.set_type		= stmpe_gpio_irq_set_type,
+};
+
+static irqreturn_t stmpe_gpio_irq(int irq, void *dev)
+{
+	struct stmpe_gpio *stmpe_gpio = dev;
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	u8 statmsbreg = stmpe->regs[STMPE_IDX_ISGPIOR_MSB];
+	int num_banks = stmpe->num_gpios / 8;
+	u8 status[num_banks];
+	int ret;
+	int i;
+
+	ret = stmpe_block_read(stmpe, statmsbreg, num_banks, status);
+	if (ret < 0)
+		return IRQ_NONE;
+
+	for (i = 0; i < num_banks; i++) {
+		int bank = num_banks - i - 1;
+		unsigned int enabled = stmpe_gpio->regs[REG_IE][bank];
+		unsigned int stat = status[i];
+
+		stat &= enabled;
+		if (!stat)
+			continue;
+
+		while (stat) {
+			int bit = __ffs(stat);
+			int line = bank * 8 + bit;
+
+			handle_nested_irq(stmpe_gpio->irq_base + line);
+			stat &= ~(1 << bit);
+		}
+
+		stmpe_reg_write(stmpe, statmsbreg + i, status[i]);
+		stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_GPEDR_MSB] + i,
+				status[i]);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit stmpe_gpio_irq_init(struct stmpe_gpio *stmpe_gpio)
+{
+	int base = stmpe_gpio->irq_base;
+	int irq;
+
+	for (irq = base; irq < base + stmpe_gpio->chip.ngpio; irq++) {
+		set_irq_chip_data(irq, stmpe_gpio);
+		set_irq_chip_and_handler(irq, &stmpe_gpio_irq_chip,
+					 handle_simple_irq);
+		set_irq_nested_thread(irq, 1);
+#ifdef CONFIG_ARM
+		set_irq_flags(irq, IRQF_VALID);
+#else
+		set_irq_noprobe(irq);
+#endif
+	}
+
+	return 0;
+}
+
+static void stmpe_gpio_irq_remove(struct stmpe_gpio *stmpe_gpio)
+{
+	int base = stmpe_gpio->irq_base;
+	int irq;
+
+	for (irq = base; irq < base + stmpe_gpio->chip.ngpio; irq++) {
+#ifdef CONFIG_ARM
+		set_irq_flags(irq, 0);
+#endif
+		set_irq_chip_and_handler(irq, NULL, NULL);
+		set_irq_chip_data(irq, NULL);
+	}
+}
+
+static int __devinit stmpe_gpio_probe(struct platform_device *pdev)
+{
+	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+	struct stmpe_gpio_platform_data *pdata;
+	struct stmpe_gpio *stmpe_gpio;
+	int ret;
+	int irq;
+
+	pdata = stmpe->pdata->gpio;
+	if (!pdata)
+		return -ENODEV;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	stmpe_gpio = kzalloc(sizeof(struct stmpe_gpio), GFP_KERNEL);
+	if (!stmpe_gpio)
+		return -ENOMEM;
+
+	mutex_init(&stmpe_gpio->irq_lock);
+
+	stmpe_gpio->dev = &pdev->dev;
+	stmpe_gpio->stmpe = stmpe;
+
+	stmpe_gpio->chip = template_chip;
+	stmpe_gpio->chip.ngpio = stmpe->num_gpios;
+	stmpe_gpio->chip.dev = &pdev->dev;
+	stmpe_gpio->chip.base = pdata->gpio_base;
+
+	stmpe_gpio->irq_base = stmpe->irq_base + STMPE_INT_GPIO(0);
+
+	ret = stmpe_set_bits(stmpe, STMPE_SYSCON, STMPE_SYSCON_ENABLE_GPIO,
+			     STMPE_SYSCON_ENABLE_GPIO);
+	if (ret)
+		return ret;
+
+	ret = stmpe_gpio_irq_init(stmpe_gpio);
+	if (ret)
+		goto out_free;
+
+	ret = request_threaded_irq(irq, NULL, stmpe_gpio_irq, IRQF_ONESHOT,
+				   "stmpe-gpio", stmpe_gpio);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
+		goto out_removeirq;
+	}
+
+	ret = gpiochip_add(&stmpe_gpio->chip);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to add gpiochip: %d\n", ret);
+		goto out_freeirq;
+	}
+
+	platform_set_drvdata(pdev, stmpe_gpio);
+
+	return 0;
+
+out_freeirq:
+	free_irq(irq, stmpe_gpio);
+out_removeirq:
+	stmpe_gpio_irq_remove(stmpe_gpio);
+out_free:
+	kfree(stmpe_gpio);
+	return ret;
+}
+
+static int __devexit stmpe_gpio_remove(struct platform_device *pdev)
+{
+	struct stmpe_gpio *stmpe_gpio = platform_get_drvdata(pdev);
+	int irq = platform_get_irq(pdev, 0);
+	int ret;
+
+	ret = gpiochip_remove(&stmpe_gpio->chip);
+	if (ret < 0) {
+		dev_err(stmpe_gpio->dev,
+			"unable to remove gpiochip: %d\n", ret);
+		return ret;
+	}
+
+	free_irq(irq, stmpe_gpio);
+	stmpe_gpio_irq_remove(stmpe_gpio);
+	platform_set_drvdata(pdev, NULL);
+	kfree(stmpe_gpio);
+
+	return 0;
+}
+
+static struct platform_driver stmpe_gpio_driver = {
+	.driver.name	= "stmpe-gpio",
+	.driver.owner	= THIS_MODULE,
+	.probe		= stmpe_gpio_probe,
+	.remove		= __devexit_p(stmpe_gpio_remove),
+};
+
+static int __init stmpe_gpio_init(void)
+{
+	return platform_driver_register(&stmpe_gpio_driver);
+}
+subsys_initcall(stmpe_gpio_init);
+
+static void __exit stmpe_gpio_exit(void)
+{
+	platform_driver_unregister(&stmpe_gpio_driver);
+}
+module_exit(stmpe_gpio_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("STMPExxxx GPIO driver");
+MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
-- 
1.7.0


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH 3/3] input: add STMPExxxx keypad driver
  2010-05-31 12:17 [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Rabin Vincent
@ 2010-05-31 12:17   ` Rabin Vincent
  2010-05-31 12:17   ` Rabin Vincent
  2010-06-18 23:42 ` [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Samuel Ortiz
  2 siblings, 0 replies; 61+ messages in thread
From: Rabin Vincent @ 2010-05-31 12:17 UTC (permalink / raw)
  To: Samuel Ortiz
  Cc: linux-kernel, STEricsson_nomadik_linux, Rabin Vincent,
	linux-input, Dmitry Torokhov, Linus Walleij

Add an input driver for the keypad on STMPExxxx I/O expanders.  This
driver uses the common support provided by the STMPE MFD driver.

Cc: linux-input@vger.kernel.org
Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
---
 drivers/input/keyboard/Kconfig        |   10 +
 drivers/input/keyboard/Makefile       |    1 +
 drivers/input/keyboard/stmpe-keypad.c |  382 +++++++++++++++++++++++++++++++++
 3 files changed, 393 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/stmpe-keypad.c

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index d8fa5d7..0e57fbc 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -374,6 +374,16 @@ config KEYBOARD_SH_KEYSC
 	  To compile this driver as a module, choose M here: the
 	  module will be called sh_keysc.
 
+config KEYBOARD_STMPE
+	tristate "STMPExxxx keypad support"
+	depends on MFD_STMPE
+	help
+	  Say Y here if you want to use the keypad controller on STMPExxx I/O
+	  expanders.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called stmpe-keypad.
+
 config KEYBOARD_DAVINCI
 	tristate "TI DaVinci Key Scan"
 	depends on ARCH_DAVINCI_DM365
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 4596d0c..af24b07 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
 obj-$(CONFIG_KEYBOARD_PXA930_ROTARY)	+= pxa930_rotary.o
 obj-$(CONFIG_KEYBOARD_QT2160)		+= qt2160.o
 obj-$(CONFIG_KEYBOARD_SH_KEYSC)		+= sh_keysc.o
+obj-$(CONFIG_KEYBOARD_STMPE)		+= stmpe-keypad.o
 obj-$(CONFIG_KEYBOARD_STOWAWAY)		+= stowaway.o
 obj-$(CONFIG_KEYBOARD_SUNKBD)		+= sunkbd.o
 obj-$(CONFIG_KEYBOARD_TWL4030)		+= twl4030_keypad.o
diff --git a/drivers/input/keyboard/stmpe-keypad.c b/drivers/input/keyboard/stmpe-keypad.c
new file mode 100644
index 0000000..98ae170
--- /dev/null
+++ b/drivers/input/keyboard/stmpe-keypad.c
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/mfd/stmpe.h>
+
+/* These are at the same addresses in all STMPE variants */
+#define STMPE_KPC_COL			0x60
+#define STMPE_KPC_ROW_MSB		0x61
+#define STMPE_KPC_ROW_LSB		0x62
+#define STMPE_KPC_CTRL_MSB		0x63
+#define STMPE_KPC_CTRL_LSB		0x64
+#define STMPE_KPC_COMBI_KEY_0		0x65
+#define STMPE_KPC_COMBI_KEY_1		0x66
+#define STMPE_KPC_COMBI_KEY_2		0x67
+#define STMPE_KPC_DATA_BYTE0		0x68
+#define STMPE_KPC_DATA_BYTE1		0x69
+#define STMPE_KPC_DATA_BYTE2		0x6a
+#define STMPE_KPC_DATA_BYTE3		0x6b
+#define STMPE_KPC_DATA_BYTE4		0x6c
+
+#define STMPE_KPC_CTRL_LSB_SCAN		(0x1 << 0)
+#define STMPE_KPC_CTRL_LSB_DEBOUNCE	(0x7f << 1)
+#define STMPE_KPC_CTRL_MSB_SCAN_COUNT	(0xf << 4)
+
+#define STMPE_KPC_ROW_MSB_ROWS		0xff
+
+#define STMPE_KPC_DATA_UP		(0x1 << 7)
+#define STMPE_KPC_DATA_ROW		(0xf << 3)
+#define STMPE_KPC_DATA_COL		(0x7 << 0)
+#define STMPE_KPC_DATA_NOKEY_MASK	0x78
+
+#define STMPE_KEYPAD_MAX_DEBOUNCE	127
+#define STMPE_KEYPAD_MAX_SCAN_COUNT	15
+
+#define STMPE_KEYPAD_MAX_ROWS		8
+#define STMPE_KEYPAD_MAX_COLS		8
+#define STMPE_KEYPAD_ROW_SHIFT		3
+#define STMPE_KEYPAD_KEYMAP_SIZE	\
+	(STMPE_KEYPAD_MAX_ROWS * STMPE_KEYPAD_MAX_COLS)
+
+/**
+ * struct stmpe_keypad_variant - model-specific attributes
+ * @auto_increment: whether the KPC_DATA_BYTE register address
+ *		    auto-increments on multiple read
+ * @num_data: number of data bytes
+ * @num_normal_data: number of normal keys' data bytes
+ * @max_cols: maximum number of columns supported
+ * @max_rows: maximum number of rows supported
+ * @col_gpios: bitmask of gpios which can be used for columns
+ * @row_gpios: bitmask of gpios which can be used for rows
+ */
+struct stmpe_keypad_variant {
+	bool		auto_increment;
+	int		num_data;
+	int		num_normal_data;
+	int		max_cols;
+	int		max_rows;
+	unsigned int	col_gpios;
+	unsigned int	row_gpios;
+};
+
+static struct stmpe_keypad_variant stmpe_keypad_variants[] = {
+	[STMPE1601] = {
+		.auto_increment		= true,
+		.num_data		= 5,
+		.num_normal_data	= 3,
+		.max_cols		= 8,
+		.max_rows		= 8,
+		.col_gpios		= 0x000ff,	/* GPIO 0 - 7 */
+		.row_gpios		= 0x0ff00,	/* GPIO 8 - 15 */
+	},
+	[STMPE2401] = {
+		.auto_increment		= false,
+		.num_data		= 3,
+		.num_normal_data	= 2,
+		.max_cols		= 8,
+		.max_rows		= 12,
+		.col_gpios		= 0x0000ff,	/* GPIO 0 - 7*/
+		.row_gpios		= 0x1fef00,	/* GPIO 8-14, 16-20 */
+	},
+	[STMPE2403] = {
+		.auto_increment		= true,
+		.num_data		= 5,
+		.num_normal_data	= 3,
+		.max_cols		= 8,
+		.max_rows		= 12,
+		.col_gpios		= 0x0000ff,	/* GPIO 0 - 7*/
+		.row_gpios		= 0x1fef00,	/* GPIO 8-14, 16-20 */
+	},
+};
+
+struct stmpe_keypad {
+	struct stmpe *stmpe;
+	struct input_dev *input;
+	struct stmpe_keypad_variant *variant;
+	struct stmpe_keypad_platform_data *plat;
+
+	unsigned int rows;
+	unsigned int cols;
+
+	unsigned short keymap[STMPE_KEYPAD_KEYMAP_SIZE];
+};
+
+static int stmpe_keypad_read_data(struct stmpe_keypad *keypad, u8 *data)
+{
+	struct stmpe_keypad_variant *variant = keypad->variant;
+	struct stmpe *stmpe = keypad->stmpe;
+	int ret;
+	int i;
+
+	if (variant->auto_increment)
+		return stmpe_block_read(stmpe, STMPE_KPC_DATA_BYTE0,
+					variant->num_data, data);
+
+	for (i = 0; i < variant->num_data; i++) {
+		ret = stmpe_reg_read(stmpe, STMPE_KPC_DATA_BYTE0 + i);
+		if (ret < 0)
+			return ret;
+
+		data[i] = ret;
+	}
+
+	return 0;
+}
+
+static irqreturn_t stmpe_keypad_irq(int irq, void *dev)
+{
+	struct stmpe_keypad *keypad = dev;
+	struct input_dev *input = keypad->input;
+	struct stmpe_keypad_variant *variant = keypad->variant;
+	u8 fifo[variant->num_data];
+	int ret;
+	int i;
+
+	ret = stmpe_keypad_read_data(keypad, fifo);
+	if (ret < 0)
+		return IRQ_NONE;
+
+	for (i = 0; i < variant->num_normal_data; i++) {
+		u8 data = fifo[i];
+		int row = (data & STMPE_KPC_DATA_ROW) >> 3;
+		int col = data & STMPE_KPC_DATA_COL;
+		int code = MATRIX_SCAN_CODE(row, col, STMPE_KEYPAD_ROW_SHIFT);
+		bool up = data & STMPE_KPC_DATA_UP;
+
+		if ((data & STMPE_KPC_DATA_NOKEY_MASK)
+			== STMPE_KPC_DATA_NOKEY_MASK)
+			continue;
+
+		input_event(input, EV_MSC, MSC_SCAN, code);
+		input_report_key(input, keypad->keymap[code], !up);
+		input_sync(input);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit stmpe_keypad_altfunc_init(struct stmpe_keypad *keypad)
+{
+	struct stmpe_keypad_variant *variant = keypad->variant;
+	unsigned int col_gpios = variant->col_gpios;
+	unsigned int row_gpios = variant->row_gpios;
+	struct stmpe *stmpe = keypad->stmpe;
+	unsigned int pins = 0;
+	int i;
+
+	/*
+	 * Figure out which pins need to be set to alternate function 1.
+	 *
+	 * {cols,rows}_gpios are bitmasks of which pins on the chip can be used
+	 * for the keypad.
+	 *
+	 * keypad->{cols,rows} are a bitmask of which pins (of the ones useable
+	 * for the keypad) are used on the board.
+	 */
+
+	for (i = 0; i < variant->max_cols; i++) {
+		int num = __ffs(col_gpios);
+
+		if (keypad->cols & (1 << i))
+			pins |= 1 << num;
+
+		col_gpios &= ~(1 << num);
+	}
+
+	for (i = 0; i < variant->max_rows; i++) {
+		int num = __ffs(row_gpios);
+
+		if (keypad->rows & (1 << i))
+			pins |= 1 << num;
+
+		row_gpios &= ~(1 << num);
+	}
+
+	return stmpe_set_altfunc(stmpe, pins, 1);
+}
+
+static int __devinit stmpe_keypad_chip_init(struct stmpe_keypad *keypad)
+{
+	struct stmpe_keypad_platform_data *plat = keypad->plat;
+	struct stmpe_keypad_variant *variant = keypad->variant;
+	struct stmpe *stmpe = keypad->stmpe;
+	int ret;
+
+	if (plat->debounce_ms > STMPE_KEYPAD_MAX_DEBOUNCE)
+		return -EINVAL;
+
+	if (plat->scan_count > STMPE_KEYPAD_MAX_SCAN_COUNT)
+		return -EINVAL;
+
+	ret = stmpe_set_bits(stmpe, STMPE_SYSCON, STMPE_SYSCON_ENABLE_KPC,
+			     STMPE_SYSCON_ENABLE_KPC);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_keypad_altfunc_init(keypad);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_reg_write(stmpe, STMPE_KPC_COL, keypad->cols);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_reg_write(stmpe, STMPE_KPC_ROW_LSB, keypad->rows);
+	if (ret < 0)
+		return ret;
+
+	if (variant->max_rows > 8) {
+		ret = stmpe_set_bits(stmpe, STMPE_KPC_ROW_MSB,
+				     STMPE_KPC_ROW_MSB_ROWS,
+				     keypad->rows >> 8);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_KPC_CTRL_MSB,
+			     STMPE_KPC_CTRL_MSB_SCAN_COUNT,
+			     plat->scan_count << 4);
+	if (ret < 0)
+		return ret;
+
+	return stmpe_set_bits(stmpe, STMPE_KPC_CTRL_LSB,
+			      STMPE_KPC_CTRL_LSB_SCAN |
+			      STMPE_KPC_CTRL_LSB_DEBOUNCE,
+			      STMPE_KPC_CTRL_LSB_SCAN |
+			      (plat->debounce_ms << 1));
+}
+
+static int __devinit stmpe_keypad_probe(struct platform_device *pdev)
+{
+	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+	struct stmpe_keypad_platform_data *plat;
+	struct stmpe_keypad *keypad;
+	struct input_dev *input;
+	int ret;
+	int irq;
+	int i;
+
+	plat = stmpe->pdata->keypad;
+	if (!plat)
+		return -ENODEV;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	keypad = kzalloc(sizeof(struct stmpe_keypad), GFP_KERNEL);
+	if (!keypad)
+		return -ENOMEM;
+
+	input = input_allocate_device();
+	if (!input) {
+		ret = -ENOMEM;
+		goto out_freekeypad;
+	}
+
+	input->name = "STMPE keypad";
+	input->id.bustype = BUS_I2C;
+	input->dev.parent = &pdev->dev;
+
+	input_set_capability(input, EV_MSC, MSC_SCAN);
+
+	__set_bit(EV_KEY, input->evbit);
+	if (!plat->no_autorepeat)
+		__set_bit(EV_REP, input->evbit);
+
+	input->keycode = keypad->keymap;
+	input->keycodesize = sizeof(keypad->keymap[0]);
+	input->keycodemax = ARRAY_SIZE(keypad->keymap);
+
+	matrix_keypad_build_keymap(plat->keymap_data, STMPE_KEYPAD_ROW_SHIFT,
+				   input->keycode, input->keybit);
+
+	for (i = 0; i < plat->keymap_data->keymap_size; i++) {
+		unsigned int key = plat->keymap_data->keymap[i];
+
+		keypad->cols |= 1 << KEY_COL(key);
+		keypad->rows |= 1 << KEY_ROW(key);
+	}
+
+	keypad->stmpe = stmpe;
+	keypad->plat = plat;
+	keypad->input = input;
+	keypad->variant = &stmpe_keypad_variants[stmpe->variant];
+
+	ret = stmpe_keypad_chip_init(keypad);
+	if (ret < 0)
+		goto out_freeinput;
+
+	ret = request_threaded_irq(irq, NULL, stmpe_keypad_irq, IRQF_ONESHOT,
+				   "stmpe-keypad", keypad);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
+		goto out_freeinput;
+	}
+
+	ret = input_register_device(input);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"unable to register input device: %d\n", ret);
+		goto out_freeirq;
+	}
+
+	platform_set_drvdata(pdev, keypad);
+
+	return 0;
+
+out_freeirq:
+	free_irq(irq, keypad);
+out_freeinput:
+	input_free_device(input);
+out_freekeypad:
+	kfree(keypad);
+	return ret;
+}
+
+static int __devexit stmpe_keypad_remove(struct platform_device *pdev)
+{
+	struct stmpe_keypad *keypad = platform_get_drvdata(pdev);
+	int irq = platform_get_irq(pdev, 0);
+
+	input_unregister_device(keypad->input);
+	free_irq(irq, keypad);
+	platform_set_drvdata(pdev, NULL);
+	kfree(keypad);
+
+	return 0;
+}
+
+static struct platform_driver stmpe_keypad_driver = {
+	.driver.name	= "stmpe-keypad",
+	.driver.owner	= THIS_MODULE,
+	.probe		= stmpe_keypad_probe,
+	.remove		= __devexit_p(stmpe_keypad_remove),
+};
+
+static int __init stmpe_keypad_init(void)
+{
+	return platform_driver_register(&stmpe_keypad_driver);
+}
+module_init(stmpe_keypad_init);
+
+static void __exit stmpe_keypad_exit(void)
+{
+	platform_driver_unregister(&stmpe_keypad_driver);
+}
+module_exit(stmpe_keypad_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("STMPExxxx keypad driver");
+MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
-- 
1.7.0


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH 3/3] input: add STMPExxxx keypad driver
@ 2010-05-31 12:17   ` Rabin Vincent
  0 siblings, 0 replies; 61+ messages in thread
From: Rabin Vincent @ 2010-05-31 12:17 UTC (permalink / raw)
  To: Samuel Ortiz
  Cc: linux-kernel, STEricsson_nomadik_linux, Rabin Vincent,
	linux-input, Dmitry Torokhov, Linus Walleij

Add an input driver for the keypad on STMPExxxx I/O expanders.  This
driver uses the common support provided by the STMPE MFD driver.

Cc: linux-input@vger.kernel.org
Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
---
 drivers/input/keyboard/Kconfig        |   10 +
 drivers/input/keyboard/Makefile       |    1 +
 drivers/input/keyboard/stmpe-keypad.c |  382 +++++++++++++++++++++++++++++++++
 3 files changed, 393 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/stmpe-keypad.c

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index d8fa5d7..0e57fbc 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -374,6 +374,16 @@ config KEYBOARD_SH_KEYSC
 	  To compile this driver as a module, choose M here: the
 	  module will be called sh_keysc.
 
+config KEYBOARD_STMPE
+	tristate "STMPExxxx keypad support"
+	depends on MFD_STMPE
+	help
+	  Say Y here if you want to use the keypad controller on STMPExxx I/O
+	  expanders.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called stmpe-keypad.
+
 config KEYBOARD_DAVINCI
 	tristate "TI DaVinci Key Scan"
 	depends on ARCH_DAVINCI_DM365
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 4596d0c..af24b07 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
 obj-$(CONFIG_KEYBOARD_PXA930_ROTARY)	+= pxa930_rotary.o
 obj-$(CONFIG_KEYBOARD_QT2160)		+= qt2160.o
 obj-$(CONFIG_KEYBOARD_SH_KEYSC)		+= sh_keysc.o
+obj-$(CONFIG_KEYBOARD_STMPE)		+= stmpe-keypad.o
 obj-$(CONFIG_KEYBOARD_STOWAWAY)		+= stowaway.o
 obj-$(CONFIG_KEYBOARD_SUNKBD)		+= sunkbd.o
 obj-$(CONFIG_KEYBOARD_TWL4030)		+= twl4030_keypad.o
diff --git a/drivers/input/keyboard/stmpe-keypad.c b/drivers/input/keyboard/stmpe-keypad.c
new file mode 100644
index 0000000..98ae170
--- /dev/null
+++ b/drivers/input/keyboard/stmpe-keypad.c
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/mfd/stmpe.h>
+
+/* These are at the same addresses in all STMPE variants */
+#define STMPE_KPC_COL			0x60
+#define STMPE_KPC_ROW_MSB		0x61
+#define STMPE_KPC_ROW_LSB		0x62
+#define STMPE_KPC_CTRL_MSB		0x63
+#define STMPE_KPC_CTRL_LSB		0x64
+#define STMPE_KPC_COMBI_KEY_0		0x65
+#define STMPE_KPC_COMBI_KEY_1		0x66
+#define STMPE_KPC_COMBI_KEY_2		0x67
+#define STMPE_KPC_DATA_BYTE0		0x68
+#define STMPE_KPC_DATA_BYTE1		0x69
+#define STMPE_KPC_DATA_BYTE2		0x6a
+#define STMPE_KPC_DATA_BYTE3		0x6b
+#define STMPE_KPC_DATA_BYTE4		0x6c
+
+#define STMPE_KPC_CTRL_LSB_SCAN		(0x1 << 0)
+#define STMPE_KPC_CTRL_LSB_DEBOUNCE	(0x7f << 1)
+#define STMPE_KPC_CTRL_MSB_SCAN_COUNT	(0xf << 4)
+
+#define STMPE_KPC_ROW_MSB_ROWS		0xff
+
+#define STMPE_KPC_DATA_UP		(0x1 << 7)
+#define STMPE_KPC_DATA_ROW		(0xf << 3)
+#define STMPE_KPC_DATA_COL		(0x7 << 0)
+#define STMPE_KPC_DATA_NOKEY_MASK	0x78
+
+#define STMPE_KEYPAD_MAX_DEBOUNCE	127
+#define STMPE_KEYPAD_MAX_SCAN_COUNT	15
+
+#define STMPE_KEYPAD_MAX_ROWS		8
+#define STMPE_KEYPAD_MAX_COLS		8
+#define STMPE_KEYPAD_ROW_SHIFT		3
+#define STMPE_KEYPAD_KEYMAP_SIZE	\
+	(STMPE_KEYPAD_MAX_ROWS * STMPE_KEYPAD_MAX_COLS)
+
+/**
+ * struct stmpe_keypad_variant - model-specific attributes
+ * @auto_increment: whether the KPC_DATA_BYTE register address
+ *		    auto-increments on multiple read
+ * @num_data: number of data bytes
+ * @num_normal_data: number of normal keys' data bytes
+ * @max_cols: maximum number of columns supported
+ * @max_rows: maximum number of rows supported
+ * @col_gpios: bitmask of gpios which can be used for columns
+ * @row_gpios: bitmask of gpios which can be used for rows
+ */
+struct stmpe_keypad_variant {
+	bool		auto_increment;
+	int		num_data;
+	int		num_normal_data;
+	int		max_cols;
+	int		max_rows;
+	unsigned int	col_gpios;
+	unsigned int	row_gpios;
+};
+
+static struct stmpe_keypad_variant stmpe_keypad_variants[] = {
+	[STMPE1601] = {
+		.auto_increment		= true,
+		.num_data		= 5,
+		.num_normal_data	= 3,
+		.max_cols		= 8,
+		.max_rows		= 8,
+		.col_gpios		= 0x000ff,	/* GPIO 0 - 7 */
+		.row_gpios		= 0x0ff00,	/* GPIO 8 - 15 */
+	},
+	[STMPE2401] = {
+		.auto_increment		= false,
+		.num_data		= 3,
+		.num_normal_data	= 2,
+		.max_cols		= 8,
+		.max_rows		= 12,
+		.col_gpios		= 0x0000ff,	/* GPIO 0 - 7*/
+		.row_gpios		= 0x1fef00,	/* GPIO 8-14, 16-20 */
+	},
+	[STMPE2403] = {
+		.auto_increment		= true,
+		.num_data		= 5,
+		.num_normal_data	= 3,
+		.max_cols		= 8,
+		.max_rows		= 12,
+		.col_gpios		= 0x0000ff,	/* GPIO 0 - 7*/
+		.row_gpios		= 0x1fef00,	/* GPIO 8-14, 16-20 */
+	},
+};
+
+struct stmpe_keypad {
+	struct stmpe *stmpe;
+	struct input_dev *input;
+	struct stmpe_keypad_variant *variant;
+	struct stmpe_keypad_platform_data *plat;
+
+	unsigned int rows;
+	unsigned int cols;
+
+	unsigned short keymap[STMPE_KEYPAD_KEYMAP_SIZE];
+};
+
+static int stmpe_keypad_read_data(struct stmpe_keypad *keypad, u8 *data)
+{
+	struct stmpe_keypad_variant *variant = keypad->variant;
+	struct stmpe *stmpe = keypad->stmpe;
+	int ret;
+	int i;
+
+	if (variant->auto_increment)
+		return stmpe_block_read(stmpe, STMPE_KPC_DATA_BYTE0,
+					variant->num_data, data);
+
+	for (i = 0; i < variant->num_data; i++) {
+		ret = stmpe_reg_read(stmpe, STMPE_KPC_DATA_BYTE0 + i);
+		if (ret < 0)
+			return ret;
+
+		data[i] = ret;
+	}
+
+	return 0;
+}
+
+static irqreturn_t stmpe_keypad_irq(int irq, void *dev)
+{
+	struct stmpe_keypad *keypad = dev;
+	struct input_dev *input = keypad->input;
+	struct stmpe_keypad_variant *variant = keypad->variant;
+	u8 fifo[variant->num_data];
+	int ret;
+	int i;
+
+	ret = stmpe_keypad_read_data(keypad, fifo);
+	if (ret < 0)
+		return IRQ_NONE;
+
+	for (i = 0; i < variant->num_normal_data; i++) {
+		u8 data = fifo[i];
+		int row = (data & STMPE_KPC_DATA_ROW) >> 3;
+		int col = data & STMPE_KPC_DATA_COL;
+		int code = MATRIX_SCAN_CODE(row, col, STMPE_KEYPAD_ROW_SHIFT);
+		bool up = data & STMPE_KPC_DATA_UP;
+
+		if ((data & STMPE_KPC_DATA_NOKEY_MASK)
+			== STMPE_KPC_DATA_NOKEY_MASK)
+			continue;
+
+		input_event(input, EV_MSC, MSC_SCAN, code);
+		input_report_key(input, keypad->keymap[code], !up);
+		input_sync(input);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit stmpe_keypad_altfunc_init(struct stmpe_keypad *keypad)
+{
+	struct stmpe_keypad_variant *variant = keypad->variant;
+	unsigned int col_gpios = variant->col_gpios;
+	unsigned int row_gpios = variant->row_gpios;
+	struct stmpe *stmpe = keypad->stmpe;
+	unsigned int pins = 0;
+	int i;
+
+	/*
+	 * Figure out which pins need to be set to alternate function 1.
+	 *
+	 * {cols,rows}_gpios are bitmasks of which pins on the chip can be used
+	 * for the keypad.
+	 *
+	 * keypad->{cols,rows} are a bitmask of which pins (of the ones useable
+	 * for the keypad) are used on the board.
+	 */
+
+	for (i = 0; i < variant->max_cols; i++) {
+		int num = __ffs(col_gpios);
+
+		if (keypad->cols & (1 << i))
+			pins |= 1 << num;
+
+		col_gpios &= ~(1 << num);
+	}
+
+	for (i = 0; i < variant->max_rows; i++) {
+		int num = __ffs(row_gpios);
+
+		if (keypad->rows & (1 << i))
+			pins |= 1 << num;
+
+		row_gpios &= ~(1 << num);
+	}
+
+	return stmpe_set_altfunc(stmpe, pins, 1);
+}
+
+static int __devinit stmpe_keypad_chip_init(struct stmpe_keypad *keypad)
+{
+	struct stmpe_keypad_platform_data *plat = keypad->plat;
+	struct stmpe_keypad_variant *variant = keypad->variant;
+	struct stmpe *stmpe = keypad->stmpe;
+	int ret;
+
+	if (plat->debounce_ms > STMPE_KEYPAD_MAX_DEBOUNCE)
+		return -EINVAL;
+
+	if (plat->scan_count > STMPE_KEYPAD_MAX_SCAN_COUNT)
+		return -EINVAL;
+
+	ret = stmpe_set_bits(stmpe, STMPE_SYSCON, STMPE_SYSCON_ENABLE_KPC,
+			     STMPE_SYSCON_ENABLE_KPC);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_keypad_altfunc_init(keypad);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_reg_write(stmpe, STMPE_KPC_COL, keypad->cols);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_reg_write(stmpe, STMPE_KPC_ROW_LSB, keypad->rows);
+	if (ret < 0)
+		return ret;
+
+	if (variant->max_rows > 8) {
+		ret = stmpe_set_bits(stmpe, STMPE_KPC_ROW_MSB,
+				     STMPE_KPC_ROW_MSB_ROWS,
+				     keypad->rows >> 8);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_KPC_CTRL_MSB,
+			     STMPE_KPC_CTRL_MSB_SCAN_COUNT,
+			     plat->scan_count << 4);
+	if (ret < 0)
+		return ret;
+
+	return stmpe_set_bits(stmpe, STMPE_KPC_CTRL_LSB,
+			      STMPE_KPC_CTRL_LSB_SCAN |
+			      STMPE_KPC_CTRL_LSB_DEBOUNCE,
+			      STMPE_KPC_CTRL_LSB_SCAN |
+			      (plat->debounce_ms << 1));
+}
+
+static int __devinit stmpe_keypad_probe(struct platform_device *pdev)
+{
+	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+	struct stmpe_keypad_platform_data *plat;
+	struct stmpe_keypad *keypad;
+	struct input_dev *input;
+	int ret;
+	int irq;
+	int i;
+
+	plat = stmpe->pdata->keypad;
+	if (!plat)
+		return -ENODEV;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	keypad = kzalloc(sizeof(struct stmpe_keypad), GFP_KERNEL);
+	if (!keypad)
+		return -ENOMEM;
+
+	input = input_allocate_device();
+	if (!input) {
+		ret = -ENOMEM;
+		goto out_freekeypad;
+	}
+
+	input->name = "STMPE keypad";
+	input->id.bustype = BUS_I2C;
+	input->dev.parent = &pdev->dev;
+
+	input_set_capability(input, EV_MSC, MSC_SCAN);
+
+	__set_bit(EV_KEY, input->evbit);
+	if (!plat->no_autorepeat)
+		__set_bit(EV_REP, input->evbit);
+
+	input->keycode = keypad->keymap;
+	input->keycodesize = sizeof(keypad->keymap[0]);
+	input->keycodemax = ARRAY_SIZE(keypad->keymap);
+
+	matrix_keypad_build_keymap(plat->keymap_data, STMPE_KEYPAD_ROW_SHIFT,
+				   input->keycode, input->keybit);
+
+	for (i = 0; i < plat->keymap_data->keymap_size; i++) {
+		unsigned int key = plat->keymap_data->keymap[i];
+
+		keypad->cols |= 1 << KEY_COL(key);
+		keypad->rows |= 1 << KEY_ROW(key);
+	}
+
+	keypad->stmpe = stmpe;
+	keypad->plat = plat;
+	keypad->input = input;
+	keypad->variant = &stmpe_keypad_variants[stmpe->variant];
+
+	ret = stmpe_keypad_chip_init(keypad);
+	if (ret < 0)
+		goto out_freeinput;
+
+	ret = request_threaded_irq(irq, NULL, stmpe_keypad_irq, IRQF_ONESHOT,
+				   "stmpe-keypad", keypad);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
+		goto out_freeinput;
+	}
+
+	ret = input_register_device(input);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"unable to register input device: %d\n", ret);
+		goto out_freeirq;
+	}
+
+	platform_set_drvdata(pdev, keypad);
+
+	return 0;
+
+out_freeirq:
+	free_irq(irq, keypad);
+out_freeinput:
+	input_free_device(input);
+out_freekeypad:
+	kfree(keypad);
+	return ret;
+}
+
+static int __devexit stmpe_keypad_remove(struct platform_device *pdev)
+{
+	struct stmpe_keypad *keypad = platform_get_drvdata(pdev);
+	int irq = platform_get_irq(pdev, 0);
+
+	input_unregister_device(keypad->input);
+	free_irq(irq, keypad);
+	platform_set_drvdata(pdev, NULL);
+	kfree(keypad);
+
+	return 0;
+}
+
+static struct platform_driver stmpe_keypad_driver = {
+	.driver.name	= "stmpe-keypad",
+	.driver.owner	= THIS_MODULE,
+	.probe		= stmpe_keypad_probe,
+	.remove		= __devexit_p(stmpe_keypad_remove),
+};
+
+static int __init stmpe_keypad_init(void)
+{
+	return platform_driver_register(&stmpe_keypad_driver);
+}
+module_init(stmpe_keypad_init);
+
+static void __exit stmpe_keypad_exit(void)
+{
+	platform_driver_unregister(&stmpe_keypad_driver);
+}
+module_exit(stmpe_keypad_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("STMPExxxx keypad driver");
+MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
-- 
1.7.0

^ permalink raw reply related	[flat|nested] 61+ messages in thread

* Re: [PATCH 3/3] input: add STMPExxxx keypad driver
  2010-05-31 12:17   ` Rabin Vincent
  (?)
@ 2010-06-01 22:16   ` Dmitry Torokhov
  2010-06-02 13:56     ` Rabin VINCENT
  -1 siblings, 1 reply; 61+ messages in thread
From: Dmitry Torokhov @ 2010-06-01 22:16 UTC (permalink / raw)
  To: Rabin Vincent
  Cc: Samuel Ortiz, linux-kernel, STEricsson_nomadik_linux,
	linux-input, Linus Walleij

Hi Rabin,

Looks like a nice driver, just a couple of comments...

On Mon, May 31, 2010 at 05:47:16PM +0530, Rabin Vincent wrote:
> Add an input driver for the keypad on STMPExxxx I/O expanders.  This
> driver uses the common support provided by the STMPE MFD driver.
> 
> Cc: linux-input@vger.kernel.org
> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> Acked-by: Linus Walleij <linus.walleij@stericsson.com>
> Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
> ---
>  drivers/input/keyboard/Kconfig        |   10 +
>  drivers/input/keyboard/Makefile       |    1 +
>  drivers/input/keyboard/stmpe-keypad.c |  382 +++++++++++++++++++++++++++++++++
>  3 files changed, 393 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/input/keyboard/stmpe-keypad.c
> 
> diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
> index d8fa5d7..0e57fbc 100644
> --- a/drivers/input/keyboard/Kconfig
> +++ b/drivers/input/keyboard/Kconfig
> @@ -374,6 +374,16 @@ config KEYBOARD_SH_KEYSC
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called sh_keysc.
>  
> +config KEYBOARD_STMPE
> +	tristate "STMPExxxx keypad support"
> +	depends on MFD_STMPE
> +	help
> +	  Say Y here if you want to use the keypad controller on STMPExxx I/O
> +	  expanders.
> +
> +	  To compile this driver as a module, choose M here: the module will be
> +	  called stmpe-keypad.
> +
>  config KEYBOARD_DAVINCI
>  	tristate "TI DaVinci Key Scan"
>  	depends on ARCH_DAVINCI_DM365
> diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
> index 4596d0c..af24b07 100644
> --- a/drivers/input/keyboard/Makefile
> +++ b/drivers/input/keyboard/Makefile
> @@ -33,6 +33,7 @@ obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
>  obj-$(CONFIG_KEYBOARD_PXA930_ROTARY)	+= pxa930_rotary.o
>  obj-$(CONFIG_KEYBOARD_QT2160)		+= qt2160.o
>  obj-$(CONFIG_KEYBOARD_SH_KEYSC)		+= sh_keysc.o
> +obj-$(CONFIG_KEYBOARD_STMPE)		+= stmpe-keypad.o
>  obj-$(CONFIG_KEYBOARD_STOWAWAY)		+= stowaway.o
>  obj-$(CONFIG_KEYBOARD_SUNKBD)		+= sunkbd.o
>  obj-$(CONFIG_KEYBOARD_TWL4030)		+= twl4030_keypad.o
> diff --git a/drivers/input/keyboard/stmpe-keypad.c b/drivers/input/keyboard/stmpe-keypad.c
> new file mode 100644
> index 0000000..98ae170
> --- /dev/null
> +++ b/drivers/input/keyboard/stmpe-keypad.c
> @@ -0,0 +1,382 @@
> +/*
> + * Copyright (C) ST-Ericsson SA 2010
> + *
> + * License Terms: GNU General Public License, version 2
> + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
> + */
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/input.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <linux/input/matrix_keypad.h>
> +#include <linux/mfd/stmpe.h>
> +
> +/* These are at the same addresses in all STMPE variants */
> +#define STMPE_KPC_COL			0x60
> +#define STMPE_KPC_ROW_MSB		0x61
> +#define STMPE_KPC_ROW_LSB		0x62
> +#define STMPE_KPC_CTRL_MSB		0x63
> +#define STMPE_KPC_CTRL_LSB		0x64
> +#define STMPE_KPC_COMBI_KEY_0		0x65
> +#define STMPE_KPC_COMBI_KEY_1		0x66
> +#define STMPE_KPC_COMBI_KEY_2		0x67
> +#define STMPE_KPC_DATA_BYTE0		0x68
> +#define STMPE_KPC_DATA_BYTE1		0x69
> +#define STMPE_KPC_DATA_BYTE2		0x6a
> +#define STMPE_KPC_DATA_BYTE3		0x6b
> +#define STMPE_KPC_DATA_BYTE4		0x6c
> +
> +#define STMPE_KPC_CTRL_LSB_SCAN		(0x1 << 0)
> +#define STMPE_KPC_CTRL_LSB_DEBOUNCE	(0x7f << 1)
> +#define STMPE_KPC_CTRL_MSB_SCAN_COUNT	(0xf << 4)
> +
> +#define STMPE_KPC_ROW_MSB_ROWS		0xff
> +
> +#define STMPE_KPC_DATA_UP		(0x1 << 7)
> +#define STMPE_KPC_DATA_ROW		(0xf << 3)
> +#define STMPE_KPC_DATA_COL		(0x7 << 0)
> +#define STMPE_KPC_DATA_NOKEY_MASK	0x78
> +
> +#define STMPE_KEYPAD_MAX_DEBOUNCE	127
> +#define STMPE_KEYPAD_MAX_SCAN_COUNT	15
> +
> +#define STMPE_KEYPAD_MAX_ROWS		8
> +#define STMPE_KEYPAD_MAX_COLS		8
> +#define STMPE_KEYPAD_ROW_SHIFT		3
> +#define STMPE_KEYPAD_KEYMAP_SIZE	\
> +	(STMPE_KEYPAD_MAX_ROWS * STMPE_KEYPAD_MAX_COLS)
> +
> +/**
> + * struct stmpe_keypad_variant - model-specific attributes
> + * @auto_increment: whether the KPC_DATA_BYTE register address
> + *		    auto-increments on multiple read
> + * @num_data: number of data bytes
> + * @num_normal_data: number of normal keys' data bytes
> + * @max_cols: maximum number of columns supported
> + * @max_rows: maximum number of rows supported
> + * @col_gpios: bitmask of gpios which can be used for columns
> + * @row_gpios: bitmask of gpios which can be used for rows
> + */
> +struct stmpe_keypad_variant {
> +	bool		auto_increment;
> +	int		num_data;
> +	int		num_normal_data;
> +	int		max_cols;
> +	int		max_rows;
> +	unsigned int	col_gpios;
> +	unsigned int	row_gpios;
> +};
> +
> +static struct stmpe_keypad_variant stmpe_keypad_variants[] = {
> +	[STMPE1601] = {
> +		.auto_increment		= true,
> +		.num_data		= 5,
> +		.num_normal_data	= 3,
> +		.max_cols		= 8,
> +		.max_rows		= 8,
> +		.col_gpios		= 0x000ff,	/* GPIO 0 - 7 */
> +		.row_gpios		= 0x0ff00,	/* GPIO 8 - 15 */
> +	},
> +	[STMPE2401] = {
> +		.auto_increment		= false,
> +		.num_data		= 3,
> +		.num_normal_data	= 2,
> +		.max_cols		= 8,
> +		.max_rows		= 12,
> +		.col_gpios		= 0x0000ff,	/* GPIO 0 - 7*/
> +		.row_gpios		= 0x1fef00,	/* GPIO 8-14, 16-20 */
> +	},
> +	[STMPE2403] = {
> +		.auto_increment		= true,
> +		.num_data		= 5,
> +		.num_normal_data	= 3,
> +		.max_cols		= 8,
> +		.max_rows		= 12,
> +		.col_gpios		= 0x0000ff,	/* GPIO 0 - 7*/
> +		.row_gpios		= 0x1fef00,	/* GPIO 8-14, 16-20 */
> +	},
> +};

I think it would be better if you moved stmpe_keypad_variant into a
separate header had have board code provide variant instead of needing
to add new variants to the driver itself. Or it is not defined by the
board?

> +
> +struct stmpe_keypad {
> +	struct stmpe *stmpe;
> +	struct input_dev *input;
> +	struct stmpe_keypad_variant *variant;

const?

> +	struct stmpe_keypad_platform_data *plat;

const?

> +
> +	unsigned int rows;
> +	unsigned int cols;
> +
> +	unsigned short keymap[STMPE_KEYPAD_KEYMAP_SIZE];
> +};
> +
> +static int stmpe_keypad_read_data(struct stmpe_keypad *keypad, u8 *data)
> +{
> +	struct stmpe_keypad_variant *variant = keypad->variant;
> +	struct stmpe *stmpe = keypad->stmpe;
> +	int ret;
> +	int i;
> +
> +	if (variant->auto_increment)
> +		return stmpe_block_read(stmpe, STMPE_KPC_DATA_BYTE0,
> +					variant->num_data, data);
> +
> +	for (i = 0; i < variant->num_data; i++) {
> +		ret = stmpe_reg_read(stmpe, STMPE_KPC_DATA_BYTE0 + i);
> +		if (ret < 0)
> +			return ret;
> +
> +		data[i] = ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static irqreturn_t stmpe_keypad_irq(int irq, void *dev)
> +{
> +	struct stmpe_keypad *keypad = dev;
> +	struct input_dev *input = keypad->input;
> +	struct stmpe_keypad_variant *variant = keypad->variant;
> +	u8 fifo[variant->num_data];
> +	int ret;
> +	int i;
> +
> +	ret = stmpe_keypad_read_data(keypad, fifo);
> +	if (ret < 0)
> +		return IRQ_NONE;
> +
> +	for (i = 0; i < variant->num_normal_data; i++) {
> +		u8 data = fifo[i];
> +		int row = (data & STMPE_KPC_DATA_ROW) >> 3;
> +		int col = data & STMPE_KPC_DATA_COL;
> +		int code = MATRIX_SCAN_CODE(row, col, STMPE_KEYPAD_ROW_SHIFT);
> +		bool up = data & STMPE_KPC_DATA_UP;
> +
> +		if ((data & STMPE_KPC_DATA_NOKEY_MASK)
> +			== STMPE_KPC_DATA_NOKEY_MASK)
> +			continue;
> +
> +		input_event(input, EV_MSC, MSC_SCAN, code);
> +		input_report_key(input, keypad->keymap[code], !up);
> +		input_sync(input);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int __devinit stmpe_keypad_altfunc_init(struct stmpe_keypad *keypad)
> +{
> +	struct stmpe_keypad_variant *variant = keypad->variant;
> +	unsigned int col_gpios = variant->col_gpios;
> +	unsigned int row_gpios = variant->row_gpios;
> +	struct stmpe *stmpe = keypad->stmpe;
> +	unsigned int pins = 0;
> +	int i;
> +
> +	/*
> +	 * Figure out which pins need to be set to alternate function 1.
> +	 *
> +	 * {cols,rows}_gpios are bitmasks of which pins on the chip can be used
> +	 * for the keypad.
> +	 *
> +	 * keypad->{cols,rows} are a bitmask of which pins (of the ones useable
> +	 * for the keypad) are used on the board.
> +	 */
> +
> +	for (i = 0; i < variant->max_cols; i++) {
> +		int num = __ffs(col_gpios);
> +
> +		if (keypad->cols & (1 << i))
> +			pins |= 1 << num;
> +
> +		col_gpios &= ~(1 << num);
> +	}
> +
> +	for (i = 0; i < variant->max_rows; i++) {
> +		int num = __ffs(row_gpios);
> +
> +		if (keypad->rows & (1 << i))
> +			pins |= 1 << num;
> +
> +		row_gpios &= ~(1 << num);
> +	}
> +
> +	return stmpe_set_altfunc(stmpe, pins, 1);
> +}
> +
> +static int __devinit stmpe_keypad_chip_init(struct stmpe_keypad *keypad)
> +{
> +	struct stmpe_keypad_platform_data *plat = keypad->plat;
> +	struct stmpe_keypad_variant *variant = keypad->variant;
> +	struct stmpe *stmpe = keypad->stmpe;
> +	int ret;
> +
> +	if (plat->debounce_ms > STMPE_KEYPAD_MAX_DEBOUNCE)
> +		return -EINVAL;
> +
> +	if (plat->scan_count > STMPE_KEYPAD_MAX_SCAN_COUNT)
> +		return -EINVAL;
> +
> +	ret = stmpe_set_bits(stmpe, STMPE_SYSCON, STMPE_SYSCON_ENABLE_KPC,
> +			     STMPE_SYSCON_ENABLE_KPC);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = stmpe_keypad_altfunc_init(keypad);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = stmpe_reg_write(stmpe, STMPE_KPC_COL, keypad->cols);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = stmpe_reg_write(stmpe, STMPE_KPC_ROW_LSB, keypad->rows);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (variant->max_rows > 8) {
> +		ret = stmpe_set_bits(stmpe, STMPE_KPC_ROW_MSB,
> +				     STMPE_KPC_ROW_MSB_ROWS,
> +				     keypad->rows >> 8);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	ret = stmpe_set_bits(stmpe, STMPE_KPC_CTRL_MSB,
> +			     STMPE_KPC_CTRL_MSB_SCAN_COUNT,
> +			     plat->scan_count << 4);
> +	if (ret < 0)
> +		return ret;
> +
> +	return stmpe_set_bits(stmpe, STMPE_KPC_CTRL_LSB,
> +			      STMPE_KPC_CTRL_LSB_SCAN |
> +			      STMPE_KPC_CTRL_LSB_DEBOUNCE,
> +			      STMPE_KPC_CTRL_LSB_SCAN |
> +			      (plat->debounce_ms << 1));
> +}
> +
> +static int __devinit stmpe_keypad_probe(struct platform_device *pdev)
> +{
> +	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
> +	struct stmpe_keypad_platform_data *plat;
> +	struct stmpe_keypad *keypad;
> +	struct input_dev *input;
> +	int ret;
> +	int irq;
> +	int i;
> +
> +	plat = stmpe->pdata->keypad;
> +	if (!plat)
> +		return -ENODEV;
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0)
> +		return irq;
> +
> +	keypad = kzalloc(sizeof(struct stmpe_keypad), GFP_KERNEL);
> +	if (!keypad)
> +		return -ENOMEM;
> +
> +	input = input_allocate_device();
> +	if (!input) {
> +		ret = -ENOMEM;
> +		goto out_freekeypad;
> +	}
> +
> +	input->name = "STMPE keypad";
> +	input->id.bustype = BUS_I2C;
> +	input->dev.parent = &pdev->dev;
> +
> +	input_set_capability(input, EV_MSC, MSC_SCAN);
> +
> +	__set_bit(EV_KEY, input->evbit);
> +	if (!plat->no_autorepeat)
> +		__set_bit(EV_REP, input->evbit);
> +
> +	input->keycode = keypad->keymap;
> +	input->keycodesize = sizeof(keypad->keymap[0]);
> +	input->keycodemax = ARRAY_SIZE(keypad->keymap);
> +
> +	matrix_keypad_build_keymap(plat->keymap_data, STMPE_KEYPAD_ROW_SHIFT,
> +				   input->keycode, input->keybit);
> +
> +	for (i = 0; i < plat->keymap_data->keymap_size; i++) {
> +		unsigned int key = plat->keymap_data->keymap[i];
> +
> +		keypad->cols |= 1 << KEY_COL(key);
> +		keypad->rows |= 1 << KEY_ROW(key);
> +	}
> +
> +	keypad->stmpe = stmpe;
> +	keypad->plat = plat;
> +	keypad->input = input;
> +	keypad->variant = &stmpe_keypad_variants[stmpe->variant];
> +
> +	ret = stmpe_keypad_chip_init(keypad);
> +	if (ret < 0)
> +		goto out_freeinput;
> +
> +	ret = request_threaded_irq(irq, NULL, stmpe_keypad_irq, IRQF_ONESHOT,
> +				   "stmpe-keypad", keypad);
> +	if (ret) {
> +		dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
> +		goto out_freeinput;
> +	}
> +
> +	ret = input_register_device(input);
> +	if (ret) {
> +		dev_err(&pdev->dev,
> +			"unable to register input device: %d\n", ret);
> +		goto out_freeirq;
> +	}
> +
> +	platform_set_drvdata(pdev, keypad);
> +
> +	return 0;
> +
> +out_freeirq:
> +	free_irq(irq, keypad);
> +out_freeinput:
> +	input_free_device(input);
> +out_freekeypad:
> +	kfree(keypad);
> +	return ret;
> +}
> +
> +static int __devexit stmpe_keypad_remove(struct platform_device *pdev)
> +{
> +	struct stmpe_keypad *keypad = platform_get_drvdata(pdev);
> +	int irq = platform_get_irq(pdev, 0);
> +
> +	input_unregister_device(keypad->input);
> +	free_irq(irq, keypad);

You want to free IRQ first, before unregisterin the device. Also, is
there a way to power down keypad parts?

> +	platform_set_drvdata(pdev, NULL);
> +	kfree(keypad);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver stmpe_keypad_driver = {
> +	.driver.name	= "stmpe-keypad",
> +	.driver.owner	= THIS_MODULE,
> +	.probe		= stmpe_keypad_probe,
> +	.remove		= __devexit_p(stmpe_keypad_remove),
> +};
> +
> +static int __init stmpe_keypad_init(void)
> +{
> +	return platform_driver_register(&stmpe_keypad_driver);
> +}
> +module_init(stmpe_keypad_init);
> +
> +static void __exit stmpe_keypad_exit(void)
> +{
> +	platform_driver_unregister(&stmpe_keypad_driver);
> +}
> +module_exit(stmpe_keypad_exit);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("STMPExxxx keypad driver");
> +MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
> -- 
> 1.7.0
> 

-- 
Dmitry

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 3/3] input: add STMPExxxx keypad driver
  2010-06-01 22:16   ` Dmitry Torokhov
@ 2010-06-02 13:56     ` Rabin VINCENT
  2010-06-02 16:05       ` Dmitry Torokhov
  0 siblings, 1 reply; 61+ messages in thread
From: Rabin VINCENT @ 2010-06-02 13:56 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Samuel Ortiz, linux-kernel, STEricsson_nomadik_linux,
	linux-input, Linus WALLEIJ

Hi Dmitry,

On Wed, Jun 02, 2010 at 00:16:11 +0200, Dmitry Torokhov wrote:
> On Mon, May 31, 2010 at 05:47:16PM +0530, Rabin Vincent wrote:
> > +static struct stmpe_keypad_variant stmpe_keypad_variants[] = {
> > +     [STMPE1601] = {
> > +             .auto_increment         = true,
> > +             .num_data               = 5,
> > +             .num_normal_data        = 3,
> > +             .max_cols               = 8,
> > +             .max_rows               = 8,
> > +             .col_gpios              = 0x000ff,      /* GPIO 0 - 7 */
> > +             .row_gpios              = 0x0ff00,      /* GPIO 8 - 15 */
> > +     },
> > +     [STMPE2401] = {
> > +             .auto_increment         = false,
> > +             .num_data               = 3,
> > +             .num_normal_data        = 2,
> > +             .max_cols               = 8,
> > +             .max_rows               = 12,
> > +             .col_gpios              = 0x0000ff,     /* GPIO 0 - 7*/
> > +             .row_gpios              = 0x1fef00,     /* GPIO 8-14, 16-20 */
> > +     },
> > +     [STMPE2403] = {
> > +             .auto_increment         = true,
> > +             .num_data               = 5,
> > +             .num_normal_data        = 3,
> > +             .max_cols               = 8,
> > +             .max_rows               = 12,
> > +             .col_gpios              = 0x0000ff,     /* GPIO 0 - 7*/
> > +             .row_gpios              = 0x1fef00,     /* GPIO 8-14, 16-20 */
> > +     },
> > +};
> 
> I think it would be better if you moved stmpe_keypad_variant into a
> separate header had have board code provide variant instead of needing
> to add new variants to the driver itself. Or it is not defined by the
> board?

The data in this table is not really dependent on the board, but only on
the model number of the STMPExxxx used (this is dynamically detected in
the MFD driver).

For example, col_gpios lists the pins that can possibly be used for
columns on that STMPE model, not the ones which are actually used on the
board.  The ones that are actually used on the board are determined from
the platform data.

> 
> > +
> > +struct stmpe_keypad {
> > +     struct stmpe *stmpe;
> > +     struct input_dev *input;
> > +     struct stmpe_keypad_variant *variant;
> 
> const?
> 
> > +     struct stmpe_keypad_platform_data *plat;
> 
> const?

I've changed these.

[...]
> > +static int __devinit stmpe_keypad_probe(struct platform_device *pdev)
> > +{
[...]
> > +     ret = request_threaded_irq(irq, NULL, stmpe_keypad_irq, IRQF_ONESHOT,
> > +                                "stmpe-keypad", keypad);
> > +     if (ret) {
> > +             dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
> > +             goto out_freeinput;
> > +     }
> > +
> > +     ret = input_register_device(input);
> > +     if (ret) {
> > +             dev_err(&pdev->dev,
> > +                     "unable to register input device: %d\n", ret);
> > +             goto out_freeirq;
> > +     }
> > +
> > +     platform_set_drvdata(pdev, keypad);
> > +
> > +     return 0;
> > +
> > +out_freeirq:
> > +     free_irq(irq, keypad);
> > +out_freeinput:
> > +     input_free_device(input);
> > +out_freekeypad:
> > +     kfree(keypad);
> > +     return ret;
> > +}
> > +
> > +static int __devexit stmpe_keypad_remove(struct platform_device *pdev)
> > +{
> > +     struct stmpe_keypad *keypad = platform_get_drvdata(pdev);
> > +     int irq = platform_get_irq(pdev, 0);
> > +
> > +     input_unregister_device(keypad->input);
> > +     free_irq(irq, keypad);
> 
> You want to free IRQ first, before unregisterin the device. Also, is

Done.  I've also changed the probe sequence to register the device
before requesting the IRQ.

> there a way to power down keypad parts?

I've added code to do this.

> 
> > +     platform_set_drvdata(pdev, NULL);
> > +     kfree(keypad);
> > +
> > +     return 0;
> > +}

Updated patch below.

Thanks,
Rabin

>From c6848c5825d8cadfb22b3a6a7363d50496938293 Mon Sep 17 00:00:00 2001
From: Rabin Vincent <rabin.vincent@stericsson.com>
Date: Fri, 14 May 2010 13:21:12 +0530
Subject: [PATCHv2] input: add STMPExxxx keypad driver

Add an input driver for the keypad on STMPExxxx I/O expanders.  This
driver uses the common support provided by the STMPE MFD driver.

Cc: linux-input@vger.kernel.org
Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
---
 drivers/input/keyboard/Kconfig        |   10 +
 drivers/input/keyboard/Makefile       |    1 +
 drivers/input/keyboard/stmpe-keypad.c |  387 +++++++++++++++++++++++++++++++++
 3 files changed, 398 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/stmpe-keypad.c

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index d8fa5d7..0e57fbc 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -374,6 +374,16 @@ config KEYBOARD_SH_KEYSC
 	  To compile this driver as a module, choose M here: the
 	  module will be called sh_keysc.
 
+config KEYBOARD_STMPE
+	tristate "STMPExxxx keypad support"
+	depends on MFD_STMPE
+	help
+	  Say Y here if you want to use the keypad controller on STMPExxx I/O
+	  expanders.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called stmpe-keypad.
+
 config KEYBOARD_DAVINCI
 	tristate "TI DaVinci Key Scan"
 	depends on ARCH_DAVINCI_DM365
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 4596d0c..af24b07 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
 obj-$(CONFIG_KEYBOARD_PXA930_ROTARY)	+= pxa930_rotary.o
 obj-$(CONFIG_KEYBOARD_QT2160)		+= qt2160.o
 obj-$(CONFIG_KEYBOARD_SH_KEYSC)		+= sh_keysc.o
+obj-$(CONFIG_KEYBOARD_STMPE)		+= stmpe-keypad.o
 obj-$(CONFIG_KEYBOARD_STOWAWAY)		+= stowaway.o
 obj-$(CONFIG_KEYBOARD_SUNKBD)		+= sunkbd.o
 obj-$(CONFIG_KEYBOARD_TWL4030)		+= twl4030_keypad.o
diff --git a/drivers/input/keyboard/stmpe-keypad.c b/drivers/input/keyboard/stmpe-keypad.c
new file mode 100644
index 0000000..1ef8875
--- /dev/null
+++ b/drivers/input/keyboard/stmpe-keypad.c
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/mfd/stmpe.h>
+
+/* These are at the same addresses in all STMPE variants */
+#define STMPE_KPC_COL			0x60
+#define STMPE_KPC_ROW_MSB		0x61
+#define STMPE_KPC_ROW_LSB		0x62
+#define STMPE_KPC_CTRL_MSB		0x63
+#define STMPE_KPC_CTRL_LSB		0x64
+#define STMPE_KPC_COMBI_KEY_0		0x65
+#define STMPE_KPC_COMBI_KEY_1		0x66
+#define STMPE_KPC_COMBI_KEY_2		0x67
+#define STMPE_KPC_DATA_BYTE0		0x68
+#define STMPE_KPC_DATA_BYTE1		0x69
+#define STMPE_KPC_DATA_BYTE2		0x6a
+#define STMPE_KPC_DATA_BYTE3		0x6b
+#define STMPE_KPC_DATA_BYTE4		0x6c
+
+#define STMPE_KPC_CTRL_LSB_SCAN		(0x1 << 0)
+#define STMPE_KPC_CTRL_LSB_DEBOUNCE	(0x7f << 1)
+#define STMPE_KPC_CTRL_MSB_SCAN_COUNT	(0xf << 4)
+
+#define STMPE_KPC_ROW_MSB_ROWS		0xff
+
+#define STMPE_KPC_DATA_UP		(0x1 << 7)
+#define STMPE_KPC_DATA_ROW		(0xf << 3)
+#define STMPE_KPC_DATA_COL		(0x7 << 0)
+#define STMPE_KPC_DATA_NOKEY_MASK	0x78
+
+#define STMPE_KEYPAD_MAX_DEBOUNCE	127
+#define STMPE_KEYPAD_MAX_SCAN_COUNT	15
+
+#define STMPE_KEYPAD_MAX_ROWS		8
+#define STMPE_KEYPAD_MAX_COLS		8
+#define STMPE_KEYPAD_ROW_SHIFT		3
+#define STMPE_KEYPAD_KEYMAP_SIZE	\
+	(STMPE_KEYPAD_MAX_ROWS * STMPE_KEYPAD_MAX_COLS)
+
+/**
+ * struct stmpe_keypad_variant - model-specific attributes
+ * @auto_increment: whether the KPC_DATA_BYTE register address
+ *		    auto-increments on multiple read
+ * @num_data: number of data bytes
+ * @num_normal_data: number of normal keys' data bytes
+ * @max_cols: maximum number of columns supported
+ * @max_rows: maximum number of rows supported
+ * @col_gpios: bitmask of gpios which can be used for columns
+ * @row_gpios: bitmask of gpios which can be used for rows
+ */
+struct stmpe_keypad_variant {
+	bool		auto_increment;
+	int		num_data;
+	int		num_normal_data;
+	int		max_cols;
+	int		max_rows;
+	unsigned int	col_gpios;
+	unsigned int	row_gpios;
+};
+
+static const struct stmpe_keypad_variant stmpe_keypad_variants[] = {
+	[STMPE1601] = {
+		.auto_increment		= true,
+		.num_data		= 5,
+		.num_normal_data	= 3,
+		.max_cols		= 8,
+		.max_rows		= 8,
+		.col_gpios		= 0x000ff,	/* GPIO 0 - 7 */
+		.row_gpios		= 0x0ff00,	/* GPIO 8 - 15 */
+	},
+	[STMPE2401] = {
+		.auto_increment		= false,
+		.num_data		= 3,
+		.num_normal_data	= 2,
+		.max_cols		= 8,
+		.max_rows		= 12,
+		.col_gpios		= 0x0000ff,	/* GPIO 0 - 7*/
+		.row_gpios		= 0x1fef00,	/* GPIO 8-14, 16-20 */
+	},
+	[STMPE2403] = {
+		.auto_increment		= true,
+		.num_data		= 5,
+		.num_normal_data	= 3,
+		.max_cols		= 8,
+		.max_rows		= 12,
+		.col_gpios		= 0x0000ff,	/* GPIO 0 - 7*/
+		.row_gpios		= 0x1fef00,	/* GPIO 8-14, 16-20 */
+	},
+};
+
+struct stmpe_keypad {
+	struct stmpe *stmpe;
+	struct input_dev *input;
+	const struct stmpe_keypad_variant *variant;
+	const struct stmpe_keypad_platform_data *plat;
+
+	unsigned int rows;
+	unsigned int cols;
+
+	unsigned short keymap[STMPE_KEYPAD_KEYMAP_SIZE];
+};
+
+static int stmpe_keypad_read_data(struct stmpe_keypad *keypad, u8 *data)
+{
+	const struct stmpe_keypad_variant *variant = keypad->variant;
+	struct stmpe *stmpe = keypad->stmpe;
+	int ret;
+	int i;
+
+	if (variant->auto_increment)
+		return stmpe_block_read(stmpe, STMPE_KPC_DATA_BYTE0,
+					variant->num_data, data);
+
+	for (i = 0; i < variant->num_data; i++) {
+		ret = stmpe_reg_read(stmpe, STMPE_KPC_DATA_BYTE0 + i);
+		if (ret < 0)
+			return ret;
+
+		data[i] = ret;
+	}
+
+	return 0;
+}
+
+static irqreturn_t stmpe_keypad_irq(int irq, void *dev)
+{
+	struct stmpe_keypad *keypad = dev;
+	struct input_dev *input = keypad->input;
+	const struct stmpe_keypad_variant *variant = keypad->variant;
+	u8 fifo[variant->num_data];
+	int ret;
+	int i;
+
+	ret = stmpe_keypad_read_data(keypad, fifo);
+	if (ret < 0)
+		return IRQ_NONE;
+
+	for (i = 0; i < variant->num_normal_data; i++) {
+		u8 data = fifo[i];
+		int row = (data & STMPE_KPC_DATA_ROW) >> 3;
+		int col = data & STMPE_KPC_DATA_COL;
+		int code = MATRIX_SCAN_CODE(row, col, STMPE_KEYPAD_ROW_SHIFT);
+		bool up = data & STMPE_KPC_DATA_UP;
+
+		if ((data & STMPE_KPC_DATA_NOKEY_MASK)
+			== STMPE_KPC_DATA_NOKEY_MASK)
+			continue;
+
+		input_event(input, EV_MSC, MSC_SCAN, code);
+		input_report_key(input, keypad->keymap[code], !up);
+		input_sync(input);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit stmpe_keypad_altfunc_init(struct stmpe_keypad *keypad)
+{
+	const struct stmpe_keypad_variant *variant = keypad->variant;
+	unsigned int col_gpios = variant->col_gpios;
+	unsigned int row_gpios = variant->row_gpios;
+	struct stmpe *stmpe = keypad->stmpe;
+	unsigned int pins = 0;
+	int i;
+
+	/*
+	 * Figure out which pins need to be set to alternate function 1.
+	 *
+	 * {cols,rows}_gpios are bitmasks of which pins on the chip can be used
+	 * for the keypad.
+	 *
+	 * keypad->{cols,rows} are a bitmask of which pins (of the ones useable
+	 * for the keypad) are used on the board.
+	 */
+
+	for (i = 0; i < variant->max_cols; i++) {
+		int num = __ffs(col_gpios);
+
+		if (keypad->cols & (1 << i))
+			pins |= 1 << num;
+
+		col_gpios &= ~(1 << num);
+	}
+
+	for (i = 0; i < variant->max_rows; i++) {
+		int num = __ffs(row_gpios);
+
+		if (keypad->rows & (1 << i))
+			pins |= 1 << num;
+
+		row_gpios &= ~(1 << num);
+	}
+
+	return stmpe_set_altfunc(stmpe, pins, 1);
+}
+
+static int __devinit stmpe_keypad_chip_init(struct stmpe_keypad *keypad)
+{
+	const struct stmpe_keypad_platform_data *plat = keypad->plat;
+	const struct stmpe_keypad_variant *variant = keypad->variant;
+	struct stmpe *stmpe = keypad->stmpe;
+	int ret;
+
+	if (plat->debounce_ms > STMPE_KEYPAD_MAX_DEBOUNCE)
+		return -EINVAL;
+
+	if (plat->scan_count > STMPE_KEYPAD_MAX_SCAN_COUNT)
+		return -EINVAL;
+
+	ret = stmpe_set_bits(stmpe, STMPE_SYSCON, STMPE_SYSCON_ENABLE_KPC,
+			     STMPE_SYSCON_ENABLE_KPC);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_keypad_altfunc_init(keypad);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_reg_write(stmpe, STMPE_KPC_COL, keypad->cols);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_reg_write(stmpe, STMPE_KPC_ROW_LSB, keypad->rows);
+	if (ret < 0)
+		return ret;
+
+	if (variant->max_rows > 8) {
+		ret = stmpe_set_bits(stmpe, STMPE_KPC_ROW_MSB,
+				     STMPE_KPC_ROW_MSB_ROWS,
+				     keypad->rows >> 8);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_KPC_CTRL_MSB,
+			     STMPE_KPC_CTRL_MSB_SCAN_COUNT,
+			     plat->scan_count << 4);
+	if (ret < 0)
+		return ret;
+
+	return stmpe_set_bits(stmpe, STMPE_KPC_CTRL_LSB,
+			      STMPE_KPC_CTRL_LSB_SCAN |
+			      STMPE_KPC_CTRL_LSB_DEBOUNCE,
+			      STMPE_KPC_CTRL_LSB_SCAN |
+			      (plat->debounce_ms << 1));
+}
+
+static int __devinit stmpe_keypad_probe(struct platform_device *pdev)
+{
+	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+	struct stmpe_keypad_platform_data *plat;
+	struct stmpe_keypad *keypad;
+	struct input_dev *input;
+	int ret;
+	int irq;
+	int i;
+
+	plat = stmpe->pdata->keypad;
+	if (!plat)
+		return -ENODEV;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	keypad = kzalloc(sizeof(struct stmpe_keypad), GFP_KERNEL);
+	if (!keypad)
+		return -ENOMEM;
+
+	input = input_allocate_device();
+	if (!input) {
+		ret = -ENOMEM;
+		goto out_freekeypad;
+	}
+
+	input->name = "STMPE keypad";
+	input->id.bustype = BUS_I2C;
+	input->dev.parent = &pdev->dev;
+
+	input_set_capability(input, EV_MSC, MSC_SCAN);
+
+	__set_bit(EV_KEY, input->evbit);
+	if (!plat->no_autorepeat)
+		__set_bit(EV_REP, input->evbit);
+
+	input->keycode = keypad->keymap;
+	input->keycodesize = sizeof(keypad->keymap[0]);
+	input->keycodemax = ARRAY_SIZE(keypad->keymap);
+
+	matrix_keypad_build_keymap(plat->keymap_data, STMPE_KEYPAD_ROW_SHIFT,
+				   input->keycode, input->keybit);
+
+	for (i = 0; i < plat->keymap_data->keymap_size; i++) {
+		unsigned int key = plat->keymap_data->keymap[i];
+
+		keypad->cols |= 1 << KEY_COL(key);
+		keypad->rows |= 1 << KEY_ROW(key);
+	}
+
+	keypad->stmpe = stmpe;
+	keypad->plat = plat;
+	keypad->input = input;
+	keypad->variant = &stmpe_keypad_variants[stmpe->variant];
+
+	ret = stmpe_keypad_chip_init(keypad);
+	if (ret < 0)
+		goto out_freeinput;
+
+	ret = input_register_device(input);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"unable to register input device: %d\n", ret);
+		goto out_freeinput;
+	}
+
+	ret = request_threaded_irq(irq, NULL, stmpe_keypad_irq, IRQF_ONESHOT,
+				   "stmpe-keypad", keypad);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
+		goto out_unregisterinput;
+	}
+
+	platform_set_drvdata(pdev, keypad);
+
+	return 0;
+
+out_unregisterinput:
+	input_unregister_device(input);
+	input = NULL;
+out_freeinput:
+	input_free_device(input);
+out_freekeypad:
+	kfree(keypad);
+	return ret;
+}
+
+static int __devexit stmpe_keypad_remove(struct platform_device *pdev)
+{
+	struct stmpe_keypad *keypad = platform_get_drvdata(pdev);
+	struct stmpe *stmpe = keypad->stmpe;
+	int irq = platform_get_irq(pdev, 0);
+
+	/* Disable the keypad module.  Ignore any errors. */
+	stmpe_set_bits(stmpe, STMPE_SYSCON, STMPE_SYSCON_ENABLE_KPC, 0);
+
+	free_irq(irq, keypad);
+	input_unregister_device(keypad->input);
+	platform_set_drvdata(pdev, NULL);
+	kfree(keypad);
+
+	return 0;
+}
+
+static struct platform_driver stmpe_keypad_driver = {
+	.driver.name	= "stmpe-keypad",
+	.driver.owner	= THIS_MODULE,
+	.probe		= stmpe_keypad_probe,
+	.remove		= __devexit_p(stmpe_keypad_remove),
+};
+
+static int __init stmpe_keypad_init(void)
+{
+	return platform_driver_register(&stmpe_keypad_driver);
+}
+module_init(stmpe_keypad_init);
+
+static void __exit stmpe_keypad_exit(void)
+{
+	platform_driver_unregister(&stmpe_keypad_driver);
+}
+module_exit(stmpe_keypad_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("STMPExxxx keypad driver");
+MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
-- 
1.7.0


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* Re: [PATCH 3/3] input: add STMPExxxx keypad driver
  2010-06-02 13:56     ` Rabin VINCENT
@ 2010-06-02 16:05       ` Dmitry Torokhov
  0 siblings, 0 replies; 61+ messages in thread
From: Dmitry Torokhov @ 2010-06-02 16:05 UTC (permalink / raw)
  To: Rabin VINCENT
  Cc: Samuel Ortiz, linux-kernel, STEricsson_nomadik_linux,
	linux-input, Linus WALLEIJ

On Wed, Jun 02, 2010 at 07:26:37PM +0530, Rabin VINCENT wrote:
> > > +
> > > +static int __devexit stmpe_keypad_remove(struct platform_device *pdev)
> > > +{
> > > +     struct stmpe_keypad *keypad = platform_get_drvdata(pdev);
> > > +     int irq = platform_get_irq(pdev, 0);
> > > +
> > > +     input_unregister_device(keypad->input);
> > > +     free_irq(irq, keypad);
> > 
> > You want to free IRQ first, before unregisterin the device. Also, is
> 
> Done.  I've also changed the probe sequence to register the device
> before requesting the IRQ.
> 
> > there a way to power down keypad parts?
> 
> I've added code to do this.
> 
> > 
> > > +     platform_set_drvdata(pdev, NULL);
> > > +     kfree(keypad);
> > > +
> > > +     return 0;
> > > +}
> 
> Updated patch below.
> 

Thank you for making the changes. Please merge through MFD tree.

Acked-by: Dmitry Torokhov <dtor@mai.ru>

> 
> From c6848c5825d8cadfb22b3a6a7363d50496938293 Mon Sep 17 00:00:00 2001
> From: Rabin Vincent <rabin.vincent@stericsson.com>
> Date: Fri, 14 May 2010 13:21:12 +0530
> Subject: [PATCHv2] input: add STMPExxxx keypad driver
> 
> Add an input driver for the keypad on STMPExxxx I/O expanders.  This
> driver uses the common support provided by the STMPE MFD driver.
> 
> Cc: linux-input@vger.kernel.org
> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> Acked-by: Linus Walleij <linus.walleij@stericsson.com>
> Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
> ---
>  drivers/input/keyboard/Kconfig        |   10 +
>  drivers/input/keyboard/Makefile       |    1 +
>  drivers/input/keyboard/stmpe-keypad.c |  387 +++++++++++++++++++++++++++++++++
>  3 files changed, 398 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/input/keyboard/stmpe-keypad.c
> 
> diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
> index d8fa5d7..0e57fbc 100644
> --- a/drivers/input/keyboard/Kconfig
> +++ b/drivers/input/keyboard/Kconfig
> @@ -374,6 +374,16 @@ config KEYBOARD_SH_KEYSC
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called sh_keysc.
>  
> +config KEYBOARD_STMPE
> +	tristate "STMPExxxx keypad support"
> +	depends on MFD_STMPE
> +	help
> +	  Say Y here if you want to use the keypad controller on STMPExxx I/O
> +	  expanders.
> +
> +	  To compile this driver as a module, choose M here: the module will be
> +	  called stmpe-keypad.
> +
>  config KEYBOARD_DAVINCI
>  	tristate "TI DaVinci Key Scan"
>  	depends on ARCH_DAVINCI_DM365
> diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
> index 4596d0c..af24b07 100644
> --- a/drivers/input/keyboard/Makefile
> +++ b/drivers/input/keyboard/Makefile
> @@ -33,6 +33,7 @@ obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
>  obj-$(CONFIG_KEYBOARD_PXA930_ROTARY)	+= pxa930_rotary.o
>  obj-$(CONFIG_KEYBOARD_QT2160)		+= qt2160.o
>  obj-$(CONFIG_KEYBOARD_SH_KEYSC)		+= sh_keysc.o
> +obj-$(CONFIG_KEYBOARD_STMPE)		+= stmpe-keypad.o
>  obj-$(CONFIG_KEYBOARD_STOWAWAY)		+= stowaway.o
>  obj-$(CONFIG_KEYBOARD_SUNKBD)		+= sunkbd.o
>  obj-$(CONFIG_KEYBOARD_TWL4030)		+= twl4030_keypad.o
> diff --git a/drivers/input/keyboard/stmpe-keypad.c b/drivers/input/keyboard/stmpe-keypad.c
> new file mode 100644
> index 0000000..1ef8875
> --- /dev/null
> +++ b/drivers/input/keyboard/stmpe-keypad.c
> @@ -0,0 +1,387 @@
> +/*
> + * Copyright (C) ST-Ericsson SA 2010
> + *
> + * License Terms: GNU General Public License, version 2
> + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
> + */
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/input.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <linux/input/matrix_keypad.h>
> +#include <linux/mfd/stmpe.h>
> +
> +/* These are at the same addresses in all STMPE variants */
> +#define STMPE_KPC_COL			0x60
> +#define STMPE_KPC_ROW_MSB		0x61
> +#define STMPE_KPC_ROW_LSB		0x62
> +#define STMPE_KPC_CTRL_MSB		0x63
> +#define STMPE_KPC_CTRL_LSB		0x64
> +#define STMPE_KPC_COMBI_KEY_0		0x65
> +#define STMPE_KPC_COMBI_KEY_1		0x66
> +#define STMPE_KPC_COMBI_KEY_2		0x67
> +#define STMPE_KPC_DATA_BYTE0		0x68
> +#define STMPE_KPC_DATA_BYTE1		0x69
> +#define STMPE_KPC_DATA_BYTE2		0x6a
> +#define STMPE_KPC_DATA_BYTE3		0x6b
> +#define STMPE_KPC_DATA_BYTE4		0x6c
> +
> +#define STMPE_KPC_CTRL_LSB_SCAN		(0x1 << 0)
> +#define STMPE_KPC_CTRL_LSB_DEBOUNCE	(0x7f << 1)
> +#define STMPE_KPC_CTRL_MSB_SCAN_COUNT	(0xf << 4)
> +
> +#define STMPE_KPC_ROW_MSB_ROWS		0xff
> +
> +#define STMPE_KPC_DATA_UP		(0x1 << 7)
> +#define STMPE_KPC_DATA_ROW		(0xf << 3)
> +#define STMPE_KPC_DATA_COL		(0x7 << 0)
> +#define STMPE_KPC_DATA_NOKEY_MASK	0x78
> +
> +#define STMPE_KEYPAD_MAX_DEBOUNCE	127
> +#define STMPE_KEYPAD_MAX_SCAN_COUNT	15
> +
> +#define STMPE_KEYPAD_MAX_ROWS		8
> +#define STMPE_KEYPAD_MAX_COLS		8
> +#define STMPE_KEYPAD_ROW_SHIFT		3
> +#define STMPE_KEYPAD_KEYMAP_SIZE	\
> +	(STMPE_KEYPAD_MAX_ROWS * STMPE_KEYPAD_MAX_COLS)
> +
> +/**
> + * struct stmpe_keypad_variant - model-specific attributes
> + * @auto_increment: whether the KPC_DATA_BYTE register address
> + *		    auto-increments on multiple read
> + * @num_data: number of data bytes
> + * @num_normal_data: number of normal keys' data bytes
> + * @max_cols: maximum number of columns supported
> + * @max_rows: maximum number of rows supported
> + * @col_gpios: bitmask of gpios which can be used for columns
> + * @row_gpios: bitmask of gpios which can be used for rows
> + */
> +struct stmpe_keypad_variant {
> +	bool		auto_increment;
> +	int		num_data;
> +	int		num_normal_data;
> +	int		max_cols;
> +	int		max_rows;
> +	unsigned int	col_gpios;
> +	unsigned int	row_gpios;
> +};
> +
> +static const struct stmpe_keypad_variant stmpe_keypad_variants[] = {
> +	[STMPE1601] = {
> +		.auto_increment		= true,
> +		.num_data		= 5,
> +		.num_normal_data	= 3,
> +		.max_cols		= 8,
> +		.max_rows		= 8,
> +		.col_gpios		= 0x000ff,	/* GPIO 0 - 7 */
> +		.row_gpios		= 0x0ff00,	/* GPIO 8 - 15 */
> +	},
> +	[STMPE2401] = {
> +		.auto_increment		= false,
> +		.num_data		= 3,
> +		.num_normal_data	= 2,
> +		.max_cols		= 8,
> +		.max_rows		= 12,
> +		.col_gpios		= 0x0000ff,	/* GPIO 0 - 7*/
> +		.row_gpios		= 0x1fef00,	/* GPIO 8-14, 16-20 */
> +	},
> +	[STMPE2403] = {
> +		.auto_increment		= true,
> +		.num_data		= 5,
> +		.num_normal_data	= 3,
> +		.max_cols		= 8,
> +		.max_rows		= 12,
> +		.col_gpios		= 0x0000ff,	/* GPIO 0 - 7*/
> +		.row_gpios		= 0x1fef00,	/* GPIO 8-14, 16-20 */
> +	},
> +};
> +
> +struct stmpe_keypad {
> +	struct stmpe *stmpe;
> +	struct input_dev *input;
> +	const struct stmpe_keypad_variant *variant;
> +	const struct stmpe_keypad_platform_data *plat;
> +
> +	unsigned int rows;
> +	unsigned int cols;
> +
> +	unsigned short keymap[STMPE_KEYPAD_KEYMAP_SIZE];
> +};
> +
> +static int stmpe_keypad_read_data(struct stmpe_keypad *keypad, u8 *data)
> +{
> +	const struct stmpe_keypad_variant *variant = keypad->variant;
> +	struct stmpe *stmpe = keypad->stmpe;
> +	int ret;
> +	int i;
> +
> +	if (variant->auto_increment)
> +		return stmpe_block_read(stmpe, STMPE_KPC_DATA_BYTE0,
> +					variant->num_data, data);
> +
> +	for (i = 0; i < variant->num_data; i++) {
> +		ret = stmpe_reg_read(stmpe, STMPE_KPC_DATA_BYTE0 + i);
> +		if (ret < 0)
> +			return ret;
> +
> +		data[i] = ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static irqreturn_t stmpe_keypad_irq(int irq, void *dev)
> +{
> +	struct stmpe_keypad *keypad = dev;
> +	struct input_dev *input = keypad->input;
> +	const struct stmpe_keypad_variant *variant = keypad->variant;
> +	u8 fifo[variant->num_data];
> +	int ret;
> +	int i;
> +
> +	ret = stmpe_keypad_read_data(keypad, fifo);
> +	if (ret < 0)
> +		return IRQ_NONE;
> +
> +	for (i = 0; i < variant->num_normal_data; i++) {
> +		u8 data = fifo[i];
> +		int row = (data & STMPE_KPC_DATA_ROW) >> 3;
> +		int col = data & STMPE_KPC_DATA_COL;
> +		int code = MATRIX_SCAN_CODE(row, col, STMPE_KEYPAD_ROW_SHIFT);
> +		bool up = data & STMPE_KPC_DATA_UP;
> +
> +		if ((data & STMPE_KPC_DATA_NOKEY_MASK)
> +			== STMPE_KPC_DATA_NOKEY_MASK)
> +			continue;
> +
> +		input_event(input, EV_MSC, MSC_SCAN, code);
> +		input_report_key(input, keypad->keymap[code], !up);
> +		input_sync(input);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int __devinit stmpe_keypad_altfunc_init(struct stmpe_keypad *keypad)
> +{
> +	const struct stmpe_keypad_variant *variant = keypad->variant;
> +	unsigned int col_gpios = variant->col_gpios;
> +	unsigned int row_gpios = variant->row_gpios;
> +	struct stmpe *stmpe = keypad->stmpe;
> +	unsigned int pins = 0;
> +	int i;
> +
> +	/*
> +	 * Figure out which pins need to be set to alternate function 1.
> +	 *
> +	 * {cols,rows}_gpios are bitmasks of which pins on the chip can be used
> +	 * for the keypad.
> +	 *
> +	 * keypad->{cols,rows} are a bitmask of which pins (of the ones useable
> +	 * for the keypad) are used on the board.
> +	 */
> +
> +	for (i = 0; i < variant->max_cols; i++) {
> +		int num = __ffs(col_gpios);
> +
> +		if (keypad->cols & (1 << i))
> +			pins |= 1 << num;
> +
> +		col_gpios &= ~(1 << num);
> +	}
> +
> +	for (i = 0; i < variant->max_rows; i++) {
> +		int num = __ffs(row_gpios);
> +
> +		if (keypad->rows & (1 << i))
> +			pins |= 1 << num;
> +
> +		row_gpios &= ~(1 << num);
> +	}
> +
> +	return stmpe_set_altfunc(stmpe, pins, 1);
> +}
> +
> +static int __devinit stmpe_keypad_chip_init(struct stmpe_keypad *keypad)
> +{
> +	const struct stmpe_keypad_platform_data *plat = keypad->plat;
> +	const struct stmpe_keypad_variant *variant = keypad->variant;
> +	struct stmpe *stmpe = keypad->stmpe;
> +	int ret;
> +
> +	if (plat->debounce_ms > STMPE_KEYPAD_MAX_DEBOUNCE)
> +		return -EINVAL;
> +
> +	if (plat->scan_count > STMPE_KEYPAD_MAX_SCAN_COUNT)
> +		return -EINVAL;
> +
> +	ret = stmpe_set_bits(stmpe, STMPE_SYSCON, STMPE_SYSCON_ENABLE_KPC,
> +			     STMPE_SYSCON_ENABLE_KPC);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = stmpe_keypad_altfunc_init(keypad);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = stmpe_reg_write(stmpe, STMPE_KPC_COL, keypad->cols);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = stmpe_reg_write(stmpe, STMPE_KPC_ROW_LSB, keypad->rows);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (variant->max_rows > 8) {
> +		ret = stmpe_set_bits(stmpe, STMPE_KPC_ROW_MSB,
> +				     STMPE_KPC_ROW_MSB_ROWS,
> +				     keypad->rows >> 8);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	ret = stmpe_set_bits(stmpe, STMPE_KPC_CTRL_MSB,
> +			     STMPE_KPC_CTRL_MSB_SCAN_COUNT,
> +			     plat->scan_count << 4);
> +	if (ret < 0)
> +		return ret;
> +
> +	return stmpe_set_bits(stmpe, STMPE_KPC_CTRL_LSB,
> +			      STMPE_KPC_CTRL_LSB_SCAN |
> +			      STMPE_KPC_CTRL_LSB_DEBOUNCE,
> +			      STMPE_KPC_CTRL_LSB_SCAN |
> +			      (plat->debounce_ms << 1));
> +}
> +
> +static int __devinit stmpe_keypad_probe(struct platform_device *pdev)
> +{
> +	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
> +	struct stmpe_keypad_platform_data *plat;
> +	struct stmpe_keypad *keypad;
> +	struct input_dev *input;
> +	int ret;
> +	int irq;
> +	int i;
> +
> +	plat = stmpe->pdata->keypad;
> +	if (!plat)
> +		return -ENODEV;
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0)
> +		return irq;
> +
> +	keypad = kzalloc(sizeof(struct stmpe_keypad), GFP_KERNEL);
> +	if (!keypad)
> +		return -ENOMEM;
> +
> +	input = input_allocate_device();
> +	if (!input) {
> +		ret = -ENOMEM;
> +		goto out_freekeypad;
> +	}
> +
> +	input->name = "STMPE keypad";
> +	input->id.bustype = BUS_I2C;
> +	input->dev.parent = &pdev->dev;
> +
> +	input_set_capability(input, EV_MSC, MSC_SCAN);
> +
> +	__set_bit(EV_KEY, input->evbit);
> +	if (!plat->no_autorepeat)
> +		__set_bit(EV_REP, input->evbit);
> +
> +	input->keycode = keypad->keymap;
> +	input->keycodesize = sizeof(keypad->keymap[0]);
> +	input->keycodemax = ARRAY_SIZE(keypad->keymap);
> +
> +	matrix_keypad_build_keymap(plat->keymap_data, STMPE_KEYPAD_ROW_SHIFT,
> +				   input->keycode, input->keybit);
> +
> +	for (i = 0; i < plat->keymap_data->keymap_size; i++) {
> +		unsigned int key = plat->keymap_data->keymap[i];
> +
> +		keypad->cols |= 1 << KEY_COL(key);
> +		keypad->rows |= 1 << KEY_ROW(key);
> +	}
> +
> +	keypad->stmpe = stmpe;
> +	keypad->plat = plat;
> +	keypad->input = input;
> +	keypad->variant = &stmpe_keypad_variants[stmpe->variant];
> +
> +	ret = stmpe_keypad_chip_init(keypad);
> +	if (ret < 0)
> +		goto out_freeinput;
> +
> +	ret = input_register_device(input);
> +	if (ret) {
> +		dev_err(&pdev->dev,
> +			"unable to register input device: %d\n", ret);
> +		goto out_freeinput;
> +	}
> +
> +	ret = request_threaded_irq(irq, NULL, stmpe_keypad_irq, IRQF_ONESHOT,
> +				   "stmpe-keypad", keypad);
> +	if (ret) {
> +		dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
> +		goto out_unregisterinput;
> +	}
> +
> +	platform_set_drvdata(pdev, keypad);
> +
> +	return 0;
> +
> +out_unregisterinput:
> +	input_unregister_device(input);
> +	input = NULL;
> +out_freeinput:
> +	input_free_device(input);
> +out_freekeypad:
> +	kfree(keypad);
> +	return ret;
> +}
> +
> +static int __devexit stmpe_keypad_remove(struct platform_device *pdev)
> +{
> +	struct stmpe_keypad *keypad = platform_get_drvdata(pdev);
> +	struct stmpe *stmpe = keypad->stmpe;
> +	int irq = platform_get_irq(pdev, 0);
> +
> +	/* Disable the keypad module.  Ignore any errors. */
> +	stmpe_set_bits(stmpe, STMPE_SYSCON, STMPE_SYSCON_ENABLE_KPC, 0);
> +
> +	free_irq(irq, keypad);
> +	input_unregister_device(keypad->input);
> +	platform_set_drvdata(pdev, NULL);
> +	kfree(keypad);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver stmpe_keypad_driver = {
> +	.driver.name	= "stmpe-keypad",
> +	.driver.owner	= THIS_MODULE,
> +	.probe		= stmpe_keypad_probe,
> +	.remove		= __devexit_p(stmpe_keypad_remove),
> +};
> +
> +static int __init stmpe_keypad_init(void)
> +{
> +	return platform_driver_register(&stmpe_keypad_driver);
> +}
> +module_init(stmpe_keypad_init);
> +
> +static void __exit stmpe_keypad_exit(void)
> +{
> +	platform_driver_unregister(&stmpe_keypad_driver);
> +}
> +module_exit(stmpe_keypad_exit);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("STMPExxxx keypad driver");
> +MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
> -- 
> 1.7.0
> 

-- 
Dmitry

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 1/3] mfd: add STMPExxxx I/O Expander support
  2010-05-31 12:17 [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Rabin Vincent
  2010-05-31 12:17 ` [PATCH 2/3] gpio: add STMPExxxx GPIO driver Rabin Vincent
  2010-05-31 12:17   ` Rabin Vincent
@ 2010-06-18 23:42 ` Samuel Ortiz
  2010-06-19 13:50   ` Luotao Fu
  2010-06-21 13:33   ` Rabin VINCENT
  2 siblings, 2 replies; 61+ messages in thread
From: Samuel Ortiz @ 2010-06-18 23:42 UTC (permalink / raw)
  To: Rabin Vincent; +Cc: linux-kernel, STEricsson_nomadik_linux, Linus Walleij, l.fu

Hi Rabin,

On Mon, May 31, 2010 at 05:47:14PM +0530, Rabin Vincent wrote:
> Add support for the STMPExxxx family of I/O Expanders from
> STMicroelectronics.  These devices include upto 24 gpios, a PWM
> controller, and a keypad controller.  This patch adds the MFD core.
The patchset looks fairly good, but before merging it I'd like to know of we
could merge it with this one:
https://patchwork.kernel.org/patch/106173/

I don't know enough about the hardware, and although the register layouts don't
look like they have much in common, I'd like to know from the actual HW
manufacturer (i.e. you :)) if there's something we can do here.
I'm cc'ing Luotao here so that we get his input as well.

Cheers,
Samuel.


> Acked-by: Linus Walleij <linus.walleij@stericsson.com>
> Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
> ---
>  drivers/mfd/Kconfig       |   12 +
>  drivers/mfd/Makefile      |    1 +
>  drivers/mfd/stmpe.c       |  600 +++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/stmpe.h |  135 ++++++++++
>  4 files changed, 748 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/mfd/stmpe.c
>  create mode 100644 include/linux/mfd/stmpe.h
> 
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 9da0e50..e4ee19d 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -177,6 +177,18 @@ config TWL4030_CODEC
>  	select MFD_CORE
>  	default n
>  
> +config MFD_STMPE
> +	bool "Support STMicroelectronics STMPExxxx"
> +	depends on I2C=y && GENERIC_HARDIRQS
> +	select MFD_CORE
> +	help
> +	  Support for the STMPExxxx family of I/O Expanders from
> +	  STMicroelectronics.
> +
> +	  This driver provides common support for accessing the device,
> +	  additional drivers must be enabled in order to use the
> +	  functionality of the device.
> +
>  config MFD_TC35892
>  	bool "Support Toshiba TC35892"
>  	depends on I2C=y && GENERIC_HARDIRQS
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index fb503e7..4410747 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -15,6 +15,7 @@ obj-$(CONFIG_HTC_I2CPLD)	+= htc-i2cpld.o
>  obj-$(CONFIG_MFD_DAVINCI_VOICECODEC)	+= davinci_voicecodec.o
>  obj-$(CONFIG_MFD_DM355EVM_MSP)	+= dm355evm_msp.o
>  
> +obj-$(CONFIG_MFD_STMPE)		+= stmpe.o
>  obj-$(CONFIG_MFD_TC35892)	+= tc35892.o
>  obj-$(CONFIG_MFD_T7L66XB)	+= t7l66xb.o tmio_core.o
>  obj-$(CONFIG_MFD_TC6387XB)	+= tc6387xb.o tmio_core.o
> diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c
> new file mode 100644
> index 0000000..53e72b6
> --- /dev/null
> +++ b/drivers/mfd/stmpe.c
> @@ -0,0 +1,600 @@
> +/*
> + * Copyright (C) ST-Ericsson SA 2010
> + *
> + * License Terms: GNU General Public License, version 2
> + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/slab.h>
> +#include <linux/i2c.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/stmpe.h>
> +
> +/* Interrupts */
> +#define STMPE_INT_GPIOC		8
> +#define STMPE1601_INT_PWM3	7
> +#define STMPE1601_INT_PWM2	6
> +#define STMPE1601_INT_PWM1	5
> +#define STMPE1601_INT_PWM0	4
> +#define STMPE24XX_INT_PWM2	7
> +#define STMPE24XX_INT_PWM1	6
> +#define STMPE24XX_INT_PWM0	5
> +#define STMPE24XX_INT_ROT_OVER	4
> +#define STMPE24XX_INT_ROT	3
> +#define STMPE_INT_KEYPAD_OVER	2
> +#define STMPE_INT_KEYPAD	1
> +#define STMPE_INT_WAKEUP	0
> +
> +/* Core registers at same addresses on all variants */
> +#define STMPE_ICR_LSB		0x11
> +#define STMPE_IER_LSB		0x13
> +#define STMPE_ISR_MSB		0x14
> +#define STMPE_CHIP_ID		0x80
> +
> +#define STMPE_ICR_LSB_HIGH	(1 << 2)
> +#define STMPE_ICR_LSB_EDGE	(1 << 1)
> +#define STMPE_ICR_LSB_GIM	(1 << 0)
> +
> +/*
> + * The following registers are at different addresses on different variants.
> + * We provide a set of register indices and a translation table.
> + */
> +
> +#define STMPE1601_INT_EN_GPIO_MASK_LSB	0x17
> +#define STMPE1601_INT_STA_GPIO_MSB	0x18
> +#define STMPE1601_GPIO_MP_LSB		0x87
> +#define STMPE1601_GPIO_SET_LSB		0x83
> +#define STMPE1601_GPIO_CLR_LSB		0x85
> +#define STMPE1601_GPIO_SET_DIR_LSB	0x89
> +#define STMPE1601_GPIO_ED_MSB		0x8A
> +#define STMPE1601_GPIO_RE_LSB		0x8D
> +#define STMPE1601_GPIO_FE_LSB		0x8F
> +#define STMPE1601_GPIO_AF_U_MSB		0x92
> +
> +#define STMPE24XX_IEGPIOR_LSB		0x18
> +#define STMPE24XX_ISGPIOR_MSB		0x19
> +#define STMPE24XX_GPMR_LSB		0xA5
> +#define STMPE24XX_GPSR_LSB		0x85
> +#define STMPE24XX_GPCR_LSB		0x88
> +#define STMPE24XX_GPDR_LSB		0x8B
> +#define STMPE24XX_GPEDR_MSB		0x8C
> +#define STMPE24XX_GPRER_LSB		0x91
> +#define STMPE24XX_GPFER_LSB		0x94
> +#define STMPE24XX_GPAFR_U_MSB		0x9B
> +
> +static const u8 stmpe1601_regs[] = {
> +	[STMPE_IDX_GPMR_LSB]	= STMPE1601_GPIO_MP_LSB,
> +	[STMPE_IDX_GPSR_LSB]	= STMPE1601_GPIO_SET_LSB,
> +	[STMPE_IDX_GPCR_LSB]	= STMPE1601_GPIO_CLR_LSB,
> +	[STMPE_IDX_GPDR_LSB]	= STMPE1601_GPIO_SET_DIR_LSB,
> +	[STMPE_IDX_GPRER_LSB]	= STMPE1601_GPIO_RE_LSB,
> +	[STMPE_IDX_GPFER_LSB]	= STMPE1601_GPIO_FE_LSB,
> +	[STMPE_IDX_GPAFR_U_MSB]	= STMPE1601_GPIO_AF_U_MSB,
> +	[STMPE_IDX_IEGPIOR_LSB]	= STMPE1601_INT_EN_GPIO_MASK_LSB,
> +	[STMPE_IDX_ISGPIOR_MSB]	= STMPE1601_INT_STA_GPIO_MSB,
> +	[STMPE_IDX_GPEDR_MSB]	= STMPE1601_GPIO_ED_MSB,
> +};
> +
> +static const u8 stmpe24xx_regs[] = {
> +	[STMPE_IDX_GPMR_LSB]	= STMPE24XX_GPMR_LSB,
> +	[STMPE_IDX_GPSR_LSB]	= STMPE24XX_GPSR_LSB,
> +	[STMPE_IDX_GPCR_LSB]	= STMPE24XX_GPCR_LSB,
> +	[STMPE_IDX_GPDR_LSB]	= STMPE24XX_GPDR_LSB,
> +	[STMPE_IDX_GPRER_LSB]	= STMPE24XX_GPRER_LSB,
> +	[STMPE_IDX_GPFER_LSB]	= STMPE24XX_GPFER_LSB,
> +	[STMPE_IDX_GPAFR_U_MSB]	= STMPE24XX_GPAFR_U_MSB,
> +	[STMPE_IDX_IEGPIOR_LSB]	= STMPE24XX_IEGPIOR_LSB,
> +	[STMPE_IDX_ISGPIOR_MSB]	= STMPE24XX_ISGPIOR_MSB,
> +	[STMPE_IDX_GPEDR_MSB]	= STMPE24XX_GPEDR_MSB,
> +};
> +
> +/**
> + * stmpe_reg_read() - read a single STMPE register
> + * @stmpe:	Device to read from
> + * @reg:	Register to read
> + */
> +int stmpe_reg_read(struct stmpe *stmpe, u8 reg)
> +{
> +	int ret;
> +
> +	ret = i2c_smbus_read_byte_data(stmpe->i2c, reg);
> +	if (ret < 0)
> +		dev_err(stmpe->dev, "failed to read reg %#x: %d\n",
> +			reg, ret);
> +
> +	dev_vdbg(stmpe->dev, "rd: reg %#x => data %#x\n", reg, ret);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(stmpe_reg_read);
> +
> +/**
> + * stmpe_reg_write() - write a single STMPE register
> + * @stmpe:	Device to write to
> + * @reg:	Register to write
> + * @val:	Value to write
> + */
> +int stmpe_reg_write(struct stmpe *stmpe, u8 reg, u8 val)
> +{
> +	int ret;
> +
> +	dev_vdbg(stmpe->dev, "wr: reg %#x <= %#x\n", reg, val);
> +
> +	ret = i2c_smbus_write_byte_data(stmpe->i2c, reg, val);
> +	if (ret < 0)
> +		dev_err(stmpe->dev, "failed to write reg %#x: %d\n",
> +			reg, ret);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(stmpe_reg_write);
> +
> +/**
> + * stmpe_set_bits() - set the value of a bitfield in a STMPE register
> + * @stmpe:	Device to write to
> + * @reg:	Register to write
> + * @mask:	Mask of bits to set
> + * @val:	Value to set
> + */
> +int stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val)
> +{
> +	int ret;
> +
> +	mutex_lock(&stmpe->lock);
> +
> +	ret = stmpe_reg_read(stmpe, reg);
> +	if (ret < 0)
> +		goto out;
> +
> +	ret &= ~mask;
> +	ret |= val;
> +
> +	ret = stmpe_reg_write(stmpe, reg, ret);
> +
> +out:
> +	mutex_unlock(&stmpe->lock);
> +	return ret;
> +}
> +EXPORT_SYMBOL(stmpe_set_bits);
> +
> +/**
> + * stmpe_block_read() - read multiple STMPE registers
> + * @stmpe:	Device to read from
> + * @reg:	First register
> + * @length:	Number of registers
> + * @values:	Buffer to write to
> + */
> +int stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length, u8 *values)
> +{
> +	int ret;
> +
> +	ret = i2c_smbus_read_i2c_block_data(stmpe->i2c, reg, length, values);
> +	if (ret < 0)
> +		dev_err(stmpe->dev, "failed to read regs %#x: %d\n",
> +			reg, ret);
> +
> +	dev_vdbg(stmpe->dev, "rd: reg %#x (%d) => ret %#x\n", reg, length, ret);
> +#ifdef VERBOSE_DEBUG
> +	print_hex_dump_bytes("stmpe rd: ", DUMP_PREFIX_OFFSET, values, length);
> +#endif
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(stmpe_block_read);
> +
> +/**
> + * stmpe_block_write() - write multiple STMPE registers
> + * @stmpe:	Device to write to
> + * @reg:	First register
> + * @length:	Number of registers
> + * @values:	Values to write
> + */
> +int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,
> +			const u8 *values)
> +{
> +	int ret;
> +
> +	dev_vdbg(stmpe->dev, "wr: regs %#x (%d)\n", reg, length);
> +#ifdef VERBOSE_DEBUG
> +	print_hex_dump_bytes("stmpe wr: ", DUMP_PREFIX_OFFSET, values, length);
> +#endif
> +
> +	ret = i2c_smbus_write_i2c_block_data(stmpe->i2c, reg, length,
> +					     values);
> +	if (ret < 0)
> +		dev_err(stmpe->dev, "failed to write regs %#x: %d\n",
> +			reg, ret);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(stmpe_block_write);
> +
> +/**
> + * stmpe_set_altfunc: set the alternate function for STMPE pins
> + * @stmpe:	Device to configure
> + * @pins:	Bitmask of pins to affect
> + * @af:		Alternate function number (0 - 3)
> + *
> + * @pins is assumed to have a bit set for each of the bits whose alternate
> + * function is to be changed, numbered according to the GPIOXY numbers.
> + *
> + * If the GPIO module is not enabled, this function automatically enables it in
> + * order to perform the change.
> + */
> +int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins, int af)
> +{
> +	u8 regaddr = stmpe->regs[STMPE_IDX_GPAFR_U_MSB];
> +	int numregs = stmpe->num_gpios / 4;
> +	u8 regs[numregs];
> +	bool gpioon;
> +	int syscon;
> +	int ret;
> +
> +	mutex_lock(&stmpe->lock);
> +
> +	syscon = stmpe_reg_read(stmpe, STMPE_SYSCON);
> +	if (syscon < 0)
> +		return syscon;
> +
> +	gpioon = syscon & STMPE_SYSCON_ENABLE_GPIO;
> +	if (!gpioon) {
> +		ret = stmpe_reg_write(stmpe, STMPE_SYSCON,
> +				      syscon | STMPE_SYSCON_ENABLE_GPIO);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	ret = stmpe_block_read(stmpe, regaddr, numregs, regs);
> +	if (ret < 0)
> +		goto out;
> +
> +	while (pins) {
> +		int pin = __ffs(pins);
> +		int regoffset = numregs - (pin / 4) - 1;
> +		int pos = (pin % 4) * 2;
> +
> +		regs[regoffset] &= ~(0x3 << pos);
> +		regs[regoffset] |= af << pos;
> +
> +		pins &= ~(1 << pin);
> +	}
> +
> +	ret = stmpe_block_write(stmpe, regaddr, numregs, regs);
> +
> +	if (!gpioon)
> +		stmpe_reg_write(stmpe, STMPE_SYSCON, syscon);
> +
> +out:
> +	mutex_unlock(&stmpe->lock);
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(stmpe_set_altfunc);
> +
> +static struct resource gpio_resources[] = {
> +	{
> +		.start	= STMPE_INT_GPIOC,
> +		.end	= STMPE_INT_GPIOC,
> +		.flags	= IORESOURCE_IRQ,
> +	},
> +};
> +
> +static struct resource keypad_resources[] = {
> +	{
> +		.start	= STMPE_INT_KEYPAD,
> +		.end	= STMPE_INT_KEYPAD,
> +		.flags	= IORESOURCE_IRQ,
> +	},
> +	{
> +		.start	= STMPE_INT_KEYPAD_OVER,
> +		.end	= STMPE_INT_KEYPAD_OVER,
> +		.flags	= IORESOURCE_IRQ,
> +	},
> +};
> +
> +static struct mfd_cell stmpe_devs[] = {
> +	{
> +		.name		= "stmpe-gpio",
> +		.resources	= gpio_resources,
> +		.num_resources	= ARRAY_SIZE(gpio_resources),
> +	},
> +	{
> +		.name		= "stmpe-keypad",
> +		.resources	= keypad_resources,
> +		.num_resources	= ARRAY_SIZE(keypad_resources),
> +	},
> +};
> +
> +static irqreturn_t stmpe_irq(int irq, void *data)
> +{
> +	struct stmpe *stmpe = data;
> +	int num = ARRAY_SIZE(stmpe->ier);
> +	u8 isr[num];
> +	int ret;
> +	int i;
> +
> +	ret = stmpe_block_read(stmpe, STMPE_ISR_MSB, num, isr);
> +	if (ret < 0)
> +		return IRQ_NONE;
> +
> +	for (i = 0; i < num; i++) {
> +		int bank = num - i - 1;
> +		u8 status = isr[i];
> +		u8 clear;
> +
> +		status &= stmpe->ier[bank];
> +		if (!status)
> +			continue;
> +
> +		clear = status;
> +		while (status) {
> +			int bit = __ffs(status);
> +			int line = bank * 8 + bit;
> +
> +			handle_nested_irq(stmpe->irq_base + line);
> +			status &= ~(1 << bit);
> +		}
> +
> +		stmpe_reg_write(stmpe, STMPE_ISR_MSB + i, clear);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static void stmpe_irq_lock(unsigned int irq)
> +{
> +	struct stmpe *stmpe = get_irq_chip_data(irq);
> +
> +	mutex_lock(&stmpe->irq_lock);
> +}
> +
> +static void stmpe_irq_sync_unlock(unsigned int irq)
> +{
> +	struct stmpe *stmpe = get_irq_chip_data(irq);
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(stmpe->ier); i++) {
> +		u8 new = stmpe->ier[i];
> +		u8 old = stmpe->oldier[i];
> +
> +		if (new == old)
> +			continue;
> +
> +		stmpe->oldier[i] = new;
> +		stmpe_reg_write(stmpe, STMPE_IER_LSB - i, new);
> +	}
> +
> +	mutex_unlock(&stmpe->irq_lock);
> +}
> +
> +static void stmpe_irq_mask(unsigned int irq)
> +{
> +	struct stmpe *stmpe = get_irq_chip_data(irq);
> +	int offset = irq - stmpe->irq_base;
> +	int regoffset = offset / 8;
> +	int mask = 1 << (offset % 8);
> +
> +	stmpe->ier[regoffset] &= ~mask;
> +}
> +
> +static void stmpe_irq_unmask(unsigned int irq)
> +{
> +	struct stmpe *stmpe = get_irq_chip_data(irq);
> +	int offset = irq - stmpe->irq_base;
> +	int regoffset = offset / 8;
> +	int mask = 1 << (offset % 8);
> +
> +	stmpe->ier[regoffset] |= mask;
> +}
> +
> +static struct irq_chip stmpe_irq_chip = {
> +	.name			= "stmpe",
> +	.bus_lock		= stmpe_irq_lock,
> +	.bus_sync_unlock	= stmpe_irq_sync_unlock,
> +	.mask			= stmpe_irq_mask,
> +	.unmask			= stmpe_irq_unmask,
> +};
> +
> +static int __devinit stmpe_irq_init(struct stmpe *stmpe)
> +{
> +	int base = stmpe->irq_base;
> +	int irq;
> +
> +	for (irq = base; irq < base + STMPE_NR_INTERNAL_IRQS; irq++) {
> +		set_irq_chip_data(irq, stmpe);
> +		set_irq_chip_and_handler(irq, &stmpe_irq_chip,
> +					 handle_edge_irq);
> +		set_irq_nested_thread(irq, 1);
> +#ifdef CONFIG_ARM
> +		set_irq_flags(irq, IRQF_VALID);
> +#else
> +		set_irq_noprobe(irq);
> +#endif
> +	}
> +
> +	return 0;
> +}
> +
> +static void stmpe_irq_remove(struct stmpe *stmpe)
> +{
> +	int base = stmpe->irq_base;
> +	int irq;
> +
> +	for (irq = base; irq < base + STMPE_NR_INTERNAL_IRQS; irq++) {
> +#ifdef CONFIG_ARM
> +		set_irq_flags(irq, 0);
> +#endif
> +		set_irq_chip_and_handler(irq, NULL, NULL);
> +		set_irq_chip_data(irq, NULL);
> +	}
> +}
> +
> +static int __devinit stmpe_chip_init(struct stmpe *stmpe)
> +{
> +	unsigned int irq_trigger = stmpe->pdata->irq_trigger;
> +	u8 icr = STMPE_ICR_LSB_GIM;
> +	const char *name;
> +	unsigned int id;
> +	u8 data[2];
> +	int ret;
> +
> +	ret = stmpe_block_read(stmpe, STMPE_CHIP_ID, ARRAY_SIZE(data), data);
> +	if (ret < 0)
> +		return ret;
> +
> +	id = (data[0] << 8) | data[1];
> +
> +	switch (id) {
> +	case 0x0210:
> +	case 0x0212:
> +		name = "STMPE1601";
> +		stmpe->variant = STMPE1601;
> +		stmpe->regs = stmpe1601_regs;
> +		stmpe->num_gpios = 16;
> +		break;
> +
> +	case 0x0101:
> +		name = "STMPE2401";
> +		stmpe->variant = STMPE2401;
> +		stmpe->regs = stmpe24xx_regs;
> +		stmpe->num_gpios = 24;
> +		break;
> +
> +	case 0x0120:
> +		name = "STMPE2403";
> +		stmpe->variant = STMPE2403;
> +		stmpe->regs = stmpe24xx_regs;
> +		stmpe->num_gpios = 24;
> +		break;
> +
> +	default:
> +		dev_err(stmpe->dev, "unknown id: %#x\n", id);
> +		return -EINVAL;
> +	}
> +
> +	dev_info(stmpe->dev, "%s detected\n", name);
> +
> +	/* Disable all modules -- subdrivers should enable what they need. */
> +	ret = stmpe_set_bits(stmpe, STMPE_SYSCON, STMPE_SYSCON_ENABLE, 0);
> +	if (ret)
> +		return ret;
> +
> +	if (irq_trigger == IRQF_TRIGGER_FALLING ||
> +	    irq_trigger == IRQF_TRIGGER_RISING)
> +		icr |= STMPE_ICR_LSB_EDGE;
> +
> +	if (irq_trigger == IRQF_TRIGGER_RISING ||
> +	    irq_trigger == IRQF_TRIGGER_HIGH)
> +		icr |= STMPE_ICR_LSB_HIGH;
> +
> +	return stmpe_reg_write(stmpe, STMPE_ICR_LSB, icr);
> +}
> +
> +static int __devinit stmpe_probe(struct i2c_client *i2c,
> +				 const struct i2c_device_id *id)
> +{
> +	struct stmpe_platform_data *pdata = i2c->dev.platform_data;
> +	struct stmpe *stmpe;
> +	int ret;
> +
> +	if (!pdata)
> +		return -EINVAL;
> +
> +	stmpe = kzalloc(sizeof(struct stmpe), GFP_KERNEL);
> +	if (!stmpe)
> +		return -ENOMEM;
> +
> +	mutex_init(&stmpe->irq_lock);
> +	mutex_init(&stmpe->lock);
> +
> +	stmpe->dev = &i2c->dev;
> +	stmpe->i2c = i2c;
> +
> +	stmpe->pdata = pdata;
> +	stmpe->irq_base = pdata->irq_base;
> +
> +	i2c_set_clientdata(i2c, stmpe);
> +
> +	ret = stmpe_chip_init(stmpe);
> +	if (ret)
> +		goto out_free;
> +
> +	ret = stmpe_irq_init(stmpe);
> +	if (ret)
> +		goto out_free;
> +
> +	ret = request_threaded_irq(stmpe->i2c->irq, NULL, stmpe_irq,
> +				   pdata->irq_trigger | IRQF_ONESHOT,
> +				   "stmpe", stmpe);
> +	if (ret) {
> +		dev_err(stmpe->dev, "failed to request IRQ: %d\n", ret);
> +		goto out_removeirq;
> +	}
> +
> +	ret = mfd_add_devices(stmpe->dev, pdata->id, stmpe_devs,
> +			      ARRAY_SIZE(stmpe_devs), NULL,
> +			      stmpe->irq_base);
> +	if (ret) {
> +		dev_err(stmpe->dev, "failed to add children\n");
> +		goto out_freeirq;
> +	}
> +
> +	return 0;
> +
> +out_freeirq:
> +	free_irq(stmpe->i2c->irq, stmpe);
> +out_removeirq:
> +	stmpe_irq_remove(stmpe);
> +out_free:
> +	i2c_set_clientdata(i2c, NULL);
> +	kfree(stmpe);
> +	return ret;
> +}
> +
> +static int __devexit stmpe_remove(struct i2c_client *client)
> +{
> +	struct stmpe *stmpe = i2c_get_clientdata(client);
> +
> +	mfd_remove_devices(stmpe->dev);
> +
> +	free_irq(stmpe->i2c->irq, stmpe);
> +	stmpe_irq_remove(stmpe);
> +
> +	i2c_set_clientdata(client, NULL);
> +	kfree(stmpe);
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id stmpe_id[] = {
> +	{ "stmpe", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, stmpe_id);
> +
> +static struct i2c_driver stmpe_driver = {
> +	.driver.name	= "stmpe",
> +	.driver.owner	= THIS_MODULE,
> +	.probe		= stmpe_probe,
> +	.remove		= __devexit_p(stmpe_remove),
> +	.id_table	= stmpe_id,
> +};
> +
> +static int __init stmpe_init(void)
> +{
> +	return i2c_add_driver(&stmpe_driver);
> +}
> +subsys_initcall(stmpe_init);
> +
> +static void __exit stmpe_exit(void)
> +{
> +	i2c_del_driver(&stmpe_driver);
> +}
> +module_exit(stmpe_exit);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("STMPExxxx MFD core driver");
> +MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
> diff --git a/include/linux/mfd/stmpe.h b/include/linux/mfd/stmpe.h
> new file mode 100644
> index 0000000..7c6733b
> --- /dev/null
> +++ b/include/linux/mfd/stmpe.h
> @@ -0,0 +1,135 @@
> +/*
> + * Copyright (C) ST-Ericsson SA 2010
> + *
> + * License Terms: GNU General Public License, version 2
> + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
> + */
> +
> +#ifndef __LINUX_MFD_STMPE_H
> +#define __LINUX_MFD_STMPE_H
> +
> +#include <linux/device.h>
> +
> +#define STMPE_SYSCON			0x02
> +
> +#define STMPE_SYSCON_ENABLE		(0xf << 0)
> +#define STMPE_SYSCON_ENABLE_GPIO	(1 << 3)
> +#define STMPE24XX_SYSCON_ENABLE_PWM	(1 << 2)
> +#define STMPE_SYSCON_ENABLE_KPC		(1 << 1)
> +#define STMPE16XX_SYSCON_ENABLE_SPWM	(1 << 0)
> +#define STMPE24XX_SYSCON_ENABLE_ROT	(1 << 0)
> +
> +enum stmpe_variant {
> +	STMPE1601,
> +	STMPE2401,
> +	STMPE2403,
> +};
> +
> +/*
> + * For registers whose locations differ on variants,  the correct address is
> + * obtained by indexing stmpe->regs with one of the following.
> + */
> +enum {
> +	STMPE_IDX_GPMR_LSB,
> +	STMPE_IDX_GPSR_LSB,
> +	STMPE_IDX_GPCR_LSB,
> +	STMPE_IDX_GPDR_LSB,
> +	STMPE_IDX_GPEDR_MSB,
> +	STMPE_IDX_GPRER_LSB,
> +	STMPE_IDX_GPFER_LSB,
> +	STMPE_IDX_GPAFR_U_MSB,
> +	STMPE_IDX_IEGPIOR_LSB,
> +	STMPE_IDX_ISGPIOR_MSB,
> +};
> +
> +/**
> + * struct stmpe - STMPE MFD structure
> + * @lock: lock protecting I/O operations
> + * @irq_lock: IRQ bus lock
> + * @dev: device, mostly for dev_dbg()
> + * @i2c: i2c client
> + * @variant: the detected STMPExxxx model number
> + * @regs: list of addresses of registers which are at different addresses on
> + *	  different variants.  Indexed by one of STMPE_IDX_*.
> + * @irq_base: starting IRQ number for internal IRQs
> + * @num_gpios: number of gpios, differs for variants
> + * @ier: cache of IER registers for bus_lock
> + * @oldier: cache of IER registers for bus_lock
> + * @pdata: platform data
> + */
> +struct stmpe {
> +	struct mutex lock;
> +	struct mutex irq_lock;
> +	struct device *dev;
> +	struct i2c_client *i2c;
> +	enum stmpe_variant variant;
> +	const u8 *regs;
> +
> +	int irq_base;
> +	int num_gpios;
> +	u8 ier[2];
> +	u8 oldier[2];
> +	struct stmpe_platform_data *pdata;
> +};
> +
> +extern int stmpe_reg_write(struct stmpe *stmpe, u8 reg, u8 data);
> +extern int stmpe_reg_read(struct stmpe *stmpe, u8 reg);
> +extern int stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length,
> +			    u8 *values);
> +extern int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,
> +			     const u8 *values);
> +extern int stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val);
> +extern int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins, int af);
> +
> +struct matrix_keymap_data;
> +
> +/**
> + * struct stmpe_keypad_platform_data - STMPE keypad platform data
> + * @keymap_data: key map table and size
> + * @debounce_ms: debounce interval, in ms.  Maximum is
> + *		 %STMPE_KEYPAD_MAX_DEBOUNCE.
> + * @scan_count: number of key scanning cycles to confirm key data.
> + *		Maximum is %STMPE_KEYPAD_MAX_SCAN_COUNT.
> + * @no_autorepeat: disable key autorepeat
> + */
> +struct stmpe_keypad_platform_data {
> +	struct matrix_keymap_data *keymap_data;
> +	unsigned int debounce_ms;
> +	unsigned int scan_count;
> +	bool no_autorepeat;
> +};
> +
> +/**
> + * struct stmpe_gpio_platform_data - STMPE GPIO platform data
> + * @gpio_base: first gpio number assigned.  A maximum of
> + *	       %STMPE_NR_GPIOS GPIOs will be allocated.
> + */
> +struct stmpe_gpio_platform_data {
> +	int gpio_base;
> +};
> +
> +/**
> + * struct stmpe_platform_data - STMPE platform data
> + * @id: device id to distinguish between multiple STMPEs on the same board
> + * @irq_trigger: IRQ trigger to use for the interrupt to the host
> + * @irq_base: base IRQ number.  %STMPE_NR_IRQS irqs will be used, or
> + *	      %STMPE_NR_INTERNAL_IRQS if the GPIO driver is not used.
> + * @gpio: GPIO-specific platform data
> + * @keypad: keypad-specific platform data
> + */
> +struct stmpe_platform_data {
> +	int id;
> +	int irq_base;
> +	unsigned int irq_trigger;
> +
> +	struct stmpe_gpio_platform_data *gpio;
> +	struct stmpe_keypad_platform_data *keypad;
> +};
> +
> +#define STMPE_NR_INTERNAL_IRQS	9
> +#define STMPE_INT_GPIO(x)	(STMPE_NR_INTERNAL_IRQS + (x))
> +
> +#define STMPE_NR_GPIOS		24
> +#define STMPE_NR_IRQS		STMPE_INT_GPIO(STMPE_NR_GPIOS)
> +
> +#endif
> -- 
> 1.7.0
> 

-- 
Intel Open Source Technology Centre
http://oss.intel.com/

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 1/3] mfd: add STMPExxxx I/O Expander support
  2010-06-18 23:42 ` [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Samuel Ortiz
@ 2010-06-19 13:50   ` Luotao Fu
  2010-06-21 13:33   ` Rabin VINCENT
  1 sibling, 0 replies; 61+ messages in thread
From: Luotao Fu @ 2010-06-19 13:50 UTC (permalink / raw)
  To: Samuel Ortiz
  Cc: Rabin Vincent, linux-kernel, STEricsson_nomadik_linux,
	Linus Walleij, l.fu

[-- Attachment #1: Type: text/plain, Size: 1391 bytes --]

Hi,

On Sat, Jun 19, 2010 at 01:42:24AM +0200, Samuel Ortiz wrote:
> Hi Rabin,
> 
> On Mon, May 31, 2010 at 05:47:14PM +0530, Rabin Vincent wrote:
> > Add support for the STMPExxxx family of I/O Expanders from
> > STMicroelectronics.  These devices include upto 24 gpios, a PWM
> > controller, and a keypad controller.  This patch adds the MFD core.
> The patchset looks fairly good, but before merging it I'd like to know of we
> could merge it with this one:
> https://patchwork.kernel.org/patch/106173/
> 
> I don't know enough about the hardware, and although the register layouts don't
> look like they have much in common, I'd like to know from the actual HW
> manufacturer (i.e. you :)) if there's something we can do here.
> I'm cc'ing Luotao here so that we get his input as well.
> 

hmm, I took a quick look into the core driver. The register layout
seems, as Samuel mentioned, quite different. However, the r/w
functionalities and irq handling are quite the same. For now I'd say
that should be possible to merge the stuff.

cheers
Luotao Fu

-- 
Pengutronix e.K.                           | Dipl.-Ing. Luotao Fu        |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 1/3] mfd: add STMPExxxx I/O Expander support
  2010-06-18 23:42 ` [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Samuel Ortiz
  2010-06-19 13:50   ` Luotao Fu
@ 2010-06-21 13:33   ` Rabin VINCENT
  2010-06-21 15:45     ` Luotao Fu
  1 sibling, 1 reply; 61+ messages in thread
From: Rabin VINCENT @ 2010-06-21 13:33 UTC (permalink / raw)
  To: Samuel Ortiz; +Cc: linux-kernel, STEricsson_nomadik_linux, Linus WALLEIJ, l.fu

On Sat, Jun 19, 2010 at 01:42:24 +0200, Samuel Ortiz wrote:
> On Mon, May 31, 2010 at 05:47:14PM +0530, Rabin Vincent wrote:
> > Add support for the STMPExxxx family of I/O Expanders from
> > STMicroelectronics.  These devices include upto 24 gpios, a PWM
> > controller, and a keypad controller.  This patch adds the MFD core.
> The patchset looks fairly good, but before merging it I'd like to know of we
> could merge it with this one:
> https://patchwork.kernel.org/patch/106173/
> 
> I don't know enough about the hardware, and although the register layouts don't
> look like they have much in common, I'd like to know from the actual HW
> manufacturer (i.e. you :)) if there's something we can do here.

I don't have any inside information about these parts (we just use them
on our U8500 dev boards), but all the datasheets are publicly
available[1].  There seems to be more than a dozen variants of this MFD
with varying combinations of devices.

[1] http://www.st.com/stonline/stappl/productcatalog/app?path=/pages/stcom/PcStComPartNumberSearch.searchPartNumber&search=stmpe

On Sat, Jun 19, 2010 at 15:50:16 +0200, Luotao Fu wrote:
> hmm, I took a quick look into the core driver. The register layout
> seems, as Samuel mentioned, quite different. However, the r/w
> functionalities and irq handling are quite the same. For now I'd say
> that should be possible to merge the stuff.

The IRQ handling and GPIO block seem to be about the same (registers are
at different offsets, but this is also the case between STMPE1601 and
STMPE24XX and is thus already handled in the STMPExxxx driver).  The
STMPExxxx GPIO driver should also already be able to handle a variant
with lesser gpios, such as STMPE811.

Similarities:
 - I2C access functions
 - GPIO block (same registers, different offsets)
 - IRQ block (same registers and handling, different irqs and different
   register offsets)

Differences:
 - Different blocks (but sharing between different groups of variants)
 - SYSCTRL register bits (reset, clock enabling)
 - GPIO altfunc bits
 - The STMPE811 also has a SPI interface, while most other support only I2C
   This is also not avaiabile in Luotao's driver so I'm not addressing
   this for now.

Here's a preliminary patch (untested!) which shows how the STMPExxx can
be made more generic to support 811 and hopefully other variants.  If
this looks sane, I'll complete it up, fold it in, and repost the
STMPExxxx series for review and also 811 testing and touchscreen
addition from Luotao.

Rabin

---
 drivers/gpio/stmpe-gpio.c             |   13 +-
 drivers/input/keyboard/stmpe-keypad.c |    7 +-
 drivers/mfd/Makefile                  |    2 +-
 drivers/mfd/stmpe-variants.c          |  328 +++++++++++++++++++++++++++++++++
 drivers/mfd/stmpe.c                   |  261 +++++++++-----------------
 drivers/mfd/stmpe.h                   |  138 ++++++++++++++
 include/linux/mfd/stmpe.h             |   41 +++-
 7 files changed, 602 insertions(+), 188 deletions(-)
 create mode 100644 drivers/mfd/stmpe-variants.c
 create mode 100644 drivers/mfd/stmpe.h

diff --git a/drivers/gpio/stmpe-gpio.c b/drivers/gpio/stmpe-gpio.c
index 857d3f5..fac2cb5 100644
--- a/drivers/gpio/stmpe-gpio.c
+++ b/drivers/gpio/stmpe-gpio.c
@@ -297,12 +297,11 @@ static int __devinit stmpe_gpio_probe(struct platform_device *pdev)
 	stmpe_gpio->chip = template_chip;
 	stmpe_gpio->chip.ngpio = stmpe->num_gpios;
 	stmpe_gpio->chip.dev = &pdev->dev;
-	stmpe_gpio->chip.base = pdata->gpio_base;
+	stmpe_gpio->chip.base = pdata ? pdata->gpio_base : - 1;
 
 	stmpe_gpio->irq_base = stmpe->irq_base + STMPE_INT_GPIO(0);
 
-	ret = stmpe_set_bits(stmpe, STMPE_SYSCON, STMPE_SYSCON_ENABLE_GPIO,
-			     STMPE_SYSCON_ENABLE_GPIO);
+	ret = stmpe_enable(stmpe, STMPE_BLOCK_GPIO);
 	if (ret)
 		return ret;
 
@@ -323,6 +322,9 @@ static int __devinit stmpe_gpio_probe(struct platform_device *pdev)
 		goto out_freeirq;
 	}
 
+	if (pdata && pdata->setup)
+		pdata->setup(stmpe, stmpe_gpio->chip.base);
+
 	platform_set_drvdata(pdev, stmpe_gpio);
 
 	return 0;
@@ -342,6 +344,9 @@ static int __devexit stmpe_gpio_remove(struct platform_device *pdev)
 	int irq = platform_get_irq(pdev, 0);
 	int ret;
 
+	if (pdata && pdata->remove)
+		pdata->remove(stmpe_gpio->stmpe, stmpe_gpio->chip.base);
+
 	ret = gpiochip_remove(&stmpe_gpio->chip);
 	if (ret < 0) {
 		dev_err(stmpe_gpio->dev,
@@ -349,6 +354,8 @@ static int __devexit stmpe_gpio_remove(struct platform_device *pdev)
 		return ret;
 	}
 
+	stmpe_disable(stmpe_gpio->stmpe, STMPE_BLOCK_GPIO);
+
 	free_irq(irq, stmpe_gpio);
 	stmpe_gpio_irq_remove(stmpe_gpio);
 	platform_set_drvdata(pdev, NULL);
diff --git a/drivers/input/keyboard/stmpe-keypad.c b/drivers/input/keyboard/stmpe-keypad.c
index 1ef8875..efb02ba 100644
--- a/drivers/input/keyboard/stmpe-keypad.c
+++ b/drivers/input/keyboard/stmpe-keypad.c
@@ -219,8 +219,7 @@ static int __devinit stmpe_keypad_chip_init(struct stmpe_keypad *keypad)
 	if (plat->scan_count > STMPE_KEYPAD_MAX_SCAN_COUNT)
 		return -EINVAL;
 
-	ret = stmpe_set_bits(stmpe, STMPE_SYSCON, STMPE_SYSCON_ENABLE_KPC,
-			     STMPE_SYSCON_ENABLE_KPC);
+	ret = stmpe_enable(stmpe, STMPE_BLOCK_KEYPAD);
 	if (ret < 0)
 		return ret;
 
@@ -312,7 +311,7 @@ static int __devinit stmpe_keypad_probe(struct platform_device *pdev)
 	keypad->stmpe = stmpe;
 	keypad->plat = plat;
 	keypad->input = input;
-	keypad->variant = &stmpe_keypad_variants[stmpe->variant];
+	keypad->variant = &stmpe_keypad_variants[stmpe->partnum];
 
 	ret = stmpe_keypad_chip_init(keypad);
 	if (ret < 0)
@@ -353,7 +352,7 @@ static int __devexit stmpe_keypad_remove(struct platform_device *pdev)
 	int irq = platform_get_irq(pdev, 0);
 
 	/* Disable the keypad module.  Ignore any errors. */
-	stmpe_set_bits(stmpe, STMPE_SYSCON, STMPE_SYSCON_ENABLE_KPC, 0);
+	stmpe_disable(stmpe, STMPE_BLOCK_KEYPAD);
 
 	free_irq(irq, keypad);
 	input_unregister_device(keypad->input);
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 4410747..74ce677 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -15,7 +15,7 @@ obj-$(CONFIG_HTC_I2CPLD)	+= htc-i2cpld.o
 obj-$(CONFIG_MFD_DAVINCI_VOICECODEC)	+= davinci_voicecodec.o
 obj-$(CONFIG_MFD_DM355EVM_MSP)	+= dm355evm_msp.o
 
-obj-$(CONFIG_MFD_STMPE)		+= stmpe.o
+obj-$(CONFIG_MFD_STMPE)		+= stmpe.o stmpe-variants.o
 obj-$(CONFIG_MFD_TC35892)	+= tc35892.o
 obj-$(CONFIG_MFD_T7L66XB)	+= t7l66xb.o tmio_core.o
 obj-$(CONFIG_MFD_TC6387XB)	+= tc6387xb.o tmio_core.o
diff --git a/drivers/mfd/stmpe-variants.c b/drivers/mfd/stmpe-variants.c
new file mode 100644
index 0000000..49257c1
--- /dev/null
+++ b/drivers/mfd/stmpe-variants.c
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/stmpe.h>
+#include "stmpe.h"
+
+/*
+ * GPIO (all variants)
+ */
+
+static struct resource stmpe_gpio_resources[] = {
+	/* Start and end filled dynamically */
+	{
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct mfd_cell stmpe_gpio_cell = {
+	.name		= "stmpe-gpio",
+	.resources	= stmpe_gpio_resources,
+	.num_resources	= ARRAY_SIZE(stmpe_gpio_resources),
+};
+
+/*
+ * Keypad (1601, 2401, 2403)
+ */
+
+static struct resource stmpe_keypad_resources[] = {
+	{
+		.name	= "KEYPAD",
+		.start	= 0,
+		.end	= 0,
+		.flags	= IORESOURCE_IRQ,
+	},
+	{
+		.name	= "KEYPAD_OVER",
+		.start	= 1,
+		.end	= 1,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct mfd_cell stmpe_keypad_cell = {
+	.name		= "stmpe-keypad",
+	.resources	= stmpe_keypad_resources,
+	.num_resources	= ARRAY_SIZE(stmpe_keypad_resources),
+};
+
+/*
+ * Touchscreen (STMPE811)
+ */
+
+static struct resource stmpe_ts_resources[] = {
+	{
+		.name	= "TOUCH_DET",
+		.start	= 0,
+		.end	= 0,
+		.flags	= IORESOURCE_IRQ,
+	},
+	{
+		.name	= "FIFO_TH",
+		.start	= 1,
+		.end	= 1,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct mfd_cell stmpe_ts_cell = {
+	.name		= "stmpe-ts",
+	.resources	= stmpe_ts_resources,
+	.num_resources	= ARRAY_SIZE(stmpe_ts_resources),
+};
+
+/*
+ * STMPE811
+ */
+
+static const u8 stmpe811_regs[] = {
+	[STMPE_IDX_CHIP_ID]	= STMPE811_REG_CHIP_ID,
+	[STMPE_IDX_ICR_LSB]	= STMPE811_REG_INT_CTRL,
+	[STMPE_IDX_IER_LSB]	= STMPE811_REG_INT_EN,
+	[STMPE_IDX_ISR_MSB]	= STMPE811_REG_INT_STA,
+	[STMPE_IDX_GPMR_LSB]	= STMPE811_REG_GPIO_MP_STA,
+	[STMPE_IDX_GPSR_LSB]	= STMPE811_REG_GPIO_SET_PIN,
+	[STMPE_IDX_GPCR_LSB]	= STMPE811_REG_GPIO_CLR_PIN,
+	[STMPE_IDX_GPDR_LSB]	= STMPE811_REG_GPIO_DIR,
+	[STMPE_IDX_GPRER_LSB]	= STMPE811_REG_GPIO_RE,
+	[STMPE_IDX_GPFER_LSB]	= STMPE811_REG_GPIO_FE,
+	[STMPE_IDX_GPAFR_U_MSB]	= STMPE811_REG_GPIO_AF,
+	[STMPE_IDX_IEGPIOR_LSB]	= STMPE811_REG_GPIO_INT_EN,
+	[STMPE_IDX_ISGPIOR_MSB]	= STMPE811_REG_GPIO_INT_STA,
+	[STMPE_IDX_GPEDR_MSB]	= STMPE811_REG_GPIO_ED,
+};
+
+static struct stmpe_variant_block stmpe811_blocks[] = {
+	{
+		.cell	= &stmpe_gpio_cell,
+		.irq	= STMPE811_IRQ_GPIOC,
+		.block	= STMPE_BLOCK_GPIO,
+	},
+	{
+		.cell	= &stmpe_ts_cell,
+		.irq	= STMPE811_IRQ_TOUCH_DET,
+		.block	= STMPE_BLOCK_TOUCHSCREEN,
+	},
+};
+
+static int stmpe811_enable(struct stmpe *stmpe, unsigned int blocks,
+			   bool enable)
+{
+	unsigned int mask = 0;
+
+	if (blocks & STMPE_BLOCK_GPIO)
+		mask |= STMPE811_SYS_CTRL2_GPIO_OFF;
+
+	if (blocks & STMPE_BLOCK_ADC)
+		mask |= STMPE811_SYS_CTRL2_ADC_OFF;
+
+	if (blocks & STMPE_BLOCK_KEYPAD)
+		mask |= STMPE811_SYS_CTRL2_TSC_OFF;
+
+	return stmpe_set_bits(stmpe, STMPE811_REG_SYS_CTRL2, mask,
+			      enable ? 0 : mask);
+}
+
+static int stmpe811_get_altfunc(struct stmpe *stmpe, enum stmpe_block block)
+{
+	/* 0 for touchscreen, 1 for GPIO */
+	return block != STMPE_BLOCK_TOUCHSCREEN;
+}
+
+static struct stmpe_variant_info stmpe811 = {
+	.name		= "stmpe811",
+	.id_val		= 0x0811,
+	.id_mask	= 0xffff,
+	.num_gpios	= 8,
+	.af_per_reg	= 8,
+	.regs		= stmpe811_regs,
+	.blocks		= stmpe811_blocks,
+	.num_blocks	= ARRAY_SIZE(stmpe811_blocks),
+	.num_irqs	= STMPE811_NR_INTERNAL_IRQS,
+	.enable		= stmpe811_enable,
+	.get_altfunc	= stmpe811_get_altfunc,
+};
+
+/*
+ * STMPE1601
+ */
+
+static const u8 stmpe1601_regs[] = {
+	[STMPE_IDX_CHIP_ID]	= STMPE1601_REG_CHIP_ID,
+	[STMPE_IDX_ICR_LSB]	= STMPE1601_REG_ICR_LSB,
+	[STMPE_IDX_IER_LSB]	= STMPE1601_REG_IER_LSB,
+	[STMPE_IDX_ISR_MSB]	= STMPE1601_REG_ISR_MSB,
+	[STMPE_IDX_GPMR_LSB]	= STMPE1601_REG_GPIO_MP_LSB,
+	[STMPE_IDX_GPSR_LSB]	= STMPE1601_REG_GPIO_SET_LSB,
+	[STMPE_IDX_GPCR_LSB]	= STMPE1601_REG_GPIO_CLR_LSB,
+	[STMPE_IDX_GPDR_LSB]	= STMPE1601_REG_GPIO_SET_DIR_LSB,
+	[STMPE_IDX_GPRER_LSB]	= STMPE1601_REG_GPIO_RE_LSB,
+	[STMPE_IDX_GPFER_LSB]	= STMPE1601_REG_GPIO_FE_LSB,
+	[STMPE_IDX_GPAFR_U_MSB]	= STMPE1601_REG_GPIO_AF_U_MSB,
+	[STMPE_IDX_IEGPIOR_LSB]	= STMPE1601_REG_INT_EN_GPIO_MASK_LSB,
+	[STMPE_IDX_ISGPIOR_MSB]	= STMPE1601_REG_INT_STA_GPIO_MSB,
+	[STMPE_IDX_GPEDR_MSB]	= STMPE1601_REG_GPIO_ED_MSB,
+};
+
+static struct stmpe_variant_block stmpe1601_blocks[] = {
+	{
+		.cell	= &stmpe_gpio_cell,
+		.irq	= STMPE24XX_IRQ_GPIOC,
+		.block	= STMPE_BLOCK_GPIO,
+	},
+	{
+		.cell	= &stmpe_keypad_cell,
+		.irq	= STMPE24XX_IRQ_KEYPAD,
+		.block	= STMPE_BLOCK_KEYPAD,
+	},
+};
+
+static int stmpe1601_enable(struct stmpe *stmpe, unsigned int blocks,
+			    bool enable)
+{
+	unsigned int mask = 0;
+
+	if (blocks & STMPE_BLOCK_GPIO)
+		mask |= STMPE1601_SYS_CTRL_ENABLE_GPIO;
+
+	if (blocks & STMPE_BLOCK_KEYPAD)
+		mask |= STMPE1601_SYS_CTRL_ENABLE_KPC;
+
+	return stmpe_set_bits(stmpe, STMPE1601_REG_SYS_CTRL, mask,
+			      enable ? mask : 0);
+}
+
+static int stmpe1601_get_altfunc(struct stmpe *stmpe, enum stmpe_block block)
+{
+	switch (block) {
+	case STMPE_BLOCK_PWM:
+		return 2;
+
+	case STMPE_BLOCK_KEYPAD:
+		return 1;
+
+	case STMPE_BLOCK_GPIO:
+	default:
+		return 0;
+	}
+}
+
+static struct stmpe_variant_info stmpe1601 = {
+	.name		= "stmpe1601",
+	.id_val		= 0x0210,
+	.id_mask	= 0xfff0,	/* at least 0x0210 and 0x0212 */
+	.num_gpios	= 16,
+	.af_per_reg	= 4,
+	.regs		= stmpe1601_regs,
+	.blocks		= stmpe1601_blocks,
+	.num_blocks	= ARRAY_SIZE(stmpe1601_blocks),
+	.num_irqs	= STMPE1601_NR_INTERNAL_IRQS,
+	.enable		= stmpe1601_enable,
+	.get_altfunc	= stmpe1601_get_altfunc,
+};
+
+/*
+ * STMPE24XX
+ */
+
+static const u8 stmpe24xx_regs[] = {
+	[STMPE_IDX_CHIP_ID]	= STMPE24XX_REG_CHIP_ID,
+	[STMPE_IDX_ICR_LSB]	= STMPE24XX_REG_ICR_LSB,
+	[STMPE_IDX_IER_LSB]	= STMPE24XX_REG_IER_LSB,
+	[STMPE_IDX_ISR_MSB]	= STMPE24XX_REG_ISR_MSB,
+	[STMPE_IDX_GPMR_LSB]	= STMPE24XX_REG_GPMR_LSB,
+	[STMPE_IDX_GPSR_LSB]	= STMPE24XX_REG_GPSR_LSB,
+	[STMPE_IDX_GPCR_LSB]	= STMPE24XX_REG_GPCR_LSB,
+	[STMPE_IDX_GPDR_LSB]	= STMPE24XX_REG_GPDR_LSB,
+	[STMPE_IDX_GPRER_LSB]	= STMPE24XX_REG_GPRER_LSB,
+	[STMPE_IDX_GPFER_LSB]	= STMPE24XX_REG_GPFER_LSB,
+	[STMPE_IDX_GPAFR_U_MSB]	= STMPE24XX_REG_GPAFR_U_MSB,
+	[STMPE_IDX_IEGPIOR_LSB]	= STMPE24XX_REG_IEGPIOR_LSB,
+	[STMPE_IDX_ISGPIOR_MSB]	= STMPE24XX_REG_ISGPIOR_MSB,
+	[STMPE_IDX_GPEDR_MSB]	= STMPE24XX_REG_GPEDR_MSB,
+};
+
+static struct stmpe_variant_block stmpe24xx_blocks[] = {
+	{
+		.cell	= &stmpe_gpio_cell,
+		.irq	= STMPE24XX_IRQ_GPIOC,
+		.block	= STMPE_BLOCK_GPIO,
+	},
+	{
+		.cell	= &stmpe_keypad_cell,
+		.irq	= STMPE24XX_IRQ_KEYPAD,
+		.block	= STMPE_BLOCK_KEYPAD,
+	},
+};
+
+static int stmpe24xx_enable(struct stmpe *stmpe, unsigned int blocks,
+			    bool enable)
+{
+	unsigned int mask = 0;
+
+	if (blocks & STMPE_BLOCK_GPIO)
+		mask |= STMPE24XX_SYS_CTRL_ENABLE_GPIO;
+
+	if (blocks & STMPE_BLOCK_KEYPAD)
+		mask |= STMPE24XX_SYS_CTRL_ENABLE_KPC;
+
+	return stmpe_set_bits(stmpe, STMPE24XX_REG_SYS_CTRL, mask,
+			      enable ? mask : 0);
+}
+
+static int stmpe24xx_get_altfunc(struct stmpe *stmpe, enum stmpe_block block)
+{
+	switch (block) {
+	case STMPE_BLOCK_ROTATOR:
+		return 2;
+
+	case STMPE_BLOCK_KEYPAD:
+		return 1;
+
+	case STMPE_BLOCK_GPIO:
+	default:
+		return 0;
+	}
+}
+
+static struct stmpe_variant_info stmpe2401 = {
+	.name		= "stmpe2401",
+	.id_val		= 0x0101,
+	.id_mask	= 0xffff,
+	.num_gpios	= 24,
+	.af_per_reg	= 4,
+	.regs		= stmpe24xx_regs,
+	.blocks		= stmpe24xx_blocks,
+	.num_blocks	= ARRAY_SIZE(stmpe24xx_blocks),
+	.num_irqs	= STMPE24XX_NR_INTERNAL_IRQS,
+	.enable		= stmpe24xx_enable,
+	.get_altfunc	= stmpe24xx_get_altfunc,
+};
+
+static struct stmpe_variant_info stmpe2403 = {
+	.name		= "stmpe2403",
+	.id_val		= 0x0120,
+	.id_mask	= 0xffff,
+	.num_gpios	= 24,
+	.af_per_reg	= 4,
+	.regs		= stmpe24xx_regs,
+	.blocks		= stmpe24xx_blocks,
+	.num_blocks	= ARRAY_SIZE(stmpe24xx_blocks),
+	.num_irqs	= STMPE24XX_NR_INTERNAL_IRQS,
+	.enable		= stmpe24xx_enable,
+	.get_altfunc	= stmpe24xx_get_altfunc,
+};
+
+struct stmpe_variant_info *stmpe_variant_info[] = {
+	[STMPE811]	= &stmpe811,
+	[STMPE1601]	= &stmpe1601,
+	[STMPE2401]	= &stmpe2401,
+	[STMPE2403]	= &stmpe2403,
+};
diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c
index 53e72b6..06ff885 100644
--- a/drivers/mfd/stmpe.c
+++ b/drivers/mfd/stmpe.c
@@ -13,84 +13,25 @@
 #include <linux/i2c.h>
 #include <linux/mfd/core.h>
 #include <linux/mfd/stmpe.h>
-
-/* Interrupts */
-#define STMPE_INT_GPIOC		8
-#define STMPE1601_INT_PWM3	7
-#define STMPE1601_INT_PWM2	6
-#define STMPE1601_INT_PWM1	5
-#define STMPE1601_INT_PWM0	4
-#define STMPE24XX_INT_PWM2	7
-#define STMPE24XX_INT_PWM1	6
-#define STMPE24XX_INT_PWM0	5
-#define STMPE24XX_INT_ROT_OVER	4
-#define STMPE24XX_INT_ROT	3
-#define STMPE_INT_KEYPAD_OVER	2
-#define STMPE_INT_KEYPAD	1
-#define STMPE_INT_WAKEUP	0
-
-/* Core registers at same addresses on all variants */
-#define STMPE_ICR_LSB		0x11
-#define STMPE_IER_LSB		0x13
-#define STMPE_ISR_MSB		0x14
-#define STMPE_CHIP_ID		0x80
+#include "stmpe.h"
 
 #define STMPE_ICR_LSB_HIGH	(1 << 2)
 #define STMPE_ICR_LSB_EDGE	(1 << 1)
 #define STMPE_ICR_LSB_GIM	(1 << 0)
 
-/*
- * The following registers are at different addresses on different variants.
- * We provide a set of register indices and a translation table.
- */
-
-#define STMPE1601_INT_EN_GPIO_MASK_LSB	0x17
-#define STMPE1601_INT_STA_GPIO_MSB	0x18
-#define STMPE1601_GPIO_MP_LSB		0x87
-#define STMPE1601_GPIO_SET_LSB		0x83
-#define STMPE1601_GPIO_CLR_LSB		0x85
-#define STMPE1601_GPIO_SET_DIR_LSB	0x89
-#define STMPE1601_GPIO_ED_MSB		0x8A
-#define STMPE1601_GPIO_RE_LSB		0x8D
-#define STMPE1601_GPIO_FE_LSB		0x8F
-#define STMPE1601_GPIO_AF_U_MSB		0x92
-
-#define STMPE24XX_IEGPIOR_LSB		0x18
-#define STMPE24XX_ISGPIOR_MSB		0x19
-#define STMPE24XX_GPMR_LSB		0xA5
-#define STMPE24XX_GPSR_LSB		0x85
-#define STMPE24XX_GPCR_LSB		0x88
-#define STMPE24XX_GPDR_LSB		0x8B
-#define STMPE24XX_GPEDR_MSB		0x8C
-#define STMPE24XX_GPRER_LSB		0x91
-#define STMPE24XX_GPFER_LSB		0x94
-#define STMPE24XX_GPAFR_U_MSB		0x9B
-
-static const u8 stmpe1601_regs[] = {
-	[STMPE_IDX_GPMR_LSB]	= STMPE1601_GPIO_MP_LSB,
-	[STMPE_IDX_GPSR_LSB]	= STMPE1601_GPIO_SET_LSB,
-	[STMPE_IDX_GPCR_LSB]	= STMPE1601_GPIO_CLR_LSB,
-	[STMPE_IDX_GPDR_LSB]	= STMPE1601_GPIO_SET_DIR_LSB,
-	[STMPE_IDX_GPRER_LSB]	= STMPE1601_GPIO_RE_LSB,
-	[STMPE_IDX_GPFER_LSB]	= STMPE1601_GPIO_FE_LSB,
-	[STMPE_IDX_GPAFR_U_MSB]	= STMPE1601_GPIO_AF_U_MSB,
-	[STMPE_IDX_IEGPIOR_LSB]	= STMPE1601_INT_EN_GPIO_MASK_LSB,
-	[STMPE_IDX_ISGPIOR_MSB]	= STMPE1601_INT_STA_GPIO_MSB,
-	[STMPE_IDX_GPEDR_MSB]	= STMPE1601_GPIO_ED_MSB,
-};
+int stmpe_enable(struct stmpe *stmpe, unsigned int blocks)
+{
+	struct stmpe_variant_info *variant = stmpe->variant;
+	return variant->enable(stmpe, blocks, true);
+}
+EXPORT_SYMBOL_GPL(stmpe_enable);
 
-static const u8 stmpe24xx_regs[] = {
-	[STMPE_IDX_GPMR_LSB]	= STMPE24XX_GPMR_LSB,
-	[STMPE_IDX_GPSR_LSB]	= STMPE24XX_GPSR_LSB,
-	[STMPE_IDX_GPCR_LSB]	= STMPE24XX_GPCR_LSB,
-	[STMPE_IDX_GPDR_LSB]	= STMPE24XX_GPDR_LSB,
-	[STMPE_IDX_GPRER_LSB]	= STMPE24XX_GPRER_LSB,
-	[STMPE_IDX_GPFER_LSB]	= STMPE24XX_GPFER_LSB,
-	[STMPE_IDX_GPAFR_U_MSB]	= STMPE24XX_GPAFR_U_MSB,
-	[STMPE_IDX_IEGPIOR_LSB]	= STMPE24XX_IEGPIOR_LSB,
-	[STMPE_IDX_ISGPIOR_MSB]	= STMPE24XX_ISGPIOR_MSB,
-	[STMPE_IDX_GPEDR_MSB]	= STMPE24XX_GPEDR_MSB,
-};
+int stmpe_disable(struct stmpe *stmpe, unsigned int blocks)
+{
+	struct stmpe_variant_info *variant = stmpe->variant;
+	return variant->enable(stmpe, blocks, false);
+}
+EXPORT_SYMBOL_GPL(stmpe_disable);
 
 /**
  * stmpe_reg_read() - read a single STMPE register
@@ -225,28 +166,23 @@ EXPORT_SYMBOL_GPL(stmpe_block_write);
  * If the GPIO module is not enabled, this function automatically enables it in
  * order to perform the change.
  */
-int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins, int af)
+int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins, enum stmpe_block block)
 {
+	struct stmpe_variant_info *variant = stmpe->variant;
 	u8 regaddr = stmpe->regs[STMPE_IDX_GPAFR_U_MSB];
-	int numregs = stmpe->num_gpios / 4;
+	int afperreg = variant->af_per_reg;
+	int numregs = stmpe->num_gpios / afperreg;
 	u8 regs[numregs];
-	bool gpioon;
-	int syscon;
+	int af;
 	int ret;
 
-	mutex_lock(&stmpe->lock);
+	ret = stmpe_enable(stmpe, STMPE_BLOCK_GPIO);
+	if (ret < 0)
+		return ret;
 
-	syscon = stmpe_reg_read(stmpe, STMPE_SYSCON);
-	if (syscon < 0)
-		return syscon;
+	mutex_lock(&stmpe->lock);
 
-	gpioon = syscon & STMPE_SYSCON_ENABLE_GPIO;
-	if (!gpioon) {
-		ret = stmpe_reg_write(stmpe, STMPE_SYSCON,
-				      syscon | STMPE_SYSCON_ENABLE_GPIO);
-		if (ret < 0)
-			return ret;
-	}
+	af = variant->get_altfunc(stmpe, block);
 
 	ret = stmpe_block_read(stmpe, regaddr, numregs, regs);
 	if (ret < 0)
@@ -254,10 +190,11 @@ int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins, int af)
 
 	while (pins) {
 		int pin = __ffs(pins);
-		int regoffset = numregs - (pin / 4) - 1;
-		int pos = (pin % 4) * 2;
+		int regoffset = numregs - (pin / afperreg) - 1;
+		int pos = (pin % afperreg) * (8 / afperreg);
+		int mask = afperreg == 4 ? 0x3 : 1;
 
-		regs[regoffset] &= ~(0x3 << pos);
+		regs[regoffset] &= ~(mask << pos);
 		regs[regoffset] |= af << pos;
 
 		pins &= ~(1 << pin);
@@ -265,58 +202,23 @@ int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins, int af)
 
 	ret = stmpe_block_write(stmpe, regaddr, numregs, regs);
 
-	if (!gpioon)
-		stmpe_reg_write(stmpe, STMPE_SYSCON, syscon);
-
 out:
 	mutex_unlock(&stmpe->lock);
 	return ret;
 }
 EXPORT_SYMBOL_GPL(stmpe_set_altfunc);
 
-static struct resource gpio_resources[] = {
-	{
-		.start	= STMPE_INT_GPIOC,
-		.end	= STMPE_INT_GPIOC,
-		.flags	= IORESOURCE_IRQ,
-	},
-};
-
-static struct resource keypad_resources[] = {
-	{
-		.start	= STMPE_INT_KEYPAD,
-		.end	= STMPE_INT_KEYPAD,
-		.flags	= IORESOURCE_IRQ,
-	},
-	{
-		.start	= STMPE_INT_KEYPAD_OVER,
-		.end	= STMPE_INT_KEYPAD_OVER,
-		.flags	= IORESOURCE_IRQ,
-	},
-};
-
-static struct mfd_cell stmpe_devs[] = {
-	{
-		.name		= "stmpe-gpio",
-		.resources	= gpio_resources,
-		.num_resources	= ARRAY_SIZE(gpio_resources),
-	},
-	{
-		.name		= "stmpe-keypad",
-		.resources	= keypad_resources,
-		.num_resources	= ARRAY_SIZE(keypad_resources),
-	},
-};
-
 static irqreturn_t stmpe_irq(int irq, void *data)
 {
 	struct stmpe *stmpe = data;
-	int num = ARRAY_SIZE(stmpe->ier);
+	struct stmpe_variant_info *variant = stmpe->variant;
+	int num = DIV_ROUND_UP(variant->num_irqs, 8);
+	u8 israddr = stmpe->regs[STMPE_IDX_ISR_MSB];
 	u8 isr[num];
 	int ret;
 	int i;
 
-	ret = stmpe_block_read(stmpe, STMPE_ISR_MSB, num, isr);
+	ret = stmpe_block_read(stmpe, israddr, num, isr);
 	if (ret < 0)
 		return IRQ_NONE;
 
@@ -338,7 +240,7 @@ static irqreturn_t stmpe_irq(int irq, void *data)
 			status &= ~(1 << bit);
 		}
 
-		stmpe_reg_write(stmpe, STMPE_ISR_MSB + i, clear);
+		stmpe_reg_write(stmpe, israddr + i, clear);
 	}
 
 	return IRQ_HANDLED;
@@ -354,9 +256,11 @@ static void stmpe_irq_lock(unsigned int irq)
 static void stmpe_irq_sync_unlock(unsigned int irq)
 {
 	struct stmpe *stmpe = get_irq_chip_data(irq);
+	struct stmpe_variant_info *variant = stmpe->variant;
+	int num = DIV_ROUND_UP(variant->num_irqs, 8);
 	int i;
 
-	for (i = 0; i < ARRAY_SIZE(stmpe->ier); i++) {
+	for (i = 0; i < num; i++) {
 		u8 new = stmpe->ier[i];
 		u8 old = stmpe->oldier[i];
 
@@ -364,7 +268,7 @@ static void stmpe_irq_sync_unlock(unsigned int irq)
 			continue;
 
 		stmpe->oldier[i] = new;
-		stmpe_reg_write(stmpe, STMPE_IER_LSB - i, new);
+		stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_IER_LSB] - i, new);
 	}
 
 	mutex_unlock(&stmpe->irq_lock);
@@ -400,10 +304,11 @@ static struct irq_chip stmpe_irq_chip = {
 
 static int __devinit stmpe_irq_init(struct stmpe *stmpe)
 {
+	int num_irqs = stmpe->variant->num_irqs;
 	int base = stmpe->irq_base;
 	int irq;
 
-	for (irq = base; irq < base + STMPE_NR_INTERNAL_IRQS; irq++) {
+	for (irq = base; irq < base + num_irqs; irq++) {
 		set_irq_chip_data(irq, stmpe);
 		set_irq_chip_and_handler(irq, &stmpe_irq_chip,
 					 handle_edge_irq);
@@ -420,10 +325,11 @@ static int __devinit stmpe_irq_init(struct stmpe *stmpe)
 
 static void stmpe_irq_remove(struct stmpe *stmpe)
 {
+	int num_irqs = stmpe->variant->num_irqs;
 	int base = stmpe->irq_base;
 	int irq;
 
-	for (irq = base; irq < base + STMPE_NR_INTERNAL_IRQS; irq++) {
+	for (irq = base; irq < base + num_irqs; irq++) {
 #ifdef CONFIG_ARM
 		set_irq_flags(irq, 0);
 #endif
@@ -435,50 +341,27 @@ static void stmpe_irq_remove(struct stmpe *stmpe)
 static int __devinit stmpe_chip_init(struct stmpe *stmpe)
 {
 	unsigned int irq_trigger = stmpe->pdata->irq_trigger;
+	struct stmpe_variant_info *variant = stmpe->variant;
 	u8 icr = STMPE_ICR_LSB_GIM;
-	const char *name;
 	unsigned int id;
 	u8 data[2];
 	int ret;
 
-	ret = stmpe_block_read(stmpe, STMPE_CHIP_ID, ARRAY_SIZE(data), data);
+	ret = stmpe_block_read(stmpe, stmpe->regs[STMPE_IDX_CHIP_ID],
+			       ARRAY_SIZE(data), data);
 	if (ret < 0)
 		return ret;
 
 	id = (data[0] << 8) | data[1];
-
-	switch (id) {
-	case 0x0210:
-	case 0x0212:
-		name = "STMPE1601";
-		stmpe->variant = STMPE1601;
-		stmpe->regs = stmpe1601_regs;
-		stmpe->num_gpios = 16;
-		break;
-
-	case 0x0101:
-		name = "STMPE2401";
-		stmpe->variant = STMPE2401;
-		stmpe->regs = stmpe24xx_regs;
-		stmpe->num_gpios = 24;
-		break;
-
-	case 0x0120:
-		name = "STMPE2403";
-		stmpe->variant = STMPE2403;
-		stmpe->regs = stmpe24xx_regs;
-		stmpe->num_gpios = 24;
-		break;
-
-	default:
-		dev_err(stmpe->dev, "unknown id: %#x\n", id);
+	if ((id & variant->id_mask) != variant->id_val) {
+		dev_err(stmpe->dev, "unknown chip id: %#x\n", id);
 		return -EINVAL;
 	}
 
-	dev_info(stmpe->dev, "%s detected\n", name);
+	dev_info(stmpe->dev, "%s detected, chip id: %#x\n", variant->name, id);
 
 	/* Disable all modules -- subdrivers should enable what they need. */
-	ret = stmpe_set_bits(stmpe, STMPE_SYSCON, STMPE_SYSCON_ENABLE, 0);
+	ret = stmpe_disable(stmpe, ~0);
 	if (ret)
 		return ret;
 
@@ -490,7 +373,42 @@ static int __devinit stmpe_chip_init(struct stmpe *stmpe)
 	    irq_trigger == IRQF_TRIGGER_HIGH)
 		icr |= STMPE_ICR_LSB_HIGH;
 
-	return stmpe_reg_write(stmpe, STMPE_ICR_LSB, icr);
+	if (stmpe->pdata->irq_invert_polarity)
+		icr ^= STMPE_ICR_LSB_HIGH;
+
+	return stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_ICR_LSB], icr);
+}
+
+static int __devinit stmpe_add_device(struct stmpe *stmpe,
+				      struct mfd_cell *cell, int irq)
+{
+	return mfd_add_devices(stmpe->dev, stmpe->pdata->id, cell, 1,
+			       NULL, stmpe->irq_base + irq);
+}
+
+static int __devinit stmpe_devices_init(struct stmpe *stmpe)
+{
+	struct stmpe_variant_info *variant = stmpe->variant;
+	unsigned int platform_blocks = stmpe->pdata->blocks;
+	int ret;
+	int i;
+
+	for (i = 0; i < variant->num_blocks; i++) {
+		struct stmpe_variant_block *block = &variant->blocks[i];
+
+		if (!(platform_blocks & block->block))
+			continue;
+
+		platform_blocks &= ~block->block;
+		ret = stmpe_add_device(stmpe, block->cell, block->irq);
+	}
+
+	if (platform_blocks)
+		dev_warn(stmpe->dev,
+			 "platform wants blocks (%#x) not present on variant",
+			 platform_blocks);
+
+	return ret;
 }
 
 static int __devinit stmpe_probe(struct i2c_client *i2c,
@@ -516,6 +434,10 @@ static int __devinit stmpe_probe(struct i2c_client *i2c,
 	stmpe->pdata = pdata;
 	stmpe->irq_base = pdata->irq_base;
 
+	stmpe->partnum = id->driver_data;
+	stmpe->variant = stmpe_variant_info[stmpe->partnum];
+	stmpe->regs = stmpe->variant->regs;
+
 	i2c_set_clientdata(i2c, stmpe);
 
 	ret = stmpe_chip_init(stmpe);
@@ -534,9 +456,7 @@ static int __devinit stmpe_probe(struct i2c_client *i2c,
 		goto out_removeirq;
 	}
 
-	ret = mfd_add_devices(stmpe->dev, pdata->id, stmpe_devs,
-			      ARRAY_SIZE(stmpe_devs), NULL,
-			      stmpe->irq_base);
+	ret = stmpe_devices_init(stmpe);
 	if (ret) {
 		dev_err(stmpe->dev, "failed to add children\n");
 		goto out_freeirq;
@@ -570,7 +490,10 @@ static int __devexit stmpe_remove(struct i2c_client *client)
 }
 
 static const struct i2c_device_id stmpe_id[] = {
-	{ "stmpe", 0 },
+	{ "stmpe811", STMPE811 },
+	{ "stmpe1601", STMPE1601 },
+	{ "stmpe2401", STMPE2401 },
+	{ "stmpe2403", STMPE2403 },
 	{ }
 };
 MODULE_DEVICE_TABLE(i2c, stmpe_id);
diff --git a/drivers/mfd/stmpe.h b/drivers/mfd/stmpe.h
new file mode 100644
index 0000000..b1e4815
--- /dev/null
+++ b/drivers/mfd/stmpe.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#ifndef __STMPE_H
+#define __STMPE_H
+
+struct stmpe_variant_block {
+	struct mfd_cell		*cell;
+	int			irq;
+	enum stmpe_block	block;
+};
+
+struct stmpe_variant_info {
+	const char *name;
+	u16 id_val;
+	u16 id_mask;
+	int num_gpios;
+	int af_per_reg;
+	const u8 *regs;
+	struct stmpe_variant_block *blocks;
+	int num_blocks;
+	int num_irqs;
+	int (*enable)(struct stmpe *stmpe, unsigned int blocks, bool enable);
+	int (*get_altfunc)(struct stmpe *stmpe, enum stmpe_block block);
+};
+
+extern struct stmpe_variant_info *stmpe_variant_info[];
+
+/*
+ * STMPE811
+ */
+
+#define STMPE811_IRQ_TOUCH_DET		0
+#define STMPE811_IRQ_FIFO_TH		1
+#define STMPE811_IRQ_FIFO_OFLOW		2
+#define STMPE811_IRQ_FIFO_FULL		3
+#define STMPE811_IRQ_FIFO_EMPTY		4
+#define STMPE811_IRQ_TEMP_SENS		5
+#define STMPE811_IRQ_ADC		6
+#define STMPE811_IRQ_GPIOC		7
+#define STMPE811_NR_INTERNAL_IRQS	8
+
+#define STMPE811_REG_CHIP_ID		0x00
+#define STMPE811_REG_SYS_CTRL2		0x04
+#define STMPE811_REG_INT_CTRL		0x09
+#define STMPE811_REG_INT_EN		0x0A
+#define STMPE811_REG_INT_STA		0x0B
+#define STMPE811_REG_GPIO_INT_EN	0x0C
+#define STMPE811_REG_GPIO_INT_STA	0x0D
+#define STMPE811_REG_GPIO_SET_PIN	0x10
+#define STMPE811_REG_GPIO_CLR_PIN	0x11
+#define STMPE811_REG_GPIO_MP_STA	0x12
+#define STMPE811_REG_GPIO_DIR		0x13
+#define STMPE811_REG_GPIO_ED		0x14
+#define STMPE811_REG_GPIO_RE		0x15
+#define STMPE811_REG_GPIO_FE		0x16
+#define STMPE811_REG_GPIO_AF		0x17
+
+#define STMPE811_SYS_CTRL2_ADC_OFF	(1 << 0)
+#define STMPE811_SYS_CTRL2_TSC_OFF	(1 << 1)
+#define STMPE811_SYS_CTRL2_GPIO_OFF	(1 << 2)
+#define STMPE811_SYS_CTRL2_TS_OFF	(1 << 3)
+
+/*
+ * STMPE1601
+ */
+
+#define STMPE1601_IRQ_GPIOC		8
+#define STMPE1601_IRQ_PWM3		7
+#define STMPE1601_IRQ_PWM2		6
+#define STMPE1601_IRQ_PWM1		5
+#define STMPE1601_IRQ_PWM0		4
+#define STMPE1601_IRQ_KEYPAD_OVER	2
+#define STMPE1601_IRQ_KEYPAD		1
+#define STMPE1601_IRQ_WAKEUP		0
+#define STMPE1601_NR_INTERNAL_IRQS	9
+
+#define STMPE1601_REG_SYS_CTRL			0x02
+#define STMPE1601_REG_ICR_LSB			0x11
+#define STMPE1601_REG_IER_LSB			0x13
+#define STMPE1601_REG_ISR_MSB			0x14
+#define STMPE1601_REG_CHIP_ID			0x80
+#define STMPE1601_REG_INT_EN_GPIO_MASK_LSB	0x17
+#define STMPE1601_REG_INT_STA_GPIO_MSB		0x18
+#define STMPE1601_REG_GPIO_MP_LSB		0x87
+#define STMPE1601_REG_GPIO_SET_LSB		0x83
+#define STMPE1601_REG_GPIO_CLR_LSB		0x85
+#define STMPE1601_REG_GPIO_SET_DIR_LSB		0x89
+#define STMPE1601_REG_GPIO_ED_MSB		0x8A
+#define STMPE1601_REG_GPIO_RE_LSB		0x8D
+#define STMPE1601_REG_GPIO_FE_LSB		0x8F
+#define STMPE1601_REG_GPIO_AF_U_MSB		0x92
+
+#define STMPE1601_SYS_CTRL_ENABLE_GPIO		(1 << 3)
+#define STMPE1601_SYS_CTRL_ENABLE_KPC		(1 << 1)
+#define STMPE1601_SYSCON_ENABLE_SPWM		(1 << 0)
+
+/*
+ * STMPE24xx
+ */
+
+#define STMPE24XX_IRQ_GPIOC		8
+#define STMPE24XX_IRQ_PWM2		7
+#define STMPE24XX_IRQ_PWM1		6
+#define STMPE24XX_IRQ_PWM0		5
+#define STMPE24XX_IRQ_ROT_OVER		4
+#define STMPE24XX_IRQ_ROT		3
+#define STMPE24XX_IRQ_KEYPAD_OVER	2
+#define STMPE24XX_IRQ_KEYPAD		1
+#define STMPE24XX_IRQ_WAKEUP		0
+#define STMPE24XX_NR_INTERNAL_IRQS	9
+
+#define STMPE24XX_REG_SYS_CTRL		0x02
+#define STMPE24XX_REG_ICR_LSB		0x11
+#define STMPE24XX_REG_IER_LSB		0x13
+#define STMPE24XX_REG_ISR_MSB		0x14
+#define STMPE24XX_REG_CHIP_ID		0x80
+#define STMPE24XX_REG_IEGPIOR_LSB	0x18
+#define STMPE24XX_REG_ISGPIOR_MSB	0x19
+#define STMPE24XX_REG_GPMR_LSB		0xA5
+#define STMPE24XX_REG_GPSR_LSB		0x85
+#define STMPE24XX_REG_GPCR_LSB		0x88
+#define STMPE24XX_REG_GPDR_LSB		0x8B
+#define STMPE24XX_REG_GPEDR_MSB		0x8C
+#define STMPE24XX_REG_GPRER_LSB		0x91
+#define STMPE24XX_REG_GPFER_LSB		0x94
+#define STMPE24XX_REG_GPAFR_U_MSB	0x9B
+
+#define STMPE24XX_SYS_CTRL_ENABLE_GPIO		(1 << 3)
+#define STMPE24XX_SYSCON_ENABLE_PWM		(1 << 2)
+#define STMPE24XX_SYS_CTRL_ENABLE_KPC		(1 << 1)
+#define STMPE24XX_SYSCON_ENABLE_ROT		(1 << 0)
+
+#endif
diff --git a/include/linux/mfd/stmpe.h b/include/linux/mfd/stmpe.h
index 7c6733b..cf2155f 100644
--- a/include/linux/mfd/stmpe.h
+++ b/include/linux/mfd/stmpe.h
@@ -10,16 +10,17 @@
 
 #include <linux/device.h>
 
-#define STMPE_SYSCON			0x02
-
-#define STMPE_SYSCON_ENABLE		(0xf << 0)
-#define STMPE_SYSCON_ENABLE_GPIO	(1 << 3)
-#define STMPE24XX_SYSCON_ENABLE_PWM	(1 << 2)
-#define STMPE_SYSCON_ENABLE_KPC		(1 << 1)
-#define STMPE16XX_SYSCON_ENABLE_SPWM	(1 << 0)
-#define STMPE24XX_SYSCON_ENABLE_ROT	(1 << 0)
+enum stmpe_block {
+	STMPE_BLOCK_GPIO	= 1 << 0,
+	STMPE_BLOCK_KEYPAD	= 1 << 1,
+	STMPE_BLOCK_TOUCHSCREEN	= 1 << 2,
+	STMPE_BLOCK_ADC		= 1 << 3,
+	STMPE_BLOCK_PWM		= 1 << 4,
+	STMPE_BLOCK_ROTATOR	= 1 << 5,
+};
 
-enum stmpe_variant {
+enum stmpe_partnum {
+	STMPE811,
 	STMPE1601,
 	STMPE2401,
 	STMPE2403,
@@ -30,6 +31,10 @@ enum stmpe_variant {
  * obtained by indexing stmpe->regs with one of the following.
  */
 enum {
+	STMPE_IDX_CHIP_ID,
+	STMPE_IDX_ICR_LSB,
+	STMPE_IDX_IER_LSB,
+	STMPE_IDX_ISR_MSB,
 	STMPE_IDX_GPMR_LSB,
 	STMPE_IDX_GPSR_LSB,
 	STMPE_IDX_GPCR_LSB,
@@ -40,8 +45,12 @@ enum {
 	STMPE_IDX_GPAFR_U_MSB,
 	STMPE_IDX_IEGPIOR_LSB,
 	STMPE_IDX_ISGPIOR_MSB,
+	STMPE_IDX_MAX,
 };
 
+
+struct stmpe_variant_info;
+
 /**
  * struct stmpe - STMPE MFD structure
  * @lock: lock protecting I/O operations
@@ -62,7 +71,8 @@ struct stmpe {
 	struct mutex irq_lock;
 	struct device *dev;
 	struct i2c_client *i2c;
-	enum stmpe_variant variant;
+	enum stmpe_partnum partnum;
+	struct stmpe_variant_info *variant;
 	const u8 *regs;
 
 	int irq_base;
@@ -79,7 +89,10 @@ extern int stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length,
 extern int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,
 			     const u8 *values);
 extern int stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val);
-extern int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins, int af);
+extern int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins,
+			     enum stmpe_block block);
+extern int stmpe_enable(struct stmpe *stmpe, unsigned int blocks);
+extern int stmpe_disable(struct stmpe *stmpe, unsigned int blocks);
 
 struct matrix_keymap_data;
 
@@ -106,12 +119,16 @@ struct stmpe_keypad_platform_data {
  */
 struct stmpe_gpio_platform_data {
 	int gpio_base;
+	void (*setup)(struct stmpe *stmpe, unsigned gpio_base);
+	void (*remove)(struct stmpe *stmpe, unsigned gpio_base);
 };
 
 /**
  * struct stmpe_platform_data - STMPE platform data
  * @id: device id to distinguish between multiple STMPEs on the same board
+ * @blocks: bitmask of blocks to enable (use STMPE_BLOCK_*)
  * @irq_trigger: IRQ trigger to use for the interrupt to the host
+ * @irq_invert_polarity: IRQ line is connected with reversed polarity
  * @irq_base: base IRQ number.  %STMPE_NR_IRQS irqs will be used, or
  *	      %STMPE_NR_INTERNAL_IRQS if the GPIO driver is not used.
  * @gpio: GPIO-specific platform data
@@ -119,8 +136,10 @@ struct stmpe_gpio_platform_data {
  */
 struct stmpe_platform_data {
 	int id;
+	unsigned int blocks;
 	int irq_base;
 	unsigned int irq_trigger;
+	bool irq_invert_polarity;
 
 	struct stmpe_gpio_platform_data *gpio;
 	struct stmpe_keypad_platform_data *keypad;
-- 
1.7.0


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* Re: [PATCH 1/3] mfd: add STMPExxxx I/O Expander support
  2010-06-21 13:33   ` Rabin VINCENT
@ 2010-06-21 15:45     ` Luotao Fu
  2010-06-22 13:55       ` [PATCHv2 1/3] mfd: add STMPE " Rabin Vincent
                         ` (3 more replies)
  0 siblings, 4 replies; 61+ messages in thread
From: Luotao Fu @ 2010-06-21 15:45 UTC (permalink / raw)
  To: Rabin VINCENT
  Cc: Samuel Ortiz, linux-kernel, STEricsson_nomadik_linux,
	Linus WALLEIJ, l.fu

[-- Attachment #1: Type: text/plain, Size: 3520 bytes --]

On Mon, Jun 21, 2010 at 07:03:07PM +0530, Rabin VINCENT wrote:
> On Sat, Jun 19, 2010 at 01:42:24 +0200, Samuel Ortiz wrote:
> > On Mon, May 31, 2010 at 05:47:14PM +0530, Rabin Vincent wrote:
> > > Add support for the STMPExxxx family of I/O Expanders from
> > > STMicroelectronics.  These devices include upto 24 gpios, a PWM
> > > controller, and a keypad controller.  This patch adds the MFD core.
> > The patchset looks fairly good, but before merging it I'd like to know of we
> > could merge it with this one:
> > https://patchwork.kernel.org/patch/106173/
> > 
> > I don't know enough about the hardware, and although the register layouts don't
> > look like they have much in common, I'd like to know from the actual HW
> > manufacturer (i.e. you :)) if there's something we can do here.
> 
> I don't have any inside information about these parts (we just use them
> on our U8500 dev boards), but all the datasheets are publicly
> available[1].  There seems to be more than a dozen variants of this MFD
> with varying combinations of devices.
> 
> [1] http://www.st.com/stonline/stappl/productcatalog/app?path=/pages/stcom/PcStComPartNumberSearch.searchPartNumber&search=stmpe
> 
> On Sat, Jun 19, 2010 at 15:50:16 +0200, Luotao Fu wrote:
> > hmm, I took a quick look into the core driver. The register layout
> > seems, as Samuel mentioned, quite different. However, the r/w
> > functionalities and irq handling are quite the same. For now I'd say
> > that should be possible to merge the stuff.
> 
> The IRQ handling and GPIO block seem to be about the same (registers are
> at different offsets, but this is also the case between STMPE1601 and
> STMPE24XX and is thus already handled in the STMPExxxx driver).  The
> STMPExxxx GPIO driver should also already be able to handle a variant
> with lesser gpios, such as STMPE811.
> 
> Similarities:
>  - I2C access functions
>  - GPIO block (same registers, different offsets)
>  - IRQ block (same registers and handling, different irqs and different
>    register offsets)
> 
> Differences:
>  - Different blocks (but sharing between different groups of variants)
>  - SYSCTRL register bits (reset, clock enabling)
>  - GPIO altfunc bits
>  - The STMPE811 also has a SPI interface, while most other support only I2C
>    This is also not avaiabile in Luotao's driver so I'm not addressing
>    this for now.
> 
I2C is the most common usage variant. Otherwise adding spi access
routines should be no big deal. Some additional to the probe function
might be needed besides the r/w core functions. I do think that we leave
the stuff this way and take care of them if somebody does need SPI.

> Here's a preliminary patch (untested!) which shows how the STMPExxx can
> be made more generic to support 811 and hopefully other variants.  If
> this looks sane, I'll complete it up, fold it in, and repost the
> STMPExxxx series for review and also 811 testing and touchscreen
> addition from Luotao.
> 

I'll test the patch as soon as I can. Do you have any public GIT tree
with the stmpexxx stuffs in them, so that I might be able to merge it
with my board stuffs directly?

cheers
Luotao Fu

-- 
Pengutronix e.K.                           | Dipl.-Ing. Luotao Fu        |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 61+ messages in thread

* [PATCHv2 1/3] mfd: add STMPE I/O Expander support
  2010-06-21 15:45     ` Luotao Fu
@ 2010-06-22 13:55       ` Rabin Vincent
  2010-06-27 23:55         ` Samuel Ortiz
  2010-06-22 13:55       ` [PATCHv2 2/3] gpio: add STMPE GPIO driver Rabin Vincent
                         ` (2 subsequent siblings)
  3 siblings, 1 reply; 61+ messages in thread
From: Rabin Vincent @ 2010-06-22 13:55 UTC (permalink / raw)
  To: linux-kernel
  Cc: Samuel Ortiz, STEricsson_nomadik_linux, linus.walleij, l.fu,
	Rabin Vincent

Add support for the STMPE family of I/O Expanders from
STMicroelectronics.  These devices include upto 24 gpios and a varying
selection of blocks, including PWM, keypad, and touchscreen controllers.
This patch adds the MFD core.

Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
---

CHANGELOG (for the whole set)
v1 -> v2:
  - Variant data to support other STMPE* variants
  - Enable/disable API
  - GPIO setup/remove callbacks
  - pdata made optional for GPIO
  - Platform specifies which blocks it wants to use
  - IRQ invert polarity option
  - STMPE811 support (needs testing)

 drivers/mfd/Kconfig         |   12 +
 drivers/mfd/Makefile        |    1 +
 drivers/mfd/stmpe-devices.c |  328 ++++++++++++++++++++++++++
 drivers/mfd/stmpe.c         |  530 +++++++++++++++++++++++++++++++++++++++++++
 drivers/mfd/stmpe.h         |  164 +++++++++++++
 include/linux/mfd/stmpe.h   |  154 +++++++++++++
 6 files changed, 1189 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/stmpe-devices.c
 create mode 100644 drivers/mfd/stmpe.c
 create mode 100644 drivers/mfd/stmpe.h
 create mode 100644 include/linux/mfd/stmpe.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 9da0e50..63dce71 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -177,6 +177,18 @@ config TWL4030_CODEC
 	select MFD_CORE
 	default n
 
+config MFD_STMPE
+	bool "Support STMicroelectronics STMPE"
+	depends on I2C=y && GENERIC_HARDIRQS
+	select MFD_CORE
+	help
+	  Support for the STMPE family of I/O Expanders from
+	  STMicroelectronics.
+
+	  This driver provides common support for accessing the device,
+	  additional drivers must be enabled in order to use the
+	  functionality of the device.
+
 config MFD_TC35892
 	bool "Support Toshiba TC35892"
 	depends on I2C=y && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index fb503e7..012412a 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_HTC_I2CPLD)	+= htc-i2cpld.o
 obj-$(CONFIG_MFD_DAVINCI_VOICECODEC)	+= davinci_voicecodec.o
 obj-$(CONFIG_MFD_DM355EVM_MSP)	+= dm355evm_msp.o
 
+obj-$(CONFIG_MFD_STMPE)		+= stmpe.o stmpe-devices.o
 obj-$(CONFIG_MFD_TC35892)	+= tc35892.o
 obj-$(CONFIG_MFD_T7L66XB)	+= t7l66xb.o tmio_core.o
 obj-$(CONFIG_MFD_TC6387XB)	+= tc6387xb.o tmio_core.o
diff --git a/drivers/mfd/stmpe-devices.c b/drivers/mfd/stmpe-devices.c
new file mode 100644
index 0000000..fa6934d
--- /dev/null
+++ b/drivers/mfd/stmpe-devices.c
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/stmpe.h>
+#include "stmpe.h"
+
+/*
+ * GPIO (all variants)
+ */
+
+static struct resource stmpe_gpio_resources[] = {
+	/* Start and end filled dynamically */
+	{
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct mfd_cell stmpe_gpio_cell = {
+	.name		= "stmpe-gpio",
+	.resources	= stmpe_gpio_resources,
+	.num_resources	= ARRAY_SIZE(stmpe_gpio_resources),
+};
+
+/*
+ * Keypad (1601, 2401, 2403)
+ */
+
+static struct resource stmpe_keypad_resources[] = {
+	{
+		.name	= "KEYPAD",
+		.start	= 0,
+		.end	= 0,
+		.flags	= IORESOURCE_IRQ,
+	},
+	{
+		.name	= "KEYPAD_OVER",
+		.start	= 1,
+		.end	= 1,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct mfd_cell stmpe_keypad_cell = {
+	.name		= "stmpe-keypad",
+	.resources	= stmpe_keypad_resources,
+	.num_resources	= ARRAY_SIZE(stmpe_keypad_resources),
+};
+
+/*
+ * Touchscreen (STMPE811)
+ */
+
+static struct resource stmpe_ts_resources[] = {
+	{
+		.name	= "TOUCH_DET",
+		.start	= 0,
+		.end	= 0,
+		.flags	= IORESOURCE_IRQ,
+	},
+	{
+		.name	= "FIFO_TH",
+		.start	= 1,
+		.end	= 1,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct mfd_cell stmpe_ts_cell = {
+	.name		= "stmpe-ts",
+	.resources	= stmpe_ts_resources,
+	.num_resources	= ARRAY_SIZE(stmpe_ts_resources),
+};
+
+/*
+ * STMPE811
+ */
+
+static const u8 stmpe811_regs[] = {
+	[STMPE_IDX_CHIP_ID]	= STMPE811_REG_CHIP_ID,
+	[STMPE_IDX_ICR_LSB]	= STMPE811_REG_INT_CTRL,
+	[STMPE_IDX_IER_LSB]	= STMPE811_REG_INT_EN,
+	[STMPE_IDX_ISR_MSB]	= STMPE811_REG_INT_STA,
+	[STMPE_IDX_GPMR_LSB]	= STMPE811_REG_GPIO_MP_STA,
+	[STMPE_IDX_GPSR_LSB]	= STMPE811_REG_GPIO_SET_PIN,
+	[STMPE_IDX_GPCR_LSB]	= STMPE811_REG_GPIO_CLR_PIN,
+	[STMPE_IDX_GPDR_LSB]	= STMPE811_REG_GPIO_DIR,
+	[STMPE_IDX_GPRER_LSB]	= STMPE811_REG_GPIO_RE,
+	[STMPE_IDX_GPFER_LSB]	= STMPE811_REG_GPIO_FE,
+	[STMPE_IDX_GPAFR_U_MSB]	= STMPE811_REG_GPIO_AF,
+	[STMPE_IDX_IEGPIOR_LSB]	= STMPE811_REG_GPIO_INT_EN,
+	[STMPE_IDX_ISGPIOR_MSB]	= STMPE811_REG_GPIO_INT_STA,
+	[STMPE_IDX_GPEDR_MSB]	= STMPE811_REG_GPIO_ED,
+};
+
+static struct stmpe_variant_block stmpe811_blocks[] = {
+	{
+		.cell	= &stmpe_gpio_cell,
+		.irq	= STMPE811_IRQ_GPIOC,
+		.block	= STMPE_BLOCK_GPIO,
+	},
+	{
+		.cell	= &stmpe_ts_cell,
+		.irq	= STMPE811_IRQ_TOUCH_DET,
+		.block	= STMPE_BLOCK_TOUCHSCREEN,
+	},
+};
+
+static int stmpe811_enable(struct stmpe *stmpe, unsigned int blocks,
+			   bool enable)
+{
+	unsigned int mask = 0;
+
+	if (blocks & STMPE_BLOCK_GPIO)
+		mask |= STMPE811_SYS_CTRL2_GPIO_OFF;
+
+	if (blocks & STMPE_BLOCK_ADC)
+		mask |= STMPE811_SYS_CTRL2_ADC_OFF;
+
+	if (blocks & STMPE_BLOCK_KEYPAD)
+		mask |= STMPE811_SYS_CTRL2_TSC_OFF;
+
+	return stmpe_set_bits(stmpe, STMPE811_REG_SYS_CTRL2, mask,
+			      enable ? 0 : mask);
+}
+
+static int stmpe811_get_altfunc(struct stmpe *stmpe, enum stmpe_block block)
+{
+	/* 0 for touchscreen, 1 for GPIO */
+	return block != STMPE_BLOCK_TOUCHSCREEN;
+}
+
+static struct stmpe_variant_info stmpe811 = {
+	.name		= "stmpe811",
+	.id_val		= 0x0811,
+	.id_mask	= 0xffff,
+	.num_gpios	= 8,
+	.af_bits	= 1,
+	.regs		= stmpe811_regs,
+	.blocks		= stmpe811_blocks,
+	.num_blocks	= ARRAY_SIZE(stmpe811_blocks),
+	.num_irqs	= STMPE811_NR_INTERNAL_IRQS,
+	.enable		= stmpe811_enable,
+	.get_altfunc	= stmpe811_get_altfunc,
+};
+
+/*
+ * STMPE1601
+ */
+
+static const u8 stmpe1601_regs[] = {
+	[STMPE_IDX_CHIP_ID]	= STMPE1601_REG_CHIP_ID,
+	[STMPE_IDX_ICR_LSB]	= STMPE1601_REG_ICR_LSB,
+	[STMPE_IDX_IER_LSB]	= STMPE1601_REG_IER_LSB,
+	[STMPE_IDX_ISR_MSB]	= STMPE1601_REG_ISR_MSB,
+	[STMPE_IDX_GPMR_LSB]	= STMPE1601_REG_GPIO_MP_LSB,
+	[STMPE_IDX_GPSR_LSB]	= STMPE1601_REG_GPIO_SET_LSB,
+	[STMPE_IDX_GPCR_LSB]	= STMPE1601_REG_GPIO_CLR_LSB,
+	[STMPE_IDX_GPDR_LSB]	= STMPE1601_REG_GPIO_SET_DIR_LSB,
+	[STMPE_IDX_GPRER_LSB]	= STMPE1601_REG_GPIO_RE_LSB,
+	[STMPE_IDX_GPFER_LSB]	= STMPE1601_REG_GPIO_FE_LSB,
+	[STMPE_IDX_GPAFR_U_MSB]	= STMPE1601_REG_GPIO_AF_U_MSB,
+	[STMPE_IDX_IEGPIOR_LSB]	= STMPE1601_REG_INT_EN_GPIO_MASK_LSB,
+	[STMPE_IDX_ISGPIOR_MSB]	= STMPE1601_REG_INT_STA_GPIO_MSB,
+	[STMPE_IDX_GPEDR_MSB]	= STMPE1601_REG_GPIO_ED_MSB,
+};
+
+static struct stmpe_variant_block stmpe1601_blocks[] = {
+	{
+		.cell	= &stmpe_gpio_cell,
+		.irq	= STMPE24XX_IRQ_GPIOC,
+		.block	= STMPE_BLOCK_GPIO,
+	},
+	{
+		.cell	= &stmpe_keypad_cell,
+		.irq	= STMPE24XX_IRQ_KEYPAD,
+		.block	= STMPE_BLOCK_KEYPAD,
+	},
+};
+
+static int stmpe1601_enable(struct stmpe *stmpe, unsigned int blocks,
+			    bool enable)
+{
+	unsigned int mask = 0;
+
+	if (blocks & STMPE_BLOCK_GPIO)
+		mask |= STMPE1601_SYS_CTRL_ENABLE_GPIO;
+
+	if (blocks & STMPE_BLOCK_KEYPAD)
+		mask |= STMPE1601_SYS_CTRL_ENABLE_KPC;
+
+	return stmpe_set_bits(stmpe, STMPE1601_REG_SYS_CTRL, mask,
+			      enable ? mask : 0);
+}
+
+static int stmpe1601_get_altfunc(struct stmpe *stmpe, enum stmpe_block block)
+{
+	switch (block) {
+	case STMPE_BLOCK_PWM:
+		return 2;
+
+	case STMPE_BLOCK_KEYPAD:
+		return 1;
+
+	case STMPE_BLOCK_GPIO:
+	default:
+		return 0;
+	}
+}
+
+static struct stmpe_variant_info stmpe1601 = {
+	.name		= "stmpe1601",
+	.id_val		= 0x0210,
+	.id_mask	= 0xfff0,	/* at least 0x0210 and 0x0212 */
+	.num_gpios	= 16,
+	.af_bits	= 2,
+	.regs		= stmpe1601_regs,
+	.blocks		= stmpe1601_blocks,
+	.num_blocks	= ARRAY_SIZE(stmpe1601_blocks),
+	.num_irqs	= STMPE1601_NR_INTERNAL_IRQS,
+	.enable		= stmpe1601_enable,
+	.get_altfunc	= stmpe1601_get_altfunc,
+};
+
+/*
+ * STMPE24XX
+ */
+
+static const u8 stmpe24xx_regs[] = {
+	[STMPE_IDX_CHIP_ID]	= STMPE24XX_REG_CHIP_ID,
+	[STMPE_IDX_ICR_LSB]	= STMPE24XX_REG_ICR_LSB,
+	[STMPE_IDX_IER_LSB]	= STMPE24XX_REG_IER_LSB,
+	[STMPE_IDX_ISR_MSB]	= STMPE24XX_REG_ISR_MSB,
+	[STMPE_IDX_GPMR_LSB]	= STMPE24XX_REG_GPMR_LSB,
+	[STMPE_IDX_GPSR_LSB]	= STMPE24XX_REG_GPSR_LSB,
+	[STMPE_IDX_GPCR_LSB]	= STMPE24XX_REG_GPCR_LSB,
+	[STMPE_IDX_GPDR_LSB]	= STMPE24XX_REG_GPDR_LSB,
+	[STMPE_IDX_GPRER_LSB]	= STMPE24XX_REG_GPRER_LSB,
+	[STMPE_IDX_GPFER_LSB]	= STMPE24XX_REG_GPFER_LSB,
+	[STMPE_IDX_GPAFR_U_MSB]	= STMPE24XX_REG_GPAFR_U_MSB,
+	[STMPE_IDX_IEGPIOR_LSB]	= STMPE24XX_REG_IEGPIOR_LSB,
+	[STMPE_IDX_ISGPIOR_MSB]	= STMPE24XX_REG_ISGPIOR_MSB,
+	[STMPE_IDX_GPEDR_MSB]	= STMPE24XX_REG_GPEDR_MSB,
+};
+
+static struct stmpe_variant_block stmpe24xx_blocks[] = {
+	{
+		.cell	= &stmpe_gpio_cell,
+		.irq	= STMPE24XX_IRQ_GPIOC,
+		.block	= STMPE_BLOCK_GPIO,
+	},
+	{
+		.cell	= &stmpe_keypad_cell,
+		.irq	= STMPE24XX_IRQ_KEYPAD,
+		.block	= STMPE_BLOCK_KEYPAD,
+	},
+};
+
+static int stmpe24xx_enable(struct stmpe *stmpe, unsigned int blocks,
+			    bool enable)
+{
+	unsigned int mask = 0;
+
+	if (blocks & STMPE_BLOCK_GPIO)
+		mask |= STMPE24XX_SYS_CTRL_ENABLE_GPIO;
+
+	if (blocks & STMPE_BLOCK_KEYPAD)
+		mask |= STMPE24XX_SYS_CTRL_ENABLE_KPC;
+
+	return stmpe_set_bits(stmpe, STMPE24XX_REG_SYS_CTRL, mask,
+			      enable ? mask : 0);
+}
+
+static int stmpe24xx_get_altfunc(struct stmpe *stmpe, enum stmpe_block block)
+{
+	switch (block) {
+	case STMPE_BLOCK_ROTATOR:
+		return 2;
+
+	case STMPE_BLOCK_KEYPAD:
+		return 1;
+
+	case STMPE_BLOCK_GPIO:
+	default:
+		return 0;
+	}
+}
+
+static struct stmpe_variant_info stmpe2401 = {
+	.name		= "stmpe2401",
+	.id_val		= 0x0101,
+	.id_mask	= 0xffff,
+	.num_gpios	= 24,
+	.af_bits	= 2,
+	.regs		= stmpe24xx_regs,
+	.blocks		= stmpe24xx_blocks,
+	.num_blocks	= ARRAY_SIZE(stmpe24xx_blocks),
+	.num_irqs	= STMPE24XX_NR_INTERNAL_IRQS,
+	.enable		= stmpe24xx_enable,
+	.get_altfunc	= stmpe24xx_get_altfunc,
+};
+
+static struct stmpe_variant_info stmpe2403 = {
+	.name		= "stmpe2403",
+	.id_val		= 0x0120,
+	.id_mask	= 0xffff,
+	.num_gpios	= 24,
+	.af_bits	= 2,
+	.regs		= stmpe24xx_regs,
+	.blocks		= stmpe24xx_blocks,
+	.num_blocks	= ARRAY_SIZE(stmpe24xx_blocks),
+	.num_irqs	= STMPE24XX_NR_INTERNAL_IRQS,
+	.enable		= stmpe24xx_enable,
+	.get_altfunc	= stmpe24xx_get_altfunc,
+};
+
+struct stmpe_variant_info *stmpe_variant_info[] = {
+	[STMPE811]	= &stmpe811,
+	[STMPE1601]	= &stmpe1601,
+	[STMPE2401]	= &stmpe2401,
+	[STMPE2403]	= &stmpe2403,
+};
diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c
new file mode 100644
index 0000000..dc938f6
--- /dev/null
+++ b/drivers/mfd/stmpe.c
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/stmpe.h>
+#include "stmpe.h"
+
+/**
+ * stmpe_enable - enable blocks on an STMPE device
+ * @stmpe:	Device to work on
+ * @blocks:	Mask of blocks (enum stmpe_block values) to enable
+ */
+int stmpe_enable(struct stmpe *stmpe, unsigned int blocks)
+{
+	return stmpe->variant->enable(stmpe, blocks, true);
+}
+EXPORT_SYMBOL_GPL(stmpe_enable);
+
+/**
+ * stmpe_disable - disable blocks on an STMPE device
+ * @stmpe:	Device to work on
+ * @blocks:	Mask of blocks (enum stmpe_block values) to enable
+ */
+int stmpe_disable(struct stmpe *stmpe, unsigned int blocks)
+{
+	return stmpe->variant->enable(stmpe, blocks, false);
+}
+EXPORT_SYMBOL_GPL(stmpe_disable);
+
+/**
+ * stmpe_reg_read() - read a single STMPE register
+ * @stmpe:	Device to read from
+ * @reg:	Register to read
+ */
+int stmpe_reg_read(struct stmpe *stmpe, u8 reg)
+{
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(stmpe->i2c, reg);
+	if (ret < 0)
+		dev_err(stmpe->dev, "failed to read reg %#x: %d\n",
+			reg, ret);
+
+	dev_vdbg(stmpe->dev, "rd: reg %#x => data %#x\n", reg, ret);
+
+	return ret;
+}
+EXPORT_SYMBOL(stmpe_reg_read);
+
+/**
+ * stmpe_reg_write() - write a single STMPE register
+ * @stmpe:	Device to write to
+ * @reg:	Register to write
+ * @val:	Value to write
+ */
+int stmpe_reg_write(struct stmpe *stmpe, u8 reg, u8 val)
+{
+	int ret;
+
+	dev_vdbg(stmpe->dev, "wr: reg %#x <= %#x\n", reg, val);
+
+	ret = i2c_smbus_write_byte_data(stmpe->i2c, reg, val);
+	if (ret < 0)
+		dev_err(stmpe->dev, "failed to write reg %#x: %d\n",
+			reg, ret);
+
+	return ret;
+}
+EXPORT_SYMBOL(stmpe_reg_write);
+
+/**
+ * stmpe_set_bits() - set the value of a bitfield in a STMPE register
+ * @stmpe:	Device to write to
+ * @reg:	Register to write
+ * @mask:	Mask of bits to set
+ * @val:	Value to set
+ */
+int stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val)
+{
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+
+	ret = stmpe_reg_read(stmpe, reg);
+	if (ret < 0)
+		goto out;
+
+	ret &= ~mask;
+	ret |= val;
+
+	ret = stmpe_reg_write(stmpe, reg, ret);
+
+out:
+	mutex_unlock(&stmpe->lock);
+	return ret;
+}
+EXPORT_SYMBOL(stmpe_set_bits);
+
+/**
+ * stmpe_block_read() - read multiple STMPE registers
+ * @stmpe:	Device to read from
+ * @reg:	First register
+ * @length:	Number of registers
+ * @values:	Buffer to write to
+ */
+int stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length, u8 *values)
+{
+	int ret;
+
+	ret = i2c_smbus_read_i2c_block_data(stmpe->i2c, reg, length, values);
+	if (ret < 0)
+		dev_err(stmpe->dev, "failed to read regs %#x: %d\n",
+			reg, ret);
+
+	dev_vdbg(stmpe->dev, "rd: reg %#x (%d) => ret %#x\n", reg, length, ret);
+#ifdef VERBOSE_DEBUG
+	print_hex_dump_bytes("stmpe rd: ", DUMP_PREFIX_OFFSET, values, length);
+#endif
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_block_read);
+
+/**
+ * stmpe_block_write() - write multiple STMPE registers
+ * @stmpe:	Device to write to
+ * @reg:	First register
+ * @length:	Number of registers
+ * @values:	Values to write
+ */
+int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,
+			const u8 *values)
+{
+	int ret;
+
+	dev_vdbg(stmpe->dev, "wr: regs %#x (%d)\n", reg, length);
+#ifdef VERBOSE_DEBUG
+	print_hex_dump_bytes("stmpe wr: ", DUMP_PREFIX_OFFSET, values, length);
+#endif
+
+	ret = i2c_smbus_write_i2c_block_data(stmpe->i2c, reg, length,
+					     values);
+	if (ret < 0)
+		dev_err(stmpe->dev, "failed to write regs %#x: %d\n",
+			reg, ret);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_block_write);
+
+/**
+ * stmpe_set_altfunc: set the alternate function for STMPE pins
+ * @stmpe:	Device to configure
+ * @pins:	Bitmask of pins to affect
+ * @block:	block to enable alternate functions for
+ *
+ * @pins is assumed to have a bit set for each of the bits whose alternate
+ * function is to be changed, numbered according to the GPIOXY numbers.
+ *
+ * If the GPIO module is not enabled, this function automatically enables it in
+ * order to perform the change.
+ */
+int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins, enum stmpe_block block)
+{
+	struct stmpe_variant_info *variant = stmpe->variant;
+	u8 regaddr = stmpe->regs[STMPE_IDX_GPAFR_U_MSB];
+	int af_bits = variant->af_bits;
+	int numregs = DIV_ROUND_UP(stmpe->num_gpios * af_bits, 8);
+	int afperreg = 8 / af_bits;
+	int mask = (1 << af_bits) - 1;
+	u8 regs[numregs];
+	int af;
+	int ret;
+
+	ret = stmpe_enable(stmpe, STMPE_BLOCK_GPIO);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&stmpe->lock);
+
+	ret = stmpe_block_read(stmpe, regaddr, numregs, regs);
+	if (ret < 0)
+		goto out;
+
+	af = variant->get_altfunc(stmpe, block);
+
+	while (pins) {
+		int pin = __ffs(pins);
+		int regoffset = numregs - (pin / afperreg) - 1;
+		int pos = (pin % afperreg) * (8 / afperreg);
+
+		regs[regoffset] &= ~(mask << pos);
+		regs[regoffset] |= af << pos;
+
+		pins &= ~(1 << pin);
+	}
+
+	ret = stmpe_block_write(stmpe, regaddr, numregs, regs);
+
+out:
+	mutex_unlock(&stmpe->lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_set_altfunc);
+
+static irqreturn_t stmpe_irq(int irq, void *data)
+{
+	struct stmpe *stmpe = data;
+	struct stmpe_variant_info *variant = stmpe->variant;
+	int num = DIV_ROUND_UP(variant->num_irqs, 8);
+	u8 israddr = stmpe->regs[STMPE_IDX_ISR_MSB];
+	u8 isr[num];
+	int ret;
+	int i;
+
+	ret = stmpe_block_read(stmpe, israddr, num, isr);
+	if (ret < 0)
+		return IRQ_NONE;
+
+	for (i = 0; i < num; i++) {
+		int bank = num - i - 1;
+		u8 status = isr[i];
+		u8 clear;
+
+		status &= stmpe->ier[bank];
+		if (!status)
+			continue;
+
+		clear = status;
+		while (status) {
+			int bit = __ffs(status);
+			int line = bank * 8 + bit;
+
+			handle_nested_irq(stmpe->irq_base + line);
+			status &= ~(1 << bit);
+		}
+
+		stmpe_reg_write(stmpe, israddr + i, clear);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void stmpe_irq_lock(unsigned int irq)
+{
+	struct stmpe *stmpe = get_irq_chip_data(irq);
+
+	mutex_lock(&stmpe->irq_lock);
+}
+
+static void stmpe_irq_sync_unlock(unsigned int irq)
+{
+	struct stmpe *stmpe = get_irq_chip_data(irq);
+	struct stmpe_variant_info *variant = stmpe->variant;
+	int num = DIV_ROUND_UP(variant->num_irqs, 8);
+	int i;
+
+	for (i = 0; i < num; i++) {
+		u8 new = stmpe->ier[i];
+		u8 old = stmpe->oldier[i];
+
+		if (new == old)
+			continue;
+
+		stmpe->oldier[i] = new;
+		stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_IER_LSB] - i, new);
+	}
+
+	mutex_unlock(&stmpe->irq_lock);
+}
+
+static void stmpe_irq_mask(unsigned int irq)
+{
+	struct stmpe *stmpe = get_irq_chip_data(irq);
+	int offset = irq - stmpe->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	stmpe->ier[regoffset] &= ~mask;
+}
+
+static void stmpe_irq_unmask(unsigned int irq)
+{
+	struct stmpe *stmpe = get_irq_chip_data(irq);
+	int offset = irq - stmpe->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	stmpe->ier[regoffset] |= mask;
+}
+
+static struct irq_chip stmpe_irq_chip = {
+	.name			= "stmpe",
+	.bus_lock		= stmpe_irq_lock,
+	.bus_sync_unlock	= stmpe_irq_sync_unlock,
+	.mask			= stmpe_irq_mask,
+	.unmask			= stmpe_irq_unmask,
+};
+
+static int __devinit stmpe_irq_init(struct stmpe *stmpe)
+{
+	int num_irqs = stmpe->variant->num_irqs;
+	int base = stmpe->irq_base;
+	int irq;
+
+	for (irq = base; irq < base + num_irqs; irq++) {
+		set_irq_chip_data(irq, stmpe);
+		set_irq_chip_and_handler(irq, &stmpe_irq_chip,
+					 handle_edge_irq);
+		set_irq_nested_thread(irq, 1);
+#ifdef CONFIG_ARM
+		set_irq_flags(irq, IRQF_VALID);
+#else
+		set_irq_noprobe(irq);
+#endif
+	}
+
+	return 0;
+}
+
+static void stmpe_irq_remove(struct stmpe *stmpe)
+{
+	int num_irqs = stmpe->variant->num_irqs;
+	int base = stmpe->irq_base;
+	int irq;
+
+	for (irq = base; irq < base + num_irqs; irq++) {
+#ifdef CONFIG_ARM
+		set_irq_flags(irq, 0);
+#endif
+		set_irq_chip_and_handler(irq, NULL, NULL);
+		set_irq_chip_data(irq, NULL);
+	}
+}
+
+static int __devinit stmpe_chip_init(struct stmpe *stmpe)
+{
+	unsigned int irq_trigger = stmpe->pdata->irq_trigger;
+	struct stmpe_variant_info *variant = stmpe->variant;
+	u8 icr = STMPE_ICR_LSB_GIM;
+	unsigned int id;
+	u8 data[2];
+	int ret;
+
+	ret = stmpe_block_read(stmpe, stmpe->regs[STMPE_IDX_CHIP_ID],
+			       ARRAY_SIZE(data), data);
+	if (ret < 0)
+		return ret;
+
+	id = (data[0] << 8) | data[1];
+	if ((id & variant->id_mask) != variant->id_val) {
+		dev_err(stmpe->dev, "unknown chip id: %#x\n", id);
+		return -EINVAL;
+	}
+
+	dev_info(stmpe->dev, "%s detected, chip id: %#x\n", variant->name, id);
+
+	/* Disable all modules -- subdrivers should enable what they need. */
+	ret = stmpe_disable(stmpe, ~0);
+	if (ret)
+		return ret;
+
+	if (irq_trigger == IRQF_TRIGGER_FALLING ||
+	    irq_trigger == IRQF_TRIGGER_RISING)
+		icr |= STMPE_ICR_LSB_EDGE;
+
+	if (irq_trigger == IRQF_TRIGGER_RISING ||
+	    irq_trigger == IRQF_TRIGGER_HIGH)
+		icr |= STMPE_ICR_LSB_HIGH;
+
+	if (stmpe->pdata->irq_invert_polarity)
+		icr ^= STMPE_ICR_LSB_HIGH;
+
+	return stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_ICR_LSB], icr);
+}
+
+static int __devinit stmpe_add_device(struct stmpe *stmpe,
+				      struct mfd_cell *cell, int irq)
+{
+	return mfd_add_devices(stmpe->dev, stmpe->pdata->id, cell, 1,
+			       NULL, stmpe->irq_base + irq);
+}
+
+static int __devinit stmpe_devices_init(struct stmpe *stmpe)
+{
+	struct stmpe_variant_info *variant = stmpe->variant;
+	unsigned int platform_blocks = stmpe->pdata->blocks;
+	int ret = -EINVAL;
+	int i;
+
+	for (i = 0; i < variant->num_blocks; i++) {
+		struct stmpe_variant_block *block = &variant->blocks[i];
+
+		if (!(platform_blocks & block->block))
+			continue;
+
+		platform_blocks &= ~block->block;
+		ret = stmpe_add_device(stmpe, block->cell, block->irq);
+		if (ret)
+			return ret;
+	}
+
+	if (platform_blocks)
+		dev_warn(stmpe->dev,
+			 "platform wants blocks (%#x) not present on variant",
+			 platform_blocks);
+
+	return ret;
+}
+
+static int __devinit stmpe_probe(struct i2c_client *i2c,
+				 const struct i2c_device_id *id)
+{
+	struct stmpe_platform_data *pdata = i2c->dev.platform_data;
+	struct stmpe *stmpe;
+	int ret;
+
+	if (!pdata)
+		return -EINVAL;
+
+	stmpe = kzalloc(sizeof(struct stmpe), GFP_KERNEL);
+	if (!stmpe)
+		return -ENOMEM;
+
+	mutex_init(&stmpe->irq_lock);
+	mutex_init(&stmpe->lock);
+
+	stmpe->dev = &i2c->dev;
+	stmpe->i2c = i2c;
+
+	stmpe->pdata = pdata;
+	stmpe->irq_base = pdata->irq_base;
+
+	stmpe->partnum = id->driver_data;
+	stmpe->variant = stmpe_variant_info[stmpe->partnum];
+	stmpe->regs = stmpe->variant->regs;
+	stmpe->num_gpios = stmpe->variant->num_gpios;
+
+	i2c_set_clientdata(i2c, stmpe);
+
+	ret = stmpe_chip_init(stmpe);
+	if (ret)
+		goto out_free;
+
+	ret = stmpe_irq_init(stmpe);
+	if (ret)
+		goto out_free;
+
+	ret = request_threaded_irq(stmpe->i2c->irq, NULL, stmpe_irq,
+				   pdata->irq_trigger | IRQF_ONESHOT,
+				   "stmpe", stmpe);
+	if (ret) {
+		dev_err(stmpe->dev, "failed to request IRQ: %d\n", ret);
+		goto out_removeirq;
+	}
+
+	ret = stmpe_devices_init(stmpe);
+	if (ret) {
+		dev_err(stmpe->dev, "failed to add children\n");
+		goto out_removedevs;
+	}
+
+	return 0;
+
+out_removedevs:
+	mfd_remove_devices(stmpe->dev);
+	free_irq(stmpe->i2c->irq, stmpe);
+out_removeirq:
+	stmpe_irq_remove(stmpe);
+out_free:
+	kfree(stmpe);
+	return ret;
+}
+
+static int __devexit stmpe_remove(struct i2c_client *client)
+{
+	struct stmpe *stmpe = i2c_get_clientdata(client);
+
+	mfd_remove_devices(stmpe->dev);
+
+	free_irq(stmpe->i2c->irq, stmpe);
+	stmpe_irq_remove(stmpe);
+
+	kfree(stmpe);
+
+	return 0;
+}
+
+static const struct i2c_device_id stmpe_id[] = {
+	{ "stmpe811", STMPE811 },
+	{ "stmpe1601", STMPE1601 },
+	{ "stmpe2401", STMPE2401 },
+	{ "stmpe2403", STMPE2403 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, stmpe_id);
+
+static struct i2c_driver stmpe_driver = {
+	.driver.name	= "stmpe",
+	.driver.owner	= THIS_MODULE,
+	.probe		= stmpe_probe,
+	.remove		= __devexit_p(stmpe_remove),
+	.id_table	= stmpe_id,
+};
+
+static int __init stmpe_init(void)
+{
+	return i2c_add_driver(&stmpe_driver);
+}
+subsys_initcall(stmpe_init);
+
+static void __exit stmpe_exit(void)
+{
+	i2c_del_driver(&stmpe_driver);
+}
+module_exit(stmpe_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("STMPExxxx MFD core driver");
+MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
diff --git a/drivers/mfd/stmpe.h b/drivers/mfd/stmpe.h
new file mode 100644
index 0000000..406aa2b
--- /dev/null
+++ b/drivers/mfd/stmpe.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#ifndef __STMPE_H
+#define __STMPE_H
+
+/**
+ * struct stmpe_variant_block - information about block
+ * @cell:	base mfd cell
+ * @irq:	interrupt number to be added to each IORESOURCE_IRQ
+ *		in the cell
+ * @block:	block id; used for identification with platform data and for
+ *		enable and altfunc callbacks
+ */
+struct stmpe_variant_block {
+	struct mfd_cell		*cell;
+	int			irq;
+	enum stmpe_block	block;
+};
+
+/**
+ * struct stmpe_variant_info - variant-specific information
+ * @name:	part name
+ * @id_val:	content of CHIPID register
+ * @id_mask:	bits valid in CHIPID register for comparison with id_val
+ * @num_gpios:	number of GPIOS
+ * @af_bits:	number of bits used to specify the alternate function
+ * @blocks:	list of blocks present on this device
+ * @num_blocks:	number of blocks present on this device
+ * @num_irqs:	number of internal IRQs available on this device
+ * @enable:	callback to enable the specified blocks
+ * @get_altfunc: calllback to get the alternate function number for the
+ *		 specific block
+ */
+struct stmpe_variant_info {
+	const char *name;
+	u16 id_val;
+	u16 id_mask;
+	int num_gpios;
+	int af_bits;
+	const u8 *regs;
+	struct stmpe_variant_block *blocks;
+	int num_blocks;
+	int num_irqs;
+	int (*enable)(struct stmpe *stmpe, unsigned int blocks, bool enable);
+	int (*get_altfunc)(struct stmpe *stmpe, enum stmpe_block block);
+};
+
+extern struct stmpe_variant_info *stmpe_variant_info[];
+
+#define STMPE_ICR_LSB_HIGH	(1 << 2)
+#define STMPE_ICR_LSB_EDGE	(1 << 1)
+#define STMPE_ICR_LSB_GIM	(1 << 0)
+
+/*
+ * STMPE811
+ */
+
+#define STMPE811_IRQ_TOUCH_DET		0
+#define STMPE811_IRQ_FIFO_TH		1
+#define STMPE811_IRQ_FIFO_OFLOW		2
+#define STMPE811_IRQ_FIFO_FULL		3
+#define STMPE811_IRQ_FIFO_EMPTY		4
+#define STMPE811_IRQ_TEMP_SENS		5
+#define STMPE811_IRQ_ADC		6
+#define STMPE811_IRQ_GPIOC		7
+#define STMPE811_NR_INTERNAL_IRQS	8
+
+#define STMPE811_REG_CHIP_ID		0x00
+#define STMPE811_REG_SYS_CTRL2		0x04
+#define STMPE811_REG_INT_CTRL		0x09
+#define STMPE811_REG_INT_EN		0x0A
+#define STMPE811_REG_INT_STA		0x0B
+#define STMPE811_REG_GPIO_INT_EN	0x0C
+#define STMPE811_REG_GPIO_INT_STA	0x0D
+#define STMPE811_REG_GPIO_SET_PIN	0x10
+#define STMPE811_REG_GPIO_CLR_PIN	0x11
+#define STMPE811_REG_GPIO_MP_STA	0x12
+#define STMPE811_REG_GPIO_DIR		0x13
+#define STMPE811_REG_GPIO_ED		0x14
+#define STMPE811_REG_GPIO_RE		0x15
+#define STMPE811_REG_GPIO_FE		0x16
+#define STMPE811_REG_GPIO_AF		0x17
+
+#define STMPE811_SYS_CTRL2_ADC_OFF	(1 << 0)
+#define STMPE811_SYS_CTRL2_TSC_OFF	(1 << 1)
+#define STMPE811_SYS_CTRL2_GPIO_OFF	(1 << 2)
+#define STMPE811_SYS_CTRL2_TS_OFF	(1 << 3)
+
+/*
+ * STMPE1601
+ */
+
+#define STMPE1601_IRQ_GPIOC		8
+#define STMPE1601_IRQ_PWM3		7
+#define STMPE1601_IRQ_PWM2		6
+#define STMPE1601_IRQ_PWM1		5
+#define STMPE1601_IRQ_PWM0		4
+#define STMPE1601_IRQ_KEYPAD_OVER	2
+#define STMPE1601_IRQ_KEYPAD		1
+#define STMPE1601_IRQ_WAKEUP		0
+#define STMPE1601_NR_INTERNAL_IRQS	9
+
+#define STMPE1601_REG_SYS_CTRL			0x02
+#define STMPE1601_REG_ICR_LSB			0x11
+#define STMPE1601_REG_IER_LSB			0x13
+#define STMPE1601_REG_ISR_MSB			0x14
+#define STMPE1601_REG_CHIP_ID			0x80
+#define STMPE1601_REG_INT_EN_GPIO_MASK_LSB	0x17
+#define STMPE1601_REG_INT_STA_GPIO_MSB		0x18
+#define STMPE1601_REG_GPIO_MP_LSB		0x87
+#define STMPE1601_REG_GPIO_SET_LSB		0x83
+#define STMPE1601_REG_GPIO_CLR_LSB		0x85
+#define STMPE1601_REG_GPIO_SET_DIR_LSB		0x89
+#define STMPE1601_REG_GPIO_ED_MSB		0x8A
+#define STMPE1601_REG_GPIO_RE_LSB		0x8D
+#define STMPE1601_REG_GPIO_FE_LSB		0x8F
+#define STMPE1601_REG_GPIO_AF_U_MSB		0x92
+
+#define STMPE1601_SYS_CTRL_ENABLE_GPIO		(1 << 3)
+#define STMPE1601_SYS_CTRL_ENABLE_KPC		(1 << 1)
+#define STMPE1601_SYSCON_ENABLE_SPWM		(1 << 0)
+
+/*
+ * STMPE24xx
+ */
+
+#define STMPE24XX_IRQ_GPIOC		8
+#define STMPE24XX_IRQ_PWM2		7
+#define STMPE24XX_IRQ_PWM1		6
+#define STMPE24XX_IRQ_PWM0		5
+#define STMPE24XX_IRQ_ROT_OVER		4
+#define STMPE24XX_IRQ_ROT		3
+#define STMPE24XX_IRQ_KEYPAD_OVER	2
+#define STMPE24XX_IRQ_KEYPAD		1
+#define STMPE24XX_IRQ_WAKEUP		0
+#define STMPE24XX_NR_INTERNAL_IRQS	9
+
+#define STMPE24XX_REG_SYS_CTRL		0x02
+#define STMPE24XX_REG_ICR_LSB		0x11
+#define STMPE24XX_REG_IER_LSB		0x13
+#define STMPE24XX_REG_ISR_MSB		0x14
+#define STMPE24XX_REG_CHIP_ID		0x80
+#define STMPE24XX_REG_IEGPIOR_LSB	0x18
+#define STMPE24XX_REG_ISGPIOR_MSB	0x19
+#define STMPE24XX_REG_GPMR_LSB		0xA5
+#define STMPE24XX_REG_GPSR_LSB		0x85
+#define STMPE24XX_REG_GPCR_LSB		0x88
+#define STMPE24XX_REG_GPDR_LSB		0x8B
+#define STMPE24XX_REG_GPEDR_MSB		0x8C
+#define STMPE24XX_REG_GPRER_LSB		0x91
+#define STMPE24XX_REG_GPFER_LSB		0x94
+#define STMPE24XX_REG_GPAFR_U_MSB	0x9B
+
+#define STMPE24XX_SYS_CTRL_ENABLE_GPIO		(1 << 3)
+#define STMPE24XX_SYSCON_ENABLE_PWM		(1 << 2)
+#define STMPE24XX_SYS_CTRL_ENABLE_KPC		(1 << 1)
+#define STMPE24XX_SYSCON_ENABLE_ROT		(1 << 0)
+
+#endif
diff --git a/include/linux/mfd/stmpe.h b/include/linux/mfd/stmpe.h
new file mode 100644
index 0000000..cf2155f
--- /dev/null
+++ b/include/linux/mfd/stmpe.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#ifndef __LINUX_MFD_STMPE_H
+#define __LINUX_MFD_STMPE_H
+
+#include <linux/device.h>
+
+enum stmpe_block {
+	STMPE_BLOCK_GPIO	= 1 << 0,
+	STMPE_BLOCK_KEYPAD	= 1 << 1,
+	STMPE_BLOCK_TOUCHSCREEN	= 1 << 2,
+	STMPE_BLOCK_ADC		= 1 << 3,
+	STMPE_BLOCK_PWM		= 1 << 4,
+	STMPE_BLOCK_ROTATOR	= 1 << 5,
+};
+
+enum stmpe_partnum {
+	STMPE811,
+	STMPE1601,
+	STMPE2401,
+	STMPE2403,
+};
+
+/*
+ * For registers whose locations differ on variants,  the correct address is
+ * obtained by indexing stmpe->regs with one of the following.
+ */
+enum {
+	STMPE_IDX_CHIP_ID,
+	STMPE_IDX_ICR_LSB,
+	STMPE_IDX_IER_LSB,
+	STMPE_IDX_ISR_MSB,
+	STMPE_IDX_GPMR_LSB,
+	STMPE_IDX_GPSR_LSB,
+	STMPE_IDX_GPCR_LSB,
+	STMPE_IDX_GPDR_LSB,
+	STMPE_IDX_GPEDR_MSB,
+	STMPE_IDX_GPRER_LSB,
+	STMPE_IDX_GPFER_LSB,
+	STMPE_IDX_GPAFR_U_MSB,
+	STMPE_IDX_IEGPIOR_LSB,
+	STMPE_IDX_ISGPIOR_MSB,
+	STMPE_IDX_MAX,
+};
+
+
+struct stmpe_variant_info;
+
+/**
+ * struct stmpe - STMPE MFD structure
+ * @lock: lock protecting I/O operations
+ * @irq_lock: IRQ bus lock
+ * @dev: device, mostly for dev_dbg()
+ * @i2c: i2c client
+ * @variant: the detected STMPExxxx model number
+ * @regs: list of addresses of registers which are at different addresses on
+ *	  different variants.  Indexed by one of STMPE_IDX_*.
+ * @irq_base: starting IRQ number for internal IRQs
+ * @num_gpios: number of gpios, differs for variants
+ * @ier: cache of IER registers for bus_lock
+ * @oldier: cache of IER registers for bus_lock
+ * @pdata: platform data
+ */
+struct stmpe {
+	struct mutex lock;
+	struct mutex irq_lock;
+	struct device *dev;
+	struct i2c_client *i2c;
+	enum stmpe_partnum partnum;
+	struct stmpe_variant_info *variant;
+	const u8 *regs;
+
+	int irq_base;
+	int num_gpios;
+	u8 ier[2];
+	u8 oldier[2];
+	struct stmpe_platform_data *pdata;
+};
+
+extern int stmpe_reg_write(struct stmpe *stmpe, u8 reg, u8 data);
+extern int stmpe_reg_read(struct stmpe *stmpe, u8 reg);
+extern int stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length,
+			    u8 *values);
+extern int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,
+			     const u8 *values);
+extern int stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val);
+extern int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins,
+			     enum stmpe_block block);
+extern int stmpe_enable(struct stmpe *stmpe, unsigned int blocks);
+extern int stmpe_disable(struct stmpe *stmpe, unsigned int blocks);
+
+struct matrix_keymap_data;
+
+/**
+ * struct stmpe_keypad_platform_data - STMPE keypad platform data
+ * @keymap_data: key map table and size
+ * @debounce_ms: debounce interval, in ms.  Maximum is
+ *		 %STMPE_KEYPAD_MAX_DEBOUNCE.
+ * @scan_count: number of key scanning cycles to confirm key data.
+ *		Maximum is %STMPE_KEYPAD_MAX_SCAN_COUNT.
+ * @no_autorepeat: disable key autorepeat
+ */
+struct stmpe_keypad_platform_data {
+	struct matrix_keymap_data *keymap_data;
+	unsigned int debounce_ms;
+	unsigned int scan_count;
+	bool no_autorepeat;
+};
+
+/**
+ * struct stmpe_gpio_platform_data - STMPE GPIO platform data
+ * @gpio_base: first gpio number assigned.  A maximum of
+ *	       %STMPE_NR_GPIOS GPIOs will be allocated.
+ */
+struct stmpe_gpio_platform_data {
+	int gpio_base;
+	void (*setup)(struct stmpe *stmpe, unsigned gpio_base);
+	void (*remove)(struct stmpe *stmpe, unsigned gpio_base);
+};
+
+/**
+ * struct stmpe_platform_data - STMPE platform data
+ * @id: device id to distinguish between multiple STMPEs on the same board
+ * @blocks: bitmask of blocks to enable (use STMPE_BLOCK_*)
+ * @irq_trigger: IRQ trigger to use for the interrupt to the host
+ * @irq_invert_polarity: IRQ line is connected with reversed polarity
+ * @irq_base: base IRQ number.  %STMPE_NR_IRQS irqs will be used, or
+ *	      %STMPE_NR_INTERNAL_IRQS if the GPIO driver is not used.
+ * @gpio: GPIO-specific platform data
+ * @keypad: keypad-specific platform data
+ */
+struct stmpe_platform_data {
+	int id;
+	unsigned int blocks;
+	int irq_base;
+	unsigned int irq_trigger;
+	bool irq_invert_polarity;
+
+	struct stmpe_gpio_platform_data *gpio;
+	struct stmpe_keypad_platform_data *keypad;
+};
+
+#define STMPE_NR_INTERNAL_IRQS	9
+#define STMPE_INT_GPIO(x)	(STMPE_NR_INTERNAL_IRQS + (x))
+
+#define STMPE_NR_GPIOS		24
+#define STMPE_NR_IRQS		STMPE_INT_GPIO(STMPE_NR_GPIOS)
+
+#endif
-- 
1.7.0


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCHv2 2/3] gpio: add STMPE GPIO driver
  2010-06-21 15:45     ` Luotao Fu
  2010-06-22 13:55       ` [PATCHv2 1/3] mfd: add STMPE " Rabin Vincent
@ 2010-06-22 13:55       ` Rabin Vincent
  2010-06-22 13:55       ` [PATCHv2 3/3] input: add STMPE keypad driver Rabin Vincent
  2010-06-22 13:56       ` [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Rabin VINCENT
  3 siblings, 0 replies; 61+ messages in thread
From: Rabin Vincent @ 2010-06-22 13:55 UTC (permalink / raw)
  To: linux-kernel
  Cc: Samuel Ortiz, STEricsson_nomadik_linux, linus.walleij, l.fu,
	Rabin Vincent

Add support for the GPIOs on STMPE I/O Expanders.

Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
---
 drivers/gpio/Kconfig      |    7 +
 drivers/gpio/Makefile     |    1 +
 drivers/gpio/stmpe-gpio.c |  390 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 398 insertions(+), 0 deletions(-)
 create mode 100644 drivers/gpio/stmpe-gpio.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 724038d..27e5f8e 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -195,6 +195,13 @@ config GPIO_PCF857X
 	  This driver provides an in-kernel interface to those GPIOs using
 	  platform-neutral GPIO calls.
 
+config GPIO_STMPE
+	bool "STMPE GPIOs"
+	depends on MFD_STMPE
+	help
+	  This enables support for the GPIOs found on the STMPE I/O
+	  Expanders.
+
 config GPIO_TC35892
 	bool "TC35892 GPIOs"
 	depends on MFD_TC35892
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 51c3cdd..a9b93c3 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_GPIO_MCP23S08)	+= mcp23s08.o
 obj-$(CONFIG_GPIO_PCA953X)	+= pca953x.o
 obj-$(CONFIG_GPIO_PCF857X)	+= pcf857x.o
 obj-$(CONFIG_GPIO_PL061)	+= pl061.o
+obj-$(CONFIG_GPIO_STMPE)	+= stmpe-gpio.o
 obj-$(CONFIG_GPIO_TC35892)	+= tc35892-gpio.o
 obj-$(CONFIG_GPIO_TIMBERDALE)	+= timbgpio.o
 obj-$(CONFIG_GPIO_TWL4030)	+= twl4030-gpio.o
diff --git a/drivers/gpio/stmpe-gpio.c b/drivers/gpio/stmpe-gpio.c
new file mode 100644
index 0000000..bd49a3a
--- /dev/null
+++ b/drivers/gpio/stmpe-gpio.c
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/stmpe.h>
+
+/*
+ * These registers are modified under the irq bus lock and cached to avoid
+ * unnecessary writes in bus_sync_unlock.
+ */
+enum { REG_RE, REG_FE, REG_IE };
+
+#define CACHE_NR_REGS	3
+#define CACHE_NR_BANKS	(STMPE_NR_GPIOS / 8)
+
+struct stmpe_gpio {
+	struct gpio_chip chip;
+	struct stmpe *stmpe;
+	struct device *dev;
+	struct mutex irq_lock;
+
+	int irq_base;
+
+	/* Caches of interrupt control registers for bus_lock */
+	u8 regs[CACHE_NR_REGS][CACHE_NR_BANKS];
+	u8 oldregs[CACHE_NR_REGS][CACHE_NR_BANKS];
+};
+
+static inline struct stmpe_gpio *to_stmpe_gpio(struct gpio_chip *chip)
+{
+	return container_of(chip, struct stmpe_gpio, chip);
+}
+
+static int stmpe_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	u8 reg = stmpe->regs[STMPE_IDX_GPMR_LSB] - (offset / 8);
+	u8 mask = 1 << (offset % 8);
+	int ret;
+
+	ret = stmpe_reg_read(stmpe, reg);
+	if (ret < 0)
+		return ret;
+
+	return ret & mask;
+}
+
+static void stmpe_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	int which = val ? STMPE_IDX_GPSR_LSB : STMPE_IDX_GPCR_LSB;
+	u8 reg = stmpe->regs[which] - (offset / 8);
+	u8 mask = 1 << (offset % 8);
+
+	stmpe_reg_write(stmpe, reg, mask);
+}
+
+static int stmpe_gpio_direction_output(struct gpio_chip *chip,
+					 unsigned offset, int val)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	u8 reg = stmpe->regs[STMPE_IDX_GPDR_LSB] - (offset / 8);
+	u8 mask = 1 << (offset % 8);
+
+	stmpe_gpio_set(chip, offset, val);
+
+	return stmpe_set_bits(stmpe, reg, mask, mask);
+}
+
+static int stmpe_gpio_direction_input(struct gpio_chip *chip,
+					unsigned offset)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	u8 reg = stmpe->regs[STMPE_IDX_GPDR_LSB] - (offset / 8);
+	u8 mask = 1 << (offset % 8);
+
+	return stmpe_set_bits(stmpe, reg, mask, mask);
+}
+
+static int stmpe_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+
+	return stmpe_gpio->irq_base + offset;
+}
+
+static struct gpio_chip template_chip = {
+	.label			= "stmpe",
+	.owner			= THIS_MODULE,
+	.direction_input	= stmpe_gpio_direction_input,
+	.get			= stmpe_gpio_get,
+	.direction_output	= stmpe_gpio_direction_output,
+	.set			= stmpe_gpio_set,
+	.to_irq			= stmpe_gpio_to_irq,
+	.can_sleep		= 1,
+};
+
+static int stmpe_gpio_irq_set_type(unsigned int irq, unsigned int type)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+	int offset = irq - stmpe_gpio->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH)
+		return -EINVAL;
+
+	if (type == IRQ_TYPE_EDGE_RISING)
+		stmpe_gpio->regs[REG_RE][regoffset] |= mask;
+	else
+		stmpe_gpio->regs[REG_RE][regoffset] &= ~mask;
+
+	if (type == IRQ_TYPE_EDGE_FALLING)
+		stmpe_gpio->regs[REG_FE][regoffset] |= mask;
+	else
+		stmpe_gpio->regs[REG_FE][regoffset] &= ~mask;
+
+	return 0;
+}
+
+static void stmpe_gpio_irq_lock(unsigned int irq)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+
+	mutex_lock(&stmpe_gpio->irq_lock);
+}
+
+static void stmpe_gpio_irq_sync_unlock(unsigned int irq)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	int num_banks = DIV_ROUND_UP(stmpe->num_gpios, 8);
+	static const u8 regmap[] = {
+		[REG_RE]	= STMPE_IDX_GPRER_LSB,
+		[REG_FE]	= STMPE_IDX_GPFER_LSB,
+		[REG_IE]	= STMPE_IDX_IEGPIOR_LSB,
+	};
+	int i, j;
+
+	for (i = 0; i < CACHE_NR_REGS; i++) {
+		for (j = 0; j < num_banks; j++) {
+			u8 old = stmpe_gpio->oldregs[i][j];
+			u8 new = stmpe_gpio->regs[i][j];
+
+			if (new == old)
+				continue;
+
+			stmpe_gpio->oldregs[i][j] = new;
+			stmpe_reg_write(stmpe, stmpe->regs[regmap[i]] - j, new);
+		}
+	}
+
+	mutex_unlock(&stmpe_gpio->irq_lock);
+}
+
+static void stmpe_gpio_irq_mask(unsigned int irq)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+	int offset = irq - stmpe_gpio->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	stmpe_gpio->regs[REG_IE][regoffset] &= ~mask;
+}
+
+static void stmpe_gpio_irq_unmask(unsigned int irq)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+	int offset = irq - stmpe_gpio->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	stmpe_gpio->regs[REG_IE][regoffset] |= mask;
+}
+
+static struct irq_chip stmpe_gpio_irq_chip = {
+	.name			= "stmpe-gpio",
+	.bus_lock		= stmpe_gpio_irq_lock,
+	.bus_sync_unlock	= stmpe_gpio_irq_sync_unlock,
+	.mask			= stmpe_gpio_irq_mask,
+	.unmask			= stmpe_gpio_irq_unmask,
+	.set_type		= stmpe_gpio_irq_set_type,
+};
+
+static irqreturn_t stmpe_gpio_irq(int irq, void *dev)
+{
+	struct stmpe_gpio *stmpe_gpio = dev;
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	u8 statmsbreg = stmpe->regs[STMPE_IDX_ISGPIOR_MSB];
+	int num_banks = DIV_ROUND_UP(stmpe->num_gpios, 8);
+	u8 status[num_banks];
+	int ret;
+	int i;
+
+	ret = stmpe_block_read(stmpe, statmsbreg, num_banks, status);
+	if (ret < 0)
+		return IRQ_NONE;
+
+	for (i = 0; i < num_banks; i++) {
+		int bank = num_banks - i - 1;
+		unsigned int enabled = stmpe_gpio->regs[REG_IE][bank];
+		unsigned int stat = status[i];
+
+		stat &= enabled;
+		if (!stat)
+			continue;
+
+		while (stat) {
+			int bit = __ffs(stat);
+			int line = bank * 8 + bit;
+
+			handle_nested_irq(stmpe_gpio->irq_base + line);
+			stat &= ~(1 << bit);
+		}
+
+		stmpe_reg_write(stmpe, statmsbreg + i, status[i]);
+		stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_GPEDR_MSB] + i,
+				status[i]);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit stmpe_gpio_irq_init(struct stmpe_gpio *stmpe_gpio)
+{
+	int base = stmpe_gpio->irq_base;
+	int irq;
+
+	for (irq = base; irq < base + stmpe_gpio->chip.ngpio; irq++) {
+		set_irq_chip_data(irq, stmpe_gpio);
+		set_irq_chip_and_handler(irq, &stmpe_gpio_irq_chip,
+					 handle_simple_irq);
+		set_irq_nested_thread(irq, 1);
+#ifdef CONFIG_ARM
+		set_irq_flags(irq, IRQF_VALID);
+#else
+		set_irq_noprobe(irq);
+#endif
+	}
+
+	return 0;
+}
+
+static void stmpe_gpio_irq_remove(struct stmpe_gpio *stmpe_gpio)
+{
+	int base = stmpe_gpio->irq_base;
+	int irq;
+
+	for (irq = base; irq < base + stmpe_gpio->chip.ngpio; irq++) {
+#ifdef CONFIG_ARM
+		set_irq_flags(irq, 0);
+#endif
+		set_irq_chip_and_handler(irq, NULL, NULL);
+		set_irq_chip_data(irq, NULL);
+	}
+}
+
+static int __devinit stmpe_gpio_probe(struct platform_device *pdev)
+{
+	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+	struct stmpe_gpio_platform_data *pdata;
+	struct stmpe_gpio *stmpe_gpio;
+	int ret;
+	int irq;
+
+	pdata = stmpe->pdata->gpio;
+	if (!pdata)
+		return -ENODEV;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	stmpe_gpio = kzalloc(sizeof(struct stmpe_gpio), GFP_KERNEL);
+	if (!stmpe_gpio)
+		return -ENOMEM;
+
+	mutex_init(&stmpe_gpio->irq_lock);
+
+	stmpe_gpio->dev = &pdev->dev;
+	stmpe_gpio->stmpe = stmpe;
+
+	stmpe_gpio->chip = template_chip;
+	stmpe_gpio->chip.ngpio = stmpe->num_gpios;
+	stmpe_gpio->chip.dev = &pdev->dev;
+	stmpe_gpio->chip.base = pdata ? pdata->gpio_base : -1;
+
+	stmpe_gpio->irq_base = stmpe->irq_base + STMPE_INT_GPIO(0);
+
+	ret = stmpe_enable(stmpe, STMPE_BLOCK_GPIO);
+	if (ret)
+		return ret;
+
+	ret = stmpe_gpio_irq_init(stmpe_gpio);
+	if (ret)
+		goto out_free;
+
+	ret = request_threaded_irq(irq, NULL, stmpe_gpio_irq, IRQF_ONESHOT,
+				   "stmpe-gpio", stmpe_gpio);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
+		goto out_removeirq;
+	}
+
+	ret = gpiochip_add(&stmpe_gpio->chip);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to add gpiochip: %d\n", ret);
+		goto out_freeirq;
+	}
+
+	if (pdata && pdata->setup)
+		pdata->setup(stmpe, stmpe_gpio->chip.base);
+
+	platform_set_drvdata(pdev, stmpe_gpio);
+
+	return 0;
+
+out_freeirq:
+	free_irq(irq, stmpe_gpio);
+out_removeirq:
+	stmpe_gpio_irq_remove(stmpe_gpio);
+out_free:
+	kfree(stmpe_gpio);
+	return ret;
+}
+
+static int __devexit stmpe_gpio_remove(struct platform_device *pdev)
+{
+	struct stmpe_gpio *stmpe_gpio = platform_get_drvdata(pdev);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	struct stmpe_gpio_platform_data *pdata = stmpe->pdata->gpio;
+	int irq = platform_get_irq(pdev, 0);
+	int ret;
+
+	if (pdata && pdata->remove)
+		pdata->remove(stmpe, stmpe_gpio->chip.base);
+
+	ret = gpiochip_remove(&stmpe_gpio->chip);
+	if (ret < 0) {
+		dev_err(stmpe_gpio->dev,
+			"unable to remove gpiochip: %d\n", ret);
+		return ret;
+	}
+
+	stmpe_disable(stmpe, STMPE_BLOCK_GPIO);
+
+	free_irq(irq, stmpe_gpio);
+	stmpe_gpio_irq_remove(stmpe_gpio);
+	platform_set_drvdata(pdev, NULL);
+	kfree(stmpe_gpio);
+
+	return 0;
+}
+
+static struct platform_driver stmpe_gpio_driver = {
+	.driver.name	= "stmpe-gpio",
+	.driver.owner	= THIS_MODULE,
+	.probe		= stmpe_gpio_probe,
+	.remove		= __devexit_p(stmpe_gpio_remove),
+};
+
+static int __init stmpe_gpio_init(void)
+{
+	return platform_driver_register(&stmpe_gpio_driver);
+}
+subsys_initcall(stmpe_gpio_init);
+
+static void __exit stmpe_gpio_exit(void)
+{
+	platform_driver_unregister(&stmpe_gpio_driver);
+}
+module_exit(stmpe_gpio_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("STMPExxxx GPIO driver");
+MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
-- 
1.7.0


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCHv2 3/3] input: add STMPE keypad driver
  2010-06-21 15:45     ` Luotao Fu
  2010-06-22 13:55       ` [PATCHv2 1/3] mfd: add STMPE " Rabin Vincent
  2010-06-22 13:55       ` [PATCHv2 2/3] gpio: add STMPE GPIO driver Rabin Vincent
@ 2010-06-22 13:55       ` Rabin Vincent
  2010-06-22 13:56       ` [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Rabin VINCENT
  3 siblings, 0 replies; 61+ messages in thread
From: Rabin Vincent @ 2010-06-22 13:55 UTC (permalink / raw)
  To: linux-kernel
  Cc: Samuel Ortiz, STEricsson_nomadik_linux, linus.walleij, l.fu,
	Rabin Vincent

Add an input driver for the keypad on STMPE I/O expanders.  This driver
uses the common support provided by the STMPE MFD driver.

Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
---
 drivers/input/keyboard/Kconfig        |   10 +
 drivers/input/keyboard/Makefile       |    1 +
 drivers/input/keyboard/stmpe-keypad.c |  386 +++++++++++++++++++++++++++++++++
 3 files changed, 397 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/stmpe-keypad.c

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index d8fa5d7..699aff9 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -374,6 +374,16 @@ config KEYBOARD_SH_KEYSC
 	  To compile this driver as a module, choose M here: the
 	  module will be called sh_keysc.
 
+config KEYBOARD_STMPE
+	tristate "STMPE keypad support"
+	depends on MFD_STMPE
+	help
+	  Say Y here if you want to use the keypad controller on STMPE I/O
+	  expanders.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called stmpe-keypad.
+
 config KEYBOARD_DAVINCI
 	tristate "TI DaVinci Key Scan"
 	depends on ARCH_DAVINCI_DM365
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 4596d0c..af24b07 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
 obj-$(CONFIG_KEYBOARD_PXA930_ROTARY)	+= pxa930_rotary.o
 obj-$(CONFIG_KEYBOARD_QT2160)		+= qt2160.o
 obj-$(CONFIG_KEYBOARD_SH_KEYSC)		+= sh_keysc.o
+obj-$(CONFIG_KEYBOARD_STMPE)		+= stmpe-keypad.o
 obj-$(CONFIG_KEYBOARD_STOWAWAY)		+= stowaway.o
 obj-$(CONFIG_KEYBOARD_SUNKBD)		+= sunkbd.o
 obj-$(CONFIG_KEYBOARD_TWL4030)		+= twl4030_keypad.o
diff --git a/drivers/input/keyboard/stmpe-keypad.c b/drivers/input/keyboard/stmpe-keypad.c
new file mode 100644
index 0000000..ab7610c
--- /dev/null
+++ b/drivers/input/keyboard/stmpe-keypad.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/mfd/stmpe.h>
+
+/* These are at the same addresses in all STMPE variants */
+#define STMPE_KPC_COL			0x60
+#define STMPE_KPC_ROW_MSB		0x61
+#define STMPE_KPC_ROW_LSB		0x62
+#define STMPE_KPC_CTRL_MSB		0x63
+#define STMPE_KPC_CTRL_LSB		0x64
+#define STMPE_KPC_COMBI_KEY_0		0x65
+#define STMPE_KPC_COMBI_KEY_1		0x66
+#define STMPE_KPC_COMBI_KEY_2		0x67
+#define STMPE_KPC_DATA_BYTE0		0x68
+#define STMPE_KPC_DATA_BYTE1		0x69
+#define STMPE_KPC_DATA_BYTE2		0x6a
+#define STMPE_KPC_DATA_BYTE3		0x6b
+#define STMPE_KPC_DATA_BYTE4		0x6c
+
+#define STMPE_KPC_CTRL_LSB_SCAN		(0x1 << 0)
+#define STMPE_KPC_CTRL_LSB_DEBOUNCE	(0x7f << 1)
+#define STMPE_KPC_CTRL_MSB_SCAN_COUNT	(0xf << 4)
+
+#define STMPE_KPC_ROW_MSB_ROWS		0xff
+
+#define STMPE_KPC_DATA_UP		(0x1 << 7)
+#define STMPE_KPC_DATA_ROW		(0xf << 3)
+#define STMPE_KPC_DATA_COL		(0x7 << 0)
+#define STMPE_KPC_DATA_NOKEY_MASK	0x78
+
+#define STMPE_KEYPAD_MAX_DEBOUNCE	127
+#define STMPE_KEYPAD_MAX_SCAN_COUNT	15
+
+#define STMPE_KEYPAD_MAX_ROWS		8
+#define STMPE_KEYPAD_MAX_COLS		8
+#define STMPE_KEYPAD_ROW_SHIFT		3
+#define STMPE_KEYPAD_KEYMAP_SIZE	\
+	(STMPE_KEYPAD_MAX_ROWS * STMPE_KEYPAD_MAX_COLS)
+
+/**
+ * struct stmpe_keypad_variant - model-specific attributes
+ * @auto_increment: whether the KPC_DATA_BYTE register address
+ *		    auto-increments on multiple read
+ * @num_data: number of data bytes
+ * @num_normal_data: number of normal keys' data bytes
+ * @max_cols: maximum number of columns supported
+ * @max_rows: maximum number of rows supported
+ * @col_gpios: bitmask of gpios which can be used for columns
+ * @row_gpios: bitmask of gpios which can be used for rows
+ */
+struct stmpe_keypad_variant {
+	bool		auto_increment;
+	int		num_data;
+	int		num_normal_data;
+	int		max_cols;
+	int		max_rows;
+	unsigned int	col_gpios;
+	unsigned int	row_gpios;
+};
+
+static const struct stmpe_keypad_variant stmpe_keypad_variants[] = {
+	[STMPE1601] = {
+		.auto_increment		= true,
+		.num_data		= 5,
+		.num_normal_data	= 3,
+		.max_cols		= 8,
+		.max_rows		= 8,
+		.col_gpios		= 0x000ff,	/* GPIO 0 - 7 */
+		.row_gpios		= 0x0ff00,	/* GPIO 8 - 15 */
+	},
+	[STMPE2401] = {
+		.auto_increment		= false,
+		.num_data		= 3,
+		.num_normal_data	= 2,
+		.max_cols		= 8,
+		.max_rows		= 12,
+		.col_gpios		= 0x0000ff,	/* GPIO 0 - 7*/
+		.row_gpios		= 0x1fef00,	/* GPIO 8-14, 16-20 */
+	},
+	[STMPE2403] = {
+		.auto_increment		= true,
+		.num_data		= 5,
+		.num_normal_data	= 3,
+		.max_cols		= 8,
+		.max_rows		= 12,
+		.col_gpios		= 0x0000ff,	/* GPIO 0 - 7*/
+		.row_gpios		= 0x1fef00,	/* GPIO 8-14, 16-20 */
+	},
+};
+
+struct stmpe_keypad {
+	struct stmpe *stmpe;
+	struct input_dev *input;
+	const struct stmpe_keypad_variant *variant;
+	const struct stmpe_keypad_platform_data *plat;
+
+	unsigned int rows;
+	unsigned int cols;
+
+	unsigned short keymap[STMPE_KEYPAD_KEYMAP_SIZE];
+};
+
+static int stmpe_keypad_read_data(struct stmpe_keypad *keypad, u8 *data)
+{
+	const struct stmpe_keypad_variant *variant = keypad->variant;
+	struct stmpe *stmpe = keypad->stmpe;
+	int ret;
+	int i;
+
+	if (variant->auto_increment)
+		return stmpe_block_read(stmpe, STMPE_KPC_DATA_BYTE0,
+					variant->num_data, data);
+
+	for (i = 0; i < variant->num_data; i++) {
+		ret = stmpe_reg_read(stmpe, STMPE_KPC_DATA_BYTE0 + i);
+		if (ret < 0)
+			return ret;
+
+		data[i] = ret;
+	}
+
+	return 0;
+}
+
+static irqreturn_t stmpe_keypad_irq(int irq, void *dev)
+{
+	struct stmpe_keypad *keypad = dev;
+	struct input_dev *input = keypad->input;
+	const struct stmpe_keypad_variant *variant = keypad->variant;
+	u8 fifo[variant->num_data];
+	int ret;
+	int i;
+
+	ret = stmpe_keypad_read_data(keypad, fifo);
+	if (ret < 0)
+		return IRQ_NONE;
+
+	for (i = 0; i < variant->num_normal_data; i++) {
+		u8 data = fifo[i];
+		int row = (data & STMPE_KPC_DATA_ROW) >> 3;
+		int col = data & STMPE_KPC_DATA_COL;
+		int code = MATRIX_SCAN_CODE(row, col, STMPE_KEYPAD_ROW_SHIFT);
+		bool up = data & STMPE_KPC_DATA_UP;
+
+		if ((data & STMPE_KPC_DATA_NOKEY_MASK)
+			== STMPE_KPC_DATA_NOKEY_MASK)
+			continue;
+
+		input_event(input, EV_MSC, MSC_SCAN, code);
+		input_report_key(input, keypad->keymap[code], !up);
+		input_sync(input);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit stmpe_keypad_altfunc_init(struct stmpe_keypad *keypad)
+{
+	const struct stmpe_keypad_variant *variant = keypad->variant;
+	unsigned int col_gpios = variant->col_gpios;
+	unsigned int row_gpios = variant->row_gpios;
+	struct stmpe *stmpe = keypad->stmpe;
+	unsigned int pins = 0;
+	int i;
+
+	/*
+	 * Figure out which pins need to be set to the keypad alternate
+	 * function.
+	 *
+	 * {cols,rows}_gpios are bitmasks of which pins on the chip can be used
+	 * for the keypad.
+	 *
+	 * keypad->{cols,rows} are a bitmask of which pins (of the ones useable
+	 * for the keypad) are used on the board.
+	 */
+
+	for (i = 0; i < variant->max_cols; i++) {
+		int num = __ffs(col_gpios);
+
+		if (keypad->cols & (1 << i))
+			pins |= 1 << num;
+
+		col_gpios &= ~(1 << num);
+	}
+
+	for (i = 0; i < variant->max_rows; i++) {
+		int num = __ffs(row_gpios);
+
+		if (keypad->rows & (1 << i))
+			pins |= 1 << num;
+
+		row_gpios &= ~(1 << num);
+	}
+
+	return stmpe_set_altfunc(stmpe, pins, STMPE_BLOCK_KEYPAD);
+}
+
+static int __devinit stmpe_keypad_chip_init(struct stmpe_keypad *keypad)
+{
+	const struct stmpe_keypad_platform_data *plat = keypad->plat;
+	const struct stmpe_keypad_variant *variant = keypad->variant;
+	struct stmpe *stmpe = keypad->stmpe;
+	int ret;
+
+	if (plat->debounce_ms > STMPE_KEYPAD_MAX_DEBOUNCE)
+		return -EINVAL;
+
+	if (plat->scan_count > STMPE_KEYPAD_MAX_SCAN_COUNT)
+		return -EINVAL;
+
+	ret = stmpe_enable(stmpe, STMPE_BLOCK_KEYPAD);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_keypad_altfunc_init(keypad);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_reg_write(stmpe, STMPE_KPC_COL, keypad->cols);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_reg_write(stmpe, STMPE_KPC_ROW_LSB, keypad->rows);
+	if (ret < 0)
+		return ret;
+
+	if (variant->max_rows > 8) {
+		ret = stmpe_set_bits(stmpe, STMPE_KPC_ROW_MSB,
+				     STMPE_KPC_ROW_MSB_ROWS,
+				     keypad->rows >> 8);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_KPC_CTRL_MSB,
+			     STMPE_KPC_CTRL_MSB_SCAN_COUNT,
+			     plat->scan_count << 4);
+	if (ret < 0)
+		return ret;
+
+	return stmpe_set_bits(stmpe, STMPE_KPC_CTRL_LSB,
+			      STMPE_KPC_CTRL_LSB_SCAN |
+			      STMPE_KPC_CTRL_LSB_DEBOUNCE,
+			      STMPE_KPC_CTRL_LSB_SCAN |
+			      (plat->debounce_ms << 1));
+}
+
+static int __devinit stmpe_keypad_probe(struct platform_device *pdev)
+{
+	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+	struct stmpe_keypad_platform_data *plat;
+	struct stmpe_keypad *keypad;
+	struct input_dev *input;
+	int ret;
+	int irq;
+	int i;
+
+	plat = stmpe->pdata->keypad;
+	if (!plat)
+		return -ENODEV;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	keypad = kzalloc(sizeof(struct stmpe_keypad), GFP_KERNEL);
+	if (!keypad)
+		return -ENOMEM;
+
+	input = input_allocate_device();
+	if (!input) {
+		ret = -ENOMEM;
+		goto out_freekeypad;
+	}
+
+	input->name = "STMPE keypad";
+	input->id.bustype = BUS_I2C;
+	input->dev.parent = &pdev->dev;
+
+	input_set_capability(input, EV_MSC, MSC_SCAN);
+
+	__set_bit(EV_KEY, input->evbit);
+	if (!plat->no_autorepeat)
+		__set_bit(EV_REP, input->evbit);
+
+	input->keycode = keypad->keymap;
+	input->keycodesize = sizeof(keypad->keymap[0]);
+	input->keycodemax = ARRAY_SIZE(keypad->keymap);
+
+	matrix_keypad_build_keymap(plat->keymap_data, STMPE_KEYPAD_ROW_SHIFT,
+				   input->keycode, input->keybit);
+
+	for (i = 0; i < plat->keymap_data->keymap_size; i++) {
+		unsigned int key = plat->keymap_data->keymap[i];
+
+		keypad->cols |= 1 << KEY_COL(key);
+		keypad->rows |= 1 << KEY_ROW(key);
+	}
+
+	keypad->stmpe = stmpe;
+	keypad->plat = plat;
+	keypad->input = input;
+	keypad->variant = &stmpe_keypad_variants[stmpe->partnum];
+
+	ret = stmpe_keypad_chip_init(keypad);
+	if (ret < 0)
+		goto out_freeinput;
+
+	ret = input_register_device(input);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"unable to register input device: %d\n", ret);
+		goto out_freeinput;
+	}
+
+	ret = request_threaded_irq(irq, NULL, stmpe_keypad_irq, IRQF_ONESHOT,
+				   "stmpe-keypad", keypad);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
+		goto out_unregisterinput;
+	}
+
+	platform_set_drvdata(pdev, keypad);
+
+	return 0;
+
+out_unregisterinput:
+	input_unregister_device(input);
+	input = NULL;
+out_freeinput:
+	input_free_device(input);
+out_freekeypad:
+	kfree(keypad);
+	return ret;
+}
+
+static int __devexit stmpe_keypad_remove(struct platform_device *pdev)
+{
+	struct stmpe_keypad *keypad = platform_get_drvdata(pdev);
+	struct stmpe *stmpe = keypad->stmpe;
+	int irq = platform_get_irq(pdev, 0);
+
+	stmpe_disable(stmpe, STMPE_BLOCK_KEYPAD);
+
+	free_irq(irq, keypad);
+	input_unregister_device(keypad->input);
+	platform_set_drvdata(pdev, NULL);
+	kfree(keypad);
+
+	return 0;
+}
+
+static struct platform_driver stmpe_keypad_driver = {
+	.driver.name	= "stmpe-keypad",
+	.driver.owner	= THIS_MODULE,
+	.probe		= stmpe_keypad_probe,
+	.remove		= __devexit_p(stmpe_keypad_remove),
+};
+
+static int __init stmpe_keypad_init(void)
+{
+	return platform_driver_register(&stmpe_keypad_driver);
+}
+module_init(stmpe_keypad_init);
+
+static void __exit stmpe_keypad_exit(void)
+{
+	platform_driver_unregister(&stmpe_keypad_driver);
+}
+module_exit(stmpe_keypad_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("STMPExxxx keypad driver");
+MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
-- 
1.7.0


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* Re: [PATCH 1/3] mfd: add STMPExxxx I/O Expander support
  2010-06-21 15:45     ` Luotao Fu
                         ` (2 preceding siblings ...)
  2010-06-22 13:55       ` [PATCHv2 3/3] input: add STMPE keypad driver Rabin Vincent
@ 2010-06-22 13:56       ` Rabin VINCENT
  2010-06-24 11:13         ` mfd: STMPExxxx fixes and touch screen support Luotao Fu
                           ` (6 more replies)
  3 siblings, 7 replies; 61+ messages in thread
From: Rabin VINCENT @ 2010-06-22 13:56 UTC (permalink / raw)
  To: Samuel Ortiz, linux-kernel, STEricsson_nomadik_linux,
	Linus WALLEIJ, l.fu

On Mon, Jun 21, 2010 at 17:45:14 +0200, Luotao Fu wrote:
> I'll test the patch as soon as I can. Do you have any public GIT tree
> with the stmpexxx stuffs in them, so that I might be able to merge it
> with my board stuffs directly?

Sorry, no, I don't have a git tree.  I've posted the updated version
just now, you only need to grab the first two since the keypad doesn't
exist on 811.  Please see if you can get this to work on 811.  I've
already ported over some of the delta features from your driver.
Thanks.

Rabin

^ permalink raw reply	[flat|nested] 61+ messages in thread

* mfd: STMPExxxx fixes and touch screen support
  2010-06-22 13:56       ` [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Rabin VINCENT
@ 2010-06-24 11:13         ` Luotao Fu
  2010-06-24 11:13         ` [PATCH 1/6] gpio/stmpe-gpio: set GPIO alternate function while requesting Luotao Fu
                           ` (5 subsequent siblings)
  6 siblings, 0 replies; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 11:13 UTC (permalink / raw)
  To: Rabin VINCENT, Samuel Ortiz, Dmitry Torokhov
  Cc: Linus WALLEIJ, linux-kernel, linux-input, STEricsson_nomadik_linux

Hi Rabin,

I merged your stmpexxx patches and tested them on my board with a stmpe811 chip
(gpio + touchscreen).

In the following are few fixes for the gpio driver. In the serie is also the V4
of my stmpe touchscreen driver, which is now converted to the stmpexxx core.
There appears to be two stmpe variants with a touchscreen controller: stmpe811
and stmpe610. I checked briefly the datasheet of stmpe610. It appears to be, as
far as I can see, identical with stmpe811. Hence I think that the stmpe-ts should
work for the stmpe610 too. Since I don't have a stmpe610 here, I didn't add the
stmpe610 support to the core. Maybe someone out there has possiblities to test
stmpe610, should not be too hard to add support for the chip to the core.

cheers
Luotao Fu


^ permalink raw reply	[flat|nested] 61+ messages in thread

* [PATCH 1/6] gpio/stmpe-gpio: set GPIO alternate function while requesting
  2010-06-22 13:56       ` [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Rabin VINCENT
  2010-06-24 11:13         ` mfd: STMPExxxx fixes and touch screen support Luotao Fu
@ 2010-06-24 11:13         ` Luotao Fu
  2010-06-24 12:43           ` Rabin VINCENT
  2010-06-24 11:13         ` [PATCH 2/6] gpio/stmpe-gpio: fix set direction input Luotao Fu
                           ` (4 subsequent siblings)
  6 siblings, 1 reply; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 11:13 UTC (permalink / raw)
  To: Rabin VINCENT, Samuel Ortiz, Dmitry Torokhov
  Cc: Linus WALLEIJ, linux-kernel, linux-input,
	STEricsson_nomadik_linux, Luotao Fu

add reuqest hook to the gpio chip, so that we can make sure that the GPIO pin is
setted to the correct GPIO alternate function after we have requested the GPIO
pin.

Signed-off-by: Luotao Fu <l.fu@pengutronix.de>
---
 drivers/gpio/stmpe-gpio.c |    9 +++++++++
 1 files changed, 9 insertions(+), 0 deletions(-)

diff --git a/drivers/gpio/stmpe-gpio.c b/drivers/gpio/stmpe-gpio.c
index bd49a3a..a4de271 100644
--- a/drivers/gpio/stmpe-gpio.c
+++ b/drivers/gpio/stmpe-gpio.c
@@ -98,6 +98,14 @@ static int stmpe_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
 	return stmpe_gpio->irq_base + offset;
 }
 
+static int stmpe_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+
+	return stmpe_set_altfunc(stmpe, 1 << offset, STMPE_BLOCK_GPIO);
+}
+
 static struct gpio_chip template_chip = {
 	.label			= "stmpe",
 	.owner			= THIS_MODULE,
@@ -106,6 +114,7 @@ static struct gpio_chip template_chip = {
 	.direction_output	= stmpe_gpio_direction_output,
 	.set			= stmpe_gpio_set,
 	.to_irq			= stmpe_gpio_to_irq,
+	.request		= stmpe_gpio_request,
 	.can_sleep		= 1,
 };
 
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH 2/6] gpio/stmpe-gpio: fix set direction input
  2010-06-22 13:56       ` [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Rabin VINCENT
  2010-06-24 11:13         ` mfd: STMPExxxx fixes and touch screen support Luotao Fu
  2010-06-24 11:13         ` [PATCH 1/6] gpio/stmpe-gpio: set GPIO alternate function while requesting Luotao Fu
@ 2010-06-24 11:13         ` Luotao Fu
  2010-06-24 12:03           ` Rabin VINCENT
  2010-06-24 11:13         ` [PATCH 3/6] mfd/stmpexxx: add touchscreen platform data Luotao Fu
                           ` (3 subsequent siblings)
  6 siblings, 1 reply; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 11:13 UTC (permalink / raw)
  To: Rabin VINCENT, Samuel Ortiz, Dmitry Torokhov
  Cc: Linus WALLEIJ, linux-kernel, linux-input,
	STEricsson_nomadik_linux, Luotao Fu

to set an GPIO pin to input, the corresponding bit in GPDR needs to be cleared
instead of set. fixed with this patch.

Signed-off-by: Luotao Fu <l.fu@pengutronix.de>
---
 drivers/gpio/stmpe-gpio.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/drivers/gpio/stmpe-gpio.c b/drivers/gpio/stmpe-gpio.c
index a4de271..4e1f1b9 100644
--- a/drivers/gpio/stmpe-gpio.c
+++ b/drivers/gpio/stmpe-gpio.c
@@ -88,7 +88,7 @@ static int stmpe_gpio_direction_input(struct gpio_chip *chip,
 	u8 reg = stmpe->regs[STMPE_IDX_GPDR_LSB] - (offset / 8);
 	u8 mask = 1 << (offset % 8);
 
-	return stmpe_set_bits(stmpe, reg, mask, mask);
+	return stmpe_set_bits(stmpe, reg, mask, 0);
 }
 
 static int stmpe_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH 3/6] mfd/stmpexxx: add touchscreen platform data
  2010-06-22 13:56       ` [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Rabin VINCENT
                           ` (2 preceding siblings ...)
  2010-06-24 11:13         ` [PATCH 2/6] gpio/stmpe-gpio: fix set direction input Luotao Fu
@ 2010-06-24 11:13         ` Luotao Fu
  2010-06-24 11:13         ` [PATCH 4/6] mfd/stmpexxx: change touchscreen irq Luotao Fu
                           ` (2 subsequent siblings)
  6 siblings, 0 replies; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 11:13 UTC (permalink / raw)
  To: Rabin VINCENT, Samuel Ortiz, Dmitry Torokhov
  Cc: Linus WALLEIJ, linux-kernel, linux-input,
	STEricsson_nomadik_linux, Luotao Fu

Signed-off-by: Luotao Fu <l.fu@pengutronix.de>
---
 include/linux/mfd/stmpe.h |   43 +++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 43 insertions(+), 0 deletions(-)

diff --git a/include/linux/mfd/stmpe.h b/include/linux/mfd/stmpe.h
index cf2155f..3d319a1 100644
--- a/include/linux/mfd/stmpe.h
+++ b/include/linux/mfd/stmpe.h
@@ -124,6 +124,47 @@ struct stmpe_gpio_platform_data {
 };
 
 /**
+ * struct stmpe_ts_platform_data - stmpe811 touch screen controller platform
+ * data
+ * @sample_time: ADC converstion time in number of clock.
+ * (0 -> 36 clocks, 1 -> 44 clocks, 2 -> 56 clocks, 3 -> 64 clocks,
+ * 4 -> 80 clocks, 5 -> 96 clocks, 6 -> 144 clocks),
+ * recommended is 4.
+ * @mod_12b: ADC Bit mode (0 -> 10bit ADC, 1 -> 12bit ADC)
+ * @ref_sel: ADC reference source
+ * (0 -> internal reference, 1 -> external reference)
+ * @adc_freq: ADC Clock speed
+ * (0 -> 1.625 MHz, 1 -> 3.25 MHz, 2 || 3 -> 6.5 MHz)
+ * @ave_ctrl: Sample average control
+ * (0 -> 1 sample, 1 -> 2 samples, 2 -> 4 samples, 3 -> 8 samples)
+ * @touch_det_delay: Touch detect interrupt delay
+ * (0 -> 10 us, 1 -> 50 us, 2 -> 100 us, 3 -> 500 us,
+ * 4-> 1 ms, 5 -> 5 ms, 6 -> 10 ms, 7 -> 50 ms)
+ * recommended is 3
+ * @settling: Panel driver settling time
+ * (0 -> 10 us, 1 -> 100 us, 2 -> 500 us, 3 -> 1 ms,
+ * 4 -> 5 ms, 5 -> 10 ms, 6 for 50 ms, 7 -> 100 ms)
+ * recommended is 2
+ * @fraction_z: Length of the fractional part in z
+ * (fraction_z ([0..7]) = Count of the fractional part)
+ * recommended is 7
+ * @i_drive: current limit value of the touchscreen drivers
+ * (0 -> 20 mA typical 35 mA max, 1 -> 50 mA typical 80 mA max)
+ *
+ * */
+struct stmpe_ts_platform_data {
+       u8 sample_time;
+       u8 mod_12b;
+       u8 ref_sel;
+       u8 adc_freq;
+       u8 ave_ctrl;
+       u8 touch_det_delay;
+       u8 settling;
+       u8 fraction_z;
+       u8 i_drive;
+};
+
+/**
  * struct stmpe_platform_data - STMPE platform data
  * @id: device id to distinguish between multiple STMPEs on the same board
  * @blocks: bitmask of blocks to enable (use STMPE_BLOCK_*)
@@ -133,6 +174,7 @@ struct stmpe_gpio_platform_data {
  *	      %STMPE_NR_INTERNAL_IRQS if the GPIO driver is not used.
  * @gpio: GPIO-specific platform data
  * @keypad: keypad-specific platform data
+ * @ts: touchscreen-specific platform data
  */
 struct stmpe_platform_data {
 	int id;
@@ -143,6 +185,7 @@ struct stmpe_platform_data {
 
 	struct stmpe_gpio_platform_data *gpio;
 	struct stmpe_keypad_platform_data *keypad;
+	struct stmpe_ts_platform_data *ts;
 };
 
 #define STMPE_NR_INTERNAL_IRQS	9
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH 4/6] mfd/stmpexxx: change touchscreen irq
  2010-06-22 13:56       ` [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Rabin VINCENT
                           ` (3 preceding siblings ...)
  2010-06-24 11:13         ` [PATCH 3/6] mfd/stmpexxx: add touchscreen platform data Luotao Fu
@ 2010-06-24 11:13         ` Luotao Fu
  2010-06-24 13:09           ` Rabin VINCENT
  2010-06-24 11:13         ` [PATCH 5/6] mfd/stmpexxx: fix stmpe811 enable hook Luotao Fu
  2010-06-24 11:13         ` [PATCH 6/6 V4] input: STMPE touch controller support Luotao Fu
  6 siblings, 1 reply; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 11:13 UTC (permalink / raw)
  To: Rabin VINCENT, Samuel Ortiz, Dmitry Torokhov
  Cc: Linus WALLEIJ, linux-kernel, linux-input,
	STEricsson_nomadik_linux, Luotao Fu

the stmpe touchscreen driver uses fifo threshold irq to trigger data fetching.
The touch_det irq bit is only used while polling for release. Change the
platform resource and the irq definition in the variant block.
---
 drivers/mfd/stmpe-devices.c |   12 +++---------
 1 files changed, 3 insertions(+), 9 deletions(-)

diff --git a/drivers/mfd/stmpe-devices.c b/drivers/mfd/stmpe-devices.c
index fa6934d..3e21c26 100644
--- a/drivers/mfd/stmpe-devices.c
+++ b/drivers/mfd/stmpe-devices.c
@@ -59,17 +59,11 @@ static struct mfd_cell stmpe_keypad_cell = {
 
 static struct resource stmpe_ts_resources[] = {
 	{
-		.name	= "TOUCH_DET",
+		.name	= "FIFO_TH",
 		.start	= 0,
 		.end	= 0,
 		.flags	= IORESOURCE_IRQ,
-	},
-	{
-		.name	= "FIFO_TH",
-		.start	= 1,
-		.end	= 1,
-		.flags	= IORESOURCE_IRQ,
-	},
+	}
 };
 
 static struct mfd_cell stmpe_ts_cell = {
@@ -107,7 +101,7 @@ static struct stmpe_variant_block stmpe811_blocks[] = {
 	},
 	{
 		.cell	= &stmpe_ts_cell,
-		.irq	= STMPE811_IRQ_TOUCH_DET,
+		.irq	= STMPE811_IRQ_FIFO_TH,
 		.block	= STMPE_BLOCK_TOUCHSCREEN,
 	},
 };
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH 5/6] mfd/stmpexxx: fix stmpe811 enable hook
  2010-06-22 13:56       ` [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Rabin VINCENT
                           ` (4 preceding siblings ...)
  2010-06-24 11:13         ` [PATCH 4/6] mfd/stmpexxx: change touchscreen irq Luotao Fu
@ 2010-06-24 11:13         ` Luotao Fu
  2010-06-24 12:11           ` Rabin VINCENT
  2010-06-24 11:13         ` [PATCH 6/6 V4] input: STMPE touch controller support Luotao Fu
  6 siblings, 1 reply; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 11:13 UTC (permalink / raw)
  To: Rabin VINCENT, Samuel Ortiz, Dmitry Torokhov
  Cc: Linus WALLEIJ, linux-kernel, linux-input,
	STEricsson_nomadik_linux, Luotao Fu

Fix the stmpe811 enable hook so that we can activate the needed clocks for the
touchscreen controller.

Signed-off-by: Luotao Fu <l.fu@pengutronix.de>
---
 drivers/mfd/stmpe-devices.c |    5 +++--
 1 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/mfd/stmpe-devices.c b/drivers/mfd/stmpe-devices.c
index 3e21c26..25941ea 100644
--- a/drivers/mfd/stmpe-devices.c
+++ b/drivers/mfd/stmpe-devices.c
@@ -117,8 +117,9 @@ static int stmpe811_enable(struct stmpe *stmpe, unsigned int blocks,
 	if (blocks & STMPE_BLOCK_ADC)
 		mask |= STMPE811_SYS_CTRL2_ADC_OFF;
 
-	if (blocks & STMPE_BLOCK_KEYPAD)
-		mask |= STMPE811_SYS_CTRL2_TSC_OFF;
+	if (blocks & STMPE_BLOCK_TOUCHSCREEN)
+		mask |= STMPE811_SYS_CTRL2_ADC_OFF
+			| STMPE811_SYS_CTRL2_TSC_OFF;
 
 	return stmpe_set_bits(stmpe, STMPE811_REG_SYS_CTRL2, mask,
 			      enable ? 0 : mask);
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH 6/6 V4] input: STMPE touch controller support
  2010-06-22 13:56       ` [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Rabin VINCENT
                           ` (5 preceding siblings ...)
  2010-06-24 11:13         ` [PATCH 5/6] mfd/stmpexxx: fix stmpe811 enable hook Luotao Fu
@ 2010-06-24 11:13         ` Luotao Fu
  2010-06-24 12:27           ` [PATCH 5/6 V2] mfd/stmpexxx: fix stmpe811 enable hook Luotao Fu
                             ` (2 more replies)
  6 siblings, 3 replies; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 11:13 UTC (permalink / raw)
  To: Rabin VINCENT, Samuel Ortiz, Dmitry Torokhov
  Cc: Linus WALLEIJ, linux-kernel, linux-input,
	STEricsson_nomadik_linux, Luotao Fu

This one adds a driver for STMPE touchscreen controllers. This driver depends on
stmpexxx mfd core driver.

Signed-off-by: Luotao Fu <l.fu@pengutronix.de>

---
V2 Changes:
* include subsystem headers since they are now remove from the mfd
  core header file
* use genirq to register threaded isr. The stmpe811 own irq callbacks
  are dropped in the core driver.
* use STMPE811_TS_NAME for name all over the place
* wait for completion of core IO operation before cancelling polling of
  release event.
* switch to platform data for configuration parameters.
* add FIFO reset to open callback, also reset FIFO before reporting
  release.

V3 Changes:
* reformated platform data comments to kernel-doc style

V4 Changes:
* converted to stmpexxx core by Rabin Vincent <rabin.vincent@stericsson.com>
* wait for running workqueue callback to finish while canceling the queue
* add i_drive configuration support

 drivers/input/touchscreen/Kconfig    |   10 +
 drivers/input/touchscreen/Makefile   |    1 +
 drivers/input/touchscreen/stmpe-ts.c |  380 ++++++++++++++++++++++++++++++++++
 3 files changed, 391 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/touchscreen/stmpe-ts.c

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 3b9d5e2..96a9954 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -603,4 +603,14 @@ config TOUCHSCREEN_TPS6507X
 	  To compile this driver as a module, choose M here: the
 	  module will be called tps6507x_ts.
 
+config TOUCHSCREEN_STMPE
+	tristate "STMicroelectronics STMPE touchscreens"
+	depends on MFD_STMPE
+	help
+	  Say Y here if you want support for STMicroelectronics
+	  STMPE touchscreen controllers.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called stmpe-ts.
+
 endif
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 497964a..2691c62 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -47,3 +47,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE)	+= mainstone-wm97xx.o
 obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE)	+= zylonite-wm97xx.o
 obj-$(CONFIG_TOUCHSCREEN_W90X900)	+= w90p910_ts.o
 obj-$(CONFIG_TOUCHSCREEN_TPS6507X)	+= tps6507x-ts.o
+obj-$(CONFIG_TOUCHSCREEN_STMPE)		+= stmpe-ts.o
diff --git a/drivers/input/touchscreen/stmpe-ts.c b/drivers/input/touchscreen/stmpe-ts.c
new file mode 100644
index 0000000..260b175
--- /dev/null
+++ b/drivers/input/touchscreen/stmpe-ts.c
@@ -0,0 +1,380 @@
+/* STMicroelectronics STMPE811 Touchscreen Driver
+ *
+ * (C) 2010 Luotao Fu <l.fu@pengutronix.de>
+ * All rights reserved.
+ *
+ *  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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/stmpe.h>
+
+/* Register layouts and functionalities are identical on all stmpexxx variants
+ * with touchscreen controller
+ */
+#define STMPE_REG_INT_STA		0x0B
+#define STMPE_REG_ADC_CTRL1		0x20
+#define STMPE_REG_ADC_CTRL2		0x21
+#define STMPE_REG_TSC_CTRL		0x40
+#define STMPE_REG_TSC_CFG		0x41
+#define STMPE_REG_FIFO_TH		0x4A
+#define STMPE_REG_FIFO_STA		0x4B
+#define STMPE_REG_FIFO_SIZE		0x4C
+#define STMPE_REG_TSC_DATA_XYZ		0x52
+#define STMPE_REG_TSC_FRACTION_Z	0x56
+#define STMPE_REG_TSC_I_DRIVE		0x58
+
+#define STMPE_TSC_CTRL_OP_MOD_XYZ	(0<<1)
+
+#define STMPE_TSC_CTRL_TSC_EN		(1<<0)
+
+#define STMPE_FIFO_STA_RESET		(1<<0)
+
+#define STMPE_IRQ_TOUCH_DET		0
+
+#define SAMPLE_TIME(x)			((x & 0xf) << 4)
+#define MOD_12B(x)			((x & 0x1) << 3)
+#define REF_SEL(x)			((x & 0x1) << 1)
+#define ADC_FREQ(x)			(x & 0x3)
+#define AVE_CTRL(x)			((x & 0x3) << 6)
+#define DET_DELAY(x)			((x & 0x7) << 3)
+#define SETTLING(x)			(x & 0x7)
+#define FRACTION_Z(x)			(x & 0x7)
+#define I_DRIVE(x)			(x & 0x1)
+
+#define STMPE_TS_NAME			"stmpe-ts"
+#define XY_MASK				0xfff
+
+struct stmpe_touch {
+	struct stmpe *stmpe;
+	struct input_dev *idev;
+	struct delayed_work work;
+	u8 sample_time;
+	u8 mod_12b;
+	u8 ref_sel;
+	u8 adc_freq;
+	u8 ave_ctrl;
+	u8 touch_det_delay;
+	u8 settling;
+	u8 fraction_z;
+	u8 i_drive;
+};
+
+static void stmpe_work(struct work_struct *work)
+{
+	int int_sta;
+	u32 timeout = 40;
+
+	struct stmpe_touch *ts =
+	    container_of(work, struct stmpe_touch, work.work);
+
+	int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
+
+	/* touch_det sometimes get desasserted or just get stuck. This appears
+	 * to be a silicon bug, We still have to clearify this with the
+	 * manufacture. As a workaround We release the key anyway if the
+	 * touch_det keeps coming in after 4ms, while the FIFO contains no value
+	 * during the whole time. */
+	while ((int_sta & (1 << STMPE_IRQ_TOUCH_DET)) && (timeout > 0)) {
+		timeout--;
+		int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
+		udelay(100);
+	}
+
+	/* reset the FIFO before we report release event */
+	stmpe_set_bits(ts->stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, STMPE_FIFO_STA_RESET);
+	stmpe_set_bits(ts->stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, 0);
+
+	input_report_abs(ts->idev, ABS_PRESSURE, 0);
+	input_sync(ts->idev);
+}
+
+static irqreturn_t stmpe_ts_handler(int irq, void *data)
+{
+	u8 data_set[4];
+	int x, y, z;
+	struct stmpe_touch *ts = data;
+
+	/* Cancel scheduled polling for release if we have new value
+	 * available. Wait if the polling is already running. */
+	cancel_delayed_work_sync(&ts->work);
+
+	/*
+	 * The FIFO sometimes just crashes and stops generating interrupts. This
+	 * appears to be a silicon bug. We still have to clearify this with
+	 * the manufacture. As a workaround we disable the TSC while we are
+	 * collecting data and flush the FIFO after reading
+	 */
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+				STMPE_TSC_CTRL_TSC_EN, 0);
+
+	stmpe_block_read(ts->stmpe, STMPE_REG_TSC_DATA_XYZ, 4, data_set);
+
+	x = (data_set[0] << 4) | (data_set[1] >> 4);
+	y = ((data_set[1] & 0xf) << 8) | data_set[2];
+	z = data_set[3];
+
+	input_report_abs(ts->idev, ABS_X, x);
+	input_report_abs(ts->idev, ABS_Y, y);
+	input_report_abs(ts->idev, ABS_PRESSURE, z);
+	input_sync(ts->idev);
+
+       /* flush the FIFO after we have read out our values. */
+	stmpe_set_bits(ts->stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, STMPE_FIFO_STA_RESET);
+	stmpe_set_bits(ts->stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, 0);
+
+	/* reenable the tsc */
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
+
+	/* start polling for touch_det to detect release */
+	schedule_delayed_work(&ts->work, HZ / 50);
+
+	return IRQ_HANDLED;
+}
+
+static int stmpe_ts_open(struct input_dev *dev)
+{
+	struct stmpe_touch *ts = input_get_drvdata(dev);
+	int ret = 0;
+
+	ret = stmpe_set_bits(ts->stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, STMPE_FIFO_STA_RESET);
+	if (ret)
+		goto out;
+
+	ret = stmpe_set_bits(ts->stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, 0);
+	if (ret)
+		goto out;
+
+	ret = stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
+	if (ret)
+		goto out;
+
+out:
+	return ret;
+}
+
+static void stmpe_ts_close(struct input_dev *dev)
+{
+	struct stmpe_touch *ts = input_get_drvdata(dev);
+
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, 0);
+}
+
+static int __devinit stmpe_input_probe(struct platform_device *pdev)
+{
+	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+	struct stmpe_platform_data *pdata = stmpe->pdata;
+	struct stmpe_touch *ts;
+	struct input_dev *idev;
+	struct stmpe_ts_platform_data *ts_pdata = NULL;
+	int ret = 0;
+	unsigned int ts_irq;
+	u8 adc_ctrl1, tsc_cfg;
+
+	ts_irq = platform_get_irq(pdev, 0);
+	if (ts_irq < 0)
+		return ts_irq;
+
+	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+	if (!ts)
+		goto err_out;
+
+	idev = input_allocate_device();
+	if (!idev)
+		goto err_free_ts;
+
+	platform_set_drvdata(pdev, ts);
+	ts->stmpe = stmpe;
+	ts->idev = idev;
+
+	if (pdata)
+		ts_pdata = pdata->ts;
+
+	if (ts_pdata) {
+		ts->sample_time = ts_pdata->sample_time;
+		ts->mod_12b = ts_pdata->mod_12b;
+		ts->ref_sel = ts_pdata->ref_sel;
+		ts->adc_freq = ts_pdata->adc_freq;
+		ts->ave_ctrl = ts_pdata->ave_ctrl;
+		ts->touch_det_delay = ts_pdata->touch_det_delay;
+		ts->settling = ts_pdata->settling;
+		ts->fraction_z = ts_pdata->fraction_z;
+		ts->i_drive = ts_pdata->i_drive;
+	}
+
+	INIT_DELAYED_WORK(&ts->work, stmpe_work);
+
+	ret = request_threaded_irq(ts_irq, NULL, stmpe_ts_handler,
+			IRQF_ONESHOT, STMPE_TS_NAME, ts);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request IRQ %d\n", ts_irq);
+		goto err_free_input;
+	}
+
+	ret = stmpe_enable(stmpe, STMPE_BLOCK_TOUCHSCREEN);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not enable clock for ADC and TS\n");
+		goto err_free_irq;
+	}
+
+	adc_ctrl1 = SAMPLE_TIME(ts->sample_time) | MOD_12B(ts->mod_12b) |
+		REF_SEL(ts->ref_sel);
+	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL1,
+			adc_ctrl1, adc_ctrl1);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not setup ADC\n");
+		goto err_free_irq;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL2,
+			ADC_FREQ(ts->adc_freq), ADC_FREQ(ts->adc_freq));
+	if (ret) {
+		dev_err(&pdev->dev, "Could not setup ADC\n");
+		goto err_free_irq;
+	}
+
+	tsc_cfg = AVE_CTRL(ts->ave_ctrl) | DET_DELAY(ts->touch_det_delay) |
+			SETTLING(ts->settling);
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CFG, tsc_cfg, tsc_cfg);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not config touch\n");
+		goto err_free_irq;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_FRACTION_Z,
+			FRACTION_Z(ts->fraction_z),
+			FRACTION_Z(ts->fraction_z));
+	if (ret) {
+		dev_err(&pdev->dev, "Could not config touch\n");
+		goto err_free_irq;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_I_DRIVE,
+			I_DRIVE(ts->i_drive), I_DRIVE(ts->i_drive));
+	if (ret) {
+		dev_err(&pdev->dev, "Could not config touch\n");
+		goto err_free_irq;
+	}
+
+	/* set FIFO to 1 for single point reading */
+	ret = stmpe_reg_write(stmpe, STMPE_REG_FIFO_TH, 1);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not set FIFO\n");
+		goto err_free_irq;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_OP_MOD_XYZ,
+			STMPE_TSC_CTRL_OP_MOD_XYZ);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not set mode\n");
+		goto err_free_irq;
+	}
+
+	idev->name = STMPE_TS_NAME;
+	idev->id.bustype = BUS_I2C;
+	idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+	idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+	idev->open = stmpe_ts_open;
+	idev->close = stmpe_ts_close;
+
+	input_set_drvdata(idev, ts);
+
+	ret = input_register_device(idev);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not register input device\n");
+		goto err_free_irq;
+	}
+
+	input_set_abs_params(idev, ABS_X, 0, XY_MASK, 0, 0);
+	input_set_abs_params(idev, ABS_Y, 0, XY_MASK, 0, 0);
+	input_set_abs_params(idev, ABS_PRESSURE, 0x0, 0xff, 0, 0);
+
+	return ret;
+
+err_free_irq:
+	free_irq(ts_irq, ts);
+err_free_input:
+	input_free_device(idev);
+	platform_set_drvdata(pdev, NULL);
+err_free_ts:
+	kfree(ts);
+err_out:
+	return ret;
+}
+
+static int __devexit stmpe_ts_remove(struct platform_device *pdev)
+{
+	struct stmpe_touch *ts = platform_get_drvdata(pdev);
+	unsigned int ts_irq = platform_get_irq(pdev, 0);
+
+	cancel_delayed_work(&ts->work);
+
+	stmpe_reg_write(ts->stmpe, STMPE_REG_FIFO_TH, 0);
+
+	stmpe_disable(ts->stmpe, STMPE_BLOCK_TOUCHSCREEN);
+
+	free_irq(ts_irq, ts);
+
+	platform_set_drvdata(pdev, NULL);
+
+	input_unregister_device(ts->idev);
+	input_free_device(ts->idev);
+
+	kfree(ts);
+
+	return 0;
+}
+
+static struct platform_driver stmpe_ts_driver = {
+	.driver = {
+		   .name = STMPE_TS_NAME,
+		   },
+	.probe = stmpe_input_probe,
+	.remove = __devexit_p(stmpe_ts_remove),
+};
+
+static int __init stmpe_ts_init(void)
+{
+	return platform_driver_register(&stmpe_ts_driver);
+}
+
+module_init(stmpe_ts_init);
+
+static void __exit stmpe_ts_exit(void)
+{
+	platform_driver_unregister(&stmpe_ts_driver);
+}
+
+module_exit(stmpe_ts_exit);
+
+MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
+MODULE_DESCRIPTION("STMPEXXX touchscreen driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" STMPE_TS_NAME);
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* Re: [PATCH 2/6] gpio/stmpe-gpio: fix set direction input
  2010-06-24 11:13         ` [PATCH 2/6] gpio/stmpe-gpio: fix set direction input Luotao Fu
@ 2010-06-24 12:03           ` Rabin VINCENT
  0 siblings, 0 replies; 61+ messages in thread
From: Rabin VINCENT @ 2010-06-24 12:03 UTC (permalink / raw)
  To: Luotao Fu
  Cc: Samuel Ortiz, Dmitry Torokhov, Linus WALLEIJ, linux-kernel,
	linux-input, STEricsson_nomadik_linux

On Thu, Jun 24, 2010 at 13:13:37 +0200, Luotao Fu wrote:
> to set an GPIO pin to input, the corresponding bit in GPDR needs to be cleared
> instead of set. fixed with this patch.
> 
> Signed-off-by: Luotao Fu <l.fu@pengutronix.de>

Acked-by: Rabin Vincent <rabin.vincent@stericsson.com>

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 5/6] mfd/stmpexxx: fix stmpe811 enable hook
  2010-06-24 11:13         ` [PATCH 5/6] mfd/stmpexxx: fix stmpe811 enable hook Luotao Fu
@ 2010-06-24 12:11           ` Rabin VINCENT
  2010-06-24 12:32             ` Luotao Fu
  0 siblings, 1 reply; 61+ messages in thread
From: Rabin VINCENT @ 2010-06-24 12:11 UTC (permalink / raw)
  To: Luotao Fu
  Cc: Samuel Ortiz, Dmitry Torokhov, Linus WALLEIJ, linux-kernel,
	linux-input, STEricsson_nomadik_linux

On Thu, Jun 24, 2010 at 13:13:40 +0200, Luotao Fu wrote:
> Fix the stmpe811 enable hook so that we can activate the needed clocks for the
> touchscreen controller.
> 
> Signed-off-by: Luotao Fu <l.fu@pengutronix.de>
> ---
>  drivers/mfd/stmpe-devices.c |    5 +++--
>  1 files changed, 3 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/mfd/stmpe-devices.c b/drivers/mfd/stmpe-devices.c
> index 3e21c26..25941ea 100644
> --- a/drivers/mfd/stmpe-devices.c
> +++ b/drivers/mfd/stmpe-devices.c
> @@ -117,8 +117,9 @@ static int stmpe811_enable(struct stmpe *stmpe, unsigned int blocks,
>  	if (blocks & STMPE_BLOCK_ADC)
>  		mask |= STMPE811_SYS_CTRL2_ADC_OFF;
>  
> -	if (blocks & STMPE_BLOCK_KEYPAD)
> -		mask |= STMPE811_SYS_CTRL2_TSC_OFF;
> +	if (blocks & STMPE_BLOCK_TOUCHSCREEN)
> +		mask |= STMPE811_SYS_CTRL2_ADC_OFF
> +			| STMPE811_SYS_CTRL2_TSC_OFF;

The KEYPAD -> TOUCHSCREEN fix is fine, but for the ADC, wouldn't it be
better to pass in STMPE_BLOCK_ADC in your stmpe_enable() call instead?
You wouldn't need to add another call, you can just pass in
(STMPE_BLOCK_TOUCHSCREEN | STMPE_BLOCK_ADC) as the argument.

Rabin

^ permalink raw reply	[flat|nested] 61+ messages in thread

* [PATCH 5/6 V2] mfd/stmpexxx: fix stmpe811 enable hook
  2010-06-24 11:13         ` [PATCH 6/6 V4] input: STMPE touch controller support Luotao Fu
@ 2010-06-24 12:27           ` Luotao Fu
  2010-06-24 12:35             ` Rabin VINCENT
  2010-06-24 12:28           ` [PATCH 6/6 V5] input: STMPE touch controller support Luotao Fu
  2010-06-24 12:31           ` [PATCH 6/6 V4] " Rabin VINCENT
  2 siblings, 1 reply; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 12:27 UTC (permalink / raw)
  To: Rabin VINCENT, Samuel Ortiz, Dmitry Torokhov
  Cc: Linus WALLEIJ, linux-kernel, linux-input,
	STEricsson_nomadik_linux, Luotao Fu

Fix the stmpe811 enable hook so that we can activate the needed clocks for the
touchscreen controller.

Signed-off-by: Luotao Fu <l.fu@pengutronix.de>
---
V2 Changes:
separate ADC block enable and ts block enable.

 drivers/mfd/stmpe-devices.c |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/mfd/stmpe-devices.c b/drivers/mfd/stmpe-devices.c
index 3e21c26..ded9dcb 100644
--- a/drivers/mfd/stmpe-devices.c
+++ b/drivers/mfd/stmpe-devices.c
@@ -117,8 +117,8 @@ static int stmpe811_enable(struct stmpe *stmpe, unsigned int blocks,
 	if (blocks & STMPE_BLOCK_ADC)
 		mask |= STMPE811_SYS_CTRL2_ADC_OFF;
 
-	if (blocks & STMPE_BLOCK_KEYPAD)
-		mask |= STMPE811_SYS_CTRL2_TSC_OFF;
+	if (blocks & STMPE_BLOCK_TOUCHSCREEN)
+		mask |= STMPE811_SYS_CTRL2_ADC_OFF;
 
 	return stmpe_set_bits(stmpe, STMPE811_REG_SYS_CTRL2, mask,
 			      enable ? 0 : mask);
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH 6/6 V5] input: STMPE touch controller support
  2010-06-24 11:13         ` [PATCH 6/6 V4] input: STMPE touch controller support Luotao Fu
  2010-06-24 12:27           ` [PATCH 5/6 V2] mfd/stmpexxx: fix stmpe811 enable hook Luotao Fu
@ 2010-06-24 12:28           ` Luotao Fu
  2010-06-24 14:26             ` [PATCH 5/5] " Luotao Fu
  2010-06-24 12:31           ` [PATCH 6/6 V4] " Rabin VINCENT
  2 siblings, 1 reply; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 12:28 UTC (permalink / raw)
  To: Rabin VINCENT, Samuel Ortiz, Dmitry Torokhov
  Cc: Linus WALLEIJ, linux-kernel, linux-input,
	STEricsson_nomadik_linux, Luotao Fu

This one adds a driver for STMPE touchscreen controllers. This driver depends on
stmpexxx mfd core driver.

Signed-off-by: Luotao Fu <l.fu@pengutronix.de>

---
V2 Changes:
* include subsystem headers since they are now remove from the mfd
  core header file
* use genirq to register threaded isr. The stmpe811 own irq callbacks
  are dropped in the core driver.
* use STMPE811_TS_NAME for name all over the place
* wait for completion of core IO operation before cancelling polling of
  release event.
* switch to platform data for configuration parameters.
* add FIFO reset to open callback, also reset FIFO before reporting
  release.

V3 Changes:
* reformated platform data comments to kernel-doc style

V4 Changes:
* converted to stmpexxx core by Rabin Vincent <rabin.vincent@stericsson.com>
* wait for running workqueue callback to finish while canceling the queue
* add i_drive configuration support

V5 Changes:
* add ADC block enable to stmpe_enable call.

 drivers/input/touchscreen/Kconfig    |   10 +
 drivers/input/touchscreen/Makefile   |    1 +
 drivers/input/touchscreen/stmpe-ts.c |  380 ++++++++++++++++++++++++++++++++++
 3 files changed, 391 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/touchscreen/stmpe-ts.c

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 3b9d5e2..96a9954 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -603,4 +603,14 @@ config TOUCHSCREEN_TPS6507X
 	  To compile this driver as a module, choose M here: the
 	  module will be called tps6507x_ts.
 
+config TOUCHSCREEN_STMPE
+	tristate "STMicroelectronics STMPE touchscreens"
+	depends on MFD_STMPE
+	help
+	  Say Y here if you want support for STMicroelectronics
+	  STMPE touchscreen controllers.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called stmpe-ts.
+
 endif
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 497964a..2691c62 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -47,3 +47,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE)	+= mainstone-wm97xx.o
 obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE)	+= zylonite-wm97xx.o
 obj-$(CONFIG_TOUCHSCREEN_W90X900)	+= w90p910_ts.o
 obj-$(CONFIG_TOUCHSCREEN_TPS6507X)	+= tps6507x-ts.o
+obj-$(CONFIG_TOUCHSCREEN_STMPE)		+= stmpe-ts.o
diff --git a/drivers/input/touchscreen/stmpe-ts.c b/drivers/input/touchscreen/stmpe-ts.c
new file mode 100644
index 0000000..5094a33
--- /dev/null
+++ b/drivers/input/touchscreen/stmpe-ts.c
@@ -0,0 +1,380 @@
+/* STMicroelectronics STMPE811 Touchscreen Driver
+ *
+ * (C) 2010 Luotao Fu <l.fu@pengutronix.de>
+ * All rights reserved.
+ *
+ *  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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/stmpe.h>
+
+/* Register layouts and functionalities are identical on all stmpexxx variants
+ * with touchscreen controller
+ */
+#define STMPE_REG_INT_STA		0x0B
+#define STMPE_REG_ADC_CTRL1		0x20
+#define STMPE_REG_ADC_CTRL2		0x21
+#define STMPE_REG_TSC_CTRL		0x40
+#define STMPE_REG_TSC_CFG		0x41
+#define STMPE_REG_FIFO_TH		0x4A
+#define STMPE_REG_FIFO_STA		0x4B
+#define STMPE_REG_FIFO_SIZE		0x4C
+#define STMPE_REG_TSC_DATA_XYZ		0x52
+#define STMPE_REG_TSC_FRACTION_Z	0x56
+#define STMPE_REG_TSC_I_DRIVE		0x58
+
+#define STMPE_TSC_CTRL_OP_MOD_XYZ	(0<<1)
+
+#define STMPE_TSC_CTRL_TSC_EN		(1<<0)
+
+#define STMPE_FIFO_STA_RESET		(1<<0)
+
+#define STMPE_IRQ_TOUCH_DET		0
+
+#define SAMPLE_TIME(x)			((x & 0xf) << 4)
+#define MOD_12B(x)			((x & 0x1) << 3)
+#define REF_SEL(x)			((x & 0x1) << 1)
+#define ADC_FREQ(x)			(x & 0x3)
+#define AVE_CTRL(x)			((x & 0x3) << 6)
+#define DET_DELAY(x)			((x & 0x7) << 3)
+#define SETTLING(x)			(x & 0x7)
+#define FRACTION_Z(x)			(x & 0x7)
+#define I_DRIVE(x)			(x & 0x1)
+
+#define STMPE_TS_NAME			"stmpe-ts"
+#define XY_MASK				0xfff
+
+struct stmpe_touch {
+	struct stmpe *stmpe;
+	struct input_dev *idev;
+	struct delayed_work work;
+	u8 sample_time;
+	u8 mod_12b;
+	u8 ref_sel;
+	u8 adc_freq;
+	u8 ave_ctrl;
+	u8 touch_det_delay;
+	u8 settling;
+	u8 fraction_z;
+	u8 i_drive;
+};
+
+static void stmpe_work(struct work_struct *work)
+{
+	int int_sta;
+	u32 timeout = 40;
+
+	struct stmpe_touch *ts =
+	    container_of(work, struct stmpe_touch, work.work);
+
+	int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
+
+	/* touch_det sometimes get desasserted or just get stuck. This appears
+	 * to be a silicon bug, We still have to clearify this with the
+	 * manufacture. As a workaround We release the key anyway if the
+	 * touch_det keeps coming in after 4ms, while the FIFO contains no value
+	 * during the whole time. */
+	while ((int_sta & (1 << STMPE_IRQ_TOUCH_DET)) && (timeout > 0)) {
+		timeout--;
+		int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
+		udelay(100);
+	}
+
+	/* reset the FIFO before we report release event */
+	stmpe_set_bits(ts->stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, STMPE_FIFO_STA_RESET);
+	stmpe_set_bits(ts->stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, 0);
+
+	input_report_abs(ts->idev, ABS_PRESSURE, 0);
+	input_sync(ts->idev);
+}
+
+static irqreturn_t stmpe_ts_handler(int irq, void *data)
+{
+	u8 data_set[4];
+	int x, y, z;
+	struct stmpe_touch *ts = data;
+
+	/* Cancel scheduled polling for release if we have new value
+	 * available. Wait if the polling is already running. */
+	cancel_delayed_work_sync(&ts->work);
+
+	/*
+	 * The FIFO sometimes just crashes and stops generating interrupts. This
+	 * appears to be a silicon bug. We still have to clearify this with
+	 * the manufacture. As a workaround we disable the TSC while we are
+	 * collecting data and flush the FIFO after reading
+	 */
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+				STMPE_TSC_CTRL_TSC_EN, 0);
+
+	stmpe_block_read(ts->stmpe, STMPE_REG_TSC_DATA_XYZ, 4, data_set);
+
+	x = (data_set[0] << 4) | (data_set[1] >> 4);
+	y = ((data_set[1] & 0xf) << 8) | data_set[2];
+	z = data_set[3];
+
+	input_report_abs(ts->idev, ABS_X, x);
+	input_report_abs(ts->idev, ABS_Y, y);
+	input_report_abs(ts->idev, ABS_PRESSURE, z);
+	input_sync(ts->idev);
+
+       /* flush the FIFO after we have read out our values. */
+	stmpe_set_bits(ts->stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, STMPE_FIFO_STA_RESET);
+	stmpe_set_bits(ts->stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, 0);
+
+	/* reenable the tsc */
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
+
+	/* start polling for touch_det to detect release */
+	schedule_delayed_work(&ts->work, HZ / 50);
+
+	return IRQ_HANDLED;
+}
+
+static int stmpe_ts_open(struct input_dev *dev)
+{
+	struct stmpe_touch *ts = input_get_drvdata(dev);
+	int ret = 0;
+
+	ret = stmpe_set_bits(ts->stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, STMPE_FIFO_STA_RESET);
+	if (ret)
+		goto out;
+
+	ret = stmpe_set_bits(ts->stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, 0);
+	if (ret)
+		goto out;
+
+	ret = stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
+	if (ret)
+		goto out;
+
+out:
+	return ret;
+}
+
+static void stmpe_ts_close(struct input_dev *dev)
+{
+	struct stmpe_touch *ts = input_get_drvdata(dev);
+
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, 0);
+}
+
+static int __devinit stmpe_input_probe(struct platform_device *pdev)
+{
+	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+	struct stmpe_platform_data *pdata = stmpe->pdata;
+	struct stmpe_touch *ts;
+	struct input_dev *idev;
+	struct stmpe_ts_platform_data *ts_pdata = NULL;
+	int ret = 0;
+	unsigned int ts_irq;
+	u8 adc_ctrl1, tsc_cfg;
+
+	ts_irq = platform_get_irq(pdev, 0);
+	if (ts_irq < 0)
+		return ts_irq;
+
+	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+	if (!ts)
+		goto err_out;
+
+	idev = input_allocate_device();
+	if (!idev)
+		goto err_free_ts;
+
+	platform_set_drvdata(pdev, ts);
+	ts->stmpe = stmpe;
+	ts->idev = idev;
+
+	if (pdata)
+		ts_pdata = pdata->ts;
+
+	if (ts_pdata) {
+		ts->sample_time = ts_pdata->sample_time;
+		ts->mod_12b = ts_pdata->mod_12b;
+		ts->ref_sel = ts_pdata->ref_sel;
+		ts->adc_freq = ts_pdata->adc_freq;
+		ts->ave_ctrl = ts_pdata->ave_ctrl;
+		ts->touch_det_delay = ts_pdata->touch_det_delay;
+		ts->settling = ts_pdata->settling;
+		ts->fraction_z = ts_pdata->fraction_z;
+		ts->i_drive = ts_pdata->i_drive;
+	}
+
+	INIT_DELAYED_WORK(&ts->work, stmpe_work);
+
+	ret = request_threaded_irq(ts_irq, NULL, stmpe_ts_handler,
+			IRQF_ONESHOT, STMPE_TS_NAME, ts);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request IRQ %d\n", ts_irq);
+		goto err_free_input;
+	}
+
+	ret = stmpe_enable(stmpe, STMPE_BLOCK_TOUCHSCREEN | STMPE_BLOCK_ADC);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not enable clock for ADC and TS\n");
+		goto err_free_irq;
+	}
+
+	adc_ctrl1 = SAMPLE_TIME(ts->sample_time) | MOD_12B(ts->mod_12b) |
+		REF_SEL(ts->ref_sel);
+	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL1,
+			adc_ctrl1, adc_ctrl1);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not setup ADC\n");
+		goto err_free_irq;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL2,
+			ADC_FREQ(ts->adc_freq), ADC_FREQ(ts->adc_freq));
+	if (ret) {
+		dev_err(&pdev->dev, "Could not setup ADC\n");
+		goto err_free_irq;
+	}
+
+	tsc_cfg = AVE_CTRL(ts->ave_ctrl) | DET_DELAY(ts->touch_det_delay) |
+			SETTLING(ts->settling);
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CFG, tsc_cfg, tsc_cfg);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not config touch\n");
+		goto err_free_irq;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_FRACTION_Z,
+			FRACTION_Z(ts->fraction_z),
+			FRACTION_Z(ts->fraction_z));
+	if (ret) {
+		dev_err(&pdev->dev, "Could not config touch\n");
+		goto err_free_irq;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_I_DRIVE,
+			I_DRIVE(ts->i_drive), I_DRIVE(ts->i_drive));
+	if (ret) {
+		dev_err(&pdev->dev, "Could not config touch\n");
+		goto err_free_irq;
+	}
+
+	/* set FIFO to 1 for single point reading */
+	ret = stmpe_reg_write(stmpe, STMPE_REG_FIFO_TH, 1);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not set FIFO\n");
+		goto err_free_irq;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_OP_MOD_XYZ,
+			STMPE_TSC_CTRL_OP_MOD_XYZ);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not set mode\n");
+		goto err_free_irq;
+	}
+
+	idev->name = STMPE_TS_NAME;
+	idev->id.bustype = BUS_I2C;
+	idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+	idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+	idev->open = stmpe_ts_open;
+	idev->close = stmpe_ts_close;
+
+	input_set_drvdata(idev, ts);
+
+	ret = input_register_device(idev);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not register input device\n");
+		goto err_free_irq;
+	}
+
+	input_set_abs_params(idev, ABS_X, 0, XY_MASK, 0, 0);
+	input_set_abs_params(idev, ABS_Y, 0, XY_MASK, 0, 0);
+	input_set_abs_params(idev, ABS_PRESSURE, 0x0, 0xff, 0, 0);
+
+	return ret;
+
+err_free_irq:
+	free_irq(ts_irq, ts);
+err_free_input:
+	input_free_device(idev);
+	platform_set_drvdata(pdev, NULL);
+err_free_ts:
+	kfree(ts);
+err_out:
+	return ret;
+}
+
+static int __devexit stmpe_ts_remove(struct platform_device *pdev)
+{
+	struct stmpe_touch *ts = platform_get_drvdata(pdev);
+	unsigned int ts_irq = platform_get_irq(pdev, 0);
+
+	cancel_delayed_work(&ts->work);
+
+	stmpe_reg_write(ts->stmpe, STMPE_REG_FIFO_TH, 0);
+
+	stmpe_disable(ts->stmpe, STMPE_BLOCK_TOUCHSCREEN);
+
+	free_irq(ts_irq, ts);
+
+	platform_set_drvdata(pdev, NULL);
+
+	input_unregister_device(ts->idev);
+	input_free_device(ts->idev);
+
+	kfree(ts);
+
+	return 0;
+}
+
+static struct platform_driver stmpe_ts_driver = {
+	.driver = {
+		   .name = STMPE_TS_NAME,
+		   },
+	.probe = stmpe_input_probe,
+	.remove = __devexit_p(stmpe_ts_remove),
+};
+
+static int __init stmpe_ts_init(void)
+{
+	return platform_driver_register(&stmpe_ts_driver);
+}
+
+module_init(stmpe_ts_init);
+
+static void __exit stmpe_ts_exit(void)
+{
+	platform_driver_unregister(&stmpe_ts_driver);
+}
+
+module_exit(stmpe_ts_exit);
+
+MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
+MODULE_DESCRIPTION("STMPEXXX touchscreen driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" STMPE_TS_NAME);
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* Re: [PATCH 6/6 V4] input: STMPE touch controller support
  2010-06-24 11:13         ` [PATCH 6/6 V4] input: STMPE touch controller support Luotao Fu
  2010-06-24 12:27           ` [PATCH 5/6 V2] mfd/stmpexxx: fix stmpe811 enable hook Luotao Fu
  2010-06-24 12:28           ` [PATCH 6/6 V5] input: STMPE touch controller support Luotao Fu
@ 2010-06-24 12:31           ` Rabin VINCENT
  2010-06-24 12:42             ` Luotao Fu
  2 siblings, 1 reply; 61+ messages in thread
From: Rabin VINCENT @ 2010-06-24 12:31 UTC (permalink / raw)
  To: Luotao Fu
  Cc: Samuel Ortiz, Dmitry Torokhov, Linus WALLEIJ, linux-kernel,
	linux-input, STEricsson_nomadik_linux

On Thu, Jun 24, 2010 at 13:13:41 +0200, Luotao Fu wrote:
> +       adc_ctrl1 = SAMPLE_TIME(ts->sample_time) | MOD_12B(ts->mod_12b) |
> +               REF_SEL(ts->ref_sel);
> +       ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL1,
> +                       adc_ctrl1, adc_ctrl1);
> +       if (ret) {
> +               dev_err(&pdev->dev, "Could not setup ADC\n");
> +               goto err_free_irq;
> +       }
> +
> +       ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL2,
> +                       ADC_FREQ(ts->adc_freq), ADC_FREQ(ts->adc_freq));
> +       if (ret) {
> +               dev_err(&pdev->dev, "Could not setup ADC\n");
> +               goto err_free_irq;
> +       }
> +
> +       tsc_cfg = AVE_CTRL(ts->ave_ctrl) | DET_DELAY(ts->touch_det_delay) |
> +                       SETTLING(ts->settling);
> +       ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CFG, tsc_cfg, tsc_cfg);
> +       if (ret) {
> +               dev_err(&pdev->dev, "Could not config touch\n");
> +               goto err_free_irq;
> +       }
> +
> +       ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_FRACTION_Z,
> +                       FRACTION_Z(ts->fraction_z),
> +                       FRACTION_Z(ts->fraction_z));

I think your earlier revisions had this same behaviour, but this only
writes the bits you are trying to set, and it may be a problem if there
are other bits already set in this field.  I don't know if this is a
concern with this block, but if it is, you can do something like the
following to clear out the field before writing:

stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL2,
	       ADC_FREQ(0xff), ADC_FREQ(ts->adc_freq));

or perhaps:

stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL2,
	       0xff, ADC_FREQ(ts->adc_freq));

Rabin

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 5/6] mfd/stmpexxx: fix stmpe811 enable hook
  2010-06-24 12:11           ` Rabin VINCENT
@ 2010-06-24 12:32             ` Luotao Fu
  2010-06-24 12:47               ` [PATCH 5/6 V3] " Luotao Fu
  0 siblings, 1 reply; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 12:32 UTC (permalink / raw)
  To: Rabin VINCENT
  Cc: Luotao Fu, Samuel Ortiz, Dmitry Torokhov, Linus WALLEIJ,
	linux-kernel, linux-input, STEricsson_nomadik_linux

[-- Attachment #1: Type: text/plain, Size: 1843 bytes --]

On Thu, Jun 24, 2010 at 05:41:00PM +0530, Rabin VINCENT wrote:
> On Thu, Jun 24, 2010 at 13:13:40 +0200, Luotao Fu wrote:
> > Fix the stmpe811 enable hook so that we can activate the needed clocks for the
> > touchscreen controller.
> > 
> > Signed-off-by: Luotao Fu <l.fu@pengutronix.de>
> > ---
> >  drivers/mfd/stmpe-devices.c |    5 +++--
> >  1 files changed, 3 insertions(+), 2 deletions(-)
> > 
> > diff --git a/drivers/mfd/stmpe-devices.c b/drivers/mfd/stmpe-devices.c
> > index 3e21c26..25941ea 100644
> > --- a/drivers/mfd/stmpe-devices.c
> > +++ b/drivers/mfd/stmpe-devices.c
> > @@ -117,8 +117,9 @@ static int stmpe811_enable(struct stmpe *stmpe, unsigned int blocks,
> >  	if (blocks & STMPE_BLOCK_ADC)
> >  		mask |= STMPE811_SYS_CTRL2_ADC_OFF;
> >  
> > -	if (blocks & STMPE_BLOCK_KEYPAD)
> > -		mask |= STMPE811_SYS_CTRL2_TSC_OFF;
> > +	if (blocks & STMPE_BLOCK_TOUCHSCREEN)
> > +		mask |= STMPE811_SYS_CTRL2_ADC_OFF
> > +			| STMPE811_SYS_CTRL2_TSC_OFF;
> 
> The KEYPAD -> TOUCHSCREEN fix is fine, but for the ADC, wouldn't it be
> better to pass in STMPE_BLOCK_ADC in your stmpe_enable() call instead?
> You wouldn't need to add another call, you can just pass in
> (STMPE_BLOCK_TOUCHSCREEN | STMPE_BLOCK_ADC) as the argument.

agreed, this would be more clear. Just sent the V2 of this fix and V5 of
the ts driver. I Btw mixed message IDs up and the V2 of the enable hook fix
went out with the same "reply to" ID as the ts driver. doh! sorry for the
confusion.

cheers
Luotao Fu
-- 
Pengutronix e.K.                           | Dipl.-Ing. Luotao Fu        |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 5/6 V2] mfd/stmpexxx: fix stmpe811 enable hook
  2010-06-24 12:27           ` [PATCH 5/6 V2] mfd/stmpexxx: fix stmpe811 enable hook Luotao Fu
@ 2010-06-24 12:35             ` Rabin VINCENT
  2010-06-24 12:46               ` Luotao Fu
  0 siblings, 1 reply; 61+ messages in thread
From: Rabin VINCENT @ 2010-06-24 12:35 UTC (permalink / raw)
  To: Luotao Fu
  Cc: Samuel Ortiz, Dmitry Torokhov, Linus WALLEIJ, linux-kernel,
	linux-input, STEricsson_nomadik_linux

On Thu, Jun 24, 2010 at 14:27:46 +0200, Luotao Fu wrote:
> diff --git a/drivers/mfd/stmpe-devices.c b/drivers/mfd/stmpe-devices.c
> index 3e21c26..ded9dcb 100644
> --- a/drivers/mfd/stmpe-devices.c
> +++ b/drivers/mfd/stmpe-devices.c
> @@ -117,8 +117,8 @@ static int stmpe811_enable(struct stmpe *stmpe, unsigned int blocks,
>  	if (blocks & STMPE_BLOCK_ADC)
>  		mask |= STMPE811_SYS_CTRL2_ADC_OFF;
>  
> -	if (blocks & STMPE_BLOCK_KEYPAD)
> -		mask |= STMPE811_SYS_CTRL2_TSC_OFF;
> +	if (blocks & STMPE_BLOCK_TOUCHSCREEN)
> +		mask |= STMPE811_SYS_CTRL2_ADC_OFF;
>  

You shouldn't be changing TSC_OFF to ADC_OFF, since you're making this
for the touchscreen.

Rabin

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 6/6 V4] input: STMPE touch controller support
  2010-06-24 12:31           ` [PATCH 6/6 V4] " Rabin VINCENT
@ 2010-06-24 12:42             ` Luotao Fu
  2010-06-24 13:01               ` Rabin VINCENT
  2010-06-24 13:01               ` Rabin VINCENT
  0 siblings, 2 replies; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 12:42 UTC (permalink / raw)
  To: Rabin VINCENT
  Cc: Luotao Fu, Samuel Ortiz, Dmitry Torokhov, Linus WALLEIJ,
	linux-kernel, linux-input, STEricsson_nomadik_linux

[-- Attachment #1: Type: text/plain, Size: 2419 bytes --]

Hi Rabin,

On Thu, Jun 24, 2010 at 06:01:53PM +0530, Rabin VINCENT wrote:
> On Thu, Jun 24, 2010 at 13:13:41 +0200, Luotao Fu wrote:
> > +       adc_ctrl1 = SAMPLE_TIME(ts->sample_time) | MOD_12B(ts->mod_12b) |
> > +               REF_SEL(ts->ref_sel);
> > +       ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL1,
> > +                       adc_ctrl1, adc_ctrl1);
> > +       if (ret) {
> > +               dev_err(&pdev->dev, "Could not setup ADC\n");
> > +               goto err_free_irq;
> > +       }
> > +
> > +       ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL2,
> > +                       ADC_FREQ(ts->adc_freq), ADC_FREQ(ts->adc_freq));
> > +       if (ret) {
> > +               dev_err(&pdev->dev, "Could not setup ADC\n");
> > +               goto err_free_irq;
> > +       }
> > +
> > +       tsc_cfg = AVE_CTRL(ts->ave_ctrl) | DET_DELAY(ts->touch_det_delay) |
> > +                       SETTLING(ts->settling);
> > +       ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CFG, tsc_cfg, tsc_cfg);
> > +       if (ret) {
> > +               dev_err(&pdev->dev, "Could not config touch\n");
> > +               goto err_free_irq;
> > +       }
> > +
> > +       ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_FRACTION_Z,
> > +                       FRACTION_Z(ts->fraction_z),
> > +                       FRACTION_Z(ts->fraction_z));
> 
> I think your earlier revisions had this same behaviour, but this only
> writes the bits you are trying to set, and it may be a problem if there
> are other bits already set in this field.  I don't know if this is a
> concern with this block, but if it is, you can do something like the
> following to clear out the field before writing:
> 

This is done intentionally to leave the bits alone which I don't want.
Most of the registers here are used exclusively for the touchscreen.
Some others are shared with the ADC. The ADC however doesn't have any
own configuration and has to go with the configuration of the touch
screen. Hence it's OK, even probably better not to touch the bits we
don't need.

cheers
Luotao Fu
-- 
Pengutronix e.K.                           | Dipl.-Ing. Luotao Fu        |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 1/6] gpio/stmpe-gpio: set GPIO alternate function while requesting
  2010-06-24 11:13         ` [PATCH 1/6] gpio/stmpe-gpio: set GPIO alternate function while requesting Luotao Fu
@ 2010-06-24 12:43           ` Rabin VINCENT
  0 siblings, 0 replies; 61+ messages in thread
From: Rabin VINCENT @ 2010-06-24 12:43 UTC (permalink / raw)
  To: Luotao Fu
  Cc: Samuel Ortiz, Dmitry Torokhov, Linus WALLEIJ, linux-kernel,
	linux-input, STEricsson_nomadik_linux

On Thu, Jun 24, 2010 at 13:13:36 +0200, Luotao Fu wrote:
> add reuqest hook to the gpio chip, so that we can make sure that the GPIO pin is
> setted to the correct GPIO alternate function after we have requested the GPIO
> pin.
> 
> Signed-off-by: Luotao Fu <l.fu@pengutronix.de>

Acked-by: Rabin Vincent <rabin.vincent@stericsson.com>

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 5/6 V2] mfd/stmpexxx: fix stmpe811 enable hook
  2010-06-24 12:35             ` Rabin VINCENT
@ 2010-06-24 12:46               ` Luotao Fu
  0 siblings, 0 replies; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 12:46 UTC (permalink / raw)
  To: Rabin VINCENT
  Cc: Luotao Fu, Samuel Ortiz, Dmitry Torokhov, Linus WALLEIJ,
	linux-kernel, linux-input, STEricsson_nomadik_linux

[-- Attachment #1: Type: text/plain, Size: 1198 bytes --]

On Thu, Jun 24, 2010 at 06:05:42PM +0530, Rabin VINCENT wrote:
> On Thu, Jun 24, 2010 at 14:27:46 +0200, Luotao Fu wrote:
> > diff --git a/drivers/mfd/stmpe-devices.c b/drivers/mfd/stmpe-devices.c
> > index 3e21c26..ded9dcb 100644
> > --- a/drivers/mfd/stmpe-devices.c
> > +++ b/drivers/mfd/stmpe-devices.c
> > @@ -117,8 +117,8 @@ static int stmpe811_enable(struct stmpe *stmpe, unsigned int blocks,
> >  	if (blocks & STMPE_BLOCK_ADC)
> >  		mask |= STMPE811_SYS_CTRL2_ADC_OFF;
> >  
> > -	if (blocks & STMPE_BLOCK_KEYPAD)
> > -		mask |= STMPE811_SYS_CTRL2_TSC_OFF;
> > +	if (blocks & STMPE_BLOCK_TOUCHSCREEN)
> > +		mask |= STMPE811_SYS_CTRL2_ADC_OFF;
> >  
> 
> You shouldn't be changing TSC_OFF to ADC_OFF, since you're making this
> for the touchscreen.
> 

doh! I need more coffee. This is truly embarassing. :-) Proper fix in
following.

cheers
Luotao Fu

-- 
Pengutronix e.K.                           | Dipl.-Ing. Luotao Fu        |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 61+ messages in thread

* [PATCH 5/6 V3] mfd/stmpexxx: fix stmpe811 enable hook
  2010-06-24 12:32             ` Luotao Fu
@ 2010-06-24 12:47               ` Luotao Fu
  2010-06-24 13:05                 ` Rabin VINCENT
  0 siblings, 1 reply; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 12:47 UTC (permalink / raw)
  To: Rabin VINCENT, Samuel Ortiz, Dmitry Torokhov
  Cc: Linus WALLEIJ, linux-kernel, linux-input,
	STEricsson_nomadik_linux, Luotao Fu

Fix the stmpe811 enable hook so that we can activate the needed clocks for the
touchscreen controller.

Signed-off-by: Luotao Fu <l.fu@pengutronix.de>

---
V2 Changes:
separate ADC block enable and ts block enable.

V3 Changes:
fixed silly typo

 drivers/mfd/stmpe-devices.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/drivers/mfd/stmpe-devices.c b/drivers/mfd/stmpe-devices.c
index 3e21c26..066cab5 100644
--- a/drivers/mfd/stmpe-devices.c
+++ b/drivers/mfd/stmpe-devices.c
@@ -117,7 +117,7 @@ static int stmpe811_enable(struct stmpe *stmpe, unsigned int blocks,
 	if (blocks & STMPE_BLOCK_ADC)
 		mask |= STMPE811_SYS_CTRL2_ADC_OFF;
 
-	if (blocks & STMPE_BLOCK_KEYPAD)
+	if (blocks & STMPE_BLOCK_TOUCHSCREEN)
 		mask |= STMPE811_SYS_CTRL2_TSC_OFF;
 
 	return stmpe_set_bits(stmpe, STMPE811_REG_SYS_CTRL2, mask,
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* Re: [PATCH 6/6 V4] input: STMPE touch controller support
  2010-06-24 12:42             ` Luotao Fu
@ 2010-06-24 13:01               ` Rabin VINCENT
  2010-06-24 13:11                 ` Luotao Fu
  2010-06-24 13:01               ` Rabin VINCENT
  1 sibling, 1 reply; 61+ messages in thread
From: Rabin VINCENT @ 2010-06-24 13:01 UTC (permalink / raw)
  To: Luotao Fu, Samuel Ortiz, Dmitry Torokhov, Linus WALLEIJ,
	linux-kernel, linux-input, STEricsson_nomadik_linux

On Thu, Jun 24, 2010 at 14:42:10 +0200, Luotao Fu wrote:
> On Thu, Jun 24, 2010 at 06:01:53PM +0530, Rabin VINCENT wrote:
> > On Thu, Jun 24, 2010 at 13:13:41 +0200, Luotao Fu wrote:
> > > +       ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_FRACTION_Z,
> > > +                       FRACTION_Z(ts->fraction_z),
> > > +                       FRACTION_Z(ts->fraction_z));
> > 
> > I think your earlier revisions had this same behaviour, but this only
> > writes the bits you are trying to set, and it may be a problem if there
> > are other bits already set in this field.  I don't know if this is a
> > concern with this block, but if it is, you can do something like the
> > following to clear out the field before writing:
> > 
> 
> This is done intentionally to leave the bits alone which I don't want.
> Most of the registers here are used exclusively for the touchscreen.
> Some others are shared with the ADC. The ADC however doesn't have any
> own configuration and has to go with the configuration of the touch
> screen. Hence it's OK, even probably better not to touch the bits we
> don't need.

I meant something like: let's say you want to change the "count of the
fractional part" (I quote from your platdata).  It (the FRACTION_Z
field, I assume) has a default value of (say) 2.  Say you want to change
the count to 1.  The above write will change it to 3 instead.  If this
is really the behaviour you need, it should be OK then.

Rabin

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 6/6 V4] input: STMPE touch controller support
  2010-06-24 12:42             ` Luotao Fu
  2010-06-24 13:01               ` Rabin VINCENT
@ 2010-06-24 13:01               ` Rabin VINCENT
  1 sibling, 0 replies; 61+ messages in thread
From: Rabin VINCENT @ 2010-06-24 13:01 UTC (permalink / raw)
  To: Luotao Fu, Samuel Ortiz, Dmitry Torokhov, Linus WALLEIJ, linux-kernel

On Thu, Jun 24, 2010 at 14:42:10 +0200, Luotao Fu wrote:
> On Thu, Jun 24, 2010 at 06:01:53PM +0530, Rabin VINCENT wrote:
> > On Thu, Jun 24, 2010 at 13:13:41 +0200, Luotao Fu wrote:
> > > +       ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_FRACTION_Z,
> > > +                       FRACTION_Z(ts->fraction_z),
> > > +                       FRACTION_Z(ts->fraction_z));
> > 
> > I think your earlier revisions had this same behaviour, but this only
> > writes the bits you are trying to set, and it may be a problem if there
> > are other bits already set in this field.  I don't know if this is a
> > concern with this block, but if it is, you can do something like the
> > following to clear out the field before writing:
> > 
> 
> This is done intentionally to leave the bits alone which I don't want.
> Most of the registers here are used exclusively for the touchscreen.
> Some others are shared with the ADC. The ADC however doesn't have any
> own configuration and has to go with the configuration of the touch
> screen. Hence it's OK, even probably better not to touch the bits we
> don't need.

I meant something like: let's say you want to change the "count of the
fractional part" (I quote from your platdata).  It (the FRACTION_Z
field, I assume) has a default value of (say) 2.  Say you want to change
the count to 1.  The above write will change it to 3 instead.  If this
is really the behaviour you need, it should be OK then.

Rabin

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 5/6 V3] mfd/stmpexxx: fix stmpe811 enable hook
  2010-06-24 12:47               ` [PATCH 5/6 V3] " Luotao Fu
@ 2010-06-24 13:05                 ` Rabin VINCENT
  0 siblings, 0 replies; 61+ messages in thread
From: Rabin VINCENT @ 2010-06-24 13:05 UTC (permalink / raw)
  To: Luotao Fu
  Cc: Samuel Ortiz, Dmitry Torokhov, Linus WALLEIJ, linux-kernel,
	linux-input, STEricsson_nomadik_linux

On Thu, Jun 24, 2010 at 14:47:58 +0200, Luotao Fu wrote:
> Fix the stmpe811 enable hook so that we can activate the needed clocks for the
> touchscreen controller.
> 
> Signed-off-by: Luotao Fu <l.fu@pengutronix.de>

Acked-by: Rabin Vincent <rabin.vincent@stericsson.com>

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 4/6] mfd/stmpexxx: change touchscreen irq
  2010-06-24 11:13         ` [PATCH 4/6] mfd/stmpexxx: change touchscreen irq Luotao Fu
@ 2010-06-24 13:09           ` Rabin VINCENT
  2010-06-24 13:17             ` Luotao Fu
  0 siblings, 1 reply; 61+ messages in thread
From: Rabin VINCENT @ 2010-06-24 13:09 UTC (permalink / raw)
  To: Luotao Fu
  Cc: Samuel Ortiz, Dmitry Torokhov, Linus WALLEIJ, linux-kernel,
	linux-input, STEricsson_nomadik_linux

On Thu, Jun 24, 2010 at 13:13:39 +0200, Luotao Fu wrote:
> the stmpe touchscreen driver uses fifo threshold irq to trigger data fetching.
> The touch_det irq bit is only used while polling for release. Change the
> platform resource and the irq definition in the variant block.
> ---
>  drivers/mfd/stmpe-devices.c |   12 +++---------
>  1 files changed, 3 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/mfd/stmpe-devices.c b/drivers/mfd/stmpe-devices.c
> index fa6934d..3e21c26 100644
> --- a/drivers/mfd/stmpe-devices.c
> +++ b/drivers/mfd/stmpe-devices.c
> @@ -59,17 +59,11 @@ static struct mfd_cell stmpe_keypad_cell = {
>  
>  static struct resource stmpe_ts_resources[] = {
>  	{
> -		.name	= "TOUCH_DET",
> +		.name	= "FIFO_TH",
>  		.start	= 0,
>  		.end	= 0,
>  		.flags	= IORESOURCE_IRQ,
> -	},
> -	{
> -		.name	= "FIFO_TH",
> -		.start	= 1,
> -		.end	= 1,
> -		.flags	= IORESOURCE_IRQ,
> -	},
> +	}
>  };
>  
>  static struct mfd_cell stmpe_ts_cell = {
> @@ -107,7 +101,7 @@ static struct stmpe_variant_block stmpe811_blocks[] = {
>  	},
>  	{
>  		.cell	= &stmpe_ts_cell,
> -		.irq	= STMPE811_IRQ_TOUCH_DET,
> +		.irq	= STMPE811_IRQ_FIFO_TH,
>  		.block	= STMPE_BLOCK_TOUCHSCREEN,
>  	},
>  };

Would it be clearer if you leave this as-is, and instead use
platform_get_irq_byname("FIFO_TH") in the stmpe-ts driver, since I think
the TSC block has a whole bunch of irqs?

Rabin

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 6/6 V4] input: STMPE touch controller support
  2010-06-24 13:01               ` Rabin VINCENT
@ 2010-06-24 13:11                 ` Luotao Fu
  0 siblings, 0 replies; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 13:11 UTC (permalink / raw)
  To: Rabin VINCENT
  Cc: Luotao Fu, Samuel Ortiz, Dmitry Torokhov, Linus WALLEIJ,
	linux-kernel, linux-input, STEricsson_nomadik_linux

[-- Attachment #1: Type: text/plain, Size: 2230 bytes --]

On Thu, Jun 24, 2010 at 06:31:09PM +0530, Rabin VINCENT wrote:
> On Thu, Jun 24, 2010 at 14:42:10 +0200, Luotao Fu wrote:
> > On Thu, Jun 24, 2010 at 06:01:53PM +0530, Rabin VINCENT wrote:
> > > On Thu, Jun 24, 2010 at 13:13:41 +0200, Luotao Fu wrote:
> > > > +       ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_FRACTION_Z,
> > > > +                       FRACTION_Z(ts->fraction_z),
> > > > +                       FRACTION_Z(ts->fraction_z));
> > > 
> > > I think your earlier revisions had this same behaviour, but this only
> > > writes the bits you are trying to set, and it may be a problem if there
> > > are other bits already set in this field.  I don't know if this is a
> > > concern with this block, but if it is, you can do something like the
> > > following to clear out the field before writing:
> > > 
> > 
> > This is done intentionally to leave the bits alone which I don't want.
> > Most of the registers here are used exclusively for the touchscreen.
> > Some others are shared with the ADC. The ADC however doesn't have any
> > own configuration and has to go with the configuration of the touch
> > screen. Hence it's OK, even probably better not to touch the bits we
> > don't need.
> 
> I meant something like: let's say you want to change the "count of the
> fractional part" (I quote from your platdata).  It (the FRACTION_Z
> field, I assume) has a default value of (say) 2.  Say you want to change
> the count to 1.  The above write will change it to 3 instead.  If this
> is really the behaviour you need, it should be OK then.

hmm, good catch. I didn't think about this. Actually nobody else should
touch these registers and they are only written while doing probe,
directly after the chip get resetted. However, it is still theoretically
troublesome, if one of them get written previously by accident. I'll
fix it. Thanks for the catch.

cheers
Luotao Fu
-- 
Pengutronix e.K.                           | Dipl.-Ing. Luotao Fu        |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 4/6] mfd/stmpexxx: change touchscreen irq
  2010-06-24 13:09           ` Rabin VINCENT
@ 2010-06-24 13:17             ` Luotao Fu
  0 siblings, 0 replies; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 13:17 UTC (permalink / raw)
  To: Rabin VINCENT
  Cc: Luotao Fu, Samuel Ortiz, Dmitry Torokhov, Linus WALLEIJ,
	linux-kernel, linux-input, STEricsson_nomadik_linux

[-- Attachment #1: Type: text/plain, Size: 2345 bytes --]

On Thu, Jun 24, 2010 at 06:39:43PM +0530, Rabin VINCENT wrote:
> On Thu, Jun 24, 2010 at 13:13:39 +0200, Luotao Fu wrote:
> > the stmpe touchscreen driver uses fifo threshold irq to trigger data fetching.
> > The touch_det irq bit is only used while polling for release. Change the
> > platform resource and the irq definition in the variant block.
> > ---
> >  drivers/mfd/stmpe-devices.c |   12 +++---------
> >  1 files changed, 3 insertions(+), 9 deletions(-)
> > 
> > diff --git a/drivers/mfd/stmpe-devices.c b/drivers/mfd/stmpe-devices.c
> > index fa6934d..3e21c26 100644
> > --- a/drivers/mfd/stmpe-devices.c
> > +++ b/drivers/mfd/stmpe-devices.c
> > @@ -59,17 +59,11 @@ static struct mfd_cell stmpe_keypad_cell = {
> >  
> >  static struct resource stmpe_ts_resources[] = {
> >  	{
> > -		.name	= "TOUCH_DET",
> > +		.name	= "FIFO_TH",
> >  		.start	= 0,
> >  		.end	= 0,
> >  		.flags	= IORESOURCE_IRQ,
> > -	},
> > -	{
> > -		.name	= "FIFO_TH",
> > -		.start	= 1,
> > -		.end	= 1,
> > -		.flags	= IORESOURCE_IRQ,
> > -	},
> > +	}
> >  };
> >  
> >  static struct mfd_cell stmpe_ts_cell = {
> > @@ -107,7 +101,7 @@ static struct stmpe_variant_block stmpe811_blocks[] = {
> >  	},
> >  	{
> >  		.cell	= &stmpe_ts_cell,
> > -		.irq	= STMPE811_IRQ_TOUCH_DET,
> > +		.irq	= STMPE811_IRQ_FIFO_TH,
> >  		.block	= STMPE_BLOCK_TOUCHSCREEN,
> >  	},
> >  };
> 
> Would it be clearer if you leave this as-is, and instead use
> platform_get_irq_byname("FIFO_TH") in the stmpe-ts driver, since I think
> the TSC block has a whole bunch of irqs?

most of them belong to the ADC/FIFO and are actually of less use for the
touch driver as it is now. However, I agree with you on this. This way
the core can leave the driver to pick irqs as it wants and we are more
flexible if we want to change the algrorithmus later. You
can drop this patch than. I'll add the needed change to the ts driver
together with the register settings into V6 of the ts driver.

thanks

cheers
Luotao Fu

-- 
Pengutronix e.K.                           | Dipl.-Ing. Luotao Fu        |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 61+ messages in thread

* [PATCH 5/5] input: STMPE touch controller support
  2010-06-24 12:28           ` [PATCH 6/6 V5] input: STMPE touch controller support Luotao Fu
@ 2010-06-24 14:26             ` Luotao Fu
  2010-06-24 16:24               ` Dmitry Torokhov
  0 siblings, 1 reply; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 14:26 UTC (permalink / raw)
  To: Rabin VINCENT, Samuel Ortiz, Dmitry Torokhov
  Cc: Linus WALLEIJ, linux-kernel, linux-input,
	STEricsson_nomadik_linux, Luotao Fu

This one adds a driver for STMPE touchscreen controllers. This driver depends on
stmpexxx mfd core driver.

Signed-off-by: Luotao Fu <l.fu@pengutronix.de>

---
V2 Changes:
* include subsystem headers since they are now remove from the mfd
  core header file
* use genirq to register threaded isr. The stmpe811 own irq callbacks
  are dropped in the core driver.
* use STMPE811_TS_NAME for name all over the place
* wait for completion of core IO operation before cancelling polling of
  release event.
* switch to platform data for configuration parameters.
* add FIFO reset to open callback, also reset FIFO before reporting
  release.

V3 Changes:
* reformated platform data comments to kernel-doc style

V4 Changes:
* converted to stmpexxx core by Rabin Vincent <rabin.vincent@stericsson.com>
* wait for running workqueue callback to finish while canceling the queue
* add i_drive configuration support

V5 Changes:
* add ADC block enable to stmpe_enable call.

V6 Changes:
* add _reset_fifo callback to reduce duplicated code.
* fix mask usage while setting bits in probe.
* acquire platform irq by name

 drivers/input/touchscreen/Kconfig    |   10 +
 drivers/input/touchscreen/Makefile   |    1 +
 drivers/input/touchscreen/stmpe-ts.c |  378 ++++++++++++++++++++++++++++++++++
 3 files changed, 389 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/touchscreen/stmpe-ts.c

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 3b9d5e2..96a9954 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -603,4 +603,14 @@ config TOUCHSCREEN_TPS6507X
 	  To compile this driver as a module, choose M here: the
 	  module will be called tps6507x_ts.
 
+config TOUCHSCREEN_STMPE
+	tristate "STMicroelectronics STMPE touchscreens"
+	depends on MFD_STMPE
+	help
+	  Say Y here if you want support for STMicroelectronics
+	  STMPE touchscreen controllers.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called stmpe-ts.
+
 endif
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 497964a..2691c62 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -47,3 +47,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE)	+= mainstone-wm97xx.o
 obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE)	+= zylonite-wm97xx.o
 obj-$(CONFIG_TOUCHSCREEN_W90X900)	+= w90p910_ts.o
 obj-$(CONFIG_TOUCHSCREEN_TPS6507X)	+= tps6507x-ts.o
+obj-$(CONFIG_TOUCHSCREEN_STMPE)		+= stmpe-ts.o
diff --git a/drivers/input/touchscreen/stmpe-ts.c b/drivers/input/touchscreen/stmpe-ts.c
new file mode 100644
index 0000000..7742e63
--- /dev/null
+++ b/drivers/input/touchscreen/stmpe-ts.c
@@ -0,0 +1,378 @@
+/* STMicroelectronics STMPE811 Touchscreen Driver
+ *
+ * (C) 2010 Luotao Fu <l.fu@pengutronix.de>
+ * All rights reserved.
+ *
+ *  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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/stmpe.h>
+
+/* Register layouts and functionalities are identical on all stmpexxx variants
+ * with touchscreen controller
+ */
+#define STMPE_REG_INT_STA		0x0B
+#define STMPE_REG_ADC_CTRL1		0x20
+#define STMPE_REG_ADC_CTRL2		0x21
+#define STMPE_REG_TSC_CTRL		0x40
+#define STMPE_REG_TSC_CFG		0x41
+#define STMPE_REG_FIFO_TH		0x4A
+#define STMPE_REG_FIFO_STA		0x4B
+#define STMPE_REG_FIFO_SIZE		0x4C
+#define STMPE_REG_TSC_DATA_XYZ		0x52
+#define STMPE_REG_TSC_FRACTION_Z	0x56
+#define STMPE_REG_TSC_I_DRIVE		0x58
+
+#define OP_MOD_XYZ			0
+
+#define STMPE_TSC_CTRL_TSC_EN		(1<<0)
+
+#define STMPE_FIFO_STA_RESET		(1<<0)
+
+#define STMPE_IRQ_TOUCH_DET		0
+
+#define SAMPLE_TIME(x)			((x & 0xf) << 4)
+#define MOD_12B(x)			((x & 0x1) << 3)
+#define REF_SEL(x)			((x & 0x1) << 1)
+#define ADC_FREQ(x)			(x & 0x3)
+#define AVE_CTRL(x)			((x & 0x3) << 6)
+#define DET_DELAY(x)			((x & 0x7) << 3)
+#define SETTLING(x)			(x & 0x7)
+#define FRACTION_Z(x)			(x & 0x7)
+#define I_DRIVE(x)			(x & 0x1)
+#define OP_MODE(x)			((x & 0x7) << 1)
+
+#define STMPE_TS_NAME			"stmpe-ts"
+#define XY_MASK				0xfff
+
+struct stmpe_touch {
+	struct stmpe *stmpe;
+	struct input_dev *idev;
+	struct delayed_work work;
+	u8 sample_time;
+	u8 mod_12b;
+	u8 ref_sel;
+	u8 adc_freq;
+	u8 ave_ctrl;
+	u8 touch_det_delay;
+	u8 settling;
+	u8 fraction_z;
+	u8 i_drive;
+};
+
+static int inline __stmpe_reset_fifo(struct stmpe *stmpe)
+{
+	int ret;
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, STMPE_FIFO_STA_RESET);
+	if (ret)
+		return ret;
+
+	return stmpe_set_bits(stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, 0);
+}
+
+static void stmpe_work(struct work_struct *work)
+{
+	int int_sta;
+	u32 timeout = 40;
+
+	struct stmpe_touch *ts =
+	    container_of(work, struct stmpe_touch, work.work);
+
+	int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
+
+	/* touch_det sometimes get desasserted or just get stuck. This appears
+	 * to be a silicon bug, We still have to clearify this with the
+	 * manufacture. As a workaround We release the key anyway if the
+	 * touch_det keeps coming in after 4ms, while the FIFO contains no value
+	 * during the whole time. */
+	while ((int_sta & (1 << STMPE_IRQ_TOUCH_DET)) && (timeout > 0)) {
+		timeout--;
+		int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
+		udelay(100);
+	}
+
+	/* reset the FIFO before we report release event */
+	__stmpe_reset_fifo(ts->stmpe);
+
+	input_report_abs(ts->idev, ABS_PRESSURE, 0);
+	input_sync(ts->idev);
+}
+
+static irqreturn_t stmpe_ts_handler(int irq, void *data)
+{
+	u8 data_set[4];
+	int x, y, z;
+	struct stmpe_touch *ts = data;
+
+	/* Cancel scheduled polling for release if we have new value
+	 * available. Wait if the polling is already running. */
+	cancel_delayed_work_sync(&ts->work);
+
+	/*
+	 * The FIFO sometimes just crashes and stops generating interrupts. This
+	 * appears to be a silicon bug. We still have to clearify this with
+	 * the manufacture. As a workaround we disable the TSC while we are
+	 * collecting data and flush the FIFO after reading
+	 */
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+				STMPE_TSC_CTRL_TSC_EN, 0);
+
+	stmpe_block_read(ts->stmpe, STMPE_REG_TSC_DATA_XYZ, 4, data_set);
+
+	x = (data_set[0] << 4) | (data_set[1] >> 4);
+	y = ((data_set[1] & 0xf) << 8) | data_set[2];
+	z = data_set[3];
+
+	input_report_abs(ts->idev, ABS_X, x);
+	input_report_abs(ts->idev, ABS_Y, y);
+	input_report_abs(ts->idev, ABS_PRESSURE, z);
+	input_sync(ts->idev);
+
+       /* flush the FIFO after we have read out our values. */
+	__stmpe_reset_fifo(ts->stmpe);
+
+	/* reenable the tsc */
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
+
+	/* start polling for touch_det to detect release */
+	schedule_delayed_work(&ts->work, HZ / 50);
+
+	return IRQ_HANDLED;
+}
+
+static int stmpe_ts_open(struct input_dev *dev)
+{
+	struct stmpe_touch *ts = input_get_drvdata(dev);
+	int ret = 0;
+
+	ret = __stmpe_reset_fifo(ts->stmpe);
+	if (ret)
+		return ret;
+
+	return stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
+}
+
+static void stmpe_ts_close(struct input_dev *dev)
+{
+	struct stmpe_touch *ts = input_get_drvdata(dev);
+
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, 0);
+}
+
+static int __devinit stmpe_input_probe(struct platform_device *pdev)
+{
+	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+	struct stmpe_platform_data *pdata = stmpe->pdata;
+	struct stmpe_touch *ts;
+	struct input_dev *idev;
+	struct stmpe_ts_platform_data *ts_pdata = NULL;
+	int ret = 0;
+	unsigned int ts_irq;
+	u8 adc_ctrl1, adc_ctrl1_mask, tsc_cfg, tsc_cfg_mask;
+
+	ts_irq = platform_get_irq_byname(pdev, "FIFO_TH");
+	if (ts_irq < 0)
+		return ts_irq;
+
+	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+	if (!ts)
+		goto err_out;
+
+	idev = input_allocate_device();
+	if (!idev)
+		goto err_free_ts;
+
+	platform_set_drvdata(pdev, ts);
+	ts->stmpe = stmpe;
+	ts->idev = idev;
+
+	if (pdata)
+		ts_pdata = pdata->ts;
+
+	if (ts_pdata) {
+		ts->sample_time = ts_pdata->sample_time;
+		ts->mod_12b = ts_pdata->mod_12b;
+		ts->ref_sel = ts_pdata->ref_sel;
+		ts->adc_freq = ts_pdata->adc_freq;
+		ts->ave_ctrl = ts_pdata->ave_ctrl;
+		ts->touch_det_delay = ts_pdata->touch_det_delay;
+		ts->settling = ts_pdata->settling;
+		ts->fraction_z = ts_pdata->fraction_z;
+		ts->i_drive = ts_pdata->i_drive;
+	}
+
+	INIT_DELAYED_WORK(&ts->work, stmpe_work);
+
+	ret = request_threaded_irq(ts_irq, NULL, stmpe_ts_handler,
+			IRQF_ONESHOT, STMPE_TS_NAME, ts);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request IRQ %d\n", ts_irq);
+		goto err_free_input;
+	}
+
+	ret = stmpe_enable(stmpe, STMPE_BLOCK_TOUCHSCREEN | STMPE_BLOCK_ADC);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not enable clock for ADC and TS\n");
+		goto err_free_irq;
+	}
+
+	adc_ctrl1 = SAMPLE_TIME(ts->sample_time) | MOD_12B(ts->mod_12b) |
+		REF_SEL(ts->ref_sel);
+	adc_ctrl1_mask = SAMPLE_TIME(0xff) | MOD_12B(0xff) | REF_SEL(0xff);
+	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL1,
+			adc_ctrl1_mask, adc_ctrl1);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not setup ADC\n");
+		goto err_free_irq;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL2,
+			ADC_FREQ(0xff), ADC_FREQ(ts->adc_freq));
+	if (ret) {
+		dev_err(&pdev->dev, "Could not setup ADC\n");
+		goto err_free_irq;
+	}
+
+	tsc_cfg = AVE_CTRL(ts->ave_ctrl) | DET_DELAY(ts->touch_det_delay) |
+			SETTLING(ts->settling);
+	tsc_cfg_mask = AVE_CTRL(0xff) | DET_DELAY(0xff) | SETTLING(0xff);
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CFG, tsc_cfg_mask, tsc_cfg);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not config touch\n");
+		goto err_free_irq;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_FRACTION_Z,
+			FRACTION_Z(0xff), FRACTION_Z(ts->fraction_z));
+	if (ret) {
+		dev_err(&pdev->dev, "Could not config touch\n");
+		goto err_free_irq;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_I_DRIVE,
+			I_DRIVE(0xff), I_DRIVE(ts->i_drive));
+	if (ret) {
+		dev_err(&pdev->dev, "Could not config touch\n");
+		goto err_free_irq;
+	}
+
+	/* set FIFO to 1 for single point reading */
+	ret = stmpe_reg_write(stmpe, STMPE_REG_FIFO_TH, 1);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not set FIFO\n");
+		goto err_free_irq;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CTRL,
+			OP_MODE(0xff), OP_MODE(OP_MOD_XYZ));
+	if (ret) {
+		dev_err(&pdev->dev, "Could not set mode\n");
+		goto err_free_irq;
+	}
+
+	idev->name = STMPE_TS_NAME;
+	idev->id.bustype = BUS_I2C;
+	idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+	idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+	idev->open = stmpe_ts_open;
+	idev->close = stmpe_ts_close;
+
+	input_set_drvdata(idev, ts);
+
+	ret = input_register_device(idev);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not register input device\n");
+		goto err_free_irq;
+	}
+
+	input_set_abs_params(idev, ABS_X, 0, XY_MASK, 0, 0);
+	input_set_abs_params(idev, ABS_Y, 0, XY_MASK, 0, 0);
+	input_set_abs_params(idev, ABS_PRESSURE, 0x0, 0xff, 0, 0);
+
+	return ret;
+
+err_free_irq:
+	free_irq(ts_irq, ts);
+err_free_input:
+	input_free_device(idev);
+	platform_set_drvdata(pdev, NULL);
+err_free_ts:
+	kfree(ts);
+err_out:
+	return ret;
+}
+
+static int __devexit stmpe_ts_remove(struct platform_device *pdev)
+{
+	struct stmpe_touch *ts = platform_get_drvdata(pdev);
+	unsigned int ts_irq = platform_get_irq_byname(pdev, "FIFO_TH");
+
+	cancel_delayed_work(&ts->work);
+
+	stmpe_reg_write(ts->stmpe, STMPE_REG_FIFO_TH, 0);
+
+	stmpe_disable(ts->stmpe, STMPE_BLOCK_TOUCHSCREEN);
+
+	free_irq(ts_irq, ts);
+
+	platform_set_drvdata(pdev, NULL);
+
+	input_unregister_device(ts->idev);
+	input_free_device(ts->idev);
+
+	kfree(ts);
+
+	return 0;
+}
+
+static struct platform_driver stmpe_ts_driver = {
+	.driver = {
+		   .name = STMPE_TS_NAME,
+		   },
+	.probe = stmpe_input_probe,
+	.remove = __devexit_p(stmpe_ts_remove),
+};
+
+static int __init stmpe_ts_init(void)
+{
+	return platform_driver_register(&stmpe_ts_driver);
+}
+
+module_init(stmpe_ts_init);
+
+static void __exit stmpe_ts_exit(void)
+{
+	platform_driver_unregister(&stmpe_ts_driver);
+}
+
+module_exit(stmpe_ts_exit);
+
+MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
+MODULE_DESCRIPTION("STMPEXXX touchscreen driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" STMPE_TS_NAME);
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* Re: [PATCH 5/5] input: STMPE touch controller support
  2010-06-24 14:26             ` [PATCH 5/5] " Luotao Fu
@ 2010-06-24 16:24               ` Dmitry Torokhov
  2010-06-24 16:57                 ` Luotao Fu
  2010-06-25  8:37                 ` [PATCH 5/5 V7] " Luotao Fu
  0 siblings, 2 replies; 61+ messages in thread
From: Dmitry Torokhov @ 2010-06-24 16:24 UTC (permalink / raw)
  To: Luotao Fu
  Cc: Rabin VINCENT, Samuel Ortiz, Linus WALLEIJ, linux-kernel,
	linux-input, STEricsson_nomadik_linux

Hi Luotao,

On Thu, Jun 24, 2010 at 04:26:15PM +0200, Luotao Fu wrote:
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index 497964a..2691c62 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -47,3 +47,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE)	+= mainstone-wm97xx.o
>  obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE)	+= zylonite-wm97xx.o
>  obj-$(CONFIG_TOUCHSCREEN_W90X900)	+= w90p910_ts.o
>  obj-$(CONFIG_TOUCHSCREEN_TPS6507X)	+= tps6507x-ts.o
> +obj-$(CONFIG_TOUCHSCREEN_STMPE)		+= stmpe-ts.o

Please keep Makefile ordered alphabetically, it makes it easier to merge
stuff. Might try to do the same for Kconfig as well.

> +
> +static int inline __stmpe_reset_fifo(struct stmpe *stmpe)

Do not mark private function inline unless absolutely necessary - let
compiler figure out whether it makes sense to inline and what.

> +{
> +	int ret;
> +
> +	ret = stmpe_set_bits(stmpe, STMPE_REG_FIFO_STA,
> +			STMPE_FIFO_STA_RESET, STMPE_FIFO_STA_RESET);
> +	if (ret)
> +		return ret;
> +
> +	return stmpe_set_bits(stmpe, STMPE_REG_FIFO_STA,
> +			STMPE_FIFO_STA_RESET, 0);
> +}
> +
> +static void stmpe_work(struct work_struct *work)
> +{
> +	int int_sta;
> +	u32 timeout = 40;
> +
> +	struct stmpe_touch *ts =
> +	    container_of(work, struct stmpe_touch, work.work);
> +
> +	int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
> +
> +	/* touch_det sometimes get desasserted or just get stuck. This appears
> +	 * to be a silicon bug, We still have to clearify this with the
> +	 * manufacture. As a workaround We release the key anyway if the
> +	 * touch_det keeps coming in after 4ms, while the FIFO contains no value
> +	 * during the whole time. */

I would appreciate if we could maintain the following format for
multiline comments (within input at least):

/*
 * This is a long long
 * long comment.
 */

> +	while ((int_sta & (1 << STMPE_IRQ_TOUCH_DET)) && (timeout > 0)) {
> +		timeout--;
> +		int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
> +		udelay(100);
> +	}
> +
> +	/* reset the FIFO before we report release event */
> +	__stmpe_reset_fifo(ts->stmpe);
> +
> +	input_report_abs(ts->idev, ABS_PRESSURE, 0);
> +	input_sync(ts->idev);

Hmm, this function appears to be always reporting release, no matter
what the state of hardware is. I do not think this was the intention...
or was it?

> +}
> +
> +static irqreturn_t stmpe_ts_handler(int irq, void *data)
> +{
> +	u8 data_set[4];
> +	int x, y, z;
> +	struct stmpe_touch *ts = data;
> +
> +	/* Cancel scheduled polling for release if we have new value
> +	 * available. Wait if the polling is already running. */
> +	cancel_delayed_work_sync(&ts->work);
> +
> +	/*
> +	 * The FIFO sometimes just crashes and stops generating interrupts. This
> +	 * appears to be a silicon bug. We still have to clearify this with
> +	 * the manufacture. As a workaround we disable the TSC while we are
> +	 * collecting data and flush the FIFO after reading
> +	 */
> +	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
> +				STMPE_TSC_CTRL_TSC_EN, 0);
> +
> +	stmpe_block_read(ts->stmpe, STMPE_REG_TSC_DATA_XYZ, 4, data_set);
> +
> +	x = (data_set[0] << 4) | (data_set[1] >> 4);
> +	y = ((data_set[1] & 0xf) << 8) | data_set[2];
> +	z = data_set[3];
> +
> +	input_report_abs(ts->idev, ABS_X, x);
> +	input_report_abs(ts->idev, ABS_Y, y);
> +	input_report_abs(ts->idev, ABS_PRESSURE, z);
> +	input_sync(ts->idev);
> +
> +       /* flush the FIFO after we have read out our values. */
> +	__stmpe_reset_fifo(ts->stmpe);
> +
> +	/* reenable the tsc */
> +	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
> +			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
> +
> +	/* start polling for touch_det to detect release */
> +	schedule_delayed_work(&ts->work, HZ / 50);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int stmpe_ts_open(struct input_dev *dev)
> +{
> +	struct stmpe_touch *ts = input_get_drvdata(dev);
> +	int ret = 0;
> +
> +	ret = __stmpe_reset_fifo(ts->stmpe);
> +	if (ret)
> +		return ret;
> +
> +	return stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
> +			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
> +}
> +
> +static void stmpe_ts_close(struct input_dev *dev)
> +{
> +	struct stmpe_touch *ts = input_get_drvdata(dev);
> +
> +	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
> +			STMPE_TSC_CTRL_TSC_EN, 0);
> +}
> +
> +static int __devinit stmpe_input_probe(struct platform_device *pdev)
> +{
> +	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
> +	struct stmpe_platform_data *pdata = stmpe->pdata;
> +	struct stmpe_touch *ts;
> +	struct input_dev *idev;
> +	struct stmpe_ts_platform_data *ts_pdata = NULL;
> +	int ret = 0;
> +	unsigned int ts_irq;
> +	u8 adc_ctrl1, adc_ctrl1_mask, tsc_cfg, tsc_cfg_mask;
> +
> +	ts_irq = platform_get_irq_byname(pdev, "FIFO_TH");
> +	if (ts_irq < 0)
> +		return ts_irq;
> +
> +	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
> +	if (!ts)
> +		goto err_out;
> +
> +	idev = input_allocate_device();
> +	if (!idev)
> +		goto err_free_ts;
> +
> +	platform_set_drvdata(pdev, ts);
> +	ts->stmpe = stmpe;
> +	ts->idev = idev;
> +
> +	if (pdata)
> +		ts_pdata = pdata->ts;
> +
> +	if (ts_pdata) {
> +		ts->sample_time = ts_pdata->sample_time;
> +		ts->mod_12b = ts_pdata->mod_12b;
> +		ts->ref_sel = ts_pdata->ref_sel;
> +		ts->adc_freq = ts_pdata->adc_freq;
> +		ts->ave_ctrl = ts_pdata->ave_ctrl;
> +		ts->touch_det_delay = ts_pdata->touch_det_delay;
> +		ts->settling = ts_pdata->settling;
> +		ts->fraction_z = ts_pdata->fraction_z;
> +		ts->i_drive = ts_pdata->i_drive;
> +	}
> +
> +	INIT_DELAYED_WORK(&ts->work, stmpe_work);
> +
> +	ret = request_threaded_irq(ts_irq, NULL, stmpe_ts_handler,
> +			IRQF_ONESHOT, STMPE_TS_NAME, ts);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to request IRQ %d\n", ts_irq);
> +		goto err_free_input;
> +	}
> +
> +	ret = stmpe_enable(stmpe, STMPE_BLOCK_TOUCHSCREEN | STMPE_BLOCK_ADC);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Could not enable clock for ADC and TS\n");
> +		goto err_free_irq;
> +	}
> +
> +	adc_ctrl1 = SAMPLE_TIME(ts->sample_time) | MOD_12B(ts->mod_12b) |
> +		REF_SEL(ts->ref_sel);
> +	adc_ctrl1_mask = SAMPLE_TIME(0xff) | MOD_12B(0xff) | REF_SEL(0xff);
> +	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL1,
> +			adc_ctrl1_mask, adc_ctrl1);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Could not setup ADC\n");
> +		goto err_free_irq;
> +	}
> +
> +	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL2,
> +			ADC_FREQ(0xff), ADC_FREQ(ts->adc_freq));
> +	if (ret) {
> +		dev_err(&pdev->dev, "Could not setup ADC\n");
> +		goto err_free_irq;
> +	}
> +
> +	tsc_cfg = AVE_CTRL(ts->ave_ctrl) | DET_DELAY(ts->touch_det_delay) |
> +			SETTLING(ts->settling);
> +	tsc_cfg_mask = AVE_CTRL(0xff) | DET_DELAY(0xff) | SETTLING(0xff);
> +
> +	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CFG, tsc_cfg_mask, tsc_cfg);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Could not config touch\n");
> +		goto err_free_irq;
> +	}
> +
> +	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_FRACTION_Z,
> +			FRACTION_Z(0xff), FRACTION_Z(ts->fraction_z));
> +	if (ret) {
> +		dev_err(&pdev->dev, "Could not config touch\n");
> +		goto err_free_irq;
> +	}
> +
> +	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_I_DRIVE,
> +			I_DRIVE(0xff), I_DRIVE(ts->i_drive));
> +	if (ret) {
> +		dev_err(&pdev->dev, "Could not config touch\n");
> +		goto err_free_irq;
> +	}
> +
> +	/* set FIFO to 1 for single point reading */
> +	ret = stmpe_reg_write(stmpe, STMPE_REG_FIFO_TH, 1);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Could not set FIFO\n");
> +		goto err_free_irq;
> +	}
> +
> +	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CTRL,
> +			OP_MODE(0xff), OP_MODE(OP_MOD_XYZ));
> +	if (ret) {
> +		dev_err(&pdev->dev, "Could not set mode\n");
> +		goto err_free_irq;
> +	}


Could we pull all these stmpe_set_bits(...) settig up the hardware into
a separate function?

> +
> +	idev->name = STMPE_TS_NAME;
> +	idev->id.bustype = BUS_I2C;
> +	idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
> +	idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
> +
> +	idev->open = stmpe_ts_open;
> +	idev->close = stmpe_ts_close;
> +
> +	input_set_drvdata(idev, ts);
> +
> +	ret = input_register_device(idev);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Could not register input device\n");
> +		goto err_free_irq;
> +	}
> +
> +	input_set_abs_params(idev, ABS_X, 0, XY_MASK, 0, 0);
> +	input_set_abs_params(idev, ABS_Y, 0, XY_MASK, 0, 0);
> +	input_set_abs_params(idev, ABS_PRESSURE, 0x0, 0xff, 0, 0);

These calls better happen before registering the device.

> +
> +	return ret;
> +
> +err_free_irq:
> +	free_irq(ts_irq, ts);
> +err_free_input:
> +	input_free_device(idev);
> +	platform_set_drvdata(pdev, NULL);
> +err_free_ts:
> +	kfree(ts);
> +err_out:
> +	return ret;
> +}
> +
> +static int __devexit stmpe_ts_remove(struct platform_device *pdev)
> +{
> +	struct stmpe_touch *ts = platform_get_drvdata(pdev);
> +	unsigned int ts_irq = platform_get_irq_byname(pdev, "FIFO_TH");
> +
> +	cancel_delayed_work(&ts->work);

cancel_delayed_work_sync().

> +
> +	stmpe_reg_write(ts->stmpe, STMPE_REG_FIFO_TH, 0);
> +
> +	stmpe_disable(ts->stmpe, STMPE_BLOCK_TOUCHSCREEN);
> +

I'd expect the 3 calls above being part of stmpe_ts_close().

> +	free_irq(ts_irq, ts);
> +
> +	platform_set_drvdata(pdev, NULL);
> +
> +	input_unregister_device(ts->idev);
> +	input_free_device(ts->idev);
> +
> +	kfree(ts);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver stmpe_ts_driver = {
> +	.driver = {
> +		   .name = STMPE_TS_NAME,

		.owner = THIS_MODULE,

> +		   },
> +	.probe = stmpe_input_probe,
> +	.remove = __devexit_p(stmpe_ts_remove),

No PM? Or simply not yet?

> +};
> +
> +static int __init stmpe_ts_init(void)
> +{
> +	return platform_driver_register(&stmpe_ts_driver);
> +}
> +
> +module_init(stmpe_ts_init);
> +
> +static void __exit stmpe_ts_exit(void)
> +{
> +	platform_driver_unregister(&stmpe_ts_driver);
> +}
> +
> +module_exit(stmpe_ts_exit);
> +
> +MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
> +MODULE_DESCRIPTION("STMPEXXX touchscreen driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" STMPE_TS_NAME);
> -- 
> 1.7.1
> 

Thanks.

-- 
Dmitry

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 5/5] input: STMPE touch controller support
  2010-06-24 16:24               ` Dmitry Torokhov
@ 2010-06-24 16:57                 ` Luotao Fu
  2010-06-25  8:37                 ` [PATCH 5/5 V7] " Luotao Fu
  1 sibling, 0 replies; 61+ messages in thread
From: Luotao Fu @ 2010-06-24 16:57 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Luotao Fu, Rabin VINCENT, Samuel Ortiz, Linus WALLEIJ,
	linux-kernel, linux-input, STEricsson_nomadik_linux

[-- Attachment #1: Type: text/plain, Size: 12490 bytes --]

Hi Dmitry,

thanks for reviewing the patch.

On Thu, Jun 24, 2010 at 09:24:07AM -0700, Dmitry Torokhov wrote:
> Hi Luotao,
> 
> On Thu, Jun 24, 2010 at 04:26:15PM +0200, Luotao Fu wrote:
> > diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> > index 497964a..2691c62 100644
> > --- a/drivers/input/touchscreen/Makefile
> > +++ b/drivers/input/touchscreen/Makefile
> > @@ -47,3 +47,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE)	+= mainstone-wm97xx.o
> >  obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE)	+= zylonite-wm97xx.o
> >  obj-$(CONFIG_TOUCHSCREEN_W90X900)	+= w90p910_ts.o
> >  obj-$(CONFIG_TOUCHSCREEN_TPS6507X)	+= tps6507x-ts.o
> > +obj-$(CONFIG_TOUCHSCREEN_STMPE)		+= stmpe-ts.o
> 
> Please keep Makefile ordered alphabetically, it makes it easier to merge
> stuff. Might try to do the same for Kconfig as well.
> 

OK, will fix.
> > +
> > +static int inline __stmpe_reset_fifo(struct stmpe *stmpe)
> 
> Do not mark private function inline unless absolutely necessary - let
> compiler figure out whether it makes sense to inline and what.
> 
OK, will fix.
> > +{
> > +	int ret;
> > +
> > +	ret = stmpe_set_bits(stmpe, STMPE_REG_FIFO_STA,
> > +			STMPE_FIFO_STA_RESET, STMPE_FIFO_STA_RESET);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return stmpe_set_bits(stmpe, STMPE_REG_FIFO_STA,
> > +			STMPE_FIFO_STA_RESET, 0);
> > +}
> > +
> > +static void stmpe_work(struct work_struct *work)
> > +{
> > +	int int_sta;
> > +	u32 timeout = 40;
> > +
> > +	struct stmpe_touch *ts =
> > +	    container_of(work, struct stmpe_touch, work.work);
> > +
> > +	int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
> > +
> > +	/* touch_det sometimes get desasserted or just get stuck. This appears
> > +	 * to be a silicon bug, We still have to clearify this with the
> > +	 * manufacture. As a workaround We release the key anyway if the
> > +	 * touch_det keeps coming in after 4ms, while the FIFO contains no value
> > +	 * during the whole time. */
> 
> I would appreciate if we could maintain the following format for
> multiline comments (within input at least):
> 
> /*
>  * This is a long long
>  * long comment.
>  */
> 

OK, will fix.
> > +	while ((int_sta & (1 << STMPE_IRQ_TOUCH_DET)) && (timeout > 0)) {
> > +		timeout--;
> > +		int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
> > +		udelay(100);
> > +	}
> > +
> > +	/* reset the FIFO before we report release event */
> > +	__stmpe_reset_fifo(ts->stmpe);
> > +
> > +	input_report_abs(ts->idev, ABS_PRESSURE, 0);
> > +	input_sync(ts->idev);
> 
> Hmm, this function appears to be always reporting release, no matter
> what the state of hardware is. I do not think this was the intention...
> or was it?
> 

It unfortunately is. The original idea was polling for touch_det
and only report a release if the timeout was not reached. Unfortunately
the chip just sometime runs amok and never clears the touch_det
carelessly what happend. The only way here is report release any way
both after successed poll or timeout. This works fine, though the
polling sure looks unneccessary. I, however, would like to keep this
since I suppose a silicon bug here. Until the issue is cleared with the
vendor. Hence I'd like to have the polling for wait_det here though it
is sometimes useless.

> > +}
> > +
> > +static irqreturn_t stmpe_ts_handler(int irq, void *data)
> > +{
> > +	u8 data_set[4];
> > +	int x, y, z;
> > +	struct stmpe_touch *ts = data;
> > +
> > +	/* Cancel scheduled polling for release if we have new value
> > +	 * available. Wait if the polling is already running. */
> > +	cancel_delayed_work_sync(&ts->work);
> > +
> > +	/*
> > +	 * The FIFO sometimes just crashes and stops generating interrupts. This
> > +	 * appears to be a silicon bug. We still have to clearify this with
> > +	 * the manufacture. As a workaround we disable the TSC while we are
> > +	 * collecting data and flush the FIFO after reading
> > +	 */
> > +	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
> > +				STMPE_TSC_CTRL_TSC_EN, 0);
> > +
> > +	stmpe_block_read(ts->stmpe, STMPE_REG_TSC_DATA_XYZ, 4, data_set);
> > +
> > +	x = (data_set[0] << 4) | (data_set[1] >> 4);
> > +	y = ((data_set[1] & 0xf) << 8) | data_set[2];
> > +	z = data_set[3];
> > +
> > +	input_report_abs(ts->idev, ABS_X, x);
> > +	input_report_abs(ts->idev, ABS_Y, y);
> > +	input_report_abs(ts->idev, ABS_PRESSURE, z);
> > +	input_sync(ts->idev);
> > +
> > +       /* flush the FIFO after we have read out our values. */
> > +	__stmpe_reset_fifo(ts->stmpe);
> > +
> > +	/* reenable the tsc */
> > +	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
> > +			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
> > +
> > +	/* start polling for touch_det to detect release */
> > +	schedule_delayed_work(&ts->work, HZ / 50);
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +static int stmpe_ts_open(struct input_dev *dev)
> > +{
> > +	struct stmpe_touch *ts = input_get_drvdata(dev);
> > +	int ret = 0;
> > +
> > +	ret = __stmpe_reset_fifo(ts->stmpe);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
> > +			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
> > +}
> > +
> > +static void stmpe_ts_close(struct input_dev *dev)
> > +{
> > +	struct stmpe_touch *ts = input_get_drvdata(dev);
> > +
> > +	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
> > +			STMPE_TSC_CTRL_TSC_EN, 0);
> > +}
> > +
> > +static int __devinit stmpe_input_probe(struct platform_device *pdev)
> > +{
> > +	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
> > +	struct stmpe_platform_data *pdata = stmpe->pdata;
> > +	struct stmpe_touch *ts;
> > +	struct input_dev *idev;
> > +	struct stmpe_ts_platform_data *ts_pdata = NULL;
> > +	int ret = 0;
> > +	unsigned int ts_irq;
> > +	u8 adc_ctrl1, adc_ctrl1_mask, tsc_cfg, tsc_cfg_mask;
> > +
> > +	ts_irq = platform_get_irq_byname(pdev, "FIFO_TH");
> > +	if (ts_irq < 0)
> > +		return ts_irq;
> > +
> > +	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
> > +	if (!ts)
> > +		goto err_out;
> > +
> > +	idev = input_allocate_device();
> > +	if (!idev)
> > +		goto err_free_ts;
> > +
> > +	platform_set_drvdata(pdev, ts);
> > +	ts->stmpe = stmpe;
> > +	ts->idev = idev;
> > +
> > +	if (pdata)
> > +		ts_pdata = pdata->ts;
> > +
> > +	if (ts_pdata) {
> > +		ts->sample_time = ts_pdata->sample_time;
> > +		ts->mod_12b = ts_pdata->mod_12b;
> > +		ts->ref_sel = ts_pdata->ref_sel;
> > +		ts->adc_freq = ts_pdata->adc_freq;
> > +		ts->ave_ctrl = ts_pdata->ave_ctrl;
> > +		ts->touch_det_delay = ts_pdata->touch_det_delay;
> > +		ts->settling = ts_pdata->settling;
> > +		ts->fraction_z = ts_pdata->fraction_z;
> > +		ts->i_drive = ts_pdata->i_drive;
> > +	}
> > +
> > +	INIT_DELAYED_WORK(&ts->work, stmpe_work);
> > +
> > +	ret = request_threaded_irq(ts_irq, NULL, stmpe_ts_handler,
> > +			IRQF_ONESHOT, STMPE_TS_NAME, ts);
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Failed to request IRQ %d\n", ts_irq);
> > +		goto err_free_input;
> > +	}
> > +
> > +	ret = stmpe_enable(stmpe, STMPE_BLOCK_TOUCHSCREEN | STMPE_BLOCK_ADC);
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Could not enable clock for ADC and TS\n");
> > +		goto err_free_irq;
> > +	}
> > +
> > +	adc_ctrl1 = SAMPLE_TIME(ts->sample_time) | MOD_12B(ts->mod_12b) |
> > +		REF_SEL(ts->ref_sel);
> > +	adc_ctrl1_mask = SAMPLE_TIME(0xff) | MOD_12B(0xff) | REF_SEL(0xff);
> > +	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL1,
> > +			adc_ctrl1_mask, adc_ctrl1);
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Could not setup ADC\n");
> > +		goto err_free_irq;
> > +	}
> > +
> > +	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL2,
> > +			ADC_FREQ(0xff), ADC_FREQ(ts->adc_freq));
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Could not setup ADC\n");
> > +		goto err_free_irq;
> > +	}
> > +
> > +	tsc_cfg = AVE_CTRL(ts->ave_ctrl) | DET_DELAY(ts->touch_det_delay) |
> > +			SETTLING(ts->settling);
> > +	tsc_cfg_mask = AVE_CTRL(0xff) | DET_DELAY(0xff) | SETTLING(0xff);
> > +
> > +	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CFG, tsc_cfg_mask, tsc_cfg);
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Could not config touch\n");
> > +		goto err_free_irq;
> > +	}
> > +
> > +	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_FRACTION_Z,
> > +			FRACTION_Z(0xff), FRACTION_Z(ts->fraction_z));
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Could not config touch\n");
> > +		goto err_free_irq;
> > +	}
> > +
> > +	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_I_DRIVE,
> > +			I_DRIVE(0xff), I_DRIVE(ts->i_drive));
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Could not config touch\n");
> > +		goto err_free_irq;
> > +	}
> > +
> > +	/* set FIFO to 1 for single point reading */
> > +	ret = stmpe_reg_write(stmpe, STMPE_REG_FIFO_TH, 1);
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Could not set FIFO\n");
> > +		goto err_free_irq;
> > +	}
> > +
> > +	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CTRL,
> > +			OP_MODE(0xff), OP_MODE(OP_MOD_XYZ));
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Could not set mode\n");
> > +		goto err_free_irq;
> > +	}
> 
> 
> Could we pull all these stmpe_set_bits(...) settig up the hardware into
> a separate function?
> 

OK, will add a call ala stmpe_init_hw().

> > +
> > +	idev->name = STMPE_TS_NAME;
> > +	idev->id.bustype = BUS_I2C;
> > +	idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
> > +	idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
> > +
> > +	idev->open = stmpe_ts_open;
> > +	idev->close = stmpe_ts_close;
> > +
> > +	input_set_drvdata(idev, ts);
> > +
> > +	ret = input_register_device(idev);
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Could not register input device\n");
> > +		goto err_free_irq;
> > +	}
> > +
> > +	input_set_abs_params(idev, ABS_X, 0, XY_MASK, 0, 0);
> > +	input_set_abs_params(idev, ABS_Y, 0, XY_MASK, 0, 0);
> > +	input_set_abs_params(idev, ABS_PRESSURE, 0x0, 0xff, 0, 0);
> 
> These calls better happen before registering the device.
> 

OK. will fix.

> > +
> > +	return ret;
> > +
> > +err_free_irq:
> > +	free_irq(ts_irq, ts);
> > +err_free_input:
> > +	input_free_device(idev);
> > +	platform_set_drvdata(pdev, NULL);
> > +err_free_ts:
> > +	kfree(ts);
> > +err_out:
> > +	return ret;
> > +}
> > +
> > +static int __devexit stmpe_ts_remove(struct platform_device *pdev)
> > +{
> > +	struct stmpe_touch *ts = platform_get_drvdata(pdev);
> > +	unsigned int ts_irq = platform_get_irq_byname(pdev, "FIFO_TH");
> > +
> > +	cancel_delayed_work(&ts->work);
> 
> cancel_delayed_work_sync().
> 

Ah, forgot this, will fix.

> > +
> > +	stmpe_reg_write(ts->stmpe, STMPE_REG_FIFO_TH, 0);
> > +
> > +	stmpe_disable(ts->stmpe, STMPE_BLOCK_TOUCHSCREEN);
> > +
> 
> I'd expect the 3 calls above being part of stmpe_ts_close().
> 

OK, will fix.

> > +	free_irq(ts_irq, ts);
> > +
> > +	platform_set_drvdata(pdev, NULL);
> > +
> > +	input_unregister_device(ts->idev);
> > +	input_free_device(ts->idev);
> > +
> > +	kfree(ts);
> > +
> > +	return 0;
> > +}
> > +
> > +static struct platform_driver stmpe_ts_driver = {
> > +	.driver = {
> > +		   .name = STMPE_TS_NAME,
> 
> 		.owner = THIS_MODULE,
> 
> > +		   },
> > +	.probe = stmpe_input_probe,
> > +	.remove = __devexit_p(stmpe_ts_remove),
> 
> No PM? Or simply not yet?
> 

not yet. ;-) will add this in a later patch.

> > +};
> > +
> > +static int __init stmpe_ts_init(void)
> > +{
> > +	return platform_driver_register(&stmpe_ts_driver);
> > +}
> > +
> > +module_init(stmpe_ts_init);
> > +
> > +static void __exit stmpe_ts_exit(void)
> > +{
> > +	platform_driver_unregister(&stmpe_ts_driver);
> > +}
> > +
> > +module_exit(stmpe_ts_exit);
> > +
> > +MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
> > +MODULE_DESCRIPTION("STMPEXXX touchscreen driver");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:" STMPE_TS_NAME);
> > -- 
> > 1.7.1
> > 
> 
> Thanks.

thanks

> 
> -- 
> Dmitry

cheers
Luotao Fu
-- 
Pengutronix e.K.                           | Dipl.-Ing. Luotao Fu        |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 61+ messages in thread

* [PATCH 5/5 V7] input: STMPE touch controller support
  2010-06-24 16:24               ` Dmitry Torokhov
  2010-06-24 16:57                 ` Luotao Fu
@ 2010-06-25  8:37                 ` Luotao Fu
  2010-06-25  9:11                   ` Dmitry Torokhov
  2010-06-25  9:34                   ` [PATCH 5/5 V8] " Luotao Fu
  1 sibling, 2 replies; 61+ messages in thread
From: Luotao Fu @ 2010-06-25  8:37 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Rabin VINCENT, Samuel Ortiz, Linus WALLEIJ, linux-kernel,
	linux-input, STEricsson_nomadik_linux, Luotao Fu

This one adds a driver for STMPE touchscreen controllers. This driver depends on
stmpexxx mfd core driver.

Signed-off-by: Luotao Fu <l.fu@pengutronix.de>
---
V2 Changes:
* include subsystem headers since they are now remove from the mfd
  core header file
* use genirq to register threaded isr. The stmpe811 own irq callbacks
  are dropped in the core driver.
* use STMPE811_TS_NAME for name all over the place
* wait for completion of core IO operation before cancelling polling of
  release event.
* switch to platform data for configuration parameters.
* add FIFO reset to open callback, also reset FIFO before reporting
  release.

V3 Changes:
* reformated platform data comments to kernel-doc style

V4 Changes:
* converted to stmpexxx core by Rabin Vincent <rabin.vincent@stericsson.com>
* wait for running workqueue callback to finish while canceling the queue
* add i_drive configuration support

V5 Changes:
* add ADC block enable to stmpe_enable call.

V6 Changes:
* add _reset_fifo callback to reduce duplicated code.
* fix mask usage while setting bits in probe.
* acquire platform irq by name

V7 Changes:
* reranged entry makefile in alphabetical order
* move hw init stuff into a own function
* fix queue canceling in close. remove uneccessary register settings from remove
* removed inline declaration
* add driver owner
* set input params before registering the device

 drivers/input/touchscreen/Kconfig    |   10 +
 drivers/input/touchscreen/Makefile   |    1 +
 drivers/input/touchscreen/stmpe-ts.c |  397 ++++++++++++++++++++++++++++++++++
 3 files changed, 408 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/touchscreen/stmpe-ts.c

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 3b9d5e2..96a9954 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -603,4 +603,14 @@ config TOUCHSCREEN_TPS6507X
 	  To compile this driver as a module, choose M here: the
 	  module will be called tps6507x_ts.
 
+config TOUCHSCREEN_STMPE
+	tristate "STMicroelectronics STMPE touchscreens"
+	depends on MFD_STMPE
+	help
+	  Say Y here if you want support for STMicroelectronics
+	  STMPE touchscreen controllers.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called stmpe-ts.
+
 endif
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 497964a..10fb163 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_TOUCHSCREEN_USB_COMPOSITE)	+= usbtouchscreen.o
 obj-$(CONFIG_TOUCHSCREEN_PCAP)		+= pcap_ts.o
 obj-$(CONFIG_TOUCHSCREEN_PENMOUNT)	+= penmount.o
 obj-$(CONFIG_TOUCHSCREEN_S3C2410)	+= s3c2410_ts.o
+obj-$(CONFIG_TOUCHSCREEN_STMPE)		+= stmpe-ts.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213)	+= touchit213.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT)	+= touchright.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN)	+= touchwin.o
diff --git a/drivers/input/touchscreen/stmpe-ts.c b/drivers/input/touchscreen/stmpe-ts.c
new file mode 100644
index 0000000..f5c1d88
--- /dev/null
+++ b/drivers/input/touchscreen/stmpe-ts.c
@@ -0,0 +1,397 @@
+/* STMicroelectronics STMPE811 Touchscreen Driver
+ *
+ * (C) 2010 Luotao Fu <l.fu@pengutronix.de>
+ * All rights reserved.
+ *
+ *  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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/stmpe.h>
+
+/* Register layouts and functionalities are identical on all stmpexxx variants
+ * with touchscreen controller
+ */
+#define STMPE_REG_INT_STA		0x0B
+#define STMPE_REG_ADC_CTRL1		0x20
+#define STMPE_REG_ADC_CTRL2		0x21
+#define STMPE_REG_TSC_CTRL		0x40
+#define STMPE_REG_TSC_CFG		0x41
+#define STMPE_REG_FIFO_TH		0x4A
+#define STMPE_REG_FIFO_STA		0x4B
+#define STMPE_REG_FIFO_SIZE		0x4C
+#define STMPE_REG_TSC_DATA_XYZ		0x52
+#define STMPE_REG_TSC_FRACTION_Z	0x56
+#define STMPE_REG_TSC_I_DRIVE		0x58
+
+#define OP_MOD_XYZ			0
+
+#define STMPE_TSC_CTRL_TSC_EN		(1<<0)
+
+#define STMPE_FIFO_STA_RESET		(1<<0)
+
+#define STMPE_IRQ_TOUCH_DET		0
+
+#define SAMPLE_TIME(x)			((x & 0xf) << 4)
+#define MOD_12B(x)			((x & 0x1) << 3)
+#define REF_SEL(x)			((x & 0x1) << 1)
+#define ADC_FREQ(x)			(x & 0x3)
+#define AVE_CTRL(x)			((x & 0x3) << 6)
+#define DET_DELAY(x)			((x & 0x7) << 3)
+#define SETTLING(x)			(x & 0x7)
+#define FRACTION_Z(x)			(x & 0x7)
+#define I_DRIVE(x)			(x & 0x1)
+#define OP_MODE(x)			((x & 0x7) << 1)
+
+#define STMPE_TS_NAME			"stmpe-ts"
+#define XY_MASK				0xfff
+
+struct stmpe_touch {
+	struct stmpe *stmpe;
+	struct input_dev *idev;
+	struct delayed_work work;
+	struct device *dev;
+	u8 sample_time;
+	u8 mod_12b;
+	u8 ref_sel;
+	u8 adc_freq;
+	u8 ave_ctrl;
+	u8 touch_det_delay;
+	u8 settling;
+	u8 fraction_z;
+	u8 i_drive;
+};
+
+static int __stmpe_reset_fifo(struct stmpe *stmpe)
+{
+	int ret;
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, STMPE_FIFO_STA_RESET);
+	if (ret)
+		return ret;
+
+	return stmpe_set_bits(stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, 0);
+}
+
+static void stmpe_work(struct work_struct *work)
+{
+	int int_sta;
+	u32 timeout = 40;
+
+	struct stmpe_touch *ts =
+	    container_of(work, struct stmpe_touch, work.work);
+
+	int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
+
+	/* 
+	 * touch_det sometimes get desasserted or just get stuck. This appears
+	 * to be a silicon bug, We still have to clearify this with the
+	 * manufacture. As a workaround We release the key anyway if the
+	 * touch_det keeps coming in after 4ms, while the FIFO contains no value
+	 * during the whole time. 
+	 */
+	while ((int_sta & (1 << STMPE_IRQ_TOUCH_DET)) && (timeout > 0)) {
+		timeout--;
+		int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
+		udelay(100);
+	}
+
+	/* reset the FIFO before we report release event */
+	__stmpe_reset_fifo(ts->stmpe);
+
+	input_report_abs(ts->idev, ABS_PRESSURE, 0);
+	input_sync(ts->idev);
+}
+
+static irqreturn_t stmpe_ts_handler(int irq, void *data)
+{
+	u8 data_set[4];
+	int x, y, z;
+	struct stmpe_touch *ts = data;
+
+	/*
+	 * Cancel scheduled polling for release if we have new value
+	 * available. Wait if the polling is already running.
+	 */
+	cancel_delayed_work_sync(&ts->work);
+
+	/*
+	 * The FIFO sometimes just crashes and stops generating interrupts. This
+	 * appears to be a silicon bug. We still have to clearify this with
+	 * the manufacture. As a workaround we disable the TSC while we are
+	 * collecting data and flush the FIFO after reading
+	 */
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+				STMPE_TSC_CTRL_TSC_EN, 0);
+
+	stmpe_block_read(ts->stmpe, STMPE_REG_TSC_DATA_XYZ, 4, data_set);
+
+	x = (data_set[0] << 4) | (data_set[1] >> 4);
+	y = ((data_set[1] & 0xf) << 8) | data_set[2];
+	z = data_set[3];
+
+	input_report_abs(ts->idev, ABS_X, x);
+	input_report_abs(ts->idev, ABS_Y, y);
+	input_report_abs(ts->idev, ABS_PRESSURE, z);
+	input_sync(ts->idev);
+
+       /* flush the FIFO after we have read out our values. */
+	__stmpe_reset_fifo(ts->stmpe);
+
+	/* reenable the tsc */
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
+
+	/* start polling for touch_det to detect release */
+	schedule_delayed_work(&ts->work, HZ / 50);
+
+	return IRQ_HANDLED;
+}
+
+static int stmpe_init_hw(struct stmpe_touch *ts)
+{
+	int ret;
+	u8 adc_ctrl1, adc_ctrl1_mask, tsc_cfg, tsc_cfg_mask;
+	struct stmpe *stmpe = ts->stmpe;
+	struct device *dev = ts->dev;
+
+	ret = stmpe_enable(stmpe, STMPE_BLOCK_TOUCHSCREEN | STMPE_BLOCK_ADC);
+	if (ret) {
+		dev_err(dev, "Could not enable clock for ADC and TS\n");
+		return ret;
+	}
+
+	adc_ctrl1 = SAMPLE_TIME(ts->sample_time) | MOD_12B(ts->mod_12b) |
+		REF_SEL(ts->ref_sel);
+	adc_ctrl1_mask = SAMPLE_TIME(0xff) | MOD_12B(0xff) | REF_SEL(0xff);
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL1,
+			adc_ctrl1_mask, adc_ctrl1);
+	if (ret) {
+		dev_err(dev, "Could not setup ADC\n");
+		return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL2,
+			ADC_FREQ(0xff), ADC_FREQ(ts->adc_freq));
+	if (ret) {
+		dev_err(dev, "Could not setup ADC\n");
+		return ret;
+	}
+
+	tsc_cfg = AVE_CTRL(ts->ave_ctrl) | DET_DELAY(ts->touch_det_delay) |
+			SETTLING(ts->settling);
+	tsc_cfg_mask = AVE_CTRL(0xff) | DET_DELAY(0xff) | SETTLING(0xff);
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CFG, tsc_cfg_mask, tsc_cfg);
+	if (ret) {
+		dev_err(dev, "Could not config touch\n");
+		return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_FRACTION_Z,
+			FRACTION_Z(0xff), FRACTION_Z(ts->fraction_z));
+	if (ret) {
+		dev_err(dev, "Could not config touch\n");
+		return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_I_DRIVE,
+			I_DRIVE(0xff), I_DRIVE(ts->i_drive));
+	if (ret) {
+		dev_err(dev, "Could not config touch\n");
+		return ret;
+	}
+
+	/* set FIFO to 1 for single point reading */
+	ret = stmpe_reg_write(stmpe, STMPE_REG_FIFO_TH, 1);
+	if (ret) {
+		dev_err(dev, "Could not set FIFO\n");
+		return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CTRL,
+			OP_MODE(0xff), OP_MODE(OP_MOD_XYZ));
+	if (ret) {
+		dev_err(dev, "Could not set mode\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int stmpe_ts_open(struct input_dev *dev)
+{
+	struct stmpe_touch *ts = input_get_drvdata(dev);
+	int ret = 0;
+
+	ret = __stmpe_reset_fifo(ts->stmpe);
+	if (ret)
+		return ret;
+
+	return stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
+}
+
+static void stmpe_ts_close(struct input_dev *dev)
+{
+	struct stmpe_touch *ts = input_get_drvdata(dev);
+
+	cancel_delayed_work_sync(&ts->work);
+
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, 0);
+}
+
+static int __devinit stmpe_input_probe(struct platform_device *pdev)
+{
+	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+	struct stmpe_platform_data *pdata = stmpe->pdata;
+	struct stmpe_touch *ts;
+	struct input_dev *idev;
+	struct stmpe_ts_platform_data *ts_pdata = NULL;
+	int ret = 0;
+	unsigned int ts_irq;
+
+	ts_irq = platform_get_irq_byname(pdev, "FIFO_TH");
+	if (ts_irq < 0)
+		return ts_irq;
+
+	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+	if (!ts)
+		goto err_out;
+
+	idev = input_allocate_device();
+	if (!idev)
+		goto err_free_ts;
+
+	platform_set_drvdata(pdev, ts);
+	ts->stmpe = stmpe;
+	ts->idev = idev;
+	ts->dev = &pdev->dev;
+
+	if (pdata)
+		ts_pdata = pdata->ts;
+
+	if (ts_pdata) {
+		ts->sample_time = ts_pdata->sample_time;
+		ts->mod_12b = ts_pdata->mod_12b;
+		ts->ref_sel = ts_pdata->ref_sel;
+		ts->adc_freq = ts_pdata->adc_freq;
+		ts->ave_ctrl = ts_pdata->ave_ctrl;
+		ts->touch_det_delay = ts_pdata->touch_det_delay;
+		ts->settling = ts_pdata->settling;
+		ts->fraction_z = ts_pdata->fraction_z;
+		ts->i_drive = ts_pdata->i_drive;
+	}
+
+	INIT_DELAYED_WORK(&ts->work, stmpe_work);
+
+	ret = request_threaded_irq(ts_irq, NULL, stmpe_ts_handler,
+			IRQF_ONESHOT, STMPE_TS_NAME, ts);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request IRQ %d\n", ts_irq);
+		goto err_free_input;
+	}
+
+	ret = stmpe_init_hw(ts);
+	if (ret)
+		goto err_free_irq;
+
+	idev->name = STMPE_TS_NAME;
+	idev->id.bustype = BUS_I2C;
+	idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+	idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+	idev->open = stmpe_ts_open;
+	idev->close = stmpe_ts_close;
+
+	input_set_drvdata(idev, ts);
+
+	input_set_abs_params(idev, ABS_X, 0, XY_MASK, 0, 0);
+	input_set_abs_params(idev, ABS_Y, 0, XY_MASK, 0, 0);
+	input_set_abs_params(idev, ABS_PRESSURE, 0x0, 0xff, 0, 0);
+
+	ret = input_register_device(idev);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not register input device\n");
+		goto err_free_irq;
+	}
+
+	return ret;
+
+err_free_irq:
+	free_irq(ts_irq, ts);
+err_free_input:
+	input_free_device(idev);
+	platform_set_drvdata(pdev, NULL);
+err_free_ts:
+	kfree(ts);
+err_out:
+	return ret;
+}
+
+static int __devexit stmpe_ts_remove(struct platform_device *pdev)
+{
+	struct stmpe_touch *ts = platform_get_drvdata(pdev);
+	unsigned int ts_irq = platform_get_irq_byname(pdev, "FIFO_TH");
+
+	stmpe_disable(ts->stmpe, STMPE_BLOCK_TOUCHSCREEN);
+
+	free_irq(ts_irq, ts);
+
+	platform_set_drvdata(pdev, NULL);
+
+	input_unregister_device(ts->idev);
+	input_free_device(ts->idev);
+
+	kfree(ts);
+
+	return 0;
+}
+
+static struct platform_driver stmpe_ts_driver = {
+	.driver = {
+		   .name = STMPE_TS_NAME,
+		   .owner = THIS_MODULE,
+		   },
+	.probe = stmpe_input_probe,
+	.remove = __devexit_p(stmpe_ts_remove),
+};
+
+static int __init stmpe_ts_init(void)
+{
+	return platform_driver_register(&stmpe_ts_driver);
+}
+
+module_init(stmpe_ts_init);
+
+static void __exit stmpe_ts_exit(void)
+{
+	platform_driver_unregister(&stmpe_ts_driver);
+}
+
+module_exit(stmpe_ts_exit);
+
+MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
+MODULE_DESCRIPTION("STMPEXXX touchscreen driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" STMPE_TS_NAME);
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* Re: [PATCH 5/5 V7] input: STMPE touch controller support
  2010-06-25  8:37                 ` [PATCH 5/5 V7] " Luotao Fu
@ 2010-06-25  9:11                   ` Dmitry Torokhov
  2010-06-25  9:32                     ` Luotao Fu
  2010-06-27 21:24                     ` Samuel Ortiz
  2010-06-25  9:34                   ` [PATCH 5/5 V8] " Luotao Fu
  1 sibling, 2 replies; 61+ messages in thread
From: Dmitry Torokhov @ 2010-06-25  9:11 UTC (permalink / raw)
  To: Luotao Fu
  Cc: Rabin VINCENT, Samuel Ortiz, Linus WALLEIJ, linux-kernel,
	linux-input, STEricsson_nomadik_linux

On Fri, Jun 25, 2010 at 10:37:51AM +0200, Luotao Fu wrote:
> +
> +	/* 

Trailing whitespace.

> +	 * touch_det sometimes get desasserted or just get stuck. This appears
> +	 * to be a silicon bug, We still have to clearify this with the
> +	 * manufacture. As a workaround We release the key anyway if the
> +	 * touch_det keeps coming in after 4ms, while the FIFO contains no value
> +	 * during the whole time. 

Trailing whitespace.

> +
> +static int stmpe_init_hw(struct stmpe_touch *ts)

__devinit.

Otherwise:

	Acked-by: Dmitry Torokhov <dtor@mail.ru>

I assume it will be merged through MFD tree?

-- 
Dmitry

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH 5/5 V7] input: STMPE touch controller support
  2010-06-25  9:11                   ` Dmitry Torokhov
@ 2010-06-25  9:32                     ` Luotao Fu
  2010-06-27 21:24                     ` Samuel Ortiz
  1 sibling, 0 replies; 61+ messages in thread
From: Luotao Fu @ 2010-06-25  9:32 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Luotao Fu, Rabin VINCENT, Samuel Ortiz, Linus WALLEIJ,
	linux-kernel, linux-input, STEricsson_nomadik_linux

[-- Attachment #1: Type: text/plain, Size: 1601 bytes --]

On Fri, Jun 25, 2010 at 02:11:16AM -0700, Dmitry Torokhov wrote:
> On Fri, Jun 25, 2010 at 10:37:51AM +0200, Luotao Fu wrote:
> > +
> > +	/* 
> 
> Trailing whitespace.
> 
> > +	 * touch_det sometimes get desasserted or just get stuck. This appears
> > +	 * to be a silicon bug, We still have to clearify this with the
> > +	 * manufacture. As a workaround We release the key anyway if the
> > +	 * touch_det keeps coming in after 4ms, while the FIFO contains no value
> > +	 * during the whole time. 
> 
> Trailing whitespace.
> 
> > +
> > +static int stmpe_init_hw(struct stmpe_touch *ts)
> 
> __devinit.
> 

doh! Note to myself: never forget checkpatch.pl

> Otherwise:
> 
> 	Acked-by: Dmitry Torokhov <dtor@mail.ru>
> 

thx

> I assume it will be merged through MFD tree?
> 

to be very honest: no idea... ;-) Samuel once asked Rabin and me to
merge the stmpe stuffs since I once posted a serie exclusively for
stmpe811, now it's so far done(V8 with white space fixes is coming).
Rabin's patch series also contains input stuffs like keypad etc. I don't
know whether Rabin is going to rebase the serie to merge some other
fixes or one of you (Samuel and you) would prefer the patch serie
"as it is".

Rabin, what do you think?

Cheers
Luotao Fu
-- 
Pengutronix e.K.                           | Dipl.-Ing. Luotao Fu        |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 61+ messages in thread

* [PATCH 5/5 V8] input: STMPE touch controller support
  2010-06-25  8:37                 ` [PATCH 5/5 V7] " Luotao Fu
  2010-06-25  9:11                   ` Dmitry Torokhov
@ 2010-06-25  9:34                   ` Luotao Fu
  1 sibling, 0 replies; 61+ messages in thread
From: Luotao Fu @ 2010-06-25  9:34 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Rabin VINCENT, Samuel Ortiz, Linus WALLEIJ, linux-kernel,
	linux-input, STEricsson_nomadik_linux, Luotao Fu

This one adds a driver for STMPE touchscreen controllers. This driver depends on
stmpexxx mfd core driver.

Signed-off-by: Luotao Fu <l.fu@pengutronix.de>
Acked-by: Dmitry Torokhov <dtor@mail.ru>
---
V2 Changes:
* include subsystem headers since they are now remove from the mfd
  core header file
* use genirq to register threaded isr. The stmpe811 own irq callbacks
  are dropped in the core driver.
* use STMPE811_TS_NAME for name all over the place
* wait for completion of core IO operation before cancelling polling of
  release event.
* switch to platform data for configuration parameters.
* add FIFO reset to open callback, also reset FIFO before reporting
  release.

V3 Changes:
* reformated platform data comments to kernel-doc style

V4 Changes:
* converted to stmpexxx core by Rabin Vincent <rabin.vincent@stericsson.com>
* wait for running workqueue callback to finish while canceling the queue
* add i_drive configuration support

V5 Changes:
* add ADC block enable to stmpe_enable call.

V6 Changes:
* add _reset_fifo callback to reduce duplicated code.
* fix mask usage while setting bits in probe.
* acquire platform irq by name

V7 Changes:
* reranged entry makefile in alphabetical order
* move hw init stuff into a own function
* fix queue canceling in close. remove uneccessary register settings from remove
* removed inline declaration
* add driver owner
* set input params before registering the device

V8 Changes:
* fixed trailing whitespaces
* mark init_hw function as __devinit

 drivers/input/touchscreen/Kconfig    |   10 +
 drivers/input/touchscreen/Makefile   |    1 +
 drivers/input/touchscreen/stmpe-ts.c |  397 ++++++++++++++++++++++++++++++++++
 3 files changed, 408 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/touchscreen/stmpe-ts.c

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 3b9d5e2..96a9954 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -603,4 +603,14 @@ config TOUCHSCREEN_TPS6507X
 	  To compile this driver as a module, choose M here: the
 	  module will be called tps6507x_ts.
 
+config TOUCHSCREEN_STMPE
+	tristate "STMicroelectronics STMPE touchscreens"
+	depends on MFD_STMPE
+	help
+	  Say Y here if you want support for STMicroelectronics
+	  STMPE touchscreen controllers.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called stmpe-ts.
+
 endif
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 497964a..10fb163 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_TOUCHSCREEN_USB_COMPOSITE)	+= usbtouchscreen.o
 obj-$(CONFIG_TOUCHSCREEN_PCAP)		+= pcap_ts.o
 obj-$(CONFIG_TOUCHSCREEN_PENMOUNT)	+= penmount.o
 obj-$(CONFIG_TOUCHSCREEN_S3C2410)	+= s3c2410_ts.o
+obj-$(CONFIG_TOUCHSCREEN_STMPE)		+= stmpe-ts.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213)	+= touchit213.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT)	+= touchright.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN)	+= touchwin.o
diff --git a/drivers/input/touchscreen/stmpe-ts.c b/drivers/input/touchscreen/stmpe-ts.c
new file mode 100644
index 0000000..77f4374
--- /dev/null
+++ b/drivers/input/touchscreen/stmpe-ts.c
@@ -0,0 +1,397 @@
+/* STMicroelectronics STMPE811 Touchscreen Driver
+ *
+ * (C) 2010 Luotao Fu <l.fu@pengutronix.de>
+ * All rights reserved.
+ *
+ *  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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/stmpe.h>
+
+/* Register layouts and functionalities are identical on all stmpexxx variants
+ * with touchscreen controller
+ */
+#define STMPE_REG_INT_STA		0x0B
+#define STMPE_REG_ADC_CTRL1		0x20
+#define STMPE_REG_ADC_CTRL2		0x21
+#define STMPE_REG_TSC_CTRL		0x40
+#define STMPE_REG_TSC_CFG		0x41
+#define STMPE_REG_FIFO_TH		0x4A
+#define STMPE_REG_FIFO_STA		0x4B
+#define STMPE_REG_FIFO_SIZE		0x4C
+#define STMPE_REG_TSC_DATA_XYZ		0x52
+#define STMPE_REG_TSC_FRACTION_Z	0x56
+#define STMPE_REG_TSC_I_DRIVE		0x58
+
+#define OP_MOD_XYZ			0
+
+#define STMPE_TSC_CTRL_TSC_EN		(1<<0)
+
+#define STMPE_FIFO_STA_RESET		(1<<0)
+
+#define STMPE_IRQ_TOUCH_DET		0
+
+#define SAMPLE_TIME(x)			((x & 0xf) << 4)
+#define MOD_12B(x)			((x & 0x1) << 3)
+#define REF_SEL(x)			((x & 0x1) << 1)
+#define ADC_FREQ(x)			(x & 0x3)
+#define AVE_CTRL(x)			((x & 0x3) << 6)
+#define DET_DELAY(x)			((x & 0x7) << 3)
+#define SETTLING(x)			(x & 0x7)
+#define FRACTION_Z(x)			(x & 0x7)
+#define I_DRIVE(x)			(x & 0x1)
+#define OP_MODE(x)			((x & 0x7) << 1)
+
+#define STMPE_TS_NAME			"stmpe-ts"
+#define XY_MASK				0xfff
+
+struct stmpe_touch {
+	struct stmpe *stmpe;
+	struct input_dev *idev;
+	struct delayed_work work;
+	struct device *dev;
+	u8 sample_time;
+	u8 mod_12b;
+	u8 ref_sel;
+	u8 adc_freq;
+	u8 ave_ctrl;
+	u8 touch_det_delay;
+	u8 settling;
+	u8 fraction_z;
+	u8 i_drive;
+};
+
+static int __stmpe_reset_fifo(struct stmpe *stmpe)
+{
+	int ret;
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, STMPE_FIFO_STA_RESET);
+	if (ret)
+		return ret;
+
+	return stmpe_set_bits(stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, 0);
+}
+
+static void stmpe_work(struct work_struct *work)
+{
+	int int_sta;
+	u32 timeout = 40;
+
+	struct stmpe_touch *ts =
+	    container_of(work, struct stmpe_touch, work.work);
+
+	int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
+
+	/*
+	 * touch_det sometimes get desasserted or just get stuck. This appears
+	 * to be a silicon bug, We still have to clearify this with the
+	 * manufacture. As a workaround We release the key anyway if the
+	 * touch_det keeps coming in after 4ms, while the FIFO contains no value
+	 * during the whole time.
+	 */
+	while ((int_sta & (1 << STMPE_IRQ_TOUCH_DET)) && (timeout > 0)) {
+		timeout--;
+		int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
+		udelay(100);
+	}
+
+	/* reset the FIFO before we report release event */
+	__stmpe_reset_fifo(ts->stmpe);
+
+	input_report_abs(ts->idev, ABS_PRESSURE, 0);
+	input_sync(ts->idev);
+}
+
+static irqreturn_t stmpe_ts_handler(int irq, void *data)
+{
+	u8 data_set[4];
+	int x, y, z;
+	struct stmpe_touch *ts = data;
+
+	/*
+	 * Cancel scheduled polling for release if we have new value
+	 * available. Wait if the polling is already running.
+	 */
+	cancel_delayed_work_sync(&ts->work);
+
+	/*
+	 * The FIFO sometimes just crashes and stops generating interrupts. This
+	 * appears to be a silicon bug. We still have to clearify this with
+	 * the manufacture. As a workaround we disable the TSC while we are
+	 * collecting data and flush the FIFO after reading
+	 */
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+				STMPE_TSC_CTRL_TSC_EN, 0);
+
+	stmpe_block_read(ts->stmpe, STMPE_REG_TSC_DATA_XYZ, 4, data_set);
+
+	x = (data_set[0] << 4) | (data_set[1] >> 4);
+	y = ((data_set[1] & 0xf) << 8) | data_set[2];
+	z = data_set[3];
+
+	input_report_abs(ts->idev, ABS_X, x);
+	input_report_abs(ts->idev, ABS_Y, y);
+	input_report_abs(ts->idev, ABS_PRESSURE, z);
+	input_sync(ts->idev);
+
+       /* flush the FIFO after we have read out our values. */
+	__stmpe_reset_fifo(ts->stmpe);
+
+	/* reenable the tsc */
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
+
+	/* start polling for touch_det to detect release */
+	schedule_delayed_work(&ts->work, HZ / 50);
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit stmpe_init_hw(struct stmpe_touch *ts)
+{
+	int ret;
+	u8 adc_ctrl1, adc_ctrl1_mask, tsc_cfg, tsc_cfg_mask;
+	struct stmpe *stmpe = ts->stmpe;
+	struct device *dev = ts->dev;
+
+	ret = stmpe_enable(stmpe, STMPE_BLOCK_TOUCHSCREEN | STMPE_BLOCK_ADC);
+	if (ret) {
+		dev_err(dev, "Could not enable clock for ADC and TS\n");
+		return ret;
+	}
+
+	adc_ctrl1 = SAMPLE_TIME(ts->sample_time) | MOD_12B(ts->mod_12b) |
+		REF_SEL(ts->ref_sel);
+	adc_ctrl1_mask = SAMPLE_TIME(0xff) | MOD_12B(0xff) | REF_SEL(0xff);
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL1,
+			adc_ctrl1_mask, adc_ctrl1);
+	if (ret) {
+		dev_err(dev, "Could not setup ADC\n");
+		return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL2,
+			ADC_FREQ(0xff), ADC_FREQ(ts->adc_freq));
+	if (ret) {
+		dev_err(dev, "Could not setup ADC\n");
+		return ret;
+	}
+
+	tsc_cfg = AVE_CTRL(ts->ave_ctrl) | DET_DELAY(ts->touch_det_delay) |
+			SETTLING(ts->settling);
+	tsc_cfg_mask = AVE_CTRL(0xff) | DET_DELAY(0xff) | SETTLING(0xff);
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CFG, tsc_cfg_mask, tsc_cfg);
+	if (ret) {
+		dev_err(dev, "Could not config touch\n");
+		return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_FRACTION_Z,
+			FRACTION_Z(0xff), FRACTION_Z(ts->fraction_z));
+	if (ret) {
+		dev_err(dev, "Could not config touch\n");
+		return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_I_DRIVE,
+			I_DRIVE(0xff), I_DRIVE(ts->i_drive));
+	if (ret) {
+		dev_err(dev, "Could not config touch\n");
+		return ret;
+	}
+
+	/* set FIFO to 1 for single point reading */
+	ret = stmpe_reg_write(stmpe, STMPE_REG_FIFO_TH, 1);
+	if (ret) {
+		dev_err(dev, "Could not set FIFO\n");
+		return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CTRL,
+			OP_MODE(0xff), OP_MODE(OP_MOD_XYZ));
+	if (ret) {
+		dev_err(dev, "Could not set mode\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int stmpe_ts_open(struct input_dev *dev)
+{
+	struct stmpe_touch *ts = input_get_drvdata(dev);
+	int ret = 0;
+
+	ret = __stmpe_reset_fifo(ts->stmpe);
+	if (ret)
+		return ret;
+
+	return stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
+}
+
+static void stmpe_ts_close(struct input_dev *dev)
+{
+	struct stmpe_touch *ts = input_get_drvdata(dev);
+
+	cancel_delayed_work_sync(&ts->work);
+
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, 0);
+}
+
+static int __devinit stmpe_input_probe(struct platform_device *pdev)
+{
+	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+	struct stmpe_platform_data *pdata = stmpe->pdata;
+	struct stmpe_touch *ts;
+	struct input_dev *idev;
+	struct stmpe_ts_platform_data *ts_pdata = NULL;
+	int ret = 0;
+	unsigned int ts_irq;
+
+	ts_irq = platform_get_irq_byname(pdev, "FIFO_TH");
+	if (ts_irq < 0)
+		return ts_irq;
+
+	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+	if (!ts)
+		goto err_out;
+
+	idev = input_allocate_device();
+	if (!idev)
+		goto err_free_ts;
+
+	platform_set_drvdata(pdev, ts);
+	ts->stmpe = stmpe;
+	ts->idev = idev;
+	ts->dev = &pdev->dev;
+
+	if (pdata)
+		ts_pdata = pdata->ts;
+
+	if (ts_pdata) {
+		ts->sample_time = ts_pdata->sample_time;
+		ts->mod_12b = ts_pdata->mod_12b;
+		ts->ref_sel = ts_pdata->ref_sel;
+		ts->adc_freq = ts_pdata->adc_freq;
+		ts->ave_ctrl = ts_pdata->ave_ctrl;
+		ts->touch_det_delay = ts_pdata->touch_det_delay;
+		ts->settling = ts_pdata->settling;
+		ts->fraction_z = ts_pdata->fraction_z;
+		ts->i_drive = ts_pdata->i_drive;
+	}
+
+	INIT_DELAYED_WORK(&ts->work, stmpe_work);
+
+	ret = request_threaded_irq(ts_irq, NULL, stmpe_ts_handler,
+			IRQF_ONESHOT, STMPE_TS_NAME, ts);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request IRQ %d\n", ts_irq);
+		goto err_free_input;
+	}
+
+	ret = stmpe_init_hw(ts);
+	if (ret)
+		goto err_free_irq;
+
+	idev->name = STMPE_TS_NAME;
+	idev->id.bustype = BUS_I2C;
+	idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+	idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+	idev->open = stmpe_ts_open;
+	idev->close = stmpe_ts_close;
+
+	input_set_drvdata(idev, ts);
+
+	input_set_abs_params(idev, ABS_X, 0, XY_MASK, 0, 0);
+	input_set_abs_params(idev, ABS_Y, 0, XY_MASK, 0, 0);
+	input_set_abs_params(idev, ABS_PRESSURE, 0x0, 0xff, 0, 0);
+
+	ret = input_register_device(idev);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not register input device\n");
+		goto err_free_irq;
+	}
+
+	return ret;
+
+err_free_irq:
+	free_irq(ts_irq, ts);
+err_free_input:
+	input_free_device(idev);
+	platform_set_drvdata(pdev, NULL);
+err_free_ts:
+	kfree(ts);
+err_out:
+	return ret;
+}
+
+static int __devexit stmpe_ts_remove(struct platform_device *pdev)
+{
+	struct stmpe_touch *ts = platform_get_drvdata(pdev);
+	unsigned int ts_irq = platform_get_irq_byname(pdev, "FIFO_TH");
+
+	stmpe_disable(ts->stmpe, STMPE_BLOCK_TOUCHSCREEN);
+
+	free_irq(ts_irq, ts);
+
+	platform_set_drvdata(pdev, NULL);
+
+	input_unregister_device(ts->idev);
+	input_free_device(ts->idev);
+
+	kfree(ts);
+
+	return 0;
+}
+
+static struct platform_driver stmpe_ts_driver = {
+	.driver = {
+		   .name = STMPE_TS_NAME,
+		   .owner = THIS_MODULE,
+		   },
+	.probe = stmpe_input_probe,
+	.remove = __devexit_p(stmpe_ts_remove),
+};
+
+static int __init stmpe_ts_init(void)
+{
+	return platform_driver_register(&stmpe_ts_driver);
+}
+
+module_init(stmpe_ts_init);
+
+static void __exit stmpe_ts_exit(void)
+{
+	platform_driver_unregister(&stmpe_ts_driver);
+}
+
+module_exit(stmpe_ts_exit);
+
+MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
+MODULE_DESCRIPTION("STMPEXXX touchscreen driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" STMPE_TS_NAME);
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* Re: [PATCH 5/5 V7] input: STMPE touch controller support
  2010-06-25  9:11                   ` Dmitry Torokhov
  2010-06-25  9:32                     ` Luotao Fu
@ 2010-06-27 21:24                     ` Samuel Ortiz
  1 sibling, 0 replies; 61+ messages in thread
From: Samuel Ortiz @ 2010-06-27 21:24 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Luotao Fu, Rabin VINCENT, Linus WALLEIJ, linux-kernel,
	linux-input, STEricsson_nomadik_linux

Hi Dmitry,

On Fri, Jun 25, 2010 at 02:11:16AM -0700, Dmitry Torokhov wrote:
> On Fri, Jun 25, 2010 at 10:37:51AM +0200, Luotao Fu wrote:
> > +
> > +	/* 
> 
> Trailing whitespace.
> 
> > +	 * touch_det sometimes get desasserted or just get stuck. This appears
> > +	 * to be a silicon bug, We still have to clearify this with the
> > +	 * manufacture. As a workaround We release the key anyway if the
> > +	 * touch_det keeps coming in after 4ms, while the FIFO contains no value
> > +	 * during the whole time. 
> 
> Trailing whitespace.
> 
> > +
> > +static int stmpe_init_hw(struct stmpe_touch *ts)
> 
> __devinit.
> 
> Otherwise:
> 
> 	Acked-by: Dmitry Torokhov <dtor@mail.ru>
> 
> I assume it will be merged through MFD tree?
Yes, that's correct.

Cheers,
Samuel.


> -- 
> Dmitry

-- 
Intel Open Source Technology Centre
http://oss.intel.com/

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCHv2 1/3] mfd: add STMPE I/O Expander support
  2010-06-22 13:55       ` [PATCHv2 1/3] mfd: add STMPE " Rabin Vincent
@ 2010-06-27 23:55         ` Samuel Ortiz
  2010-06-29  3:13           ` Rabin VINCENT
                             ` (3 more replies)
  0 siblings, 4 replies; 61+ messages in thread
From: Samuel Ortiz @ 2010-06-27 23:55 UTC (permalink / raw)
  To: Rabin Vincent; +Cc: linux-kernel, STEricsson_nomadik_linux, linus.walleij, l.fu

Hi Rabin,

On Tue, Jun 22, 2010 at 07:25:27PM +0530, Rabin Vincent wrote:
> add support for the stmpe family of i/o expanders from
> stmicroelectronics.  these devices include upto 24 gpios and a varying
> selection of blocks, including pwm, keypad, and touchscreen controllers.
> this patch adds the mfd core.

Thanks for re-spinning this patchset. I have some comments though:

> diff --git a/drivers/mfd/kconfig b/drivers/mfd/kconfig
> index 9da0e50..63dce71 100644
> --- a/drivers/mfd/kconfig
> +++ b/drivers/mfd/kconfig
> @@ -177,6 +177,18 @@ config twl4030_codec
>  	select mfd_core
>  	default n
>  
> +config mfd_stmpe
> +	bool "support stmicroelectronics stmpe"
> +	depends on i2c=y && generic_hardirqs
> +	select mfd_core
> +	help
> +	  support for the stmpe family of i/o expanders from
> +	  stmicroelectronics.
Please be more verbose here in saying which chipsets are supported. Also,
describing the kind of sub devices they provide won't hurt.

> diff --git a/drivers/mfd/stmpe-devices.c b/drivers/mfd/stmpe-devices.c
> new file mode 100644
> index 0000000..fa6934d
> --- /dev/null
> +++ b/drivers/mfd/stmpe-devices.c
I like the smtpe_variant design, but I'd rather see those definitions being
part of smtpe.c directly.

> +/**
> + * stmpe_reg_read() - read a single stmpe register
> + * @stmpe:	device to read from
> + * @reg:	register to read
> + */
> +int stmpe_reg_read(struct stmpe *stmpe, u8 reg)
> +{
> +	int ret;
> +
> +	ret = i2c_smbus_read_byte_data(stmpe->i2c, reg);
> +	if (ret < 0)
> +		dev_err(stmpe->dev, "failed to read reg %#x: %d\n",
> +			reg, ret);
> +
> +	dev_vdbg(stmpe->dev, "rd: reg %#x => data %#x\n", reg, ret);
> +
> +	return ret;
> +}
> +export_symbol(stmpe_reg_read);
I think your locking is broken here.
If your exporting this routine (and the next ones below), you'd better make
sure you're under stmpe->lock for the stmpe register concurrent access.
What I suggest is that you'd have the exported routines taking your stmpe
lock, and then have an internal version (e.g. named with a __stmpe prefix)
without lock taken for your core code. In your case, you could probably call
the i2c I/O routines directly, that's up to you.

> +/**
> + * stmpe_set_bits() - set the value of a bitfield in a stmpe register
> + * @stmpe:	device to write to
> + * @reg:	register to write
> + * @mask:	mask of bits to set
> + * @val:	value to set
> + */
> +int stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val)
> +{
> +	int ret;
> +
> +	mutex_lock(&stmpe->lock);
> +
> +	ret = stmpe_reg_read(stmpe, reg);
That one for example would be __stmpe_read().

> +/**
> + * stmpe_block_write() - write multiple stmpe registers
> + * @stmpe:	device to write to
> + * @reg:	first register
> + * @length:	number of registers
> + * @values:	values to write
> + */
> +int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,
> +			const u8 *values)
> +{
> +	int ret;
> +
> +	dev_vdbg(stmpe->dev, "wr: regs %#x (%d)\n", reg, length);
> +#ifdef VERBOSE_DEBUG
> +	print_hex_dump_bytes("stmpe wr: ", dump_prefix_offset, values, length);
> +#endif
I don't really enjoy this part for 2 reasons:
- You should use a less generic ifdef switch, prefixed with STMPE_ for
example.
- I'd rather see a
	#ifdef STMPE_VERBOSE_DEBUG
	#define stmpe_hex_xump() print_hex_dump_bytes() 
	#else
	#define stmpe_hex_xump()
	#endif
defined in your stmpe.h.

The rest looks fine to me.

Cheers,
Samuel.

-- 
intel open source technology centre
http://oss.intel.com/

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCHv2 1/3] mfd: add STMPE I/O Expander support
  2010-06-27 23:55         ` Samuel Ortiz
@ 2010-06-29  3:13           ` Rabin VINCENT
  2010-06-29 15:33             ` Samuel Ortiz
  2010-07-01 12:00           ` [PATCHv3 " Rabin Vincent
                             ` (2 subsequent siblings)
  3 siblings, 1 reply; 61+ messages in thread
From: Rabin VINCENT @ 2010-06-29  3:13 UTC (permalink / raw)
  To: Samuel Ortiz; +Cc: linux-kernel, STEricsson_nomadik_linux, Linus WALLEIJ, l.fu

Hi Samuel,

On Mon, Jun 28, 2010 at 01:55:16 +0200, Samuel Ortiz wrote:
> On Tue, Jun 22, 2010 at 07:25:27PM +0530, Rabin Vincent wrote:
> > +int stmpe_reg_read(struct stmpe *stmpe, u8 reg)
> > +{
> > +	int ret;
> > +
> > +	ret = i2c_smbus_read_byte_data(stmpe->i2c, reg);
> > +	if (ret < 0)
> > +		dev_err(stmpe->dev, "failed to read reg %#x: %d\n",
> > +			reg, ret);
> > +
> > +	dev_vdbg(stmpe->dev, "rd: reg %#x => data %#x\n", reg, ret);
> > +
> > +	return ret;
> > +}
> > +EXPORT_SYMBOL(stmpe_reg_read);
> I think your locking is broken here.
> If your exporting this routine (and the next ones below), you'd better make
> sure you're under stmpe->lock for the stmpe register concurrent access.

stmpe_reg_read() and stmpe_reg_write() are just a call to one
i2c_smbus_* function, and the I2C core takes a bus_lock internally
preventing concurrent accesses.

The only place where the I2C core locking is not sufficient is the
read/modify/write sequence, and we provide stmpe_set_bits() for that,
which takes a lock.  If someone uses reg_read()/reg_write() sequences on
registers where they should be using set_bits(), adding extra locking in
reg_read()/reg_write() will not provide any additional safeguard.

The same scheme is used by adp5520.

Could you please explain why more locking is needed?

> What I suggest is that you'd have the exported routines taking your stmpe
> lock, and then have an internal version (e.g. named with a __stmpe prefix)
> without lock taken for your core code. In your case, you could probably call
> the i2c I/O routines directly, that's up to you.
> 
> > +/**
> > + * stmpe_set_bits() - set the value of a bitfield in a stmpe register
> > + * @stmpe:	device to write to
> > + * @reg:	register to write
> > + * @mask:	mask of bits to set
> > + * @val:	value to set
> > + */
> > +int stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val)
> > +{
> > +	int ret;
> > +
> > +	mutex_lock(&stmpe->lock);
> > +
> > +	ret = stmpe_reg_read(stmpe, reg);
> That one for example would be __stmpe_read().
> 
> > +/**
> > + * stmpe_block_write() - write multiple stmpe registers
> > + * @stmpe:	device to write to
> > + * @reg:	first register
> > + * @length:	number of registers
> > + * @values:	values to write
> > + */
> > +int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,
> > +			const u8 *values)
> > +{
> > +	int ret;
> > +
> > +	dev_vdbg(stmpe->dev, "wr: regs %#x (%d)\n", reg, length);
> > +#ifdef VERBOSE_DEBUG
> > +	print_hex_dump_bytes("stmpe wr: ", dump_prefix_offset, values, length);
> > +#endif
> I don't really enjoy this part for 2 reasons:
> - You should use a less generic ifdef switch, prefixed with STMPE_ for
> example.

The dev_vdbg() in the previous line is activated via VERBOSE_DEBUG, so
the idea was to have this dump use the same config.  I'll fix it as your
recommended, though.  Will fix your other comments too.

Rabin

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCHv2 1/3] mfd: add STMPE I/O Expander support
  2010-06-29  3:13           ` Rabin VINCENT
@ 2010-06-29 15:33             ` Samuel Ortiz
  0 siblings, 0 replies; 61+ messages in thread
From: Samuel Ortiz @ 2010-06-29 15:33 UTC (permalink / raw)
  To: Rabin VINCENT; +Cc: linux-kernel, STEricsson_nomadik_linux, Linus WALLEIJ, l.fu

Hi Rabin,

On Tue, Jun 29, 2010 at 08:43:26AM +0530, Rabin VINCENT wrote:
> Hi Samuel,
> 
> On Mon, Jun 28, 2010 at 01:55:16 +0200, Samuel Ortiz wrote:
> > On Tue, Jun 22, 2010 at 07:25:27PM +0530, Rabin Vincent wrote:
> > > +int stmpe_reg_read(struct stmpe *stmpe, u8 reg)
> > > +{
> > > +	int ret;
> > > +
> > > +	ret = i2c_smbus_read_byte_data(stmpe->i2c, reg);
> > > +	if (ret < 0)
> > > +		dev_err(stmpe->dev, "failed to read reg %#x: %d\n",
> > > +			reg, ret);
> > > +
> > > +	dev_vdbg(stmpe->dev, "rd: reg %#x => data %#x\n", reg, ret);
> > > +
> > > +	return ret;
> > > +}
> > > +EXPORT_SYMBOL(stmpe_reg_read);
> > I think your locking is broken here.
> > If your exporting this routine (and the next ones below), you'd better make
> > sure you're under stmpe->lock for the stmpe register concurrent access.
> 
> stmpe_reg_read() and stmpe_reg_write() are just a call to one
> i2c_smbus_* function, and the I2C core takes a bus_lock internally
> preventing concurrent accesses.
> 
> The only place where the I2C core locking is not sufficient is the
> read/modify/write sequence, and we provide stmpe_set_bits() for that,
> which takes a lock.  If someone uses reg_read()/reg_write() sequences on
> registers where they should be using set_bits(), adding extra locking in
> reg_read()/reg_write() will not provide any additional safeguard.
> 
> The same scheme is used by adp5520.
> 
> Could you please explain why more locking is needed?
Without the extra locking, there's nothing preventing me from writing to a
register while you're in the middle of a stmpe_set_bits() call.

> > > +/**
> > > + * stmpe_block_write() - write multiple stmpe registers
> > > + * @stmpe:	device to write to
> > > + * @reg:	first register
> > > + * @length:	number of registers
> > > + * @values:	values to write
> > > + */
> > > +int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,
> > > +			const u8 *values)
> > > +{
> > > +	int ret;
> > > +
> > > +	dev_vdbg(stmpe->dev, "wr: regs %#x (%d)\n", reg, length);
> > > +#ifdef VERBOSE_DEBUG
> > > +	print_hex_dump_bytes("stmpe wr: ", dump_prefix_offset, values, length);
> > > +#endif
> > I don't really enjoy this part for 2 reasons:
> > - You should use a less generic ifdef switch, prefixed with STMPE_ for
> > example.
> 
> The dev_vdbg() in the previous line is activated via VERBOSE_DEBUG, so
> the idea was to have this dump use the same config.  
Ah, I didnt realize VERBOSE_DEBUG was defined from device.h. Should have
grepped for it in your patch, sorry.
I would still like it to be part of your header file though.

> I'll fix it as your recommended, though.  Will fix your other comments too.
Thanks in advance.

Cheers,
Samuel.


> Rabin

-- 
Intel Open Source Technology Centre
http://oss.intel.com/

^ permalink raw reply	[flat|nested] 61+ messages in thread

* [PATCHv3 1/3] mfd: add STMPE I/O Expander support
  2010-06-27 23:55         ` Samuel Ortiz
  2010-06-29  3:13           ` Rabin VINCENT
@ 2010-07-01 12:00           ` Rabin Vincent
  2010-07-01 12:34             ` Luotao Fu
  2010-07-01 12:00           ` [PATCHv3 2/3] gpio: add STMPE GPIO driver Rabin Vincent
  2010-07-01 12:00           ` [PATCHv3 3/3] input: add STMPE keypad driver Rabin Vincent
  3 siblings, 1 reply; 61+ messages in thread
From: Rabin Vincent @ 2010-07-01 12:00 UTC (permalink / raw)
  To: linux-kernel
  Cc: Samuel Ortiz, STEricsson_nomadik_linux, linus.walleij, l.fu,
	Rabin Vincent

Add support for the STMPE family of I/O Expanders from
STMicroelectronics.  These devices include upto 24 gpios and a varying
selection of blocks, including PWM, keypad, and touchscreen controllers.
This patch adds the MFD core.

[l.fu@pengutronix.de: fix stmpe811 enable hook]
Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
---

CHANGELOG (for the whole set)

v2 -> v3:
  - Locking fixes
  - Expanded Kconfig description
  - Moved stmpe-devices.c content into stmpe.c
  - Moved debug ifdefs to header
  - Folded in Luotao Fu's one-liner fix for STMPE811 enable
  - Folded in Luotao Fu's one-liner fix for stmpe-gpio input
    direction

v1 -> v2
  - Variant data to support other STMPE* variants
  - Enable/disable API
  - GPIO setup/remove callbacks
  - pdata made optional for GPIO
  - Platform specifies which blocks it wants to use
  - IRQ invert polarity option
  - STMPE811 support (needs testing)

 drivers/mfd/Kconfig       |   23 ++
 drivers/mfd/Makefile      |    1 +
 drivers/mfd/stmpe.c       |  915 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/mfd/stmpe.h       |  176 +++++++++
 include/linux/mfd/stmpe.h |  154 ++++++++
 5 files changed, 1269 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/stmpe.c
 create mode 100644 drivers/mfd/stmpe.h
 create mode 100644 include/linux/mfd/stmpe.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 9da0e50..08e4693 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -177,6 +177,29 @@ config TWL4030_CODEC
 	select MFD_CORE
 	default n
 
+config MFD_STMPE
+	bool "Support STMicroelectronics STMPE"
+	depends on I2C=y && GENERIC_HARDIRQS
+	select MFD_CORE
+	help
+	  Support for the STMPE family of I/O Expanders from
+	  STMicroelectronics.
+
+	  Currently supported devices are:
+
+		STMPE811: GPIO, Touchscreen
+		STMPE1601: GPIO, Keypad
+		STMPE2401: GPIO, Keypad
+		STMPE2403: GPIO, Keypad
+
+	  This driver provides common support for accessing the device,
+	  additional drivers must be enabled in order to use the functionality
+	  of the device.  Currently available sub drivers are:
+
+		GPIO: stmpe-gpio
+		Keypad: stmpe-keypad
+		Touchscreen: stmpe-ts
+
 config MFD_TC35892
 	bool "Support Toshiba TC35892"
 	depends on I2C=y && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index fb503e7..4410747 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_HTC_I2CPLD)	+= htc-i2cpld.o
 obj-$(CONFIG_MFD_DAVINCI_VOICECODEC)	+= davinci_voicecodec.o
 obj-$(CONFIG_MFD_DM355EVM_MSP)	+= dm355evm_msp.o
 
+obj-$(CONFIG_MFD_STMPE)		+= stmpe.o
 obj-$(CONFIG_MFD_TC35892)	+= tc35892.o
 obj-$(CONFIG_MFD_T7L66XB)	+= t7l66xb.o tmio_core.o
 obj-$(CONFIG_MFD_TC6387XB)	+= tc6387xb.o tmio_core.o
diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c
new file mode 100644
index 0000000..a7f3099
--- /dev/null
+++ b/drivers/mfd/stmpe.c
@@ -0,0 +1,915 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/stmpe.h>
+#include "stmpe.h"
+
+static int __stmpe_enable(struct stmpe *stmpe, unsigned int blocks)
+{
+	return stmpe->variant->enable(stmpe, blocks, true);
+}
+
+static int __stmpe_disable(struct stmpe *stmpe, unsigned int blocks)
+{
+	return stmpe->variant->enable(stmpe, blocks, false);
+}
+
+static int __stmpe_reg_read(struct stmpe *stmpe, u8 reg)
+{
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(stmpe->i2c, reg);
+	if (ret < 0)
+		dev_err(stmpe->dev, "failed to read reg %#x: %d\n",
+			reg, ret);
+
+	dev_vdbg(stmpe->dev, "rd: reg %#x => data %#x\n", reg, ret);
+
+	return ret;
+}
+
+static int __stmpe_reg_write(struct stmpe *stmpe, u8 reg, u8 val)
+{
+	int ret;
+
+	dev_vdbg(stmpe->dev, "wr: reg %#x <= %#x\n", reg, val);
+
+	ret = i2c_smbus_write_byte_data(stmpe->i2c, reg, val);
+	if (ret < 0)
+		dev_err(stmpe->dev, "failed to write reg %#x: %d\n",
+			reg, ret);
+
+	return ret;
+}
+
+static int __stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val)
+{
+	int ret;
+
+	ret = __stmpe_reg_read(stmpe, reg);
+	if (ret < 0)
+		return ret;
+
+	ret &= ~mask;
+	ret |= val;
+
+	return __stmpe_reg_write(stmpe, reg, ret);
+}
+
+static int __stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length,
+			      u8 *values)
+{
+	int ret;
+
+	ret = i2c_smbus_read_i2c_block_data(stmpe->i2c, reg, length, values);
+	if (ret < 0)
+		dev_err(stmpe->dev, "failed to read regs %#x: %d\n",
+			reg, ret);
+
+	dev_vdbg(stmpe->dev, "rd: reg %#x (%d) => ret %#x\n", reg, length, ret);
+	stmpe_dump_bytes("stmpe rd: ", values, length);
+
+	return ret;
+}
+
+static int __stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,
+			const u8 *values)
+{
+	int ret;
+
+	dev_vdbg(stmpe->dev, "wr: regs %#x (%d)\n", reg, length);
+	stmpe_dump_bytes("stmpe wr: ", values, length);
+
+	ret = i2c_smbus_write_i2c_block_data(stmpe->i2c, reg, length,
+					     values);
+	if (ret < 0)
+		dev_err(stmpe->dev, "failed to write regs %#x: %d\n",
+			reg, ret);
+
+	return ret;
+}
+
+/**
+ * stmpe_enable - enable blocks on an STMPE device
+ * @stmpe:	Device to work on
+ * @blocks:	Mask of blocks (enum stmpe_block values) to enable
+ */
+int stmpe_enable(struct stmpe *stmpe, unsigned int blocks)
+{
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+	ret = __stmpe_enable(stmpe, blocks);
+	mutex_unlock(&stmpe->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_enable);
+
+/**
+ * stmpe_disable - disable blocks on an STMPE device
+ * @stmpe:	Device to work on
+ * @blocks:	Mask of blocks (enum stmpe_block values) to enable
+ */
+int stmpe_disable(struct stmpe *stmpe, unsigned int blocks)
+{
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+	ret = __stmpe_disable(stmpe, blocks);
+	mutex_unlock(&stmpe->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_disable);
+
+/**
+ * stmpe_reg_read() - read a single STMPE register
+ * @stmpe:	Device to read from
+ * @reg:	Register to read
+ */
+int stmpe_reg_read(struct stmpe *stmpe, u8 reg)
+{
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+	ret = __stmpe_reg_read(stmpe, reg);
+	mutex_unlock(&stmpe->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_reg_read);
+
+/**
+ * stmpe_reg_write() - write a single STMPE register
+ * @stmpe:	Device to write to
+ * @reg:	Register to write
+ * @val:	Value to write
+ */
+int stmpe_reg_write(struct stmpe *stmpe, u8 reg, u8 val)
+{
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+	ret = __stmpe_reg_write(stmpe, reg, val);
+	mutex_unlock(&stmpe->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_reg_write);
+
+/**
+ * stmpe_set_bits() - set the value of a bitfield in a STMPE register
+ * @stmpe:	Device to write to
+ * @reg:	Register to write
+ * @mask:	Mask of bits to set
+ * @val:	Value to set
+ */
+int stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val)
+{
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+	ret = __stmpe_set_bits(stmpe, reg, mask, val);
+	mutex_unlock(&stmpe->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_set_bits);
+
+/**
+ * stmpe_block_read() - read multiple STMPE registers
+ * @stmpe:	Device to read from
+ * @reg:	First register
+ * @length:	Number of registers
+ * @values:	Buffer to write to
+ */
+int stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length, u8 *values)
+{
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+	ret = __stmpe_block_read(stmpe, reg, length, values);
+	mutex_unlock(&stmpe->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_block_read);
+
+/**
+ * stmpe_block_write() - write multiple STMPE registers
+ * @stmpe:	Device to write to
+ * @reg:	First register
+ * @length:	Number of registers
+ * @values:	Values to write
+ */
+int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,
+		      const u8 *values)
+{
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+	ret = __stmpe_block_write(stmpe, reg, length, values);
+	mutex_unlock(&stmpe->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_block_write);
+
+/**
+ * stmpe_set_altfunc: set the alternate function for STMPE pins
+ * @stmpe:	Device to configure
+ * @pins:	Bitmask of pins to affect
+ * @block:	block to enable alternate functions for
+ *
+ * @pins is assumed to have a bit set for each of the bits whose alternate
+ * function is to be changed, numbered according to the GPIOXY numbers.
+ *
+ * If the GPIO module is not enabled, this function automatically enables it in
+ * order to perform the change.
+ */
+int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins, enum stmpe_block block)
+{
+	struct stmpe_variant_info *variant = stmpe->variant;
+	u8 regaddr = stmpe->regs[STMPE_IDX_GPAFR_U_MSB];
+	int af_bits = variant->af_bits;
+	int numregs = DIV_ROUND_UP(stmpe->num_gpios * af_bits, 8);
+	int afperreg = 8 / af_bits;
+	int mask = (1 << af_bits) - 1;
+	u8 regs[numregs];
+	int af;
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+
+	ret = __stmpe_enable(stmpe, STMPE_BLOCK_GPIO);
+	if (ret < 0)
+		goto out;
+
+	ret = __stmpe_block_read(stmpe, regaddr, numregs, regs);
+	if (ret < 0)
+		goto out;
+
+	af = variant->get_altfunc(stmpe, block);
+
+	while (pins) {
+		int pin = __ffs(pins);
+		int regoffset = numregs - (pin / afperreg) - 1;
+		int pos = (pin % afperreg) * (8 / afperreg);
+
+		regs[regoffset] &= ~(mask << pos);
+		regs[regoffset] |= af << pos;
+
+		pins &= ~(1 << pin);
+	}
+
+	ret = __stmpe_block_write(stmpe, regaddr, numregs, regs);
+
+out:
+	mutex_unlock(&stmpe->lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_set_altfunc);
+
+/*
+ * GPIO (all variants)
+ */
+
+static struct resource stmpe_gpio_resources[] = {
+	/* Start and end filled dynamically */
+	{
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct mfd_cell stmpe_gpio_cell = {
+	.name		= "stmpe-gpio",
+	.resources	= stmpe_gpio_resources,
+	.num_resources	= ARRAY_SIZE(stmpe_gpio_resources),
+};
+
+/*
+ * Keypad (1601, 2401, 2403)
+ */
+
+static struct resource stmpe_keypad_resources[] = {
+	{
+		.name	= "KEYPAD",
+		.start	= 0,
+		.end	= 0,
+		.flags	= IORESOURCE_IRQ,
+	},
+	{
+		.name	= "KEYPAD_OVER",
+		.start	= 1,
+		.end	= 1,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct mfd_cell stmpe_keypad_cell = {
+	.name		= "stmpe-keypad",
+	.resources	= stmpe_keypad_resources,
+	.num_resources	= ARRAY_SIZE(stmpe_keypad_resources),
+};
+
+/*
+ * Touchscreen (STMPE811)
+ */
+
+static struct resource stmpe_ts_resources[] = {
+	{
+		.name	= "TOUCH_DET",
+		.start	= 0,
+		.end	= 0,
+		.flags	= IORESOURCE_IRQ,
+	},
+	{
+		.name	= "FIFO_TH",
+		.start	= 1,
+		.end	= 1,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct mfd_cell stmpe_ts_cell = {
+	.name		= "stmpe-ts",
+	.resources	= stmpe_ts_resources,
+	.num_resources	= ARRAY_SIZE(stmpe_ts_resources),
+};
+
+/*
+ * STMPE811
+ */
+
+static const u8 stmpe811_regs[] = {
+	[STMPE_IDX_CHIP_ID]	= STMPE811_REG_CHIP_ID,
+	[STMPE_IDX_ICR_LSB]	= STMPE811_REG_INT_CTRL,
+	[STMPE_IDX_IER_LSB]	= STMPE811_REG_INT_EN,
+	[STMPE_IDX_ISR_MSB]	= STMPE811_REG_INT_STA,
+	[STMPE_IDX_GPMR_LSB]	= STMPE811_REG_GPIO_MP_STA,
+	[STMPE_IDX_GPSR_LSB]	= STMPE811_REG_GPIO_SET_PIN,
+	[STMPE_IDX_GPCR_LSB]	= STMPE811_REG_GPIO_CLR_PIN,
+	[STMPE_IDX_GPDR_LSB]	= STMPE811_REG_GPIO_DIR,
+	[STMPE_IDX_GPRER_LSB]	= STMPE811_REG_GPIO_RE,
+	[STMPE_IDX_GPFER_LSB]	= STMPE811_REG_GPIO_FE,
+	[STMPE_IDX_GPAFR_U_MSB]	= STMPE811_REG_GPIO_AF,
+	[STMPE_IDX_IEGPIOR_LSB]	= STMPE811_REG_GPIO_INT_EN,
+	[STMPE_IDX_ISGPIOR_MSB]	= STMPE811_REG_GPIO_INT_STA,
+	[STMPE_IDX_GPEDR_MSB]	= STMPE811_REG_GPIO_ED,
+};
+
+static struct stmpe_variant_block stmpe811_blocks[] = {
+	{
+		.cell	= &stmpe_gpio_cell,
+		.irq	= STMPE811_IRQ_GPIOC,
+		.block	= STMPE_BLOCK_GPIO,
+	},
+	{
+		.cell	= &stmpe_ts_cell,
+		.irq	= STMPE811_IRQ_TOUCH_DET,
+		.block	= STMPE_BLOCK_TOUCHSCREEN,
+	},
+};
+
+static int stmpe811_enable(struct stmpe *stmpe, unsigned int blocks,
+			   bool enable)
+{
+	unsigned int mask = 0;
+
+	if (blocks & STMPE_BLOCK_GPIO)
+		mask |= STMPE811_SYS_CTRL2_GPIO_OFF;
+
+	if (blocks & STMPE_BLOCK_ADC)
+		mask |= STMPE811_SYS_CTRL2_ADC_OFF;
+
+	if (blocks & STMPE_BLOCK_TOUCHSCREEN)
+		mask |= STMPE811_SYS_CTRL2_TSC_OFF;
+
+	return __stmpe_set_bits(stmpe, STMPE811_REG_SYS_CTRL2, mask,
+				enable ? 0 : mask);
+}
+
+static int stmpe811_get_altfunc(struct stmpe *stmpe, enum stmpe_block block)
+{
+	/* 0 for touchscreen, 1 for GPIO */
+	return block != STMPE_BLOCK_TOUCHSCREEN;
+}
+
+static struct stmpe_variant_info stmpe811 = {
+	.name		= "stmpe811",
+	.id_val		= 0x0811,
+	.id_mask	= 0xffff,
+	.num_gpios	= 8,
+	.af_bits	= 1,
+	.regs		= stmpe811_regs,
+	.blocks		= stmpe811_blocks,
+	.num_blocks	= ARRAY_SIZE(stmpe811_blocks),
+	.num_irqs	= STMPE811_NR_INTERNAL_IRQS,
+	.enable		= stmpe811_enable,
+	.get_altfunc	= stmpe811_get_altfunc,
+};
+
+/*
+ * STMPE1601
+ */
+
+static const u8 stmpe1601_regs[] = {
+	[STMPE_IDX_CHIP_ID]	= STMPE1601_REG_CHIP_ID,
+	[STMPE_IDX_ICR_LSB]	= STMPE1601_REG_ICR_LSB,
+	[STMPE_IDX_IER_LSB]	= STMPE1601_REG_IER_LSB,
+	[STMPE_IDX_ISR_MSB]	= STMPE1601_REG_ISR_MSB,
+	[STMPE_IDX_GPMR_LSB]	= STMPE1601_REG_GPIO_MP_LSB,
+	[STMPE_IDX_GPSR_LSB]	= STMPE1601_REG_GPIO_SET_LSB,
+	[STMPE_IDX_GPCR_LSB]	= STMPE1601_REG_GPIO_CLR_LSB,
+	[STMPE_IDX_GPDR_LSB]	= STMPE1601_REG_GPIO_SET_DIR_LSB,
+	[STMPE_IDX_GPRER_LSB]	= STMPE1601_REG_GPIO_RE_LSB,
+	[STMPE_IDX_GPFER_LSB]	= STMPE1601_REG_GPIO_FE_LSB,
+	[STMPE_IDX_GPAFR_U_MSB]	= STMPE1601_REG_GPIO_AF_U_MSB,
+	[STMPE_IDX_IEGPIOR_LSB]	= STMPE1601_REG_INT_EN_GPIO_MASK_LSB,
+	[STMPE_IDX_ISGPIOR_MSB]	= STMPE1601_REG_INT_STA_GPIO_MSB,
+	[STMPE_IDX_GPEDR_MSB]	= STMPE1601_REG_GPIO_ED_MSB,
+};
+
+static struct stmpe_variant_block stmpe1601_blocks[] = {
+	{
+		.cell	= &stmpe_gpio_cell,
+		.irq	= STMPE24XX_IRQ_GPIOC,
+		.block	= STMPE_BLOCK_GPIO,
+	},
+	{
+		.cell	= &stmpe_keypad_cell,
+		.irq	= STMPE24XX_IRQ_KEYPAD,
+		.block	= STMPE_BLOCK_KEYPAD,
+	},
+};
+
+static int stmpe1601_enable(struct stmpe *stmpe, unsigned int blocks,
+			    bool enable)
+{
+	unsigned int mask = 0;
+
+	if (blocks & STMPE_BLOCK_GPIO)
+		mask |= STMPE1601_SYS_CTRL_ENABLE_GPIO;
+
+	if (blocks & STMPE_BLOCK_KEYPAD)
+		mask |= STMPE1601_SYS_CTRL_ENABLE_KPC;
+
+	return __stmpe_set_bits(stmpe, STMPE1601_REG_SYS_CTRL, mask,
+				enable ? mask : 0);
+}
+
+static int stmpe1601_get_altfunc(struct stmpe *stmpe, enum stmpe_block block)
+{
+	switch (block) {
+	case STMPE_BLOCK_PWM:
+		return 2;
+
+	case STMPE_BLOCK_KEYPAD:
+		return 1;
+
+	case STMPE_BLOCK_GPIO:
+	default:
+		return 0;
+	}
+}
+
+static struct stmpe_variant_info stmpe1601 = {
+	.name		= "stmpe1601",
+	.id_val		= 0x0210,
+	.id_mask	= 0xfff0,	/* at least 0x0210 and 0x0212 */
+	.num_gpios	= 16,
+	.af_bits	= 2,
+	.regs		= stmpe1601_regs,
+	.blocks		= stmpe1601_blocks,
+	.num_blocks	= ARRAY_SIZE(stmpe1601_blocks),
+	.num_irqs	= STMPE1601_NR_INTERNAL_IRQS,
+	.enable		= stmpe1601_enable,
+	.get_altfunc	= stmpe1601_get_altfunc,
+};
+
+/*
+ * STMPE24XX
+ */
+
+static const u8 stmpe24xx_regs[] = {
+	[STMPE_IDX_CHIP_ID]	= STMPE24XX_REG_CHIP_ID,
+	[STMPE_IDX_ICR_LSB]	= STMPE24XX_REG_ICR_LSB,
+	[STMPE_IDX_IER_LSB]	= STMPE24XX_REG_IER_LSB,
+	[STMPE_IDX_ISR_MSB]	= STMPE24XX_REG_ISR_MSB,
+	[STMPE_IDX_GPMR_LSB]	= STMPE24XX_REG_GPMR_LSB,
+	[STMPE_IDX_GPSR_LSB]	= STMPE24XX_REG_GPSR_LSB,
+	[STMPE_IDX_GPCR_LSB]	= STMPE24XX_REG_GPCR_LSB,
+	[STMPE_IDX_GPDR_LSB]	= STMPE24XX_REG_GPDR_LSB,
+	[STMPE_IDX_GPRER_LSB]	= STMPE24XX_REG_GPRER_LSB,
+	[STMPE_IDX_GPFER_LSB]	= STMPE24XX_REG_GPFER_LSB,
+	[STMPE_IDX_GPAFR_U_MSB]	= STMPE24XX_REG_GPAFR_U_MSB,
+	[STMPE_IDX_IEGPIOR_LSB]	= STMPE24XX_REG_IEGPIOR_LSB,
+	[STMPE_IDX_ISGPIOR_MSB]	= STMPE24XX_REG_ISGPIOR_MSB,
+	[STMPE_IDX_GPEDR_MSB]	= STMPE24XX_REG_GPEDR_MSB,
+};
+
+static struct stmpe_variant_block stmpe24xx_blocks[] = {
+	{
+		.cell	= &stmpe_gpio_cell,
+		.irq	= STMPE24XX_IRQ_GPIOC,
+		.block	= STMPE_BLOCK_GPIO,
+	},
+	{
+		.cell	= &stmpe_keypad_cell,
+		.irq	= STMPE24XX_IRQ_KEYPAD,
+		.block	= STMPE_BLOCK_KEYPAD,
+	},
+};
+
+static int stmpe24xx_enable(struct stmpe *stmpe, unsigned int blocks,
+			    bool enable)
+{
+	unsigned int mask = 0;
+
+	if (blocks & STMPE_BLOCK_GPIO)
+		mask |= STMPE24XX_SYS_CTRL_ENABLE_GPIO;
+
+	if (blocks & STMPE_BLOCK_KEYPAD)
+		mask |= STMPE24XX_SYS_CTRL_ENABLE_KPC;
+
+	return __stmpe_set_bits(stmpe, STMPE24XX_REG_SYS_CTRL, mask,
+				enable ? mask : 0);
+}
+
+static int stmpe24xx_get_altfunc(struct stmpe *stmpe, enum stmpe_block block)
+{
+	switch (block) {
+	case STMPE_BLOCK_ROTATOR:
+		return 2;
+
+	case STMPE_BLOCK_KEYPAD:
+		return 1;
+
+	case STMPE_BLOCK_GPIO:
+	default:
+		return 0;
+	}
+}
+
+static struct stmpe_variant_info stmpe2401 = {
+	.name		= "stmpe2401",
+	.id_val		= 0x0101,
+	.id_mask	= 0xffff,
+	.num_gpios	= 24,
+	.af_bits	= 2,
+	.regs		= stmpe24xx_regs,
+	.blocks		= stmpe24xx_blocks,
+	.num_blocks	= ARRAY_SIZE(stmpe24xx_blocks),
+	.num_irqs	= STMPE24XX_NR_INTERNAL_IRQS,
+	.enable		= stmpe24xx_enable,
+	.get_altfunc	= stmpe24xx_get_altfunc,
+};
+
+static struct stmpe_variant_info stmpe2403 = {
+	.name		= "stmpe2403",
+	.id_val		= 0x0120,
+	.id_mask	= 0xffff,
+	.num_gpios	= 24,
+	.af_bits	= 2,
+	.regs		= stmpe24xx_regs,
+	.blocks		= stmpe24xx_blocks,
+	.num_blocks	= ARRAY_SIZE(stmpe24xx_blocks),
+	.num_irqs	= STMPE24XX_NR_INTERNAL_IRQS,
+	.enable		= stmpe24xx_enable,
+	.get_altfunc	= stmpe24xx_get_altfunc,
+};
+
+static struct stmpe_variant_info *stmpe_variant_info[] = {
+	[STMPE811]	= &stmpe811,
+	[STMPE1601]	= &stmpe1601,
+	[STMPE2401]	= &stmpe2401,
+	[STMPE2403]	= &stmpe2403,
+};
+
+static irqreturn_t stmpe_irq(int irq, void *data)
+{
+	struct stmpe *stmpe = data;
+	struct stmpe_variant_info *variant = stmpe->variant;
+	int num = DIV_ROUND_UP(variant->num_irqs, 8);
+	u8 israddr = stmpe->regs[STMPE_IDX_ISR_MSB];
+	u8 isr[num];
+	int ret;
+	int i;
+
+	ret = stmpe_block_read(stmpe, israddr, num, isr);
+	if (ret < 0)
+		return IRQ_NONE;
+
+	for (i = 0; i < num; i++) {
+		int bank = num - i - 1;
+		u8 status = isr[i];
+		u8 clear;
+
+		status &= stmpe->ier[bank];
+		if (!status)
+			continue;
+
+		clear = status;
+		while (status) {
+			int bit = __ffs(status);
+			int line = bank * 8 + bit;
+
+			handle_nested_irq(stmpe->irq_base + line);
+			status &= ~(1 << bit);
+		}
+
+		stmpe_reg_write(stmpe, israddr + i, clear);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void stmpe_irq_lock(unsigned int irq)
+{
+	struct stmpe *stmpe = get_irq_chip_data(irq);
+
+	mutex_lock(&stmpe->irq_lock);
+}
+
+static void stmpe_irq_sync_unlock(unsigned int irq)
+{
+	struct stmpe *stmpe = get_irq_chip_data(irq);
+	struct stmpe_variant_info *variant = stmpe->variant;
+	int num = DIV_ROUND_UP(variant->num_irqs, 8);
+	int i;
+
+	for (i = 0; i < num; i++) {
+		u8 new = stmpe->ier[i];
+		u8 old = stmpe->oldier[i];
+
+		if (new == old)
+			continue;
+
+		stmpe->oldier[i] = new;
+		stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_IER_LSB] - i, new);
+	}
+
+	mutex_unlock(&stmpe->irq_lock);
+}
+
+static void stmpe_irq_mask(unsigned int irq)
+{
+	struct stmpe *stmpe = get_irq_chip_data(irq);
+	int offset = irq - stmpe->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	stmpe->ier[regoffset] &= ~mask;
+}
+
+static void stmpe_irq_unmask(unsigned int irq)
+{
+	struct stmpe *stmpe = get_irq_chip_data(irq);
+	int offset = irq - stmpe->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	stmpe->ier[regoffset] |= mask;
+}
+
+static struct irq_chip stmpe_irq_chip = {
+	.name			= "stmpe",
+	.bus_lock		= stmpe_irq_lock,
+	.bus_sync_unlock	= stmpe_irq_sync_unlock,
+	.mask			= stmpe_irq_mask,
+	.unmask			= stmpe_irq_unmask,
+};
+
+static int __devinit stmpe_irq_init(struct stmpe *stmpe)
+{
+	int num_irqs = stmpe->variant->num_irqs;
+	int base = stmpe->irq_base;
+	int irq;
+
+	for (irq = base; irq < base + num_irqs; irq++) {
+		set_irq_chip_data(irq, stmpe);
+		set_irq_chip_and_handler(irq, &stmpe_irq_chip,
+					 handle_edge_irq);
+		set_irq_nested_thread(irq, 1);
+#ifdef CONFIG_ARM
+		set_irq_flags(irq, IRQF_VALID);
+#else
+		set_irq_noprobe(irq);
+#endif
+	}
+
+	return 0;
+}
+
+static void stmpe_irq_remove(struct stmpe *stmpe)
+{
+	int num_irqs = stmpe->variant->num_irqs;
+	int base = stmpe->irq_base;
+	int irq;
+
+	for (irq = base; irq < base + num_irqs; irq++) {
+#ifdef CONFIG_ARM
+		set_irq_flags(irq, 0);
+#endif
+		set_irq_chip_and_handler(irq, NULL, NULL);
+		set_irq_chip_data(irq, NULL);
+	}
+}
+
+static int __devinit stmpe_chip_init(struct stmpe *stmpe)
+{
+	unsigned int irq_trigger = stmpe->pdata->irq_trigger;
+	struct stmpe_variant_info *variant = stmpe->variant;
+	u8 icr = STMPE_ICR_LSB_GIM;
+	unsigned int id;
+	u8 data[2];
+	int ret;
+
+	ret = stmpe_block_read(stmpe, stmpe->regs[STMPE_IDX_CHIP_ID],
+			       ARRAY_SIZE(data), data);
+	if (ret < 0)
+		return ret;
+
+	id = (data[0] << 8) | data[1];
+	if ((id & variant->id_mask) != variant->id_val) {
+		dev_err(stmpe->dev, "unknown chip id: %#x\n", id);
+		return -EINVAL;
+	}
+
+	dev_info(stmpe->dev, "%s detected, chip id: %#x\n", variant->name, id);
+
+	/* Disable all modules -- subdrivers should enable what they need. */
+	ret = stmpe_disable(stmpe, ~0);
+	if (ret)
+		return ret;
+
+	if (irq_trigger == IRQF_TRIGGER_FALLING ||
+	    irq_trigger == IRQF_TRIGGER_RISING)
+		icr |= STMPE_ICR_LSB_EDGE;
+
+	if (irq_trigger == IRQF_TRIGGER_RISING ||
+	    irq_trigger == IRQF_TRIGGER_HIGH)
+		icr |= STMPE_ICR_LSB_HIGH;
+
+	if (stmpe->pdata->irq_invert_polarity)
+		icr ^= STMPE_ICR_LSB_HIGH;
+
+	return stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_ICR_LSB], icr);
+}
+
+static int __devinit stmpe_add_device(struct stmpe *stmpe,
+				      struct mfd_cell *cell, int irq)
+{
+	return mfd_add_devices(stmpe->dev, stmpe->pdata->id, cell, 1,
+			       NULL, stmpe->irq_base + irq);
+}
+
+static int __devinit stmpe_devices_init(struct stmpe *stmpe)
+{
+	struct stmpe_variant_info *variant = stmpe->variant;
+	unsigned int platform_blocks = stmpe->pdata->blocks;
+	int ret = -EINVAL;
+	int i;
+
+	for (i = 0; i < variant->num_blocks; i++) {
+		struct stmpe_variant_block *block = &variant->blocks[i];
+
+		if (!(platform_blocks & block->block))
+			continue;
+
+		platform_blocks &= ~block->block;
+		ret = stmpe_add_device(stmpe, block->cell, block->irq);
+		if (ret)
+			return ret;
+	}
+
+	if (platform_blocks)
+		dev_warn(stmpe->dev,
+			 "platform wants blocks (%#x) not present on variant",
+			 platform_blocks);
+
+	return ret;
+}
+
+static int __devinit stmpe_probe(struct i2c_client *i2c,
+				 const struct i2c_device_id *id)
+{
+	struct stmpe_platform_data *pdata = i2c->dev.platform_data;
+	struct stmpe *stmpe;
+	int ret;
+
+	if (!pdata)
+		return -EINVAL;
+
+	stmpe = kzalloc(sizeof(struct stmpe), GFP_KERNEL);
+	if (!stmpe)
+		return -ENOMEM;
+
+	mutex_init(&stmpe->irq_lock);
+	mutex_init(&stmpe->lock);
+
+	stmpe->dev = &i2c->dev;
+	stmpe->i2c = i2c;
+
+	stmpe->pdata = pdata;
+	stmpe->irq_base = pdata->irq_base;
+
+	stmpe->partnum = id->driver_data;
+	stmpe->variant = stmpe_variant_info[stmpe->partnum];
+	stmpe->regs = stmpe->variant->regs;
+	stmpe->num_gpios = stmpe->variant->num_gpios;
+
+	i2c_set_clientdata(i2c, stmpe);
+
+	ret = stmpe_chip_init(stmpe);
+	if (ret)
+		goto out_free;
+
+	ret = stmpe_irq_init(stmpe);
+	if (ret)
+		goto out_free;
+
+	ret = request_threaded_irq(stmpe->i2c->irq, NULL, stmpe_irq,
+				   pdata->irq_trigger | IRQF_ONESHOT,
+				   "stmpe", stmpe);
+	if (ret) {
+		dev_err(stmpe->dev, "failed to request IRQ: %d\n", ret);
+		goto out_removeirq;
+	}
+
+	ret = stmpe_devices_init(stmpe);
+	if (ret) {
+		dev_err(stmpe->dev, "failed to add children\n");
+		goto out_removedevs;
+	}
+
+	return 0;
+
+out_removedevs:
+	mfd_remove_devices(stmpe->dev);
+	free_irq(stmpe->i2c->irq, stmpe);
+out_removeirq:
+	stmpe_irq_remove(stmpe);
+out_free:
+	kfree(stmpe);
+	return ret;
+}
+
+static int __devexit stmpe_remove(struct i2c_client *client)
+{
+	struct stmpe *stmpe = i2c_get_clientdata(client);
+
+	mfd_remove_devices(stmpe->dev);
+
+	free_irq(stmpe->i2c->irq, stmpe);
+	stmpe_irq_remove(stmpe);
+
+	kfree(stmpe);
+
+	return 0;
+}
+
+static const struct i2c_device_id stmpe_id[] = {
+	{ "stmpe811", STMPE811 },
+	{ "stmpe1601", STMPE1601 },
+	{ "stmpe2401", STMPE2401 },
+	{ "stmpe2403", STMPE2403 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, stmpe_id);
+
+static struct i2c_driver stmpe_driver = {
+	.driver.name	= "stmpe",
+	.driver.owner	= THIS_MODULE,
+	.probe		= stmpe_probe,
+	.remove		= __devexit_p(stmpe_remove),
+	.id_table	= stmpe_id,
+};
+
+static int __init stmpe_init(void)
+{
+	return i2c_add_driver(&stmpe_driver);
+}
+subsys_initcall(stmpe_init);
+
+static void __exit stmpe_exit(void)
+{
+	i2c_del_driver(&stmpe_driver);
+}
+module_exit(stmpe_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("STMPE MFD core driver");
+MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
diff --git a/drivers/mfd/stmpe.h b/drivers/mfd/stmpe.h
new file mode 100644
index 0000000..991f0ec
--- /dev/null
+++ b/drivers/mfd/stmpe.h
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#ifndef __STMPE_H
+#define __STMPE_H
+
+#ifdef STMPE_DUMP_BYTES
+static inline void stmpe_dump_bytes(const char *str, const void *buf,
+				    size_t len)
+{
+	print_hex_dump_bytes(str, DUMP_PREFIX_OFFSET, buf, len);
+}
+#else
+static inline void stmpe_dump_bytes(const char *str, const void *buf,
+				    size_t len)
+{
+}
+#endif
+
+/**
+ * struct stmpe_variant_block - information about block
+ * @cell:	base mfd cell
+ * @irq:	interrupt number to be added to each IORESOURCE_IRQ
+ *		in the cell
+ * @block:	block id; used for identification with platform data and for
+ *		enable and altfunc callbacks
+ */
+struct stmpe_variant_block {
+	struct mfd_cell		*cell;
+	int			irq;
+	enum stmpe_block	block;
+};
+
+/**
+ * struct stmpe_variant_info - variant-specific information
+ * @name:	part name
+ * @id_val:	content of CHIPID register
+ * @id_mask:	bits valid in CHIPID register for comparison with id_val
+ * @num_gpios:	number of GPIOS
+ * @af_bits:	number of bits used to specify the alternate function
+ * @blocks:	list of blocks present on this device
+ * @num_blocks:	number of blocks present on this device
+ * @num_irqs:	number of internal IRQs available on this device
+ * @enable:	callback to enable the specified blocks.
+ *		Called with the I/O lock held.
+ * @get_altfunc: callback to get the alternate function number for the
+ *		 specific block
+ */
+struct stmpe_variant_info {
+	const char *name;
+	u16 id_val;
+	u16 id_mask;
+	int num_gpios;
+	int af_bits;
+	const u8 *regs;
+	struct stmpe_variant_block *blocks;
+	int num_blocks;
+	int num_irqs;
+	int (*enable)(struct stmpe *stmpe, unsigned int blocks, bool enable);
+	int (*get_altfunc)(struct stmpe *stmpe, enum stmpe_block block);
+};
+
+#define STMPE_ICR_LSB_HIGH	(1 << 2)
+#define STMPE_ICR_LSB_EDGE	(1 << 1)
+#define STMPE_ICR_LSB_GIM	(1 << 0)
+
+/*
+ * STMPE811
+ */
+
+#define STMPE811_IRQ_TOUCH_DET		0
+#define STMPE811_IRQ_FIFO_TH		1
+#define STMPE811_IRQ_FIFO_OFLOW		2
+#define STMPE811_IRQ_FIFO_FULL		3
+#define STMPE811_IRQ_FIFO_EMPTY		4
+#define STMPE811_IRQ_TEMP_SENS		5
+#define STMPE811_IRQ_ADC		6
+#define STMPE811_IRQ_GPIOC		7
+#define STMPE811_NR_INTERNAL_IRQS	8
+
+#define STMPE811_REG_CHIP_ID		0x00
+#define STMPE811_REG_SYS_CTRL2		0x04
+#define STMPE811_REG_INT_CTRL		0x09
+#define STMPE811_REG_INT_EN		0x0A
+#define STMPE811_REG_INT_STA		0x0B
+#define STMPE811_REG_GPIO_INT_EN	0x0C
+#define STMPE811_REG_GPIO_INT_STA	0x0D
+#define STMPE811_REG_GPIO_SET_PIN	0x10
+#define STMPE811_REG_GPIO_CLR_PIN	0x11
+#define STMPE811_REG_GPIO_MP_STA	0x12
+#define STMPE811_REG_GPIO_DIR		0x13
+#define STMPE811_REG_GPIO_ED		0x14
+#define STMPE811_REG_GPIO_RE		0x15
+#define STMPE811_REG_GPIO_FE		0x16
+#define STMPE811_REG_GPIO_AF		0x17
+
+#define STMPE811_SYS_CTRL2_ADC_OFF	(1 << 0)
+#define STMPE811_SYS_CTRL2_TSC_OFF	(1 << 1)
+#define STMPE811_SYS_CTRL2_GPIO_OFF	(1 << 2)
+#define STMPE811_SYS_CTRL2_TS_OFF	(1 << 3)
+
+/*
+ * STMPE1601
+ */
+
+#define STMPE1601_IRQ_GPIOC		8
+#define STMPE1601_IRQ_PWM3		7
+#define STMPE1601_IRQ_PWM2		6
+#define STMPE1601_IRQ_PWM1		5
+#define STMPE1601_IRQ_PWM0		4
+#define STMPE1601_IRQ_KEYPAD_OVER	2
+#define STMPE1601_IRQ_KEYPAD		1
+#define STMPE1601_IRQ_WAKEUP		0
+#define STMPE1601_NR_INTERNAL_IRQS	9
+
+#define STMPE1601_REG_SYS_CTRL			0x02
+#define STMPE1601_REG_ICR_LSB			0x11
+#define STMPE1601_REG_IER_LSB			0x13
+#define STMPE1601_REG_ISR_MSB			0x14
+#define STMPE1601_REG_CHIP_ID			0x80
+#define STMPE1601_REG_INT_EN_GPIO_MASK_LSB	0x17
+#define STMPE1601_REG_INT_STA_GPIO_MSB		0x18
+#define STMPE1601_REG_GPIO_MP_LSB		0x87
+#define STMPE1601_REG_GPIO_SET_LSB		0x83
+#define STMPE1601_REG_GPIO_CLR_LSB		0x85
+#define STMPE1601_REG_GPIO_SET_DIR_LSB		0x89
+#define STMPE1601_REG_GPIO_ED_MSB		0x8A
+#define STMPE1601_REG_GPIO_RE_LSB		0x8D
+#define STMPE1601_REG_GPIO_FE_LSB		0x8F
+#define STMPE1601_REG_GPIO_AF_U_MSB		0x92
+
+#define STMPE1601_SYS_CTRL_ENABLE_GPIO		(1 << 3)
+#define STMPE1601_SYS_CTRL_ENABLE_KPC		(1 << 1)
+#define STMPE1601_SYSCON_ENABLE_SPWM		(1 << 0)
+
+/*
+ * STMPE24xx
+ */
+
+#define STMPE24XX_IRQ_GPIOC		8
+#define STMPE24XX_IRQ_PWM2		7
+#define STMPE24XX_IRQ_PWM1		6
+#define STMPE24XX_IRQ_PWM0		5
+#define STMPE24XX_IRQ_ROT_OVER		4
+#define STMPE24XX_IRQ_ROT		3
+#define STMPE24XX_IRQ_KEYPAD_OVER	2
+#define STMPE24XX_IRQ_KEYPAD		1
+#define STMPE24XX_IRQ_WAKEUP		0
+#define STMPE24XX_NR_INTERNAL_IRQS	9
+
+#define STMPE24XX_REG_SYS_CTRL		0x02
+#define STMPE24XX_REG_ICR_LSB		0x11
+#define STMPE24XX_REG_IER_LSB		0x13
+#define STMPE24XX_REG_ISR_MSB		0x14
+#define STMPE24XX_REG_CHIP_ID		0x80
+#define STMPE24XX_REG_IEGPIOR_LSB	0x18
+#define STMPE24XX_REG_ISGPIOR_MSB	0x19
+#define STMPE24XX_REG_GPMR_LSB		0xA5
+#define STMPE24XX_REG_GPSR_LSB		0x85
+#define STMPE24XX_REG_GPCR_LSB		0x88
+#define STMPE24XX_REG_GPDR_LSB		0x8B
+#define STMPE24XX_REG_GPEDR_MSB		0x8C
+#define STMPE24XX_REG_GPRER_LSB		0x91
+#define STMPE24XX_REG_GPFER_LSB		0x94
+#define STMPE24XX_REG_GPAFR_U_MSB	0x9B
+
+#define STMPE24XX_SYS_CTRL_ENABLE_GPIO		(1 << 3)
+#define STMPE24XX_SYSCON_ENABLE_PWM		(1 << 2)
+#define STMPE24XX_SYS_CTRL_ENABLE_KPC		(1 << 1)
+#define STMPE24XX_SYSCON_ENABLE_ROT		(1 << 0)
+
+#endif
diff --git a/include/linux/mfd/stmpe.h b/include/linux/mfd/stmpe.h
new file mode 100644
index 0000000..ad10c7b
--- /dev/null
+++ b/include/linux/mfd/stmpe.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#ifndef __LINUX_MFD_STMPE_H
+#define __LINUX_MFD_STMPE_H
+
+#include <linux/device.h>
+
+enum stmpe_block {
+	STMPE_BLOCK_GPIO	= 1 << 0,
+	STMPE_BLOCK_KEYPAD	= 1 << 1,
+	STMPE_BLOCK_TOUCHSCREEN	= 1 << 2,
+	STMPE_BLOCK_ADC		= 1 << 3,
+	STMPE_BLOCK_PWM		= 1 << 4,
+	STMPE_BLOCK_ROTATOR	= 1 << 5,
+};
+
+enum stmpe_partnum {
+	STMPE811,
+	STMPE1601,
+	STMPE2401,
+	STMPE2403,
+};
+
+/*
+ * For registers whose locations differ on variants,  the correct address is
+ * obtained by indexing stmpe->regs with one of the following.
+ */
+enum {
+	STMPE_IDX_CHIP_ID,
+	STMPE_IDX_ICR_LSB,
+	STMPE_IDX_IER_LSB,
+	STMPE_IDX_ISR_MSB,
+	STMPE_IDX_GPMR_LSB,
+	STMPE_IDX_GPSR_LSB,
+	STMPE_IDX_GPCR_LSB,
+	STMPE_IDX_GPDR_LSB,
+	STMPE_IDX_GPEDR_MSB,
+	STMPE_IDX_GPRER_LSB,
+	STMPE_IDX_GPFER_LSB,
+	STMPE_IDX_GPAFR_U_MSB,
+	STMPE_IDX_IEGPIOR_LSB,
+	STMPE_IDX_ISGPIOR_MSB,
+	STMPE_IDX_MAX,
+};
+
+
+struct stmpe_variant_info;
+
+/**
+ * struct stmpe - STMPE MFD structure
+ * @lock: lock protecting I/O operations
+ * @irq_lock: IRQ bus lock
+ * @dev: device, mostly for dev_dbg()
+ * @i2c: i2c client
+ * @variant: the detected STMPE model number
+ * @regs: list of addresses of registers which are at different addresses on
+ *	  different variants.  Indexed by one of STMPE_IDX_*.
+ * @irq_base: starting IRQ number for internal IRQs
+ * @num_gpios: number of gpios, differs for variants
+ * @ier: cache of IER registers for bus_lock
+ * @oldier: cache of IER registers for bus_lock
+ * @pdata: platform data
+ */
+struct stmpe {
+	struct mutex lock;
+	struct mutex irq_lock;
+	struct device *dev;
+	struct i2c_client *i2c;
+	enum stmpe_partnum partnum;
+	struct stmpe_variant_info *variant;
+	const u8 *regs;
+
+	int irq_base;
+	int num_gpios;
+	u8 ier[2];
+	u8 oldier[2];
+	struct stmpe_platform_data *pdata;
+};
+
+extern int stmpe_reg_write(struct stmpe *stmpe, u8 reg, u8 data);
+extern int stmpe_reg_read(struct stmpe *stmpe, u8 reg);
+extern int stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length,
+			    u8 *values);
+extern int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,
+			     const u8 *values);
+extern int stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val);
+extern int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins,
+			     enum stmpe_block block);
+extern int stmpe_enable(struct stmpe *stmpe, unsigned int blocks);
+extern int stmpe_disable(struct stmpe *stmpe, unsigned int blocks);
+
+struct matrix_keymap_data;
+
+/**
+ * struct stmpe_keypad_platform_data - STMPE keypad platform data
+ * @keymap_data: key map table and size
+ * @debounce_ms: debounce interval, in ms.  Maximum is
+ *		 %STMPE_KEYPAD_MAX_DEBOUNCE.
+ * @scan_count: number of key scanning cycles to confirm key data.
+ *		Maximum is %STMPE_KEYPAD_MAX_SCAN_COUNT.
+ * @no_autorepeat: disable key autorepeat
+ */
+struct stmpe_keypad_platform_data {
+	struct matrix_keymap_data *keymap_data;
+	unsigned int debounce_ms;
+	unsigned int scan_count;
+	bool no_autorepeat;
+};
+
+/**
+ * struct stmpe_gpio_platform_data - STMPE GPIO platform data
+ * @gpio_base: first gpio number assigned.  A maximum of
+ *	       %STMPE_NR_GPIOS GPIOs will be allocated.
+ */
+struct stmpe_gpio_platform_data {
+	int gpio_base;
+	void (*setup)(struct stmpe *stmpe, unsigned gpio_base);
+	void (*remove)(struct stmpe *stmpe, unsigned gpio_base);
+};
+
+/**
+ * struct stmpe_platform_data - STMPE platform data
+ * @id: device id to distinguish between multiple STMPEs on the same board
+ * @blocks: bitmask of blocks to enable (use STMPE_BLOCK_*)
+ * @irq_trigger: IRQ trigger to use for the interrupt to the host
+ * @irq_invert_polarity: IRQ line is connected with reversed polarity
+ * @irq_base: base IRQ number.  %STMPE_NR_IRQS irqs will be used, or
+ *	      %STMPE_NR_INTERNAL_IRQS if the GPIO driver is not used.
+ * @gpio: GPIO-specific platform data
+ * @keypad: keypad-specific platform data
+ */
+struct stmpe_platform_data {
+	int id;
+	unsigned int blocks;
+	int irq_base;
+	unsigned int irq_trigger;
+	bool irq_invert_polarity;
+
+	struct stmpe_gpio_platform_data *gpio;
+	struct stmpe_keypad_platform_data *keypad;
+};
+
+#define STMPE_NR_INTERNAL_IRQS	9
+#define STMPE_INT_GPIO(x)	(STMPE_NR_INTERNAL_IRQS + (x))
+
+#define STMPE_NR_GPIOS		24
+#define STMPE_NR_IRQS		STMPE_INT_GPIO(STMPE_NR_GPIOS)
+
+#endif
-- 
1.7.0


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCHv3 2/3] gpio: add STMPE GPIO driver
  2010-06-27 23:55         ` Samuel Ortiz
  2010-06-29  3:13           ` Rabin VINCENT
  2010-07-01 12:00           ` [PATCHv3 " Rabin Vincent
@ 2010-07-01 12:00           ` Rabin Vincent
  2010-07-01 12:29             ` Luotao Fu
  2010-07-01 12:00           ` [PATCHv3 3/3] input: add STMPE keypad driver Rabin Vincent
  3 siblings, 1 reply; 61+ messages in thread
From: Rabin Vincent @ 2010-07-01 12:00 UTC (permalink / raw)
  To: linux-kernel
  Cc: Samuel Ortiz, STEricsson_nomadik_linux, linus.walleij, l.fu,
	Rabin Vincent

Add support for the GPIOs on STMPE I/O Expanders.

[l.fu@pengutronix.de: fix set direction input]
Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
---
 drivers/gpio/Kconfig      |    7 +
 drivers/gpio/Makefile     |    1 +
 drivers/gpio/stmpe-gpio.c |  390 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 398 insertions(+), 0 deletions(-)
 create mode 100644 drivers/gpio/stmpe-gpio.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 724038d..27e5f8e 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -195,6 +195,13 @@ config GPIO_PCF857X
 	  This driver provides an in-kernel interface to those GPIOs using
 	  platform-neutral GPIO calls.
 
+config GPIO_STMPE
+	bool "STMPE GPIOs"
+	depends on MFD_STMPE
+	help
+	  This enables support for the GPIOs found on the STMPE I/O
+	  Expanders.
+
 config GPIO_TC35892
 	bool "TC35892 GPIOs"
 	depends on MFD_TC35892
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 51c3cdd..a9b93c3 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_GPIO_MCP23S08)	+= mcp23s08.o
 obj-$(CONFIG_GPIO_PCA953X)	+= pca953x.o
 obj-$(CONFIG_GPIO_PCF857X)	+= pcf857x.o
 obj-$(CONFIG_GPIO_PL061)	+= pl061.o
+obj-$(CONFIG_GPIO_STMPE)	+= stmpe-gpio.o
 obj-$(CONFIG_GPIO_TC35892)	+= tc35892-gpio.o
 obj-$(CONFIG_GPIO_TIMBERDALE)	+= timbgpio.o
 obj-$(CONFIG_GPIO_TWL4030)	+= twl4030-gpio.o
diff --git a/drivers/gpio/stmpe-gpio.c b/drivers/gpio/stmpe-gpio.c
new file mode 100644
index 0000000..f1fa1df
--- /dev/null
+++ b/drivers/gpio/stmpe-gpio.c
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/stmpe.h>
+
+/*
+ * These registers are modified under the irq bus lock and cached to avoid
+ * unnecessary writes in bus_sync_unlock.
+ */
+enum { REG_RE, REG_FE, REG_IE };
+
+#define CACHE_NR_REGS	3
+#define CACHE_NR_BANKS	(STMPE_NR_GPIOS / 8)
+
+struct stmpe_gpio {
+	struct gpio_chip chip;
+	struct stmpe *stmpe;
+	struct device *dev;
+	struct mutex irq_lock;
+
+	int irq_base;
+
+	/* Caches of interrupt control registers for bus_lock */
+	u8 regs[CACHE_NR_REGS][CACHE_NR_BANKS];
+	u8 oldregs[CACHE_NR_REGS][CACHE_NR_BANKS];
+};
+
+static inline struct stmpe_gpio *to_stmpe_gpio(struct gpio_chip *chip)
+{
+	return container_of(chip, struct stmpe_gpio, chip);
+}
+
+static int stmpe_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	u8 reg = stmpe->regs[STMPE_IDX_GPMR_LSB] - (offset / 8);
+	u8 mask = 1 << (offset % 8);
+	int ret;
+
+	ret = stmpe_reg_read(stmpe, reg);
+	if (ret < 0)
+		return ret;
+
+	return ret & mask;
+}
+
+static void stmpe_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	int which = val ? STMPE_IDX_GPSR_LSB : STMPE_IDX_GPCR_LSB;
+	u8 reg = stmpe->regs[which] - (offset / 8);
+	u8 mask = 1 << (offset % 8);
+
+	stmpe_reg_write(stmpe, reg, mask);
+}
+
+static int stmpe_gpio_direction_output(struct gpio_chip *chip,
+					 unsigned offset, int val)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	u8 reg = stmpe->regs[STMPE_IDX_GPDR_LSB] - (offset / 8);
+	u8 mask = 1 << (offset % 8);
+
+	stmpe_gpio_set(chip, offset, val);
+
+	return stmpe_set_bits(stmpe, reg, mask, mask);
+}
+
+static int stmpe_gpio_direction_input(struct gpio_chip *chip,
+					unsigned offset)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	u8 reg = stmpe->regs[STMPE_IDX_GPDR_LSB] - (offset / 8);
+	u8 mask = 1 << (offset % 8);
+
+	return stmpe_set_bits(stmpe, reg, mask, 0);
+}
+
+static int stmpe_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+
+	return stmpe_gpio->irq_base + offset;
+}
+
+static struct gpio_chip template_chip = {
+	.label			= "stmpe",
+	.owner			= THIS_MODULE,
+	.direction_input	= stmpe_gpio_direction_input,
+	.get			= stmpe_gpio_get,
+	.direction_output	= stmpe_gpio_direction_output,
+	.set			= stmpe_gpio_set,
+	.to_irq			= stmpe_gpio_to_irq,
+	.can_sleep		= 1,
+};
+
+static int stmpe_gpio_irq_set_type(unsigned int irq, unsigned int type)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+	int offset = irq - stmpe_gpio->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH)
+		return -EINVAL;
+
+	if (type == IRQ_TYPE_EDGE_RISING)
+		stmpe_gpio->regs[REG_RE][regoffset] |= mask;
+	else
+		stmpe_gpio->regs[REG_RE][regoffset] &= ~mask;
+
+	if (type == IRQ_TYPE_EDGE_FALLING)
+		stmpe_gpio->regs[REG_FE][regoffset] |= mask;
+	else
+		stmpe_gpio->regs[REG_FE][regoffset] &= ~mask;
+
+	return 0;
+}
+
+static void stmpe_gpio_irq_lock(unsigned int irq)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+
+	mutex_lock(&stmpe_gpio->irq_lock);
+}
+
+static void stmpe_gpio_irq_sync_unlock(unsigned int irq)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	int num_banks = DIV_ROUND_UP(stmpe->num_gpios, 8);
+	static const u8 regmap[] = {
+		[REG_RE]	= STMPE_IDX_GPRER_LSB,
+		[REG_FE]	= STMPE_IDX_GPFER_LSB,
+		[REG_IE]	= STMPE_IDX_IEGPIOR_LSB,
+	};
+	int i, j;
+
+	for (i = 0; i < CACHE_NR_REGS; i++) {
+		for (j = 0; j < num_banks; j++) {
+			u8 old = stmpe_gpio->oldregs[i][j];
+			u8 new = stmpe_gpio->regs[i][j];
+
+			if (new == old)
+				continue;
+
+			stmpe_gpio->oldregs[i][j] = new;
+			stmpe_reg_write(stmpe, stmpe->regs[regmap[i]] - j, new);
+		}
+	}
+
+	mutex_unlock(&stmpe_gpio->irq_lock);
+}
+
+static void stmpe_gpio_irq_mask(unsigned int irq)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+	int offset = irq - stmpe_gpio->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	stmpe_gpio->regs[REG_IE][regoffset] &= ~mask;
+}
+
+static void stmpe_gpio_irq_unmask(unsigned int irq)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+	int offset = irq - stmpe_gpio->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	stmpe_gpio->regs[REG_IE][regoffset] |= mask;
+}
+
+static struct irq_chip stmpe_gpio_irq_chip = {
+	.name			= "stmpe-gpio",
+	.bus_lock		= stmpe_gpio_irq_lock,
+	.bus_sync_unlock	= stmpe_gpio_irq_sync_unlock,
+	.mask			= stmpe_gpio_irq_mask,
+	.unmask			= stmpe_gpio_irq_unmask,
+	.set_type		= stmpe_gpio_irq_set_type,
+};
+
+static irqreturn_t stmpe_gpio_irq(int irq, void *dev)
+{
+	struct stmpe_gpio *stmpe_gpio = dev;
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	u8 statmsbreg = stmpe->regs[STMPE_IDX_ISGPIOR_MSB];
+	int num_banks = DIV_ROUND_UP(stmpe->num_gpios, 8);
+	u8 status[num_banks];
+	int ret;
+	int i;
+
+	ret = stmpe_block_read(stmpe, statmsbreg, num_banks, status);
+	if (ret < 0)
+		return IRQ_NONE;
+
+	for (i = 0; i < num_banks; i++) {
+		int bank = num_banks - i - 1;
+		unsigned int enabled = stmpe_gpio->regs[REG_IE][bank];
+		unsigned int stat = status[i];
+
+		stat &= enabled;
+		if (!stat)
+			continue;
+
+		while (stat) {
+			int bit = __ffs(stat);
+			int line = bank * 8 + bit;
+
+			handle_nested_irq(stmpe_gpio->irq_base + line);
+			stat &= ~(1 << bit);
+		}
+
+		stmpe_reg_write(stmpe, statmsbreg + i, status[i]);
+		stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_GPEDR_MSB] + i,
+				status[i]);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit stmpe_gpio_irq_init(struct stmpe_gpio *stmpe_gpio)
+{
+	int base = stmpe_gpio->irq_base;
+	int irq;
+
+	for (irq = base; irq < base + stmpe_gpio->chip.ngpio; irq++) {
+		set_irq_chip_data(irq, stmpe_gpio);
+		set_irq_chip_and_handler(irq, &stmpe_gpio_irq_chip,
+					 handle_simple_irq);
+		set_irq_nested_thread(irq, 1);
+#ifdef CONFIG_ARM
+		set_irq_flags(irq, IRQF_VALID);
+#else
+		set_irq_noprobe(irq);
+#endif
+	}
+
+	return 0;
+}
+
+static void stmpe_gpio_irq_remove(struct stmpe_gpio *stmpe_gpio)
+{
+	int base = stmpe_gpio->irq_base;
+	int irq;
+
+	for (irq = base; irq < base + stmpe_gpio->chip.ngpio; irq++) {
+#ifdef CONFIG_ARM
+		set_irq_flags(irq, 0);
+#endif
+		set_irq_chip_and_handler(irq, NULL, NULL);
+		set_irq_chip_data(irq, NULL);
+	}
+}
+
+static int __devinit stmpe_gpio_probe(struct platform_device *pdev)
+{
+	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+	struct stmpe_gpio_platform_data *pdata;
+	struct stmpe_gpio *stmpe_gpio;
+	int ret;
+	int irq;
+
+	pdata = stmpe->pdata->gpio;
+	if (!pdata)
+		return -ENODEV;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	stmpe_gpio = kzalloc(sizeof(struct stmpe_gpio), GFP_KERNEL);
+	if (!stmpe_gpio)
+		return -ENOMEM;
+
+	mutex_init(&stmpe_gpio->irq_lock);
+
+	stmpe_gpio->dev = &pdev->dev;
+	stmpe_gpio->stmpe = stmpe;
+
+	stmpe_gpio->chip = template_chip;
+	stmpe_gpio->chip.ngpio = stmpe->num_gpios;
+	stmpe_gpio->chip.dev = &pdev->dev;
+	stmpe_gpio->chip.base = pdata ? pdata->gpio_base : -1;
+
+	stmpe_gpio->irq_base = stmpe->irq_base + STMPE_INT_GPIO(0);
+
+	ret = stmpe_enable(stmpe, STMPE_BLOCK_GPIO);
+	if (ret)
+		return ret;
+
+	ret = stmpe_gpio_irq_init(stmpe_gpio);
+	if (ret)
+		goto out_free;
+
+	ret = request_threaded_irq(irq, NULL, stmpe_gpio_irq, IRQF_ONESHOT,
+				   "stmpe-gpio", stmpe_gpio);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
+		goto out_removeirq;
+	}
+
+	ret = gpiochip_add(&stmpe_gpio->chip);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to add gpiochip: %d\n", ret);
+		goto out_freeirq;
+	}
+
+	if (pdata && pdata->setup)
+		pdata->setup(stmpe, stmpe_gpio->chip.base);
+
+	platform_set_drvdata(pdev, stmpe_gpio);
+
+	return 0;
+
+out_freeirq:
+	free_irq(irq, stmpe_gpio);
+out_removeirq:
+	stmpe_gpio_irq_remove(stmpe_gpio);
+out_free:
+	kfree(stmpe_gpio);
+	return ret;
+}
+
+static int __devexit stmpe_gpio_remove(struct platform_device *pdev)
+{
+	struct stmpe_gpio *stmpe_gpio = platform_get_drvdata(pdev);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	struct stmpe_gpio_platform_data *pdata = stmpe->pdata->gpio;
+	int irq = platform_get_irq(pdev, 0);
+	int ret;
+
+	if (pdata && pdata->remove)
+		pdata->remove(stmpe, stmpe_gpio->chip.base);
+
+	ret = gpiochip_remove(&stmpe_gpio->chip);
+	if (ret < 0) {
+		dev_err(stmpe_gpio->dev,
+			"unable to remove gpiochip: %d\n", ret);
+		return ret;
+	}
+
+	stmpe_disable(stmpe, STMPE_BLOCK_GPIO);
+
+	free_irq(irq, stmpe_gpio);
+	stmpe_gpio_irq_remove(stmpe_gpio);
+	platform_set_drvdata(pdev, NULL);
+	kfree(stmpe_gpio);
+
+	return 0;
+}
+
+static struct platform_driver stmpe_gpio_driver = {
+	.driver.name	= "stmpe-gpio",
+	.driver.owner	= THIS_MODULE,
+	.probe		= stmpe_gpio_probe,
+	.remove		= __devexit_p(stmpe_gpio_remove),
+};
+
+static int __init stmpe_gpio_init(void)
+{
+	return platform_driver_register(&stmpe_gpio_driver);
+}
+subsys_initcall(stmpe_gpio_init);
+
+static void __exit stmpe_gpio_exit(void)
+{
+	platform_driver_unregister(&stmpe_gpio_driver);
+}
+module_exit(stmpe_gpio_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("STMPExxxx GPIO driver");
+MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
-- 
1.7.0


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCHv3 3/3] input: add STMPE keypad driver
  2010-06-27 23:55         ` Samuel Ortiz
                             ` (2 preceding siblings ...)
  2010-07-01 12:00           ` [PATCHv3 2/3] gpio: add STMPE GPIO driver Rabin Vincent
@ 2010-07-01 12:00           ` Rabin Vincent
  3 siblings, 0 replies; 61+ messages in thread
From: Rabin Vincent @ 2010-07-01 12:00 UTC (permalink / raw)
  To: linux-kernel
  Cc: Samuel Ortiz, STEricsson_nomadik_linux, linus.walleij, l.fu,
	Rabin Vincent

Add an input driver for the keypad on STMPE I/O expanders.  This driver
uses the common support provided by the STMPE MFD driver.

Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
---
 drivers/input/keyboard/Kconfig        |   10 +
 drivers/input/keyboard/Makefile       |    1 +
 drivers/input/keyboard/stmpe-keypad.c |  386 +++++++++++++++++++++++++++++++++
 3 files changed, 397 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/stmpe-keypad.c

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index d8fa5d7..699aff9 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -374,6 +374,16 @@ config KEYBOARD_SH_KEYSC
 	  To compile this driver as a module, choose M here: the
 	  module will be called sh_keysc.
 
+config KEYBOARD_STMPE
+	tristate "STMPE keypad support"
+	depends on MFD_STMPE
+	help
+	  Say Y here if you want to use the keypad controller on STMPE I/O
+	  expanders.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called stmpe-keypad.
+
 config KEYBOARD_DAVINCI
 	tristate "TI DaVinci Key Scan"
 	depends on ARCH_DAVINCI_DM365
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 4596d0c..af24b07 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
 obj-$(CONFIG_KEYBOARD_PXA930_ROTARY)	+= pxa930_rotary.o
 obj-$(CONFIG_KEYBOARD_QT2160)		+= qt2160.o
 obj-$(CONFIG_KEYBOARD_SH_KEYSC)		+= sh_keysc.o
+obj-$(CONFIG_KEYBOARD_STMPE)		+= stmpe-keypad.o
 obj-$(CONFIG_KEYBOARD_STOWAWAY)		+= stowaway.o
 obj-$(CONFIG_KEYBOARD_SUNKBD)		+= sunkbd.o
 obj-$(CONFIG_KEYBOARD_TWL4030)		+= twl4030_keypad.o
diff --git a/drivers/input/keyboard/stmpe-keypad.c b/drivers/input/keyboard/stmpe-keypad.c
new file mode 100644
index 0000000..ab7610c
--- /dev/null
+++ b/drivers/input/keyboard/stmpe-keypad.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/mfd/stmpe.h>
+
+/* These are at the same addresses in all STMPE variants */
+#define STMPE_KPC_COL			0x60
+#define STMPE_KPC_ROW_MSB		0x61
+#define STMPE_KPC_ROW_LSB		0x62
+#define STMPE_KPC_CTRL_MSB		0x63
+#define STMPE_KPC_CTRL_LSB		0x64
+#define STMPE_KPC_COMBI_KEY_0		0x65
+#define STMPE_KPC_COMBI_KEY_1		0x66
+#define STMPE_KPC_COMBI_KEY_2		0x67
+#define STMPE_KPC_DATA_BYTE0		0x68
+#define STMPE_KPC_DATA_BYTE1		0x69
+#define STMPE_KPC_DATA_BYTE2		0x6a
+#define STMPE_KPC_DATA_BYTE3		0x6b
+#define STMPE_KPC_DATA_BYTE4		0x6c
+
+#define STMPE_KPC_CTRL_LSB_SCAN		(0x1 << 0)
+#define STMPE_KPC_CTRL_LSB_DEBOUNCE	(0x7f << 1)
+#define STMPE_KPC_CTRL_MSB_SCAN_COUNT	(0xf << 4)
+
+#define STMPE_KPC_ROW_MSB_ROWS		0xff
+
+#define STMPE_KPC_DATA_UP		(0x1 << 7)
+#define STMPE_KPC_DATA_ROW		(0xf << 3)
+#define STMPE_KPC_DATA_COL		(0x7 << 0)
+#define STMPE_KPC_DATA_NOKEY_MASK	0x78
+
+#define STMPE_KEYPAD_MAX_DEBOUNCE	127
+#define STMPE_KEYPAD_MAX_SCAN_COUNT	15
+
+#define STMPE_KEYPAD_MAX_ROWS		8
+#define STMPE_KEYPAD_MAX_COLS		8
+#define STMPE_KEYPAD_ROW_SHIFT		3
+#define STMPE_KEYPAD_KEYMAP_SIZE	\
+	(STMPE_KEYPAD_MAX_ROWS * STMPE_KEYPAD_MAX_COLS)
+
+/**
+ * struct stmpe_keypad_variant - model-specific attributes
+ * @auto_increment: whether the KPC_DATA_BYTE register address
+ *		    auto-increments on multiple read
+ * @num_data: number of data bytes
+ * @num_normal_data: number of normal keys' data bytes
+ * @max_cols: maximum number of columns supported
+ * @max_rows: maximum number of rows supported
+ * @col_gpios: bitmask of gpios which can be used for columns
+ * @row_gpios: bitmask of gpios which can be used for rows
+ */
+struct stmpe_keypad_variant {
+	bool		auto_increment;
+	int		num_data;
+	int		num_normal_data;
+	int		max_cols;
+	int		max_rows;
+	unsigned int	col_gpios;
+	unsigned int	row_gpios;
+};
+
+static const struct stmpe_keypad_variant stmpe_keypad_variants[] = {
+	[STMPE1601] = {
+		.auto_increment		= true,
+		.num_data		= 5,
+		.num_normal_data	= 3,
+		.max_cols		= 8,
+		.max_rows		= 8,
+		.col_gpios		= 0x000ff,	/* GPIO 0 - 7 */
+		.row_gpios		= 0x0ff00,	/* GPIO 8 - 15 */
+	},
+	[STMPE2401] = {
+		.auto_increment		= false,
+		.num_data		= 3,
+		.num_normal_data	= 2,
+		.max_cols		= 8,
+		.max_rows		= 12,
+		.col_gpios		= 0x0000ff,	/* GPIO 0 - 7*/
+		.row_gpios		= 0x1fef00,	/* GPIO 8-14, 16-20 */
+	},
+	[STMPE2403] = {
+		.auto_increment		= true,
+		.num_data		= 5,
+		.num_normal_data	= 3,
+		.max_cols		= 8,
+		.max_rows		= 12,
+		.col_gpios		= 0x0000ff,	/* GPIO 0 - 7*/
+		.row_gpios		= 0x1fef00,	/* GPIO 8-14, 16-20 */
+	},
+};
+
+struct stmpe_keypad {
+	struct stmpe *stmpe;
+	struct input_dev *input;
+	const struct stmpe_keypad_variant *variant;
+	const struct stmpe_keypad_platform_data *plat;
+
+	unsigned int rows;
+	unsigned int cols;
+
+	unsigned short keymap[STMPE_KEYPAD_KEYMAP_SIZE];
+};
+
+static int stmpe_keypad_read_data(struct stmpe_keypad *keypad, u8 *data)
+{
+	const struct stmpe_keypad_variant *variant = keypad->variant;
+	struct stmpe *stmpe = keypad->stmpe;
+	int ret;
+	int i;
+
+	if (variant->auto_increment)
+		return stmpe_block_read(stmpe, STMPE_KPC_DATA_BYTE0,
+					variant->num_data, data);
+
+	for (i = 0; i < variant->num_data; i++) {
+		ret = stmpe_reg_read(stmpe, STMPE_KPC_DATA_BYTE0 + i);
+		if (ret < 0)
+			return ret;
+
+		data[i] = ret;
+	}
+
+	return 0;
+}
+
+static irqreturn_t stmpe_keypad_irq(int irq, void *dev)
+{
+	struct stmpe_keypad *keypad = dev;
+	struct input_dev *input = keypad->input;
+	const struct stmpe_keypad_variant *variant = keypad->variant;
+	u8 fifo[variant->num_data];
+	int ret;
+	int i;
+
+	ret = stmpe_keypad_read_data(keypad, fifo);
+	if (ret < 0)
+		return IRQ_NONE;
+
+	for (i = 0; i < variant->num_normal_data; i++) {
+		u8 data = fifo[i];
+		int row = (data & STMPE_KPC_DATA_ROW) >> 3;
+		int col = data & STMPE_KPC_DATA_COL;
+		int code = MATRIX_SCAN_CODE(row, col, STMPE_KEYPAD_ROW_SHIFT);
+		bool up = data & STMPE_KPC_DATA_UP;
+
+		if ((data & STMPE_KPC_DATA_NOKEY_MASK)
+			== STMPE_KPC_DATA_NOKEY_MASK)
+			continue;
+
+		input_event(input, EV_MSC, MSC_SCAN, code);
+		input_report_key(input, keypad->keymap[code], !up);
+		input_sync(input);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit stmpe_keypad_altfunc_init(struct stmpe_keypad *keypad)
+{
+	const struct stmpe_keypad_variant *variant = keypad->variant;
+	unsigned int col_gpios = variant->col_gpios;
+	unsigned int row_gpios = variant->row_gpios;
+	struct stmpe *stmpe = keypad->stmpe;
+	unsigned int pins = 0;
+	int i;
+
+	/*
+	 * Figure out which pins need to be set to the keypad alternate
+	 * function.
+	 *
+	 * {cols,rows}_gpios are bitmasks of which pins on the chip can be used
+	 * for the keypad.
+	 *
+	 * keypad->{cols,rows} are a bitmask of which pins (of the ones useable
+	 * for the keypad) are used on the board.
+	 */
+
+	for (i = 0; i < variant->max_cols; i++) {
+		int num = __ffs(col_gpios);
+
+		if (keypad->cols & (1 << i))
+			pins |= 1 << num;
+
+		col_gpios &= ~(1 << num);
+	}
+
+	for (i = 0; i < variant->max_rows; i++) {
+		int num = __ffs(row_gpios);
+
+		if (keypad->rows & (1 << i))
+			pins |= 1 << num;
+
+		row_gpios &= ~(1 << num);
+	}
+
+	return stmpe_set_altfunc(stmpe, pins, STMPE_BLOCK_KEYPAD);
+}
+
+static int __devinit stmpe_keypad_chip_init(struct stmpe_keypad *keypad)
+{
+	const struct stmpe_keypad_platform_data *plat = keypad->plat;
+	const struct stmpe_keypad_variant *variant = keypad->variant;
+	struct stmpe *stmpe = keypad->stmpe;
+	int ret;
+
+	if (plat->debounce_ms > STMPE_KEYPAD_MAX_DEBOUNCE)
+		return -EINVAL;
+
+	if (plat->scan_count > STMPE_KEYPAD_MAX_SCAN_COUNT)
+		return -EINVAL;
+
+	ret = stmpe_enable(stmpe, STMPE_BLOCK_KEYPAD);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_keypad_altfunc_init(keypad);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_reg_write(stmpe, STMPE_KPC_COL, keypad->cols);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_reg_write(stmpe, STMPE_KPC_ROW_LSB, keypad->rows);
+	if (ret < 0)
+		return ret;
+
+	if (variant->max_rows > 8) {
+		ret = stmpe_set_bits(stmpe, STMPE_KPC_ROW_MSB,
+				     STMPE_KPC_ROW_MSB_ROWS,
+				     keypad->rows >> 8);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_KPC_CTRL_MSB,
+			     STMPE_KPC_CTRL_MSB_SCAN_COUNT,
+			     plat->scan_count << 4);
+	if (ret < 0)
+		return ret;
+
+	return stmpe_set_bits(stmpe, STMPE_KPC_CTRL_LSB,
+			      STMPE_KPC_CTRL_LSB_SCAN |
+			      STMPE_KPC_CTRL_LSB_DEBOUNCE,
+			      STMPE_KPC_CTRL_LSB_SCAN |
+			      (plat->debounce_ms << 1));
+}
+
+static int __devinit stmpe_keypad_probe(struct platform_device *pdev)
+{
+	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+	struct stmpe_keypad_platform_data *plat;
+	struct stmpe_keypad *keypad;
+	struct input_dev *input;
+	int ret;
+	int irq;
+	int i;
+
+	plat = stmpe->pdata->keypad;
+	if (!plat)
+		return -ENODEV;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	keypad = kzalloc(sizeof(struct stmpe_keypad), GFP_KERNEL);
+	if (!keypad)
+		return -ENOMEM;
+
+	input = input_allocate_device();
+	if (!input) {
+		ret = -ENOMEM;
+		goto out_freekeypad;
+	}
+
+	input->name = "STMPE keypad";
+	input->id.bustype = BUS_I2C;
+	input->dev.parent = &pdev->dev;
+
+	input_set_capability(input, EV_MSC, MSC_SCAN);
+
+	__set_bit(EV_KEY, input->evbit);
+	if (!plat->no_autorepeat)
+		__set_bit(EV_REP, input->evbit);
+
+	input->keycode = keypad->keymap;
+	input->keycodesize = sizeof(keypad->keymap[0]);
+	input->keycodemax = ARRAY_SIZE(keypad->keymap);
+
+	matrix_keypad_build_keymap(plat->keymap_data, STMPE_KEYPAD_ROW_SHIFT,
+				   input->keycode, input->keybit);
+
+	for (i = 0; i < plat->keymap_data->keymap_size; i++) {
+		unsigned int key = plat->keymap_data->keymap[i];
+
+		keypad->cols |= 1 << KEY_COL(key);
+		keypad->rows |= 1 << KEY_ROW(key);
+	}
+
+	keypad->stmpe = stmpe;
+	keypad->plat = plat;
+	keypad->input = input;
+	keypad->variant = &stmpe_keypad_variants[stmpe->partnum];
+
+	ret = stmpe_keypad_chip_init(keypad);
+	if (ret < 0)
+		goto out_freeinput;
+
+	ret = input_register_device(input);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"unable to register input device: %d\n", ret);
+		goto out_freeinput;
+	}
+
+	ret = request_threaded_irq(irq, NULL, stmpe_keypad_irq, IRQF_ONESHOT,
+				   "stmpe-keypad", keypad);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
+		goto out_unregisterinput;
+	}
+
+	platform_set_drvdata(pdev, keypad);
+
+	return 0;
+
+out_unregisterinput:
+	input_unregister_device(input);
+	input = NULL;
+out_freeinput:
+	input_free_device(input);
+out_freekeypad:
+	kfree(keypad);
+	return ret;
+}
+
+static int __devexit stmpe_keypad_remove(struct platform_device *pdev)
+{
+	struct stmpe_keypad *keypad = platform_get_drvdata(pdev);
+	struct stmpe *stmpe = keypad->stmpe;
+	int irq = platform_get_irq(pdev, 0);
+
+	stmpe_disable(stmpe, STMPE_BLOCK_KEYPAD);
+
+	free_irq(irq, keypad);
+	input_unregister_device(keypad->input);
+	platform_set_drvdata(pdev, NULL);
+	kfree(keypad);
+
+	return 0;
+}
+
+static struct platform_driver stmpe_keypad_driver = {
+	.driver.name	= "stmpe-keypad",
+	.driver.owner	= THIS_MODULE,
+	.probe		= stmpe_keypad_probe,
+	.remove		= __devexit_p(stmpe_keypad_remove),
+};
+
+static int __init stmpe_keypad_init(void)
+{
+	return platform_driver_register(&stmpe_keypad_driver);
+}
+module_init(stmpe_keypad_init);
+
+static void __exit stmpe_keypad_exit(void)
+{
+	platform_driver_unregister(&stmpe_keypad_driver);
+}
+module_exit(stmpe_keypad_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("STMPExxxx keypad driver");
+MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
-- 
1.7.0


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* Re: [PATCHv3 2/3] gpio: add STMPE GPIO driver
  2010-07-01 12:00           ` [PATCHv3 2/3] gpio: add STMPE GPIO driver Rabin Vincent
@ 2010-07-01 12:29             ` Luotao Fu
  0 siblings, 0 replies; 61+ messages in thread
From: Luotao Fu @ 2010-07-01 12:29 UTC (permalink / raw)
  To: Rabin Vincent
  Cc: linux-kernel, Samuel Ortiz, STEricsson_nomadik_linux,
	linus.walleij, l.fu

[-- Attachment #1: Type: text/plain, Size: 1162 bytes --]

Hi Rabin,

On Thu, Jul 01, 2010 at 05:30:47PM +0530, Rabin Vincent wrote:
> Add support for the GPIOs on STMPE I/O Expanders.
> 
> [l.fu@pengutronix.de: fix set direction input]
> Acked-by: Linus Walleij <linus.walleij@stericsson.com>
> Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
> ---
>  drivers/gpio/Kconfig      |    7 +
>  drivers/gpio/Makefile     |    1 +
>  drivers/gpio/stmpe-gpio.c |  390 +++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 398 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/gpio/stmpe-gpio.c

Any reason, that you didn't merge the patch
5ae4340192350e56dddd7537be26d1f8e57829e6 gpio/stmpe-gpio: set GPIO alternate function while requesting
https://patchwork.kernel.org/patch/107835/?

Otherwise:
Acked-by: Luotao Fu <l.fu@pengutronix.de>

cheers
Luotao Fu

-- 
Pengutronix e.K.                           | Dipl.-Ing. Luotao Fu        |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCHv3 1/3] mfd: add STMPE I/O Expander support
  2010-07-01 12:00           ` [PATCHv3 " Rabin Vincent
@ 2010-07-01 12:34             ` Luotao Fu
  2010-07-02 11:22               ` [PATCHv4 " Rabin Vincent
                                 ` (3 more replies)
  0 siblings, 4 replies; 61+ messages in thread
From: Luotao Fu @ 2010-07-01 12:34 UTC (permalink / raw)
  To: Rabin Vincent
  Cc: linux-kernel, Samuel Ortiz, STEricsson_nomadik_linux,
	linus.walleij, l.fu

[-- Attachment #1: Type: text/plain, Size: 2350 bytes --]

Hi Rabin,

On Thu, Jul 01, 2010 at 05:30:46PM +0530, Rabin Vincent wrote:
> Add support for the STMPE family of I/O Expanders from
> STMicroelectronics.  These devices include upto 24 gpios and a varying
> selection of blocks, including PWM, keypad, and touchscreen controllers.
> This patch adds the MFD core.
> 
> [l.fu@pengutronix.de: fix stmpe811 enable hook]
> Acked-by: Linus Walleij <linus.walleij@stericsson.com>
> Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
> ---
> 
> CHANGELOG (for the whole set)
> 
> v2 -> v3:
>   - Locking fixes
>   - Expanded Kconfig description
>   - Moved stmpe-devices.c content into stmpe.c
>   - Moved debug ifdefs to header
>   - Folded in Luotao Fu's one-liner fix for STMPE811 enable
>   - Folded in Luotao Fu's one-liner fix for stmpe-gpio input
>     direction
> 
> v1 -> v2
>   - Variant data to support other STMPE* variants
>   - Enable/disable API
>   - GPIO setup/remove callbacks
>   - pdata made optional for GPIO
>   - Platform specifies which blocks it wants to use
>   - IRQ invert polarity option
>   - STMPE811 support (needs testing)
> 
>  drivers/mfd/Kconfig       |   23 ++
>  drivers/mfd/Makefile      |    1 +
>  drivers/mfd/stmpe.c       |  915 +++++++++++++++++++++++++++++++++++++++++++++
>  drivers/mfd/stmpe.h       |  176 +++++++++
>  include/linux/mfd/stmpe.h |  154 ++++++++
>  5 files changed, 1269 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/mfd/stmpe.c
>  create mode 100644 drivers/mfd/stmpe.h
>  create mode 100644 include/linux/mfd/stmpe.h
> 

It might be great if you could also fold the patch
35df3fb7291f7b0281f0fce27a4e9cde0ea0b32f
mfd/stmpexxx: add touchscreen platform data
https://patchwork.kernel.org/patch/107833/
into this serie too. It simply makes little sense that the ts platform data
should be added in a separate patch, while keypad and gpio platformdata
stuffes are included in this patch.

otherwise:
Acked-by: Luotao Fu <l.fu@pengutronix.de>

cheers
Luotao Fu
-- 
Pengutronix e.K.                           | Dipl.-Ing. Luotao Fu        |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 61+ messages in thread

* [PATCHv4 1/3] mfd: add STMPE I/O Expander support
  2010-07-01 12:34             ` Luotao Fu
@ 2010-07-02 11:22               ` Rabin Vincent
  2010-07-02 15:31                 ` Samuel Ortiz
  2010-07-02 11:22               ` [PATCHv4 2/3] gpio: add STMPE GPIO driver Rabin Vincent
                                 ` (2 subsequent siblings)
  3 siblings, 1 reply; 61+ messages in thread
From: Rabin Vincent @ 2010-07-02 11:22 UTC (permalink / raw)
  To: linux-kernel
  Cc: Samuel Ortiz, STEricsson_nomadik_linux, linus.walleij, l.fu,
	Rabin Vincent

Add support for the STMPE family of I/O Expanders from
STMicroelectronics.  These devices include upto 24 gpios and a varying
selection of blocks, including PWM, keypad, and touchscreen controllers.
This patch adds the MFD core.

[l.fu@pengutronix.de: fix stmpe811 enable hook]
[l.fu@pengutronix.de: add touchscreen platform data]
Acked-by: Luotao Fu <l.fu@pengutronix.de>
Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
---

CHANGELOG (for the whole set)

v3 -> v4
  - Folded in Luotao Fu's touchscreen platform data
  - Folded in Luotao Fu's GPIO request/altfunc patch

v2 -> v3:
  - Locking fixes
  - Expanded Kconfig description
  - Moved stmpe-devices.c content into stmpe.c
  - Moved debug ifdefs to header
  - Folded in Luotao Fu's one-liner fix for STMPE811 enable
  - Folded in Luotao Fu's one-liner fix for stmpe-gpio input
    direction

v1 -> v2
  - Variant data to support other STMPE* variants
  - Enable/disable API
  - GPIO setup/remove callbacks
  - pdata made optional for GPIO
  - Platform specifies which blocks it wants to use
  - IRQ invert polarity option
  - STMPE811 support (needs testing)

 drivers/mfd/Kconfig       |   23 ++
 drivers/mfd/Makefile      |    1 +
 drivers/mfd/stmpe.c       |  915 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/mfd/stmpe.h       |  176 +++++++++
 include/linux/mfd/stmpe.h |  197 ++++++++++
 5 files changed, 1312 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/stmpe.c
 create mode 100644 drivers/mfd/stmpe.h
 create mode 100644 include/linux/mfd/stmpe.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 9da0e50..08e4693 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -177,6 +177,29 @@ config TWL4030_CODEC
 	select MFD_CORE
 	default n
 
+config MFD_STMPE
+	bool "Support STMicroelectronics STMPE"
+	depends on I2C=y && GENERIC_HARDIRQS
+	select MFD_CORE
+	help
+	  Support for the STMPE family of I/O Expanders from
+	  STMicroelectronics.
+
+	  Currently supported devices are:
+
+		STMPE811: GPIO, Touchscreen
+		STMPE1601: GPIO, Keypad
+		STMPE2401: GPIO, Keypad
+		STMPE2403: GPIO, Keypad
+
+	  This driver provides common support for accessing the device,
+	  additional drivers must be enabled in order to use the functionality
+	  of the device.  Currently available sub drivers are:
+
+		GPIO: stmpe-gpio
+		Keypad: stmpe-keypad
+		Touchscreen: stmpe-ts
+
 config MFD_TC35892
 	bool "Support Toshiba TC35892"
 	depends on I2C=y && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index fb503e7..4410747 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_HTC_I2CPLD)	+= htc-i2cpld.o
 obj-$(CONFIG_MFD_DAVINCI_VOICECODEC)	+= davinci_voicecodec.o
 obj-$(CONFIG_MFD_DM355EVM_MSP)	+= dm355evm_msp.o
 
+obj-$(CONFIG_MFD_STMPE)		+= stmpe.o
 obj-$(CONFIG_MFD_TC35892)	+= tc35892.o
 obj-$(CONFIG_MFD_T7L66XB)	+= t7l66xb.o tmio_core.o
 obj-$(CONFIG_MFD_TC6387XB)	+= tc6387xb.o tmio_core.o
diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c
new file mode 100644
index 0000000..a7f3099
--- /dev/null
+++ b/drivers/mfd/stmpe.c
@@ -0,0 +1,915 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/stmpe.h>
+#include "stmpe.h"
+
+static int __stmpe_enable(struct stmpe *stmpe, unsigned int blocks)
+{
+	return stmpe->variant->enable(stmpe, blocks, true);
+}
+
+static int __stmpe_disable(struct stmpe *stmpe, unsigned int blocks)
+{
+	return stmpe->variant->enable(stmpe, blocks, false);
+}
+
+static int __stmpe_reg_read(struct stmpe *stmpe, u8 reg)
+{
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(stmpe->i2c, reg);
+	if (ret < 0)
+		dev_err(stmpe->dev, "failed to read reg %#x: %d\n",
+			reg, ret);
+
+	dev_vdbg(stmpe->dev, "rd: reg %#x => data %#x\n", reg, ret);
+
+	return ret;
+}
+
+static int __stmpe_reg_write(struct stmpe *stmpe, u8 reg, u8 val)
+{
+	int ret;
+
+	dev_vdbg(stmpe->dev, "wr: reg %#x <= %#x\n", reg, val);
+
+	ret = i2c_smbus_write_byte_data(stmpe->i2c, reg, val);
+	if (ret < 0)
+		dev_err(stmpe->dev, "failed to write reg %#x: %d\n",
+			reg, ret);
+
+	return ret;
+}
+
+static int __stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val)
+{
+	int ret;
+
+	ret = __stmpe_reg_read(stmpe, reg);
+	if (ret < 0)
+		return ret;
+
+	ret &= ~mask;
+	ret |= val;
+
+	return __stmpe_reg_write(stmpe, reg, ret);
+}
+
+static int __stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length,
+			      u8 *values)
+{
+	int ret;
+
+	ret = i2c_smbus_read_i2c_block_data(stmpe->i2c, reg, length, values);
+	if (ret < 0)
+		dev_err(stmpe->dev, "failed to read regs %#x: %d\n",
+			reg, ret);
+
+	dev_vdbg(stmpe->dev, "rd: reg %#x (%d) => ret %#x\n", reg, length, ret);
+	stmpe_dump_bytes("stmpe rd: ", values, length);
+
+	return ret;
+}
+
+static int __stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,
+			const u8 *values)
+{
+	int ret;
+
+	dev_vdbg(stmpe->dev, "wr: regs %#x (%d)\n", reg, length);
+	stmpe_dump_bytes("stmpe wr: ", values, length);
+
+	ret = i2c_smbus_write_i2c_block_data(stmpe->i2c, reg, length,
+					     values);
+	if (ret < 0)
+		dev_err(stmpe->dev, "failed to write regs %#x: %d\n",
+			reg, ret);
+
+	return ret;
+}
+
+/**
+ * stmpe_enable - enable blocks on an STMPE device
+ * @stmpe:	Device to work on
+ * @blocks:	Mask of blocks (enum stmpe_block values) to enable
+ */
+int stmpe_enable(struct stmpe *stmpe, unsigned int blocks)
+{
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+	ret = __stmpe_enable(stmpe, blocks);
+	mutex_unlock(&stmpe->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_enable);
+
+/**
+ * stmpe_disable - disable blocks on an STMPE device
+ * @stmpe:	Device to work on
+ * @blocks:	Mask of blocks (enum stmpe_block values) to enable
+ */
+int stmpe_disable(struct stmpe *stmpe, unsigned int blocks)
+{
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+	ret = __stmpe_disable(stmpe, blocks);
+	mutex_unlock(&stmpe->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_disable);
+
+/**
+ * stmpe_reg_read() - read a single STMPE register
+ * @stmpe:	Device to read from
+ * @reg:	Register to read
+ */
+int stmpe_reg_read(struct stmpe *stmpe, u8 reg)
+{
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+	ret = __stmpe_reg_read(stmpe, reg);
+	mutex_unlock(&stmpe->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_reg_read);
+
+/**
+ * stmpe_reg_write() - write a single STMPE register
+ * @stmpe:	Device to write to
+ * @reg:	Register to write
+ * @val:	Value to write
+ */
+int stmpe_reg_write(struct stmpe *stmpe, u8 reg, u8 val)
+{
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+	ret = __stmpe_reg_write(stmpe, reg, val);
+	mutex_unlock(&stmpe->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_reg_write);
+
+/**
+ * stmpe_set_bits() - set the value of a bitfield in a STMPE register
+ * @stmpe:	Device to write to
+ * @reg:	Register to write
+ * @mask:	Mask of bits to set
+ * @val:	Value to set
+ */
+int stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val)
+{
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+	ret = __stmpe_set_bits(stmpe, reg, mask, val);
+	mutex_unlock(&stmpe->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_set_bits);
+
+/**
+ * stmpe_block_read() - read multiple STMPE registers
+ * @stmpe:	Device to read from
+ * @reg:	First register
+ * @length:	Number of registers
+ * @values:	Buffer to write to
+ */
+int stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length, u8 *values)
+{
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+	ret = __stmpe_block_read(stmpe, reg, length, values);
+	mutex_unlock(&stmpe->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_block_read);
+
+/**
+ * stmpe_block_write() - write multiple STMPE registers
+ * @stmpe:	Device to write to
+ * @reg:	First register
+ * @length:	Number of registers
+ * @values:	Values to write
+ */
+int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,
+		      const u8 *values)
+{
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+	ret = __stmpe_block_write(stmpe, reg, length, values);
+	mutex_unlock(&stmpe->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_block_write);
+
+/**
+ * stmpe_set_altfunc: set the alternate function for STMPE pins
+ * @stmpe:	Device to configure
+ * @pins:	Bitmask of pins to affect
+ * @block:	block to enable alternate functions for
+ *
+ * @pins is assumed to have a bit set for each of the bits whose alternate
+ * function is to be changed, numbered according to the GPIOXY numbers.
+ *
+ * If the GPIO module is not enabled, this function automatically enables it in
+ * order to perform the change.
+ */
+int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins, enum stmpe_block block)
+{
+	struct stmpe_variant_info *variant = stmpe->variant;
+	u8 regaddr = stmpe->regs[STMPE_IDX_GPAFR_U_MSB];
+	int af_bits = variant->af_bits;
+	int numregs = DIV_ROUND_UP(stmpe->num_gpios * af_bits, 8);
+	int afperreg = 8 / af_bits;
+	int mask = (1 << af_bits) - 1;
+	u8 regs[numregs];
+	int af;
+	int ret;
+
+	mutex_lock(&stmpe->lock);
+
+	ret = __stmpe_enable(stmpe, STMPE_BLOCK_GPIO);
+	if (ret < 0)
+		goto out;
+
+	ret = __stmpe_block_read(stmpe, regaddr, numregs, regs);
+	if (ret < 0)
+		goto out;
+
+	af = variant->get_altfunc(stmpe, block);
+
+	while (pins) {
+		int pin = __ffs(pins);
+		int regoffset = numregs - (pin / afperreg) - 1;
+		int pos = (pin % afperreg) * (8 / afperreg);
+
+		regs[regoffset] &= ~(mask << pos);
+		regs[regoffset] |= af << pos;
+
+		pins &= ~(1 << pin);
+	}
+
+	ret = __stmpe_block_write(stmpe, regaddr, numregs, regs);
+
+out:
+	mutex_unlock(&stmpe->lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stmpe_set_altfunc);
+
+/*
+ * GPIO (all variants)
+ */
+
+static struct resource stmpe_gpio_resources[] = {
+	/* Start and end filled dynamically */
+	{
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct mfd_cell stmpe_gpio_cell = {
+	.name		= "stmpe-gpio",
+	.resources	= stmpe_gpio_resources,
+	.num_resources	= ARRAY_SIZE(stmpe_gpio_resources),
+};
+
+/*
+ * Keypad (1601, 2401, 2403)
+ */
+
+static struct resource stmpe_keypad_resources[] = {
+	{
+		.name	= "KEYPAD",
+		.start	= 0,
+		.end	= 0,
+		.flags	= IORESOURCE_IRQ,
+	},
+	{
+		.name	= "KEYPAD_OVER",
+		.start	= 1,
+		.end	= 1,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct mfd_cell stmpe_keypad_cell = {
+	.name		= "stmpe-keypad",
+	.resources	= stmpe_keypad_resources,
+	.num_resources	= ARRAY_SIZE(stmpe_keypad_resources),
+};
+
+/*
+ * Touchscreen (STMPE811)
+ */
+
+static struct resource stmpe_ts_resources[] = {
+	{
+		.name	= "TOUCH_DET",
+		.start	= 0,
+		.end	= 0,
+		.flags	= IORESOURCE_IRQ,
+	},
+	{
+		.name	= "FIFO_TH",
+		.start	= 1,
+		.end	= 1,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct mfd_cell stmpe_ts_cell = {
+	.name		= "stmpe-ts",
+	.resources	= stmpe_ts_resources,
+	.num_resources	= ARRAY_SIZE(stmpe_ts_resources),
+};
+
+/*
+ * STMPE811
+ */
+
+static const u8 stmpe811_regs[] = {
+	[STMPE_IDX_CHIP_ID]	= STMPE811_REG_CHIP_ID,
+	[STMPE_IDX_ICR_LSB]	= STMPE811_REG_INT_CTRL,
+	[STMPE_IDX_IER_LSB]	= STMPE811_REG_INT_EN,
+	[STMPE_IDX_ISR_MSB]	= STMPE811_REG_INT_STA,
+	[STMPE_IDX_GPMR_LSB]	= STMPE811_REG_GPIO_MP_STA,
+	[STMPE_IDX_GPSR_LSB]	= STMPE811_REG_GPIO_SET_PIN,
+	[STMPE_IDX_GPCR_LSB]	= STMPE811_REG_GPIO_CLR_PIN,
+	[STMPE_IDX_GPDR_LSB]	= STMPE811_REG_GPIO_DIR,
+	[STMPE_IDX_GPRER_LSB]	= STMPE811_REG_GPIO_RE,
+	[STMPE_IDX_GPFER_LSB]	= STMPE811_REG_GPIO_FE,
+	[STMPE_IDX_GPAFR_U_MSB]	= STMPE811_REG_GPIO_AF,
+	[STMPE_IDX_IEGPIOR_LSB]	= STMPE811_REG_GPIO_INT_EN,
+	[STMPE_IDX_ISGPIOR_MSB]	= STMPE811_REG_GPIO_INT_STA,
+	[STMPE_IDX_GPEDR_MSB]	= STMPE811_REG_GPIO_ED,
+};
+
+static struct stmpe_variant_block stmpe811_blocks[] = {
+	{
+		.cell	= &stmpe_gpio_cell,
+		.irq	= STMPE811_IRQ_GPIOC,
+		.block	= STMPE_BLOCK_GPIO,
+	},
+	{
+		.cell	= &stmpe_ts_cell,
+		.irq	= STMPE811_IRQ_TOUCH_DET,
+		.block	= STMPE_BLOCK_TOUCHSCREEN,
+	},
+};
+
+static int stmpe811_enable(struct stmpe *stmpe, unsigned int blocks,
+			   bool enable)
+{
+	unsigned int mask = 0;
+
+	if (blocks & STMPE_BLOCK_GPIO)
+		mask |= STMPE811_SYS_CTRL2_GPIO_OFF;
+
+	if (blocks & STMPE_BLOCK_ADC)
+		mask |= STMPE811_SYS_CTRL2_ADC_OFF;
+
+	if (blocks & STMPE_BLOCK_TOUCHSCREEN)
+		mask |= STMPE811_SYS_CTRL2_TSC_OFF;
+
+	return __stmpe_set_bits(stmpe, STMPE811_REG_SYS_CTRL2, mask,
+				enable ? 0 : mask);
+}
+
+static int stmpe811_get_altfunc(struct stmpe *stmpe, enum stmpe_block block)
+{
+	/* 0 for touchscreen, 1 for GPIO */
+	return block != STMPE_BLOCK_TOUCHSCREEN;
+}
+
+static struct stmpe_variant_info stmpe811 = {
+	.name		= "stmpe811",
+	.id_val		= 0x0811,
+	.id_mask	= 0xffff,
+	.num_gpios	= 8,
+	.af_bits	= 1,
+	.regs		= stmpe811_regs,
+	.blocks		= stmpe811_blocks,
+	.num_blocks	= ARRAY_SIZE(stmpe811_blocks),
+	.num_irqs	= STMPE811_NR_INTERNAL_IRQS,
+	.enable		= stmpe811_enable,
+	.get_altfunc	= stmpe811_get_altfunc,
+};
+
+/*
+ * STMPE1601
+ */
+
+static const u8 stmpe1601_regs[] = {
+	[STMPE_IDX_CHIP_ID]	= STMPE1601_REG_CHIP_ID,
+	[STMPE_IDX_ICR_LSB]	= STMPE1601_REG_ICR_LSB,
+	[STMPE_IDX_IER_LSB]	= STMPE1601_REG_IER_LSB,
+	[STMPE_IDX_ISR_MSB]	= STMPE1601_REG_ISR_MSB,
+	[STMPE_IDX_GPMR_LSB]	= STMPE1601_REG_GPIO_MP_LSB,
+	[STMPE_IDX_GPSR_LSB]	= STMPE1601_REG_GPIO_SET_LSB,
+	[STMPE_IDX_GPCR_LSB]	= STMPE1601_REG_GPIO_CLR_LSB,
+	[STMPE_IDX_GPDR_LSB]	= STMPE1601_REG_GPIO_SET_DIR_LSB,
+	[STMPE_IDX_GPRER_LSB]	= STMPE1601_REG_GPIO_RE_LSB,
+	[STMPE_IDX_GPFER_LSB]	= STMPE1601_REG_GPIO_FE_LSB,
+	[STMPE_IDX_GPAFR_U_MSB]	= STMPE1601_REG_GPIO_AF_U_MSB,
+	[STMPE_IDX_IEGPIOR_LSB]	= STMPE1601_REG_INT_EN_GPIO_MASK_LSB,
+	[STMPE_IDX_ISGPIOR_MSB]	= STMPE1601_REG_INT_STA_GPIO_MSB,
+	[STMPE_IDX_GPEDR_MSB]	= STMPE1601_REG_GPIO_ED_MSB,
+};
+
+static struct stmpe_variant_block stmpe1601_blocks[] = {
+	{
+		.cell	= &stmpe_gpio_cell,
+		.irq	= STMPE24XX_IRQ_GPIOC,
+		.block	= STMPE_BLOCK_GPIO,
+	},
+	{
+		.cell	= &stmpe_keypad_cell,
+		.irq	= STMPE24XX_IRQ_KEYPAD,
+		.block	= STMPE_BLOCK_KEYPAD,
+	},
+};
+
+static int stmpe1601_enable(struct stmpe *stmpe, unsigned int blocks,
+			    bool enable)
+{
+	unsigned int mask = 0;
+
+	if (blocks & STMPE_BLOCK_GPIO)
+		mask |= STMPE1601_SYS_CTRL_ENABLE_GPIO;
+
+	if (blocks & STMPE_BLOCK_KEYPAD)
+		mask |= STMPE1601_SYS_CTRL_ENABLE_KPC;
+
+	return __stmpe_set_bits(stmpe, STMPE1601_REG_SYS_CTRL, mask,
+				enable ? mask : 0);
+}
+
+static int stmpe1601_get_altfunc(struct stmpe *stmpe, enum stmpe_block block)
+{
+	switch (block) {
+	case STMPE_BLOCK_PWM:
+		return 2;
+
+	case STMPE_BLOCK_KEYPAD:
+		return 1;
+
+	case STMPE_BLOCK_GPIO:
+	default:
+		return 0;
+	}
+}
+
+static struct stmpe_variant_info stmpe1601 = {
+	.name		= "stmpe1601",
+	.id_val		= 0x0210,
+	.id_mask	= 0xfff0,	/* at least 0x0210 and 0x0212 */
+	.num_gpios	= 16,
+	.af_bits	= 2,
+	.regs		= stmpe1601_regs,
+	.blocks		= stmpe1601_blocks,
+	.num_blocks	= ARRAY_SIZE(stmpe1601_blocks),
+	.num_irqs	= STMPE1601_NR_INTERNAL_IRQS,
+	.enable		= stmpe1601_enable,
+	.get_altfunc	= stmpe1601_get_altfunc,
+};
+
+/*
+ * STMPE24XX
+ */
+
+static const u8 stmpe24xx_regs[] = {
+	[STMPE_IDX_CHIP_ID]	= STMPE24XX_REG_CHIP_ID,
+	[STMPE_IDX_ICR_LSB]	= STMPE24XX_REG_ICR_LSB,
+	[STMPE_IDX_IER_LSB]	= STMPE24XX_REG_IER_LSB,
+	[STMPE_IDX_ISR_MSB]	= STMPE24XX_REG_ISR_MSB,
+	[STMPE_IDX_GPMR_LSB]	= STMPE24XX_REG_GPMR_LSB,
+	[STMPE_IDX_GPSR_LSB]	= STMPE24XX_REG_GPSR_LSB,
+	[STMPE_IDX_GPCR_LSB]	= STMPE24XX_REG_GPCR_LSB,
+	[STMPE_IDX_GPDR_LSB]	= STMPE24XX_REG_GPDR_LSB,
+	[STMPE_IDX_GPRER_LSB]	= STMPE24XX_REG_GPRER_LSB,
+	[STMPE_IDX_GPFER_LSB]	= STMPE24XX_REG_GPFER_LSB,
+	[STMPE_IDX_GPAFR_U_MSB]	= STMPE24XX_REG_GPAFR_U_MSB,
+	[STMPE_IDX_IEGPIOR_LSB]	= STMPE24XX_REG_IEGPIOR_LSB,
+	[STMPE_IDX_ISGPIOR_MSB]	= STMPE24XX_REG_ISGPIOR_MSB,
+	[STMPE_IDX_GPEDR_MSB]	= STMPE24XX_REG_GPEDR_MSB,
+};
+
+static struct stmpe_variant_block stmpe24xx_blocks[] = {
+	{
+		.cell	= &stmpe_gpio_cell,
+		.irq	= STMPE24XX_IRQ_GPIOC,
+		.block	= STMPE_BLOCK_GPIO,
+	},
+	{
+		.cell	= &stmpe_keypad_cell,
+		.irq	= STMPE24XX_IRQ_KEYPAD,
+		.block	= STMPE_BLOCK_KEYPAD,
+	},
+};
+
+static int stmpe24xx_enable(struct stmpe *stmpe, unsigned int blocks,
+			    bool enable)
+{
+	unsigned int mask = 0;
+
+	if (blocks & STMPE_BLOCK_GPIO)
+		mask |= STMPE24XX_SYS_CTRL_ENABLE_GPIO;
+
+	if (blocks & STMPE_BLOCK_KEYPAD)
+		mask |= STMPE24XX_SYS_CTRL_ENABLE_KPC;
+
+	return __stmpe_set_bits(stmpe, STMPE24XX_REG_SYS_CTRL, mask,
+				enable ? mask : 0);
+}
+
+static int stmpe24xx_get_altfunc(struct stmpe *stmpe, enum stmpe_block block)
+{
+	switch (block) {
+	case STMPE_BLOCK_ROTATOR:
+		return 2;
+
+	case STMPE_BLOCK_KEYPAD:
+		return 1;
+
+	case STMPE_BLOCK_GPIO:
+	default:
+		return 0;
+	}
+}
+
+static struct stmpe_variant_info stmpe2401 = {
+	.name		= "stmpe2401",
+	.id_val		= 0x0101,
+	.id_mask	= 0xffff,
+	.num_gpios	= 24,
+	.af_bits	= 2,
+	.regs		= stmpe24xx_regs,
+	.blocks		= stmpe24xx_blocks,
+	.num_blocks	= ARRAY_SIZE(stmpe24xx_blocks),
+	.num_irqs	= STMPE24XX_NR_INTERNAL_IRQS,
+	.enable		= stmpe24xx_enable,
+	.get_altfunc	= stmpe24xx_get_altfunc,
+};
+
+static struct stmpe_variant_info stmpe2403 = {
+	.name		= "stmpe2403",
+	.id_val		= 0x0120,
+	.id_mask	= 0xffff,
+	.num_gpios	= 24,
+	.af_bits	= 2,
+	.regs		= stmpe24xx_regs,
+	.blocks		= stmpe24xx_blocks,
+	.num_blocks	= ARRAY_SIZE(stmpe24xx_blocks),
+	.num_irqs	= STMPE24XX_NR_INTERNAL_IRQS,
+	.enable		= stmpe24xx_enable,
+	.get_altfunc	= stmpe24xx_get_altfunc,
+};
+
+static struct stmpe_variant_info *stmpe_variant_info[] = {
+	[STMPE811]	= &stmpe811,
+	[STMPE1601]	= &stmpe1601,
+	[STMPE2401]	= &stmpe2401,
+	[STMPE2403]	= &stmpe2403,
+};
+
+static irqreturn_t stmpe_irq(int irq, void *data)
+{
+	struct stmpe *stmpe = data;
+	struct stmpe_variant_info *variant = stmpe->variant;
+	int num = DIV_ROUND_UP(variant->num_irqs, 8);
+	u8 israddr = stmpe->regs[STMPE_IDX_ISR_MSB];
+	u8 isr[num];
+	int ret;
+	int i;
+
+	ret = stmpe_block_read(stmpe, israddr, num, isr);
+	if (ret < 0)
+		return IRQ_NONE;
+
+	for (i = 0; i < num; i++) {
+		int bank = num - i - 1;
+		u8 status = isr[i];
+		u8 clear;
+
+		status &= stmpe->ier[bank];
+		if (!status)
+			continue;
+
+		clear = status;
+		while (status) {
+			int bit = __ffs(status);
+			int line = bank * 8 + bit;
+
+			handle_nested_irq(stmpe->irq_base + line);
+			status &= ~(1 << bit);
+		}
+
+		stmpe_reg_write(stmpe, israddr + i, clear);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void stmpe_irq_lock(unsigned int irq)
+{
+	struct stmpe *stmpe = get_irq_chip_data(irq);
+
+	mutex_lock(&stmpe->irq_lock);
+}
+
+static void stmpe_irq_sync_unlock(unsigned int irq)
+{
+	struct stmpe *stmpe = get_irq_chip_data(irq);
+	struct stmpe_variant_info *variant = stmpe->variant;
+	int num = DIV_ROUND_UP(variant->num_irqs, 8);
+	int i;
+
+	for (i = 0; i < num; i++) {
+		u8 new = stmpe->ier[i];
+		u8 old = stmpe->oldier[i];
+
+		if (new == old)
+			continue;
+
+		stmpe->oldier[i] = new;
+		stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_IER_LSB] - i, new);
+	}
+
+	mutex_unlock(&stmpe->irq_lock);
+}
+
+static void stmpe_irq_mask(unsigned int irq)
+{
+	struct stmpe *stmpe = get_irq_chip_data(irq);
+	int offset = irq - stmpe->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	stmpe->ier[regoffset] &= ~mask;
+}
+
+static void stmpe_irq_unmask(unsigned int irq)
+{
+	struct stmpe *stmpe = get_irq_chip_data(irq);
+	int offset = irq - stmpe->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	stmpe->ier[regoffset] |= mask;
+}
+
+static struct irq_chip stmpe_irq_chip = {
+	.name			= "stmpe",
+	.bus_lock		= stmpe_irq_lock,
+	.bus_sync_unlock	= stmpe_irq_sync_unlock,
+	.mask			= stmpe_irq_mask,
+	.unmask			= stmpe_irq_unmask,
+};
+
+static int __devinit stmpe_irq_init(struct stmpe *stmpe)
+{
+	int num_irqs = stmpe->variant->num_irqs;
+	int base = stmpe->irq_base;
+	int irq;
+
+	for (irq = base; irq < base + num_irqs; irq++) {
+		set_irq_chip_data(irq, stmpe);
+		set_irq_chip_and_handler(irq, &stmpe_irq_chip,
+					 handle_edge_irq);
+		set_irq_nested_thread(irq, 1);
+#ifdef CONFIG_ARM
+		set_irq_flags(irq, IRQF_VALID);
+#else
+		set_irq_noprobe(irq);
+#endif
+	}
+
+	return 0;
+}
+
+static void stmpe_irq_remove(struct stmpe *stmpe)
+{
+	int num_irqs = stmpe->variant->num_irqs;
+	int base = stmpe->irq_base;
+	int irq;
+
+	for (irq = base; irq < base + num_irqs; irq++) {
+#ifdef CONFIG_ARM
+		set_irq_flags(irq, 0);
+#endif
+		set_irq_chip_and_handler(irq, NULL, NULL);
+		set_irq_chip_data(irq, NULL);
+	}
+}
+
+static int __devinit stmpe_chip_init(struct stmpe *stmpe)
+{
+	unsigned int irq_trigger = stmpe->pdata->irq_trigger;
+	struct stmpe_variant_info *variant = stmpe->variant;
+	u8 icr = STMPE_ICR_LSB_GIM;
+	unsigned int id;
+	u8 data[2];
+	int ret;
+
+	ret = stmpe_block_read(stmpe, stmpe->regs[STMPE_IDX_CHIP_ID],
+			       ARRAY_SIZE(data), data);
+	if (ret < 0)
+		return ret;
+
+	id = (data[0] << 8) | data[1];
+	if ((id & variant->id_mask) != variant->id_val) {
+		dev_err(stmpe->dev, "unknown chip id: %#x\n", id);
+		return -EINVAL;
+	}
+
+	dev_info(stmpe->dev, "%s detected, chip id: %#x\n", variant->name, id);
+
+	/* Disable all modules -- subdrivers should enable what they need. */
+	ret = stmpe_disable(stmpe, ~0);
+	if (ret)
+		return ret;
+
+	if (irq_trigger == IRQF_TRIGGER_FALLING ||
+	    irq_trigger == IRQF_TRIGGER_RISING)
+		icr |= STMPE_ICR_LSB_EDGE;
+
+	if (irq_trigger == IRQF_TRIGGER_RISING ||
+	    irq_trigger == IRQF_TRIGGER_HIGH)
+		icr |= STMPE_ICR_LSB_HIGH;
+
+	if (stmpe->pdata->irq_invert_polarity)
+		icr ^= STMPE_ICR_LSB_HIGH;
+
+	return stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_ICR_LSB], icr);
+}
+
+static int __devinit stmpe_add_device(struct stmpe *stmpe,
+				      struct mfd_cell *cell, int irq)
+{
+	return mfd_add_devices(stmpe->dev, stmpe->pdata->id, cell, 1,
+			       NULL, stmpe->irq_base + irq);
+}
+
+static int __devinit stmpe_devices_init(struct stmpe *stmpe)
+{
+	struct stmpe_variant_info *variant = stmpe->variant;
+	unsigned int platform_blocks = stmpe->pdata->blocks;
+	int ret = -EINVAL;
+	int i;
+
+	for (i = 0; i < variant->num_blocks; i++) {
+		struct stmpe_variant_block *block = &variant->blocks[i];
+
+		if (!(platform_blocks & block->block))
+			continue;
+
+		platform_blocks &= ~block->block;
+		ret = stmpe_add_device(stmpe, block->cell, block->irq);
+		if (ret)
+			return ret;
+	}
+
+	if (platform_blocks)
+		dev_warn(stmpe->dev,
+			 "platform wants blocks (%#x) not present on variant",
+			 platform_blocks);
+
+	return ret;
+}
+
+static int __devinit stmpe_probe(struct i2c_client *i2c,
+				 const struct i2c_device_id *id)
+{
+	struct stmpe_platform_data *pdata = i2c->dev.platform_data;
+	struct stmpe *stmpe;
+	int ret;
+
+	if (!pdata)
+		return -EINVAL;
+
+	stmpe = kzalloc(sizeof(struct stmpe), GFP_KERNEL);
+	if (!stmpe)
+		return -ENOMEM;
+
+	mutex_init(&stmpe->irq_lock);
+	mutex_init(&stmpe->lock);
+
+	stmpe->dev = &i2c->dev;
+	stmpe->i2c = i2c;
+
+	stmpe->pdata = pdata;
+	stmpe->irq_base = pdata->irq_base;
+
+	stmpe->partnum = id->driver_data;
+	stmpe->variant = stmpe_variant_info[stmpe->partnum];
+	stmpe->regs = stmpe->variant->regs;
+	stmpe->num_gpios = stmpe->variant->num_gpios;
+
+	i2c_set_clientdata(i2c, stmpe);
+
+	ret = stmpe_chip_init(stmpe);
+	if (ret)
+		goto out_free;
+
+	ret = stmpe_irq_init(stmpe);
+	if (ret)
+		goto out_free;
+
+	ret = request_threaded_irq(stmpe->i2c->irq, NULL, stmpe_irq,
+				   pdata->irq_trigger | IRQF_ONESHOT,
+				   "stmpe", stmpe);
+	if (ret) {
+		dev_err(stmpe->dev, "failed to request IRQ: %d\n", ret);
+		goto out_removeirq;
+	}
+
+	ret = stmpe_devices_init(stmpe);
+	if (ret) {
+		dev_err(stmpe->dev, "failed to add children\n");
+		goto out_removedevs;
+	}
+
+	return 0;
+
+out_removedevs:
+	mfd_remove_devices(stmpe->dev);
+	free_irq(stmpe->i2c->irq, stmpe);
+out_removeirq:
+	stmpe_irq_remove(stmpe);
+out_free:
+	kfree(stmpe);
+	return ret;
+}
+
+static int __devexit stmpe_remove(struct i2c_client *client)
+{
+	struct stmpe *stmpe = i2c_get_clientdata(client);
+
+	mfd_remove_devices(stmpe->dev);
+
+	free_irq(stmpe->i2c->irq, stmpe);
+	stmpe_irq_remove(stmpe);
+
+	kfree(stmpe);
+
+	return 0;
+}
+
+static const struct i2c_device_id stmpe_id[] = {
+	{ "stmpe811", STMPE811 },
+	{ "stmpe1601", STMPE1601 },
+	{ "stmpe2401", STMPE2401 },
+	{ "stmpe2403", STMPE2403 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, stmpe_id);
+
+static struct i2c_driver stmpe_driver = {
+	.driver.name	= "stmpe",
+	.driver.owner	= THIS_MODULE,
+	.probe		= stmpe_probe,
+	.remove		= __devexit_p(stmpe_remove),
+	.id_table	= stmpe_id,
+};
+
+static int __init stmpe_init(void)
+{
+	return i2c_add_driver(&stmpe_driver);
+}
+subsys_initcall(stmpe_init);
+
+static void __exit stmpe_exit(void)
+{
+	i2c_del_driver(&stmpe_driver);
+}
+module_exit(stmpe_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("STMPE MFD core driver");
+MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
diff --git a/drivers/mfd/stmpe.h b/drivers/mfd/stmpe.h
new file mode 100644
index 0000000..991f0ec
--- /dev/null
+++ b/drivers/mfd/stmpe.h
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#ifndef __STMPE_H
+#define __STMPE_H
+
+#ifdef STMPE_DUMP_BYTES
+static inline void stmpe_dump_bytes(const char *str, const void *buf,
+				    size_t len)
+{
+	print_hex_dump_bytes(str, DUMP_PREFIX_OFFSET, buf, len);
+}
+#else
+static inline void stmpe_dump_bytes(const char *str, const void *buf,
+				    size_t len)
+{
+}
+#endif
+
+/**
+ * struct stmpe_variant_block - information about block
+ * @cell:	base mfd cell
+ * @irq:	interrupt number to be added to each IORESOURCE_IRQ
+ *		in the cell
+ * @block:	block id; used for identification with platform data and for
+ *		enable and altfunc callbacks
+ */
+struct stmpe_variant_block {
+	struct mfd_cell		*cell;
+	int			irq;
+	enum stmpe_block	block;
+};
+
+/**
+ * struct stmpe_variant_info - variant-specific information
+ * @name:	part name
+ * @id_val:	content of CHIPID register
+ * @id_mask:	bits valid in CHIPID register for comparison with id_val
+ * @num_gpios:	number of GPIOS
+ * @af_bits:	number of bits used to specify the alternate function
+ * @blocks:	list of blocks present on this device
+ * @num_blocks:	number of blocks present on this device
+ * @num_irqs:	number of internal IRQs available on this device
+ * @enable:	callback to enable the specified blocks.
+ *		Called with the I/O lock held.
+ * @get_altfunc: callback to get the alternate function number for the
+ *		 specific block
+ */
+struct stmpe_variant_info {
+	const char *name;
+	u16 id_val;
+	u16 id_mask;
+	int num_gpios;
+	int af_bits;
+	const u8 *regs;
+	struct stmpe_variant_block *blocks;
+	int num_blocks;
+	int num_irqs;
+	int (*enable)(struct stmpe *stmpe, unsigned int blocks, bool enable);
+	int (*get_altfunc)(struct stmpe *stmpe, enum stmpe_block block);
+};
+
+#define STMPE_ICR_LSB_HIGH	(1 << 2)
+#define STMPE_ICR_LSB_EDGE	(1 << 1)
+#define STMPE_ICR_LSB_GIM	(1 << 0)
+
+/*
+ * STMPE811
+ */
+
+#define STMPE811_IRQ_TOUCH_DET		0
+#define STMPE811_IRQ_FIFO_TH		1
+#define STMPE811_IRQ_FIFO_OFLOW		2
+#define STMPE811_IRQ_FIFO_FULL		3
+#define STMPE811_IRQ_FIFO_EMPTY		4
+#define STMPE811_IRQ_TEMP_SENS		5
+#define STMPE811_IRQ_ADC		6
+#define STMPE811_IRQ_GPIOC		7
+#define STMPE811_NR_INTERNAL_IRQS	8
+
+#define STMPE811_REG_CHIP_ID		0x00
+#define STMPE811_REG_SYS_CTRL2		0x04
+#define STMPE811_REG_INT_CTRL		0x09
+#define STMPE811_REG_INT_EN		0x0A
+#define STMPE811_REG_INT_STA		0x0B
+#define STMPE811_REG_GPIO_INT_EN	0x0C
+#define STMPE811_REG_GPIO_INT_STA	0x0D
+#define STMPE811_REG_GPIO_SET_PIN	0x10
+#define STMPE811_REG_GPIO_CLR_PIN	0x11
+#define STMPE811_REG_GPIO_MP_STA	0x12
+#define STMPE811_REG_GPIO_DIR		0x13
+#define STMPE811_REG_GPIO_ED		0x14
+#define STMPE811_REG_GPIO_RE		0x15
+#define STMPE811_REG_GPIO_FE		0x16
+#define STMPE811_REG_GPIO_AF		0x17
+
+#define STMPE811_SYS_CTRL2_ADC_OFF	(1 << 0)
+#define STMPE811_SYS_CTRL2_TSC_OFF	(1 << 1)
+#define STMPE811_SYS_CTRL2_GPIO_OFF	(1 << 2)
+#define STMPE811_SYS_CTRL2_TS_OFF	(1 << 3)
+
+/*
+ * STMPE1601
+ */
+
+#define STMPE1601_IRQ_GPIOC		8
+#define STMPE1601_IRQ_PWM3		7
+#define STMPE1601_IRQ_PWM2		6
+#define STMPE1601_IRQ_PWM1		5
+#define STMPE1601_IRQ_PWM0		4
+#define STMPE1601_IRQ_KEYPAD_OVER	2
+#define STMPE1601_IRQ_KEYPAD		1
+#define STMPE1601_IRQ_WAKEUP		0
+#define STMPE1601_NR_INTERNAL_IRQS	9
+
+#define STMPE1601_REG_SYS_CTRL			0x02
+#define STMPE1601_REG_ICR_LSB			0x11
+#define STMPE1601_REG_IER_LSB			0x13
+#define STMPE1601_REG_ISR_MSB			0x14
+#define STMPE1601_REG_CHIP_ID			0x80
+#define STMPE1601_REG_INT_EN_GPIO_MASK_LSB	0x17
+#define STMPE1601_REG_INT_STA_GPIO_MSB		0x18
+#define STMPE1601_REG_GPIO_MP_LSB		0x87
+#define STMPE1601_REG_GPIO_SET_LSB		0x83
+#define STMPE1601_REG_GPIO_CLR_LSB		0x85
+#define STMPE1601_REG_GPIO_SET_DIR_LSB		0x89
+#define STMPE1601_REG_GPIO_ED_MSB		0x8A
+#define STMPE1601_REG_GPIO_RE_LSB		0x8D
+#define STMPE1601_REG_GPIO_FE_LSB		0x8F
+#define STMPE1601_REG_GPIO_AF_U_MSB		0x92
+
+#define STMPE1601_SYS_CTRL_ENABLE_GPIO		(1 << 3)
+#define STMPE1601_SYS_CTRL_ENABLE_KPC		(1 << 1)
+#define STMPE1601_SYSCON_ENABLE_SPWM		(1 << 0)
+
+/*
+ * STMPE24xx
+ */
+
+#define STMPE24XX_IRQ_GPIOC		8
+#define STMPE24XX_IRQ_PWM2		7
+#define STMPE24XX_IRQ_PWM1		6
+#define STMPE24XX_IRQ_PWM0		5
+#define STMPE24XX_IRQ_ROT_OVER		4
+#define STMPE24XX_IRQ_ROT		3
+#define STMPE24XX_IRQ_KEYPAD_OVER	2
+#define STMPE24XX_IRQ_KEYPAD		1
+#define STMPE24XX_IRQ_WAKEUP		0
+#define STMPE24XX_NR_INTERNAL_IRQS	9
+
+#define STMPE24XX_REG_SYS_CTRL		0x02
+#define STMPE24XX_REG_ICR_LSB		0x11
+#define STMPE24XX_REG_IER_LSB		0x13
+#define STMPE24XX_REG_ISR_MSB		0x14
+#define STMPE24XX_REG_CHIP_ID		0x80
+#define STMPE24XX_REG_IEGPIOR_LSB	0x18
+#define STMPE24XX_REG_ISGPIOR_MSB	0x19
+#define STMPE24XX_REG_GPMR_LSB		0xA5
+#define STMPE24XX_REG_GPSR_LSB		0x85
+#define STMPE24XX_REG_GPCR_LSB		0x88
+#define STMPE24XX_REG_GPDR_LSB		0x8B
+#define STMPE24XX_REG_GPEDR_MSB		0x8C
+#define STMPE24XX_REG_GPRER_LSB		0x91
+#define STMPE24XX_REG_GPFER_LSB		0x94
+#define STMPE24XX_REG_GPAFR_U_MSB	0x9B
+
+#define STMPE24XX_SYS_CTRL_ENABLE_GPIO		(1 << 3)
+#define STMPE24XX_SYSCON_ENABLE_PWM		(1 << 2)
+#define STMPE24XX_SYS_CTRL_ENABLE_KPC		(1 << 1)
+#define STMPE24XX_SYSCON_ENABLE_ROT		(1 << 0)
+
+#endif
diff --git a/include/linux/mfd/stmpe.h b/include/linux/mfd/stmpe.h
new file mode 100644
index 0000000..90faa98
--- /dev/null
+++ b/include/linux/mfd/stmpe.h
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#ifndef __LINUX_MFD_STMPE_H
+#define __LINUX_MFD_STMPE_H
+
+#include <linux/device.h>
+
+enum stmpe_block {
+	STMPE_BLOCK_GPIO	= 1 << 0,
+	STMPE_BLOCK_KEYPAD	= 1 << 1,
+	STMPE_BLOCK_TOUCHSCREEN	= 1 << 2,
+	STMPE_BLOCK_ADC		= 1 << 3,
+	STMPE_BLOCK_PWM		= 1 << 4,
+	STMPE_BLOCK_ROTATOR	= 1 << 5,
+};
+
+enum stmpe_partnum {
+	STMPE811,
+	STMPE1601,
+	STMPE2401,
+	STMPE2403,
+};
+
+/*
+ * For registers whose locations differ on variants,  the correct address is
+ * obtained by indexing stmpe->regs with one of the following.
+ */
+enum {
+	STMPE_IDX_CHIP_ID,
+	STMPE_IDX_ICR_LSB,
+	STMPE_IDX_IER_LSB,
+	STMPE_IDX_ISR_MSB,
+	STMPE_IDX_GPMR_LSB,
+	STMPE_IDX_GPSR_LSB,
+	STMPE_IDX_GPCR_LSB,
+	STMPE_IDX_GPDR_LSB,
+	STMPE_IDX_GPEDR_MSB,
+	STMPE_IDX_GPRER_LSB,
+	STMPE_IDX_GPFER_LSB,
+	STMPE_IDX_GPAFR_U_MSB,
+	STMPE_IDX_IEGPIOR_LSB,
+	STMPE_IDX_ISGPIOR_MSB,
+	STMPE_IDX_MAX,
+};
+
+
+struct stmpe_variant_info;
+
+/**
+ * struct stmpe - STMPE MFD structure
+ * @lock: lock protecting I/O operations
+ * @irq_lock: IRQ bus lock
+ * @dev: device, mostly for dev_dbg()
+ * @i2c: i2c client
+ * @variant: the detected STMPE model number
+ * @regs: list of addresses of registers which are at different addresses on
+ *	  different variants.  Indexed by one of STMPE_IDX_*.
+ * @irq_base: starting IRQ number for internal IRQs
+ * @num_gpios: number of gpios, differs for variants
+ * @ier: cache of IER registers for bus_lock
+ * @oldier: cache of IER registers for bus_lock
+ * @pdata: platform data
+ */
+struct stmpe {
+	struct mutex lock;
+	struct mutex irq_lock;
+	struct device *dev;
+	struct i2c_client *i2c;
+	enum stmpe_partnum partnum;
+	struct stmpe_variant_info *variant;
+	const u8 *regs;
+
+	int irq_base;
+	int num_gpios;
+	u8 ier[2];
+	u8 oldier[2];
+	struct stmpe_platform_data *pdata;
+};
+
+extern int stmpe_reg_write(struct stmpe *stmpe, u8 reg, u8 data);
+extern int stmpe_reg_read(struct stmpe *stmpe, u8 reg);
+extern int stmpe_block_read(struct stmpe *stmpe, u8 reg, u8 length,
+			    u8 *values);
+extern int stmpe_block_write(struct stmpe *stmpe, u8 reg, u8 length,
+			     const u8 *values);
+extern int stmpe_set_bits(struct stmpe *stmpe, u8 reg, u8 mask, u8 val);
+extern int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins,
+			     enum stmpe_block block);
+extern int stmpe_enable(struct stmpe *stmpe, unsigned int blocks);
+extern int stmpe_disable(struct stmpe *stmpe, unsigned int blocks);
+
+struct matrix_keymap_data;
+
+/**
+ * struct stmpe_keypad_platform_data - STMPE keypad platform data
+ * @keymap_data: key map table and size
+ * @debounce_ms: debounce interval, in ms.  Maximum is
+ *		 %STMPE_KEYPAD_MAX_DEBOUNCE.
+ * @scan_count: number of key scanning cycles to confirm key data.
+ *		Maximum is %STMPE_KEYPAD_MAX_SCAN_COUNT.
+ * @no_autorepeat: disable key autorepeat
+ */
+struct stmpe_keypad_platform_data {
+	struct matrix_keymap_data *keymap_data;
+	unsigned int debounce_ms;
+	unsigned int scan_count;
+	bool no_autorepeat;
+};
+
+/**
+ * struct stmpe_gpio_platform_data - STMPE GPIO platform data
+ * @gpio_base: first gpio number assigned.  A maximum of
+ *	       %STMPE_NR_GPIOS GPIOs will be allocated.
+ */
+struct stmpe_gpio_platform_data {
+	int gpio_base;
+	void (*setup)(struct stmpe *stmpe, unsigned gpio_base);
+	void (*remove)(struct stmpe *stmpe, unsigned gpio_base);
+};
+
+/**
+ * struct stmpe_ts_platform_data - stmpe811 touch screen controller platform
+ * data
+ * @sample_time: ADC converstion time in number of clock.
+ * (0 -> 36 clocks, 1 -> 44 clocks, 2 -> 56 clocks, 3 -> 64 clocks,
+ * 4 -> 80 clocks, 5 -> 96 clocks, 6 -> 144 clocks),
+ * recommended is 4.
+ * @mod_12b: ADC Bit mode (0 -> 10bit ADC, 1 -> 12bit ADC)
+ * @ref_sel: ADC reference source
+ * (0 -> internal reference, 1 -> external reference)
+ * @adc_freq: ADC Clock speed
+ * (0 -> 1.625 MHz, 1 -> 3.25 MHz, 2 || 3 -> 6.5 MHz)
+ * @ave_ctrl: Sample average control
+ * (0 -> 1 sample, 1 -> 2 samples, 2 -> 4 samples, 3 -> 8 samples)
+ * @touch_det_delay: Touch detect interrupt delay
+ * (0 -> 10 us, 1 -> 50 us, 2 -> 100 us, 3 -> 500 us,
+ * 4-> 1 ms, 5 -> 5 ms, 6 -> 10 ms, 7 -> 50 ms)
+ * recommended is 3
+ * @settling: Panel driver settling time
+ * (0 -> 10 us, 1 -> 100 us, 2 -> 500 us, 3 -> 1 ms,
+ * 4 -> 5 ms, 5 -> 10 ms, 6 for 50 ms, 7 -> 100 ms)
+ * recommended is 2
+ * @fraction_z: Length of the fractional part in z
+ * (fraction_z ([0..7]) = Count of the fractional part)
+ * recommended is 7
+ * @i_drive: current limit value of the touchscreen drivers
+ * (0 -> 20 mA typical 35 mA max, 1 -> 50 mA typical 80 mA max)
+ *
+ * */
+struct stmpe_ts_platform_data {
+       u8 sample_time;
+       u8 mod_12b;
+       u8 ref_sel;
+       u8 adc_freq;
+       u8 ave_ctrl;
+       u8 touch_det_delay;
+       u8 settling;
+       u8 fraction_z;
+       u8 i_drive;
+};
+
+/**
+ * struct stmpe_platform_data - STMPE platform data
+ * @id: device id to distinguish between multiple STMPEs on the same board
+ * @blocks: bitmask of blocks to enable (use STMPE_BLOCK_*)
+ * @irq_trigger: IRQ trigger to use for the interrupt to the host
+ * @irq_invert_polarity: IRQ line is connected with reversed polarity
+ * @irq_base: base IRQ number.  %STMPE_NR_IRQS irqs will be used, or
+ *	      %STMPE_NR_INTERNAL_IRQS if the GPIO driver is not used.
+ * @gpio: GPIO-specific platform data
+ * @keypad: keypad-specific platform data
+ * @ts: touchscreen-specific platform data
+ */
+struct stmpe_platform_data {
+	int id;
+	unsigned int blocks;
+	int irq_base;
+	unsigned int irq_trigger;
+	bool irq_invert_polarity;
+
+	struct stmpe_gpio_platform_data *gpio;
+	struct stmpe_keypad_platform_data *keypad;
+	struct stmpe_ts_platform_data *ts;
+};
+
+#define STMPE_NR_INTERNAL_IRQS	9
+#define STMPE_INT_GPIO(x)	(STMPE_NR_INTERNAL_IRQS + (x))
+
+#define STMPE_NR_GPIOS		24
+#define STMPE_NR_IRQS		STMPE_INT_GPIO(STMPE_NR_GPIOS)
+
+#endif
-- 
1.7.0


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCHv4 2/3] gpio: add STMPE GPIO driver
  2010-07-01 12:34             ` Luotao Fu
  2010-07-02 11:22               ` [PATCHv4 " Rabin Vincent
@ 2010-07-02 11:22               ` Rabin Vincent
  2010-07-02 11:22               ` [PATCHv4 3/3] input: add STMPE keypad driver Rabin Vincent
  2010-07-02 12:10               ` [RESEND] [PATCH V8] input: STMPE touch controller support Luotao Fu
  3 siblings, 0 replies; 61+ messages in thread
From: Rabin Vincent @ 2010-07-02 11:22 UTC (permalink / raw)
  To: linux-kernel
  Cc: Samuel Ortiz, STEricsson_nomadik_linux, linus.walleij, l.fu,
	Rabin Vincent

Add support for the GPIOs on STMPE I/O Expanders.

[l.fu@pengutronix.de: fix set direction input]
[l.fu@pengutronix.de: set GPIO alternate function while requesting]
Acked-by: Luotao Fu <l.fu@pengutronix.de>
Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
---
 drivers/gpio/Kconfig      |    7 +
 drivers/gpio/Makefile     |    1 +
 drivers/gpio/stmpe-gpio.c |  399 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 407 insertions(+), 0 deletions(-)
 create mode 100644 drivers/gpio/stmpe-gpio.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 724038d..27e5f8e 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -195,6 +195,13 @@ config GPIO_PCF857X
 	  This driver provides an in-kernel interface to those GPIOs using
 	  platform-neutral GPIO calls.
 
+config GPIO_STMPE
+	bool "STMPE GPIOs"
+	depends on MFD_STMPE
+	help
+	  This enables support for the GPIOs found on the STMPE I/O
+	  Expanders.
+
 config GPIO_TC35892
 	bool "TC35892 GPIOs"
 	depends on MFD_TC35892
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 51c3cdd..a9b93c3 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_GPIO_MCP23S08)	+= mcp23s08.o
 obj-$(CONFIG_GPIO_PCA953X)	+= pca953x.o
 obj-$(CONFIG_GPIO_PCF857X)	+= pcf857x.o
 obj-$(CONFIG_GPIO_PL061)	+= pl061.o
+obj-$(CONFIG_GPIO_STMPE)	+= stmpe-gpio.o
 obj-$(CONFIG_GPIO_TC35892)	+= tc35892-gpio.o
 obj-$(CONFIG_GPIO_TIMBERDALE)	+= timbgpio.o
 obj-$(CONFIG_GPIO_TWL4030)	+= twl4030-gpio.o
diff --git a/drivers/gpio/stmpe-gpio.c b/drivers/gpio/stmpe-gpio.c
new file mode 100644
index 0000000..4e1f1b9
--- /dev/null
+++ b/drivers/gpio/stmpe-gpio.c
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/stmpe.h>
+
+/*
+ * These registers are modified under the irq bus lock and cached to avoid
+ * unnecessary writes in bus_sync_unlock.
+ */
+enum { REG_RE, REG_FE, REG_IE };
+
+#define CACHE_NR_REGS	3
+#define CACHE_NR_BANKS	(STMPE_NR_GPIOS / 8)
+
+struct stmpe_gpio {
+	struct gpio_chip chip;
+	struct stmpe *stmpe;
+	struct device *dev;
+	struct mutex irq_lock;
+
+	int irq_base;
+
+	/* Caches of interrupt control registers for bus_lock */
+	u8 regs[CACHE_NR_REGS][CACHE_NR_BANKS];
+	u8 oldregs[CACHE_NR_REGS][CACHE_NR_BANKS];
+};
+
+static inline struct stmpe_gpio *to_stmpe_gpio(struct gpio_chip *chip)
+{
+	return container_of(chip, struct stmpe_gpio, chip);
+}
+
+static int stmpe_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	u8 reg = stmpe->regs[STMPE_IDX_GPMR_LSB] - (offset / 8);
+	u8 mask = 1 << (offset % 8);
+	int ret;
+
+	ret = stmpe_reg_read(stmpe, reg);
+	if (ret < 0)
+		return ret;
+
+	return ret & mask;
+}
+
+static void stmpe_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	int which = val ? STMPE_IDX_GPSR_LSB : STMPE_IDX_GPCR_LSB;
+	u8 reg = stmpe->regs[which] - (offset / 8);
+	u8 mask = 1 << (offset % 8);
+
+	stmpe_reg_write(stmpe, reg, mask);
+}
+
+static int stmpe_gpio_direction_output(struct gpio_chip *chip,
+					 unsigned offset, int val)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	u8 reg = stmpe->regs[STMPE_IDX_GPDR_LSB] - (offset / 8);
+	u8 mask = 1 << (offset % 8);
+
+	stmpe_gpio_set(chip, offset, val);
+
+	return stmpe_set_bits(stmpe, reg, mask, mask);
+}
+
+static int stmpe_gpio_direction_input(struct gpio_chip *chip,
+					unsigned offset)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	u8 reg = stmpe->regs[STMPE_IDX_GPDR_LSB] - (offset / 8);
+	u8 mask = 1 << (offset % 8);
+
+	return stmpe_set_bits(stmpe, reg, mask, 0);
+}
+
+static int stmpe_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+
+	return stmpe_gpio->irq_base + offset;
+}
+
+static int stmpe_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+	struct stmpe_gpio *stmpe_gpio = to_stmpe_gpio(chip);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+
+	return stmpe_set_altfunc(stmpe, 1 << offset, STMPE_BLOCK_GPIO);
+}
+
+static struct gpio_chip template_chip = {
+	.label			= "stmpe",
+	.owner			= THIS_MODULE,
+	.direction_input	= stmpe_gpio_direction_input,
+	.get			= stmpe_gpio_get,
+	.direction_output	= stmpe_gpio_direction_output,
+	.set			= stmpe_gpio_set,
+	.to_irq			= stmpe_gpio_to_irq,
+	.request		= stmpe_gpio_request,
+	.can_sleep		= 1,
+};
+
+static int stmpe_gpio_irq_set_type(unsigned int irq, unsigned int type)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+	int offset = irq - stmpe_gpio->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH)
+		return -EINVAL;
+
+	if (type == IRQ_TYPE_EDGE_RISING)
+		stmpe_gpio->regs[REG_RE][regoffset] |= mask;
+	else
+		stmpe_gpio->regs[REG_RE][regoffset] &= ~mask;
+
+	if (type == IRQ_TYPE_EDGE_FALLING)
+		stmpe_gpio->regs[REG_FE][regoffset] |= mask;
+	else
+		stmpe_gpio->regs[REG_FE][regoffset] &= ~mask;
+
+	return 0;
+}
+
+static void stmpe_gpio_irq_lock(unsigned int irq)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+
+	mutex_lock(&stmpe_gpio->irq_lock);
+}
+
+static void stmpe_gpio_irq_sync_unlock(unsigned int irq)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	int num_banks = DIV_ROUND_UP(stmpe->num_gpios, 8);
+	static const u8 regmap[] = {
+		[REG_RE]	= STMPE_IDX_GPRER_LSB,
+		[REG_FE]	= STMPE_IDX_GPFER_LSB,
+		[REG_IE]	= STMPE_IDX_IEGPIOR_LSB,
+	};
+	int i, j;
+
+	for (i = 0; i < CACHE_NR_REGS; i++) {
+		for (j = 0; j < num_banks; j++) {
+			u8 old = stmpe_gpio->oldregs[i][j];
+			u8 new = stmpe_gpio->regs[i][j];
+
+			if (new == old)
+				continue;
+
+			stmpe_gpio->oldregs[i][j] = new;
+			stmpe_reg_write(stmpe, stmpe->regs[regmap[i]] - j, new);
+		}
+	}
+
+	mutex_unlock(&stmpe_gpio->irq_lock);
+}
+
+static void stmpe_gpio_irq_mask(unsigned int irq)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+	int offset = irq - stmpe_gpio->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	stmpe_gpio->regs[REG_IE][regoffset] &= ~mask;
+}
+
+static void stmpe_gpio_irq_unmask(unsigned int irq)
+{
+	struct stmpe_gpio *stmpe_gpio = get_irq_chip_data(irq);
+	int offset = irq - stmpe_gpio->irq_base;
+	int regoffset = offset / 8;
+	int mask = 1 << (offset % 8);
+
+	stmpe_gpio->regs[REG_IE][regoffset] |= mask;
+}
+
+static struct irq_chip stmpe_gpio_irq_chip = {
+	.name			= "stmpe-gpio",
+	.bus_lock		= stmpe_gpio_irq_lock,
+	.bus_sync_unlock	= stmpe_gpio_irq_sync_unlock,
+	.mask			= stmpe_gpio_irq_mask,
+	.unmask			= stmpe_gpio_irq_unmask,
+	.set_type		= stmpe_gpio_irq_set_type,
+};
+
+static irqreturn_t stmpe_gpio_irq(int irq, void *dev)
+{
+	struct stmpe_gpio *stmpe_gpio = dev;
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	u8 statmsbreg = stmpe->regs[STMPE_IDX_ISGPIOR_MSB];
+	int num_banks = DIV_ROUND_UP(stmpe->num_gpios, 8);
+	u8 status[num_banks];
+	int ret;
+	int i;
+
+	ret = stmpe_block_read(stmpe, statmsbreg, num_banks, status);
+	if (ret < 0)
+		return IRQ_NONE;
+
+	for (i = 0; i < num_banks; i++) {
+		int bank = num_banks - i - 1;
+		unsigned int enabled = stmpe_gpio->regs[REG_IE][bank];
+		unsigned int stat = status[i];
+
+		stat &= enabled;
+		if (!stat)
+			continue;
+
+		while (stat) {
+			int bit = __ffs(stat);
+			int line = bank * 8 + bit;
+
+			handle_nested_irq(stmpe_gpio->irq_base + line);
+			stat &= ~(1 << bit);
+		}
+
+		stmpe_reg_write(stmpe, statmsbreg + i, status[i]);
+		stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_GPEDR_MSB] + i,
+				status[i]);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit stmpe_gpio_irq_init(struct stmpe_gpio *stmpe_gpio)
+{
+	int base = stmpe_gpio->irq_base;
+	int irq;
+
+	for (irq = base; irq < base + stmpe_gpio->chip.ngpio; irq++) {
+		set_irq_chip_data(irq, stmpe_gpio);
+		set_irq_chip_and_handler(irq, &stmpe_gpio_irq_chip,
+					 handle_simple_irq);
+		set_irq_nested_thread(irq, 1);
+#ifdef CONFIG_ARM
+		set_irq_flags(irq, IRQF_VALID);
+#else
+		set_irq_noprobe(irq);
+#endif
+	}
+
+	return 0;
+}
+
+static void stmpe_gpio_irq_remove(struct stmpe_gpio *stmpe_gpio)
+{
+	int base = stmpe_gpio->irq_base;
+	int irq;
+
+	for (irq = base; irq < base + stmpe_gpio->chip.ngpio; irq++) {
+#ifdef CONFIG_ARM
+		set_irq_flags(irq, 0);
+#endif
+		set_irq_chip_and_handler(irq, NULL, NULL);
+		set_irq_chip_data(irq, NULL);
+	}
+}
+
+static int __devinit stmpe_gpio_probe(struct platform_device *pdev)
+{
+	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+	struct stmpe_gpio_platform_data *pdata;
+	struct stmpe_gpio *stmpe_gpio;
+	int ret;
+	int irq;
+
+	pdata = stmpe->pdata->gpio;
+	if (!pdata)
+		return -ENODEV;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	stmpe_gpio = kzalloc(sizeof(struct stmpe_gpio), GFP_KERNEL);
+	if (!stmpe_gpio)
+		return -ENOMEM;
+
+	mutex_init(&stmpe_gpio->irq_lock);
+
+	stmpe_gpio->dev = &pdev->dev;
+	stmpe_gpio->stmpe = stmpe;
+
+	stmpe_gpio->chip = template_chip;
+	stmpe_gpio->chip.ngpio = stmpe->num_gpios;
+	stmpe_gpio->chip.dev = &pdev->dev;
+	stmpe_gpio->chip.base = pdata ? pdata->gpio_base : -1;
+
+	stmpe_gpio->irq_base = stmpe->irq_base + STMPE_INT_GPIO(0);
+
+	ret = stmpe_enable(stmpe, STMPE_BLOCK_GPIO);
+	if (ret)
+		return ret;
+
+	ret = stmpe_gpio_irq_init(stmpe_gpio);
+	if (ret)
+		goto out_free;
+
+	ret = request_threaded_irq(irq, NULL, stmpe_gpio_irq, IRQF_ONESHOT,
+				   "stmpe-gpio", stmpe_gpio);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
+		goto out_removeirq;
+	}
+
+	ret = gpiochip_add(&stmpe_gpio->chip);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to add gpiochip: %d\n", ret);
+		goto out_freeirq;
+	}
+
+	if (pdata && pdata->setup)
+		pdata->setup(stmpe, stmpe_gpio->chip.base);
+
+	platform_set_drvdata(pdev, stmpe_gpio);
+
+	return 0;
+
+out_freeirq:
+	free_irq(irq, stmpe_gpio);
+out_removeirq:
+	stmpe_gpio_irq_remove(stmpe_gpio);
+out_free:
+	kfree(stmpe_gpio);
+	return ret;
+}
+
+static int __devexit stmpe_gpio_remove(struct platform_device *pdev)
+{
+	struct stmpe_gpio *stmpe_gpio = platform_get_drvdata(pdev);
+	struct stmpe *stmpe = stmpe_gpio->stmpe;
+	struct stmpe_gpio_platform_data *pdata = stmpe->pdata->gpio;
+	int irq = platform_get_irq(pdev, 0);
+	int ret;
+
+	if (pdata && pdata->remove)
+		pdata->remove(stmpe, stmpe_gpio->chip.base);
+
+	ret = gpiochip_remove(&stmpe_gpio->chip);
+	if (ret < 0) {
+		dev_err(stmpe_gpio->dev,
+			"unable to remove gpiochip: %d\n", ret);
+		return ret;
+	}
+
+	stmpe_disable(stmpe, STMPE_BLOCK_GPIO);
+
+	free_irq(irq, stmpe_gpio);
+	stmpe_gpio_irq_remove(stmpe_gpio);
+	platform_set_drvdata(pdev, NULL);
+	kfree(stmpe_gpio);
+
+	return 0;
+}
+
+static struct platform_driver stmpe_gpio_driver = {
+	.driver.name	= "stmpe-gpio",
+	.driver.owner	= THIS_MODULE,
+	.probe		= stmpe_gpio_probe,
+	.remove		= __devexit_p(stmpe_gpio_remove),
+};
+
+static int __init stmpe_gpio_init(void)
+{
+	return platform_driver_register(&stmpe_gpio_driver);
+}
+subsys_initcall(stmpe_gpio_init);
+
+static void __exit stmpe_gpio_exit(void)
+{
+	platform_driver_unregister(&stmpe_gpio_driver);
+}
+module_exit(stmpe_gpio_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("STMPExxxx GPIO driver");
+MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
-- 
1.7.0


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCHv4 3/3] input: add STMPE keypad driver
  2010-07-01 12:34             ` Luotao Fu
  2010-07-02 11:22               ` [PATCHv4 " Rabin Vincent
  2010-07-02 11:22               ` [PATCHv4 2/3] gpio: add STMPE GPIO driver Rabin Vincent
@ 2010-07-02 11:22               ` Rabin Vincent
  2010-07-02 12:10               ` [RESEND] [PATCH V8] input: STMPE touch controller support Luotao Fu
  3 siblings, 0 replies; 61+ messages in thread
From: Rabin Vincent @ 2010-07-02 11:22 UTC (permalink / raw)
  To: linux-kernel
  Cc: Samuel Ortiz, STEricsson_nomadik_linux, linus.walleij, l.fu,
	Rabin Vincent

Add an input driver for the keypad on STMPE I/O expanders.  This driver
uses the common support provided by the STMPE MFD driver.

Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
---
 drivers/input/keyboard/Kconfig        |   10 +
 drivers/input/keyboard/Makefile       |    1 +
 drivers/input/keyboard/stmpe-keypad.c |  386 +++++++++++++++++++++++++++++++++
 3 files changed, 397 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/stmpe-keypad.c

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index d8fa5d7..699aff9 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -374,6 +374,16 @@ config KEYBOARD_SH_KEYSC
 	  To compile this driver as a module, choose M here: the
 	  module will be called sh_keysc.
 
+config KEYBOARD_STMPE
+	tristate "STMPE keypad support"
+	depends on MFD_STMPE
+	help
+	  Say Y here if you want to use the keypad controller on STMPE I/O
+	  expanders.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called stmpe-keypad.
+
 config KEYBOARD_DAVINCI
 	tristate "TI DaVinci Key Scan"
 	depends on ARCH_DAVINCI_DM365
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 4596d0c..af24b07 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
 obj-$(CONFIG_KEYBOARD_PXA930_ROTARY)	+= pxa930_rotary.o
 obj-$(CONFIG_KEYBOARD_QT2160)		+= qt2160.o
 obj-$(CONFIG_KEYBOARD_SH_KEYSC)		+= sh_keysc.o
+obj-$(CONFIG_KEYBOARD_STMPE)		+= stmpe-keypad.o
 obj-$(CONFIG_KEYBOARD_STOWAWAY)		+= stowaway.o
 obj-$(CONFIG_KEYBOARD_SUNKBD)		+= sunkbd.o
 obj-$(CONFIG_KEYBOARD_TWL4030)		+= twl4030_keypad.o
diff --git a/drivers/input/keyboard/stmpe-keypad.c b/drivers/input/keyboard/stmpe-keypad.c
new file mode 100644
index 0000000..ab7610c
--- /dev/null
+++ b/drivers/input/keyboard/stmpe-keypad.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/mfd/stmpe.h>
+
+/* These are at the same addresses in all STMPE variants */
+#define STMPE_KPC_COL			0x60
+#define STMPE_KPC_ROW_MSB		0x61
+#define STMPE_KPC_ROW_LSB		0x62
+#define STMPE_KPC_CTRL_MSB		0x63
+#define STMPE_KPC_CTRL_LSB		0x64
+#define STMPE_KPC_COMBI_KEY_0		0x65
+#define STMPE_KPC_COMBI_KEY_1		0x66
+#define STMPE_KPC_COMBI_KEY_2		0x67
+#define STMPE_KPC_DATA_BYTE0		0x68
+#define STMPE_KPC_DATA_BYTE1		0x69
+#define STMPE_KPC_DATA_BYTE2		0x6a
+#define STMPE_KPC_DATA_BYTE3		0x6b
+#define STMPE_KPC_DATA_BYTE4		0x6c
+
+#define STMPE_KPC_CTRL_LSB_SCAN		(0x1 << 0)
+#define STMPE_KPC_CTRL_LSB_DEBOUNCE	(0x7f << 1)
+#define STMPE_KPC_CTRL_MSB_SCAN_COUNT	(0xf << 4)
+
+#define STMPE_KPC_ROW_MSB_ROWS		0xff
+
+#define STMPE_KPC_DATA_UP		(0x1 << 7)
+#define STMPE_KPC_DATA_ROW		(0xf << 3)
+#define STMPE_KPC_DATA_COL		(0x7 << 0)
+#define STMPE_KPC_DATA_NOKEY_MASK	0x78
+
+#define STMPE_KEYPAD_MAX_DEBOUNCE	127
+#define STMPE_KEYPAD_MAX_SCAN_COUNT	15
+
+#define STMPE_KEYPAD_MAX_ROWS		8
+#define STMPE_KEYPAD_MAX_COLS		8
+#define STMPE_KEYPAD_ROW_SHIFT		3
+#define STMPE_KEYPAD_KEYMAP_SIZE	\
+	(STMPE_KEYPAD_MAX_ROWS * STMPE_KEYPAD_MAX_COLS)
+
+/**
+ * struct stmpe_keypad_variant - model-specific attributes
+ * @auto_increment: whether the KPC_DATA_BYTE register address
+ *		    auto-increments on multiple read
+ * @num_data: number of data bytes
+ * @num_normal_data: number of normal keys' data bytes
+ * @max_cols: maximum number of columns supported
+ * @max_rows: maximum number of rows supported
+ * @col_gpios: bitmask of gpios which can be used for columns
+ * @row_gpios: bitmask of gpios which can be used for rows
+ */
+struct stmpe_keypad_variant {
+	bool		auto_increment;
+	int		num_data;
+	int		num_normal_data;
+	int		max_cols;
+	int		max_rows;
+	unsigned int	col_gpios;
+	unsigned int	row_gpios;
+};
+
+static const struct stmpe_keypad_variant stmpe_keypad_variants[] = {
+	[STMPE1601] = {
+		.auto_increment		= true,
+		.num_data		= 5,
+		.num_normal_data	= 3,
+		.max_cols		= 8,
+		.max_rows		= 8,
+		.col_gpios		= 0x000ff,	/* GPIO 0 - 7 */
+		.row_gpios		= 0x0ff00,	/* GPIO 8 - 15 */
+	},
+	[STMPE2401] = {
+		.auto_increment		= false,
+		.num_data		= 3,
+		.num_normal_data	= 2,
+		.max_cols		= 8,
+		.max_rows		= 12,
+		.col_gpios		= 0x0000ff,	/* GPIO 0 - 7*/
+		.row_gpios		= 0x1fef00,	/* GPIO 8-14, 16-20 */
+	},
+	[STMPE2403] = {
+		.auto_increment		= true,
+		.num_data		= 5,
+		.num_normal_data	= 3,
+		.max_cols		= 8,
+		.max_rows		= 12,
+		.col_gpios		= 0x0000ff,	/* GPIO 0 - 7*/
+		.row_gpios		= 0x1fef00,	/* GPIO 8-14, 16-20 */
+	},
+};
+
+struct stmpe_keypad {
+	struct stmpe *stmpe;
+	struct input_dev *input;
+	const struct stmpe_keypad_variant *variant;
+	const struct stmpe_keypad_platform_data *plat;
+
+	unsigned int rows;
+	unsigned int cols;
+
+	unsigned short keymap[STMPE_KEYPAD_KEYMAP_SIZE];
+};
+
+static int stmpe_keypad_read_data(struct stmpe_keypad *keypad, u8 *data)
+{
+	const struct stmpe_keypad_variant *variant = keypad->variant;
+	struct stmpe *stmpe = keypad->stmpe;
+	int ret;
+	int i;
+
+	if (variant->auto_increment)
+		return stmpe_block_read(stmpe, STMPE_KPC_DATA_BYTE0,
+					variant->num_data, data);
+
+	for (i = 0; i < variant->num_data; i++) {
+		ret = stmpe_reg_read(stmpe, STMPE_KPC_DATA_BYTE0 + i);
+		if (ret < 0)
+			return ret;
+
+		data[i] = ret;
+	}
+
+	return 0;
+}
+
+static irqreturn_t stmpe_keypad_irq(int irq, void *dev)
+{
+	struct stmpe_keypad *keypad = dev;
+	struct input_dev *input = keypad->input;
+	const struct stmpe_keypad_variant *variant = keypad->variant;
+	u8 fifo[variant->num_data];
+	int ret;
+	int i;
+
+	ret = stmpe_keypad_read_data(keypad, fifo);
+	if (ret < 0)
+		return IRQ_NONE;
+
+	for (i = 0; i < variant->num_normal_data; i++) {
+		u8 data = fifo[i];
+		int row = (data & STMPE_KPC_DATA_ROW) >> 3;
+		int col = data & STMPE_KPC_DATA_COL;
+		int code = MATRIX_SCAN_CODE(row, col, STMPE_KEYPAD_ROW_SHIFT);
+		bool up = data & STMPE_KPC_DATA_UP;
+
+		if ((data & STMPE_KPC_DATA_NOKEY_MASK)
+			== STMPE_KPC_DATA_NOKEY_MASK)
+			continue;
+
+		input_event(input, EV_MSC, MSC_SCAN, code);
+		input_report_key(input, keypad->keymap[code], !up);
+		input_sync(input);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit stmpe_keypad_altfunc_init(struct stmpe_keypad *keypad)
+{
+	const struct stmpe_keypad_variant *variant = keypad->variant;
+	unsigned int col_gpios = variant->col_gpios;
+	unsigned int row_gpios = variant->row_gpios;
+	struct stmpe *stmpe = keypad->stmpe;
+	unsigned int pins = 0;
+	int i;
+
+	/*
+	 * Figure out which pins need to be set to the keypad alternate
+	 * function.
+	 *
+	 * {cols,rows}_gpios are bitmasks of which pins on the chip can be used
+	 * for the keypad.
+	 *
+	 * keypad->{cols,rows} are a bitmask of which pins (of the ones useable
+	 * for the keypad) are used on the board.
+	 */
+
+	for (i = 0; i < variant->max_cols; i++) {
+		int num = __ffs(col_gpios);
+
+		if (keypad->cols & (1 << i))
+			pins |= 1 << num;
+
+		col_gpios &= ~(1 << num);
+	}
+
+	for (i = 0; i < variant->max_rows; i++) {
+		int num = __ffs(row_gpios);
+
+		if (keypad->rows & (1 << i))
+			pins |= 1 << num;
+
+		row_gpios &= ~(1 << num);
+	}
+
+	return stmpe_set_altfunc(stmpe, pins, STMPE_BLOCK_KEYPAD);
+}
+
+static int __devinit stmpe_keypad_chip_init(struct stmpe_keypad *keypad)
+{
+	const struct stmpe_keypad_platform_data *plat = keypad->plat;
+	const struct stmpe_keypad_variant *variant = keypad->variant;
+	struct stmpe *stmpe = keypad->stmpe;
+	int ret;
+
+	if (plat->debounce_ms > STMPE_KEYPAD_MAX_DEBOUNCE)
+		return -EINVAL;
+
+	if (plat->scan_count > STMPE_KEYPAD_MAX_SCAN_COUNT)
+		return -EINVAL;
+
+	ret = stmpe_enable(stmpe, STMPE_BLOCK_KEYPAD);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_keypad_altfunc_init(keypad);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_reg_write(stmpe, STMPE_KPC_COL, keypad->cols);
+	if (ret < 0)
+		return ret;
+
+	ret = stmpe_reg_write(stmpe, STMPE_KPC_ROW_LSB, keypad->rows);
+	if (ret < 0)
+		return ret;
+
+	if (variant->max_rows > 8) {
+		ret = stmpe_set_bits(stmpe, STMPE_KPC_ROW_MSB,
+				     STMPE_KPC_ROW_MSB_ROWS,
+				     keypad->rows >> 8);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_KPC_CTRL_MSB,
+			     STMPE_KPC_CTRL_MSB_SCAN_COUNT,
+			     plat->scan_count << 4);
+	if (ret < 0)
+		return ret;
+
+	return stmpe_set_bits(stmpe, STMPE_KPC_CTRL_LSB,
+			      STMPE_KPC_CTRL_LSB_SCAN |
+			      STMPE_KPC_CTRL_LSB_DEBOUNCE,
+			      STMPE_KPC_CTRL_LSB_SCAN |
+			      (plat->debounce_ms << 1));
+}
+
+static int __devinit stmpe_keypad_probe(struct platform_device *pdev)
+{
+	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+	struct stmpe_keypad_platform_data *plat;
+	struct stmpe_keypad *keypad;
+	struct input_dev *input;
+	int ret;
+	int irq;
+	int i;
+
+	plat = stmpe->pdata->keypad;
+	if (!plat)
+		return -ENODEV;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	keypad = kzalloc(sizeof(struct stmpe_keypad), GFP_KERNEL);
+	if (!keypad)
+		return -ENOMEM;
+
+	input = input_allocate_device();
+	if (!input) {
+		ret = -ENOMEM;
+		goto out_freekeypad;
+	}
+
+	input->name = "STMPE keypad";
+	input->id.bustype = BUS_I2C;
+	input->dev.parent = &pdev->dev;
+
+	input_set_capability(input, EV_MSC, MSC_SCAN);
+
+	__set_bit(EV_KEY, input->evbit);
+	if (!plat->no_autorepeat)
+		__set_bit(EV_REP, input->evbit);
+
+	input->keycode = keypad->keymap;
+	input->keycodesize = sizeof(keypad->keymap[0]);
+	input->keycodemax = ARRAY_SIZE(keypad->keymap);
+
+	matrix_keypad_build_keymap(plat->keymap_data, STMPE_KEYPAD_ROW_SHIFT,
+				   input->keycode, input->keybit);
+
+	for (i = 0; i < plat->keymap_data->keymap_size; i++) {
+		unsigned int key = plat->keymap_data->keymap[i];
+
+		keypad->cols |= 1 << KEY_COL(key);
+		keypad->rows |= 1 << KEY_ROW(key);
+	}
+
+	keypad->stmpe = stmpe;
+	keypad->plat = plat;
+	keypad->input = input;
+	keypad->variant = &stmpe_keypad_variants[stmpe->partnum];
+
+	ret = stmpe_keypad_chip_init(keypad);
+	if (ret < 0)
+		goto out_freeinput;
+
+	ret = input_register_device(input);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"unable to register input device: %d\n", ret);
+		goto out_freeinput;
+	}
+
+	ret = request_threaded_irq(irq, NULL, stmpe_keypad_irq, IRQF_ONESHOT,
+				   "stmpe-keypad", keypad);
+	if (ret) {
+		dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
+		goto out_unregisterinput;
+	}
+
+	platform_set_drvdata(pdev, keypad);
+
+	return 0;
+
+out_unregisterinput:
+	input_unregister_device(input);
+	input = NULL;
+out_freeinput:
+	input_free_device(input);
+out_freekeypad:
+	kfree(keypad);
+	return ret;
+}
+
+static int __devexit stmpe_keypad_remove(struct platform_device *pdev)
+{
+	struct stmpe_keypad *keypad = platform_get_drvdata(pdev);
+	struct stmpe *stmpe = keypad->stmpe;
+	int irq = platform_get_irq(pdev, 0);
+
+	stmpe_disable(stmpe, STMPE_BLOCK_KEYPAD);
+
+	free_irq(irq, keypad);
+	input_unregister_device(keypad->input);
+	platform_set_drvdata(pdev, NULL);
+	kfree(keypad);
+
+	return 0;
+}
+
+static struct platform_driver stmpe_keypad_driver = {
+	.driver.name	= "stmpe-keypad",
+	.driver.owner	= THIS_MODULE,
+	.probe		= stmpe_keypad_probe,
+	.remove		= __devexit_p(stmpe_keypad_remove),
+};
+
+static int __init stmpe_keypad_init(void)
+{
+	return platform_driver_register(&stmpe_keypad_driver);
+}
+module_init(stmpe_keypad_init);
+
+static void __exit stmpe_keypad_exit(void)
+{
+	platform_driver_unregister(&stmpe_keypad_driver);
+}
+module_exit(stmpe_keypad_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("STMPExxxx keypad driver");
+MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
-- 
1.7.0


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [RESEND] [PATCH V8] input: STMPE touch controller support
  2010-07-01 12:34             ` Luotao Fu
                                 ` (2 preceding siblings ...)
  2010-07-02 11:22               ` [PATCHv4 3/3] input: add STMPE keypad driver Rabin Vincent
@ 2010-07-02 12:10               ` Luotao Fu
  2010-07-05 14:53                 ` Samuel Ortiz
  3 siblings, 1 reply; 61+ messages in thread
From: Luotao Fu @ 2010-07-02 12:10 UTC (permalink / raw)
  To: linux-kernel
  Cc: Samuel Ortiz, STEricsson_nomadik_linux, linus.walleij,
	Rabin Vincent, Luotao Fu

This one adds a driver for STMPE touchscreen controllers. This driver depends on
stmpexxx mfd core driver.

Signed-off-by: Luotao Fu <l.fu@pengutronix.de>
Acked-by: Dmitry Torokhov <dtor@mail.ru>
---
V2 Changes:
* include subsystem headers since they are now remove from the mfd
  core header file
* use genirq to register threaded isr. The stmpe811 own irq callbacks
  are dropped in the core driver.
* use STMPE811_TS_NAME for name all over the place
* wait for completion of core IO operation before cancelling polling of
  release event.
* switch to platform data for configuration parameters.
* add FIFO reset to open callback, also reset FIFO before reporting
  release.

V3 Changes:
* reformated platform data comments to kernel-doc style

V4 Changes:
* converted to stmpexxx core by Rabin Vincent <rabin.vincent@stericsson.com>
* wait for running workqueue callback to finish while canceling the queue
* add i_drive configuration support

V5 Changes:
* add ADC block enable to stmpe_enable call.

V6 Changes:
* add _reset_fifo callback to reduce duplicated code.
* fix mask usage while setting bits in probe.
* acquire platform irq by name

V7 Changes:
* reranged entry makefile in alphabetical order
* move hw init stuff into a own function
* fix queue canceling in close. remove uneccessary register settings from remove
* removed inline declaration
* add driver owner
* set input params before registering the device

V8 Changes:
* fixed trailing whitespaces
* mark init_hw function as __devinit

 drivers/input/touchscreen/Kconfig    |   10 +
 drivers/input/touchscreen/Makefile   |    1 +
 drivers/input/touchscreen/stmpe-ts.c |  397 ++++++++++++++++++++++++++++++++++
 3 files changed, 408 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/touchscreen/stmpe-ts.c

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 3b9d5e2..96a9954 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -603,4 +603,14 @@ config TOUCHSCREEN_TPS6507X
 	  To compile this driver as a module, choose M here: the
 	  module will be called tps6507x_ts.
 
+config TOUCHSCREEN_STMPE
+	tristate "STMicroelectronics STMPE touchscreens"
+	depends on MFD_STMPE
+	help
+	  Say Y here if you want support for STMicroelectronics
+	  STMPE touchscreen controllers.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called stmpe-ts.
+
 endif
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 497964a..10fb163 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_TOUCHSCREEN_USB_COMPOSITE)	+= usbtouchscreen.o
 obj-$(CONFIG_TOUCHSCREEN_PCAP)		+= pcap_ts.o
 obj-$(CONFIG_TOUCHSCREEN_PENMOUNT)	+= penmount.o
 obj-$(CONFIG_TOUCHSCREEN_S3C2410)	+= s3c2410_ts.o
+obj-$(CONFIG_TOUCHSCREEN_STMPE)		+= stmpe-ts.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213)	+= touchit213.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT)	+= touchright.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN)	+= touchwin.o
diff --git a/drivers/input/touchscreen/stmpe-ts.c b/drivers/input/touchscreen/stmpe-ts.c
new file mode 100644
index 0000000..77f4374
--- /dev/null
+++ b/drivers/input/touchscreen/stmpe-ts.c
@@ -0,0 +1,397 @@
+/* STMicroelectronics STMPE811 Touchscreen Driver
+ *
+ * (C) 2010 Luotao Fu <l.fu@pengutronix.de>
+ * All rights reserved.
+ *
+ *  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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/stmpe.h>
+
+/* Register layouts and functionalities are identical on all stmpexxx variants
+ * with touchscreen controller
+ */
+#define STMPE_REG_INT_STA		0x0B
+#define STMPE_REG_ADC_CTRL1		0x20
+#define STMPE_REG_ADC_CTRL2		0x21
+#define STMPE_REG_TSC_CTRL		0x40
+#define STMPE_REG_TSC_CFG		0x41
+#define STMPE_REG_FIFO_TH		0x4A
+#define STMPE_REG_FIFO_STA		0x4B
+#define STMPE_REG_FIFO_SIZE		0x4C
+#define STMPE_REG_TSC_DATA_XYZ		0x52
+#define STMPE_REG_TSC_FRACTION_Z	0x56
+#define STMPE_REG_TSC_I_DRIVE		0x58
+
+#define OP_MOD_XYZ			0
+
+#define STMPE_TSC_CTRL_TSC_EN		(1<<0)
+
+#define STMPE_FIFO_STA_RESET		(1<<0)
+
+#define STMPE_IRQ_TOUCH_DET		0
+
+#define SAMPLE_TIME(x)			((x & 0xf) << 4)
+#define MOD_12B(x)			((x & 0x1) << 3)
+#define REF_SEL(x)			((x & 0x1) << 1)
+#define ADC_FREQ(x)			(x & 0x3)
+#define AVE_CTRL(x)			((x & 0x3) << 6)
+#define DET_DELAY(x)			((x & 0x7) << 3)
+#define SETTLING(x)			(x & 0x7)
+#define FRACTION_Z(x)			(x & 0x7)
+#define I_DRIVE(x)			(x & 0x1)
+#define OP_MODE(x)			((x & 0x7) << 1)
+
+#define STMPE_TS_NAME			"stmpe-ts"
+#define XY_MASK				0xfff
+
+struct stmpe_touch {
+	struct stmpe *stmpe;
+	struct input_dev *idev;
+	struct delayed_work work;
+	struct device *dev;
+	u8 sample_time;
+	u8 mod_12b;
+	u8 ref_sel;
+	u8 adc_freq;
+	u8 ave_ctrl;
+	u8 touch_det_delay;
+	u8 settling;
+	u8 fraction_z;
+	u8 i_drive;
+};
+
+static int __stmpe_reset_fifo(struct stmpe *stmpe)
+{
+	int ret;
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, STMPE_FIFO_STA_RESET);
+	if (ret)
+		return ret;
+
+	return stmpe_set_bits(stmpe, STMPE_REG_FIFO_STA,
+			STMPE_FIFO_STA_RESET, 0);
+}
+
+static void stmpe_work(struct work_struct *work)
+{
+	int int_sta;
+	u32 timeout = 40;
+
+	struct stmpe_touch *ts =
+	    container_of(work, struct stmpe_touch, work.work);
+
+	int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
+
+	/*
+	 * touch_det sometimes get desasserted or just get stuck. This appears
+	 * to be a silicon bug, We still have to clearify this with the
+	 * manufacture. As a workaround We release the key anyway if the
+	 * touch_det keeps coming in after 4ms, while the FIFO contains no value
+	 * during the whole time.
+	 */
+	while ((int_sta & (1 << STMPE_IRQ_TOUCH_DET)) && (timeout > 0)) {
+		timeout--;
+		int_sta = stmpe_reg_read(ts->stmpe, STMPE_REG_INT_STA);
+		udelay(100);
+	}
+
+	/* reset the FIFO before we report release event */
+	__stmpe_reset_fifo(ts->stmpe);
+
+	input_report_abs(ts->idev, ABS_PRESSURE, 0);
+	input_sync(ts->idev);
+}
+
+static irqreturn_t stmpe_ts_handler(int irq, void *data)
+{
+	u8 data_set[4];
+	int x, y, z;
+	struct stmpe_touch *ts = data;
+
+	/*
+	 * Cancel scheduled polling for release if we have new value
+	 * available. Wait if the polling is already running.
+	 */
+	cancel_delayed_work_sync(&ts->work);
+
+	/*
+	 * The FIFO sometimes just crashes and stops generating interrupts. This
+	 * appears to be a silicon bug. We still have to clearify this with
+	 * the manufacture. As a workaround we disable the TSC while we are
+	 * collecting data and flush the FIFO after reading
+	 */
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+				STMPE_TSC_CTRL_TSC_EN, 0);
+
+	stmpe_block_read(ts->stmpe, STMPE_REG_TSC_DATA_XYZ, 4, data_set);
+
+	x = (data_set[0] << 4) | (data_set[1] >> 4);
+	y = ((data_set[1] & 0xf) << 8) | data_set[2];
+	z = data_set[3];
+
+	input_report_abs(ts->idev, ABS_X, x);
+	input_report_abs(ts->idev, ABS_Y, y);
+	input_report_abs(ts->idev, ABS_PRESSURE, z);
+	input_sync(ts->idev);
+
+       /* flush the FIFO after we have read out our values. */
+	__stmpe_reset_fifo(ts->stmpe);
+
+	/* reenable the tsc */
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
+
+	/* start polling for touch_det to detect release */
+	schedule_delayed_work(&ts->work, HZ / 50);
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit stmpe_init_hw(struct stmpe_touch *ts)
+{
+	int ret;
+	u8 adc_ctrl1, adc_ctrl1_mask, tsc_cfg, tsc_cfg_mask;
+	struct stmpe *stmpe = ts->stmpe;
+	struct device *dev = ts->dev;
+
+	ret = stmpe_enable(stmpe, STMPE_BLOCK_TOUCHSCREEN | STMPE_BLOCK_ADC);
+	if (ret) {
+		dev_err(dev, "Could not enable clock for ADC and TS\n");
+		return ret;
+	}
+
+	adc_ctrl1 = SAMPLE_TIME(ts->sample_time) | MOD_12B(ts->mod_12b) |
+		REF_SEL(ts->ref_sel);
+	adc_ctrl1_mask = SAMPLE_TIME(0xff) | MOD_12B(0xff) | REF_SEL(0xff);
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL1,
+			adc_ctrl1_mask, adc_ctrl1);
+	if (ret) {
+		dev_err(dev, "Could not setup ADC\n");
+		return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_ADC_CTRL2,
+			ADC_FREQ(0xff), ADC_FREQ(ts->adc_freq));
+	if (ret) {
+		dev_err(dev, "Could not setup ADC\n");
+		return ret;
+	}
+
+	tsc_cfg = AVE_CTRL(ts->ave_ctrl) | DET_DELAY(ts->touch_det_delay) |
+			SETTLING(ts->settling);
+	tsc_cfg_mask = AVE_CTRL(0xff) | DET_DELAY(0xff) | SETTLING(0xff);
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CFG, tsc_cfg_mask, tsc_cfg);
+	if (ret) {
+		dev_err(dev, "Could not config touch\n");
+		return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_FRACTION_Z,
+			FRACTION_Z(0xff), FRACTION_Z(ts->fraction_z));
+	if (ret) {
+		dev_err(dev, "Could not config touch\n");
+		return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_I_DRIVE,
+			I_DRIVE(0xff), I_DRIVE(ts->i_drive));
+	if (ret) {
+		dev_err(dev, "Could not config touch\n");
+		return ret;
+	}
+
+	/* set FIFO to 1 for single point reading */
+	ret = stmpe_reg_write(stmpe, STMPE_REG_FIFO_TH, 1);
+	if (ret) {
+		dev_err(dev, "Could not set FIFO\n");
+		return ret;
+	}
+
+	ret = stmpe_set_bits(stmpe, STMPE_REG_TSC_CTRL,
+			OP_MODE(0xff), OP_MODE(OP_MOD_XYZ));
+	if (ret) {
+		dev_err(dev, "Could not set mode\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int stmpe_ts_open(struct input_dev *dev)
+{
+	struct stmpe_touch *ts = input_get_drvdata(dev);
+	int ret = 0;
+
+	ret = __stmpe_reset_fifo(ts->stmpe);
+	if (ret)
+		return ret;
+
+	return stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, STMPE_TSC_CTRL_TSC_EN);
+}
+
+static void stmpe_ts_close(struct input_dev *dev)
+{
+	struct stmpe_touch *ts = input_get_drvdata(dev);
+
+	cancel_delayed_work_sync(&ts->work);
+
+	stmpe_set_bits(ts->stmpe, STMPE_REG_TSC_CTRL,
+			STMPE_TSC_CTRL_TSC_EN, 0);
+}
+
+static int __devinit stmpe_input_probe(struct platform_device *pdev)
+{
+	struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent);
+	struct stmpe_platform_data *pdata = stmpe->pdata;
+	struct stmpe_touch *ts;
+	struct input_dev *idev;
+	struct stmpe_ts_platform_data *ts_pdata = NULL;
+	int ret = 0;
+	unsigned int ts_irq;
+
+	ts_irq = platform_get_irq_byname(pdev, "FIFO_TH");
+	if (ts_irq < 0)
+		return ts_irq;
+
+	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+	if (!ts)
+		goto err_out;
+
+	idev = input_allocate_device();
+	if (!idev)
+		goto err_free_ts;
+
+	platform_set_drvdata(pdev, ts);
+	ts->stmpe = stmpe;
+	ts->idev = idev;
+	ts->dev = &pdev->dev;
+
+	if (pdata)
+		ts_pdata = pdata->ts;
+
+	if (ts_pdata) {
+		ts->sample_time = ts_pdata->sample_time;
+		ts->mod_12b = ts_pdata->mod_12b;
+		ts->ref_sel = ts_pdata->ref_sel;
+		ts->adc_freq = ts_pdata->adc_freq;
+		ts->ave_ctrl = ts_pdata->ave_ctrl;
+		ts->touch_det_delay = ts_pdata->touch_det_delay;
+		ts->settling = ts_pdata->settling;
+		ts->fraction_z = ts_pdata->fraction_z;
+		ts->i_drive = ts_pdata->i_drive;
+	}
+
+	INIT_DELAYED_WORK(&ts->work, stmpe_work);
+
+	ret = request_threaded_irq(ts_irq, NULL, stmpe_ts_handler,
+			IRQF_ONESHOT, STMPE_TS_NAME, ts);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request IRQ %d\n", ts_irq);
+		goto err_free_input;
+	}
+
+	ret = stmpe_init_hw(ts);
+	if (ret)
+		goto err_free_irq;
+
+	idev->name = STMPE_TS_NAME;
+	idev->id.bustype = BUS_I2C;
+	idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+	idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+	idev->open = stmpe_ts_open;
+	idev->close = stmpe_ts_close;
+
+	input_set_drvdata(idev, ts);
+
+	input_set_abs_params(idev, ABS_X, 0, XY_MASK, 0, 0);
+	input_set_abs_params(idev, ABS_Y, 0, XY_MASK, 0, 0);
+	input_set_abs_params(idev, ABS_PRESSURE, 0x0, 0xff, 0, 0);
+
+	ret = input_register_device(idev);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not register input device\n");
+		goto err_free_irq;
+	}
+
+	return ret;
+
+err_free_irq:
+	free_irq(ts_irq, ts);
+err_free_input:
+	input_free_device(idev);
+	platform_set_drvdata(pdev, NULL);
+err_free_ts:
+	kfree(ts);
+err_out:
+	return ret;
+}
+
+static int __devexit stmpe_ts_remove(struct platform_device *pdev)
+{
+	struct stmpe_touch *ts = platform_get_drvdata(pdev);
+	unsigned int ts_irq = platform_get_irq_byname(pdev, "FIFO_TH");
+
+	stmpe_disable(ts->stmpe, STMPE_BLOCK_TOUCHSCREEN);
+
+	free_irq(ts_irq, ts);
+
+	platform_set_drvdata(pdev, NULL);
+
+	input_unregister_device(ts->idev);
+	input_free_device(ts->idev);
+
+	kfree(ts);
+
+	return 0;
+}
+
+static struct platform_driver stmpe_ts_driver = {
+	.driver = {
+		   .name = STMPE_TS_NAME,
+		   .owner = THIS_MODULE,
+		   },
+	.probe = stmpe_input_probe,
+	.remove = __devexit_p(stmpe_ts_remove),
+};
+
+static int __init stmpe_ts_init(void)
+{
+	return platform_driver_register(&stmpe_ts_driver);
+}
+
+module_init(stmpe_ts_init);
+
+static void __exit stmpe_ts_exit(void)
+{
+	platform_driver_unregister(&stmpe_ts_driver);
+}
+
+module_exit(stmpe_ts_exit);
+
+MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
+MODULE_DESCRIPTION("STMPEXXX touchscreen driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" STMPE_TS_NAME);
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* Re: [PATCHv4 1/3] mfd: add STMPE I/O Expander support
  2010-07-02 11:22               ` [PATCHv4 " Rabin Vincent
@ 2010-07-02 15:31                 ` Samuel Ortiz
  0 siblings, 0 replies; 61+ messages in thread
From: Samuel Ortiz @ 2010-07-02 15:31 UTC (permalink / raw)
  To: Rabin Vincent; +Cc: linux-kernel, STEricsson_nomadik_linux, linus.walleij, l.fu

Hi Rabin,

On Fri, Jul 02, 2010 at 04:52:08PM +0530, Rabin Vincent wrote:
> Add support for the STMPE family of I/O Expanders from
> STMicroelectronics.  These devices include upto 24 gpios and a varying
> selection of blocks, including PWM, keypad, and touchscreen controllers.
> This patch adds the MFD core.
Looks good now, all 3 patches applied. Thanks a lot for your efforts.

Cheers,
Samuel.

-- 
Intel Open Source Technology Centre
http://oss.intel.com/

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [RESEND] [PATCH V8] input: STMPE touch controller support
  2010-07-02 12:10               ` [RESEND] [PATCH V8] input: STMPE touch controller support Luotao Fu
@ 2010-07-05 14:53                 ` Samuel Ortiz
  0 siblings, 0 replies; 61+ messages in thread
From: Samuel Ortiz @ 2010-07-05 14:53 UTC (permalink / raw)
  To: Luotao Fu
  Cc: linux-kernel, STEricsson_nomadik_linux, linus.walleij, Rabin Vincent

Hi Luotao,

On Fri, Jul 02, 2010 at 02:10:29PM +0200, Luotao Fu wrote:
> This one adds a driver for STMPE touchscreen controllers. This driver depends on
> stmpexxx mfd core driver.
> 
Applied, thanks.
As agreed, I'm taking this one through my for-next branch.

Cheers,
Samuel.

-- 
Intel Open Source Technology Centre
http://oss.intel.com/

^ permalink raw reply	[flat|nested] 61+ messages in thread

end of thread, other threads:[~2010-07-05 14:53 UTC | newest]

Thread overview: 61+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-05-31 12:17 [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Rabin Vincent
2010-05-31 12:17 ` [PATCH 2/3] gpio: add STMPExxxx GPIO driver Rabin Vincent
2010-05-31 12:17 ` [PATCH 3/3] input: add STMPExxxx keypad driver Rabin Vincent
2010-05-31 12:17   ` Rabin Vincent
2010-06-01 22:16   ` Dmitry Torokhov
2010-06-02 13:56     ` Rabin VINCENT
2010-06-02 16:05       ` Dmitry Torokhov
2010-06-18 23:42 ` [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Samuel Ortiz
2010-06-19 13:50   ` Luotao Fu
2010-06-21 13:33   ` Rabin VINCENT
2010-06-21 15:45     ` Luotao Fu
2010-06-22 13:55       ` [PATCHv2 1/3] mfd: add STMPE " Rabin Vincent
2010-06-27 23:55         ` Samuel Ortiz
2010-06-29  3:13           ` Rabin VINCENT
2010-06-29 15:33             ` Samuel Ortiz
2010-07-01 12:00           ` [PATCHv3 " Rabin Vincent
2010-07-01 12:34             ` Luotao Fu
2010-07-02 11:22               ` [PATCHv4 " Rabin Vincent
2010-07-02 15:31                 ` Samuel Ortiz
2010-07-02 11:22               ` [PATCHv4 2/3] gpio: add STMPE GPIO driver Rabin Vincent
2010-07-02 11:22               ` [PATCHv4 3/3] input: add STMPE keypad driver Rabin Vincent
2010-07-02 12:10               ` [RESEND] [PATCH V8] input: STMPE touch controller support Luotao Fu
2010-07-05 14:53                 ` Samuel Ortiz
2010-07-01 12:00           ` [PATCHv3 2/3] gpio: add STMPE GPIO driver Rabin Vincent
2010-07-01 12:29             ` Luotao Fu
2010-07-01 12:00           ` [PATCHv3 3/3] input: add STMPE keypad driver Rabin Vincent
2010-06-22 13:55       ` [PATCHv2 2/3] gpio: add STMPE GPIO driver Rabin Vincent
2010-06-22 13:55       ` [PATCHv2 3/3] input: add STMPE keypad driver Rabin Vincent
2010-06-22 13:56       ` [PATCH 1/3] mfd: add STMPExxxx I/O Expander support Rabin VINCENT
2010-06-24 11:13         ` mfd: STMPExxxx fixes and touch screen support Luotao Fu
2010-06-24 11:13         ` [PATCH 1/6] gpio/stmpe-gpio: set GPIO alternate function while requesting Luotao Fu
2010-06-24 12:43           ` Rabin VINCENT
2010-06-24 11:13         ` [PATCH 2/6] gpio/stmpe-gpio: fix set direction input Luotao Fu
2010-06-24 12:03           ` Rabin VINCENT
2010-06-24 11:13         ` [PATCH 3/6] mfd/stmpexxx: add touchscreen platform data Luotao Fu
2010-06-24 11:13         ` [PATCH 4/6] mfd/stmpexxx: change touchscreen irq Luotao Fu
2010-06-24 13:09           ` Rabin VINCENT
2010-06-24 13:17             ` Luotao Fu
2010-06-24 11:13         ` [PATCH 5/6] mfd/stmpexxx: fix stmpe811 enable hook Luotao Fu
2010-06-24 12:11           ` Rabin VINCENT
2010-06-24 12:32             ` Luotao Fu
2010-06-24 12:47               ` [PATCH 5/6 V3] " Luotao Fu
2010-06-24 13:05                 ` Rabin VINCENT
2010-06-24 11:13         ` [PATCH 6/6 V4] input: STMPE touch controller support Luotao Fu
2010-06-24 12:27           ` [PATCH 5/6 V2] mfd/stmpexxx: fix stmpe811 enable hook Luotao Fu
2010-06-24 12:35             ` Rabin VINCENT
2010-06-24 12:46               ` Luotao Fu
2010-06-24 12:28           ` [PATCH 6/6 V5] input: STMPE touch controller support Luotao Fu
2010-06-24 14:26             ` [PATCH 5/5] " Luotao Fu
2010-06-24 16:24               ` Dmitry Torokhov
2010-06-24 16:57                 ` Luotao Fu
2010-06-25  8:37                 ` [PATCH 5/5 V7] " Luotao Fu
2010-06-25  9:11                   ` Dmitry Torokhov
2010-06-25  9:32                     ` Luotao Fu
2010-06-27 21:24                     ` Samuel Ortiz
2010-06-25  9:34                   ` [PATCH 5/5 V8] " Luotao Fu
2010-06-24 12:31           ` [PATCH 6/6 V4] " Rabin VINCENT
2010-06-24 12:42             ` Luotao Fu
2010-06-24 13:01               ` Rabin VINCENT
2010-06-24 13:11                 ` Luotao Fu
2010-06-24 13:01               ` Rabin VINCENT

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.