diff mbox

[v2,2/2] input: Add support for the Semtech SX8634 controller

Message ID 1343045927-22063-2-git-send-email-thierry.reding@avionic-design.de (mailing list archive)
State New, archived
Headers show

Commit Message

Thierry Reding July 23, 2012, 12:18 p.m. UTC
This commit adds support for the Semtech SX8634 Capacitive Button and
Slider Touch controller.

Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: linux-input@vger.kernel.org
Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: devicetree-discuss@lists.ozlabs.org
Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de>

---
Changes in v2:
- add OF device table
- use smtc vendor prefix
- fix build warnings
- add power-gpios property

 Documentation/devicetree/bindings/input/sx8634.txt |  58 ++
 drivers/input/misc/Kconfig                         |  10 +
 drivers/input/misc/Makefile                        |   1 +
 drivers/input/misc/sx8634.c                        | 785 +++++++++++++++++++++
 include/linux/input/sx8634.h                       |  33 +
 5 files changed, 887 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/sx8634.txt
 create mode 100644 drivers/input/misc/sx8634.c
 create mode 100644 include/linux/input/sx8634.h

Comments

Dmitry Torokhov July 23, 2012, 4:48 p.m. UTC | #1
Hi Thierry,

On Mon, Jul 23, 2012 at 02:18:47PM +0200, Thierry Reding wrote:
> +
> +	if (gpio_is_valid(sx->power_gpio)) {
> +		err = gpio_request(sx->power_gpio, "sx8634 power");
> +		if (err < 0) {
> +			dev_err(&client->dev,
> +				"failed to request power GPIO#%u: %d\n",
> +				sx->power_gpio, err);
> +			goto free_input_device;
> +		}
> +
> +		err = gpio_direction_output(sx->power_gpio, 1);
> +		if (err < 0) {
> +			dev_err(&client->dev, "failed to enable power: %d\n",
> +				err);
> +			goto free_power_gpio;
> +		}

I think there is gpio_request_one() that will take care of tehse 2
calls.

> +
> +		msleep(150);
> +	}
> +
> +	err = sx8634_setup(sx, pdata);
> +	if (err < 0)
> +		goto free_power_gpio;
> +
> +	err = sysfs_create_group(&client->dev.kobj, &sx8634_attr_group);
> +	if (err < 0)
> +		goto free_power_gpio;
> +
> +	err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
> +					sx8634_irq, IRQF_ONESHOT, "sx8634",
> +					sx);

Please do not use devm_* interface here as it make the driverr bomb in
remove() where you unregister input device but keep interrupt handler
active until after remove() finishes.

> +
> +#ifdef CONFIG_PM_SLEEP
> +static int sx8634_i2c_suspend(struct device *dev)
> +{
> +	return 0;
> +}
> +
> +static int sx8634_i2c_resume(struct device *dev)
> +{
> +	return 0;
> +}
> +#endif
> +
> +static SIMPLE_DEV_PM_OPS(sx8634_i2c_pm, sx8634_i2c_suspend, sx8634_i2c_resume);

Why are these stubs needed?

Thanks.
Thierry Reding July 23, 2012, 8:15 p.m. UTC | #2
On Mon, Jul 23, 2012 at 09:48:16AM -0700, Dmitry Torokhov wrote:
> Hi Thierry,
> 
> On Mon, Jul 23, 2012 at 02:18:47PM +0200, Thierry Reding wrote:
> > +
> > +	if (gpio_is_valid(sx->power_gpio)) {
> > +		err = gpio_request(sx->power_gpio, "sx8634 power");
> > +		if (err < 0) {
> > +			dev_err(&client->dev,
> > +				"failed to request power GPIO#%u: %d\n",
> > +				sx->power_gpio, err);
> > +			goto free_input_device;
> > +		}
> > +
> > +		err = gpio_direction_output(sx->power_gpio, 1);
> > +		if (err < 0) {
> > +			dev_err(&client->dev, "failed to enable power: %d\n",
> > +				err);
> > +			goto free_power_gpio;
> > +		}
> 
> I think there is gpio_request_one() that will take care of tehse 2
> calls.

Right, that shortens this by half.

> 
> > +
> > +		msleep(150);
> > +	}
> > +
> > +	err = sx8634_setup(sx, pdata);
> > +	if (err < 0)
> > +		goto free_power_gpio;
> > +
> > +	err = sysfs_create_group(&client->dev.kobj, &sx8634_attr_group);
> > +	if (err < 0)
> > +		goto free_power_gpio;
> > +
> > +	err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
> > +					sx8634_irq, IRQF_ONESHOT, "sx8634",
> > +					sx);
> 
> Please do not use devm_* interface here as it make the driverr bomb in
> remove() where you unregister input device but keep interrupt handler
> active until after remove() finishes.

Okay, will do. There is devm_free_irq() but I guess it isn't worth the
overhead since the only benefit would be that it simplifies the .probe()
error unwinding a bit.

> > +#ifdef CONFIG_PM_SLEEP
> > +static int sx8634_i2c_suspend(struct device *dev)
> > +{
> > +	return 0;
> > +}
> > +
> > +static int sx8634_i2c_resume(struct device *dev)
> > +{
> > +	return 0;
> > +}
> > +#endif
> > +
> > +static SIMPLE_DEV_PM_OPS(sx8634_i2c_pm, sx8634_i2c_suspend, sx8634_i2c_resume);
> 
> Why are these stubs needed?

They're not, I'll drop them.

Thierry
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/input/sx8634.txt b/Documentation/devicetree/bindings/input/sx8634.txt
new file mode 100644
index 0000000..65e2e10
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/sx8634.txt
@@ -0,0 +1,58 @@ 
+Semtech SX8634 Capacitive Button and Slider Touch Controller
+
+The SX8634 controller is configured with the following properties:
+
+  Required properties:
+  - compatible: "smtc,sx8634"
+  - reg: I2C bus address of the device
+  - interrupts: interrupt number of the device
+  - #address-cells: must be <1>
+  - #size-cells: must be <0>
+
+  Optional Properties:
+  - threshold: number of ticks required to detect a touch/release
+    - range: 0x00 to 0xff
+    - default: 0xa0
+  - sensitivity: sensitivity of the sensors
+    - range: 0x0 to 0x7
+    - default: 0x0
+
+Each capacitive sensor is configured via a separate sub-node:
+
+  Required Properties:
+  - reg: sensor index
+  - label: name of the sensor
+  - linux,code: Keycode to emit for buttons. If absent, the capacitive sensor
+    is part of the slider element.
+
+  Optional Properties:
+  - threshold: overrides the global threshold setting
+  - sensitivity: overrides the global sensitivity setting
+
+Example:
+
+	keypad: sx8634@2b {
+		compatible = "smtc,sx8634";
+		reg = <0x2b>;
+
+		interrupt-parent = <&gpioext>;
+		interrupts = <3>;
+
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		threshold = <0xa0>;
+		sensitivity = <7>;
+
+		cap@1 {
+			reg = <1>;
+			label = "Up";
+			linux,code = <103>; /* KEY_UP */
+		};
+
+		cap@2 {
+			reg = <2>;
+			label = "Down";
+			linux,code = <108>; /* KEY_DOWN */
+		};
+	};
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 7faf4a7..61e48e5 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -577,6 +577,16 @@  config INPUT_CMA3000_I2C
 	  To compile this driver as a module, choose M here: the
 	  module will be called cma3000_d0x_i2c.
 
+config INPUT_SX8634
+	tristate "Semtech SX8634"
+	depends on I2C
+	default n
+	help
+	  Say Y here if you want to use the Semtech SX8634 controller.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called sx8634.
+
 config INPUT_XEN_KBDDEV_FRONTEND
 	tristate "Xen virtual keyboard and mouse support"
 	depends on XEN
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index f55cdf4..7a86fac 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -53,5 +53,6 @@  obj-$(CONFIG_INPUT_TWL6040_VIBRA)	+= twl6040-vibra.o
 obj-$(CONFIG_INPUT_UINPUT)		+= uinput.o
 obj-$(CONFIG_INPUT_WISTRON_BTNS)	+= wistron_btns.o
 obj-$(CONFIG_INPUT_WM831X_ON)		+= wm831x-on.o
+obj-$(CONFIG_INPUT_SX8634)		+= sx8634.o
 obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND)	+= xen-kbdfront.o
 obj-$(CONFIG_INPUT_YEALINK)		+= yealink.o
diff --git a/drivers/input/misc/sx8634.c b/drivers/input/misc/sx8634.c
new file mode 100644
index 0000000..4e58b1e
--- /dev/null
+++ b/drivers/input/misc/sx8634.c
@@ -0,0 +1,785 @@ 
+/*
+ * Copyright (C) 2011-2012 Avionic Design GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/slab.h>
+
+#include <linux/input/sx8634.h>
+
+#define I2C_IRQ_SRC 0x00
+#define I2C_IRQ_SRC_MODE (1 << 0)
+#define I2C_IRQ_SRC_COMPENSATION (1 << 1)
+#define I2C_IRQ_SRC_BUTTONS (1 << 2)
+#define I2C_IRQ_SRC_SLIDER (1 << 3)
+#define I2C_IRQ_SRC_GPI (1 << 4)
+#define I2C_IRQ_SRC_SPM (1 << 5)
+#define I2C_IRQ_SRC_NVM (1 << 6)
+#define I2C_IRQ_SRC_READY (1 << 7)
+
+#define I2C_CAP_STAT_MSB 0x01
+#define I2C_CAP_STAT_LSB 0x02
+#define I2C_SLD_POS_MSB 0x03
+#define I2C_SLD_POS_LSB 0x04
+#define I2C_GPI_STAT 0x07
+#define I2C_SPM_STAT 0x08
+#define I2C_COMP_OP_MODE 0x09
+#define I2C_GPO_CTRL 0x0a
+#define I2C_GPP_PIN_ID 0x0b
+#define I2C_GPP_INTENSITY 0x0c
+#define I2C_SPM_CFG 0x0d
+#define I2C_SPM_CFG_WRITE (0 << 3)
+#define I2C_SPM_CFG_READ (1 << 3)
+#define I2C_SPM_CFG_OFF (0 << 4)
+#define I2C_SPM_CFG_ON (1 << 4)
+#define I2C_SPM_BASE 0x0e
+#define I2C_SPM_KEY_MSB 0xac
+#define I2C_SPM_KEY_LSB 0xad
+#define I2C_SOFT_RESET 0xb1
+
+#define SPM_CFG 0x00
+#define SPM_CAP_MODE_MISC 0x09
+
+#define SPM_CAP_MODE(x) (((x) <= 3) ? 0x0c : (((x) <= 7) ? 0x0b : 0x0a))
+#define SPM_CAP_MODE_SHIFT(x) (((x) & 3) * 2)
+#define SPM_CAP_MODE_MASK 0x3
+#define SPM_CAP_MODE_MASK_SHIFTED(x) (SPM_CAP_MODE_MASK << SPM_CAP_MODE_SHIFT(x))
+
+#define SPM_CAP_SENS(x) (0x0d + ((x) / 2))
+#define SPM_CAP_SENS_MAX 0x7
+#define SPM_CAP_SENS_SHIFT(x) (((x) & 1) ? 0 : 4)
+#define SPM_CAP_SENS_MASK 0x7
+#define SPM_CAP_SENS_MASK_SHIFTED(x) (SPM_CAP_SENS_MASK << SPM_CAP_SENS_SHIFT(x))
+
+#define SPM_CAP_THRESHOLD(x) (0x13 + (x))
+#define SPM_CAP_THRESHOLD_MAX 0xff
+
+#define SPM_BLOCK_SIZE 8
+#define SPM_NUM_BLOCKS 16
+#define SPM_SIZE (SPM_BLOCK_SIZE * SPM_NUM_BLOCKS)
+
+#define SLD_POS_STEP 12
+
+struct sx8634 {
+	struct i2c_client *client;
+	struct input_dev *input;
+	unsigned short keycodes[SX8634_NUM_CAPS];
+	unsigned long spm_dirty;
+	u8 *spm_cache;
+	u16 slider_max;
+	u16 status;
+	int power_gpio;
+};
+
+static int spm_wait(struct i2c_client *client)
+{
+	unsigned int retries = 32;
+	int err;
+
+	do {
+		err = i2c_smbus_read_byte_data(client, I2C_IRQ_SRC);
+		if (err < 0)
+			return err;
+
+		if (err & I2C_IRQ_SRC_SPM)
+			break;
+
+		msleep(10);
+	} while (--retries);
+
+	return retries ? 0 : -ETIMEDOUT;
+}
+
+static ssize_t spm_read_block(struct i2c_client *client, loff_t offset,
+		void *buffer, size_t size)
+{
+	u8 enable = I2C_SPM_CFG_ON | I2C_SPM_CFG_READ;
+	int err;
+
+	BUG_ON(size < SPM_BLOCK_SIZE);
+	BUG_ON((offset & 7) != 0);
+
+	err = i2c_smbus_write_byte_data(client, I2C_SPM_CFG, enable);
+	if (err < 0)
+		return err;
+
+	err = i2c_smbus_write_byte_data(client, I2C_SPM_BASE, offset);
+	if (err < 0)
+		return err;
+
+	err = i2c_smbus_read_i2c_block_data(client, 0, SPM_BLOCK_SIZE, buffer);
+	if (err < 0)
+		return err;
+
+	err = i2c_smbus_write_byte_data(client, I2C_SPM_CFG, I2C_SPM_CFG_OFF);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static ssize_t spm_write_block(struct i2c_client *client, loff_t offset,
+		const void *buffer, size_t size)
+{
+	u8 enable = I2C_SPM_CFG_ON | I2C_SPM_CFG_WRITE;
+	int err;
+
+	BUG_ON(size < SPM_BLOCK_SIZE);
+	BUG_ON((offset & 7) != 0);
+
+	err = i2c_smbus_write_byte_data(client, I2C_SPM_CFG, enable);
+	if (err < 0)
+		return err;
+
+	err = i2c_smbus_write_byte_data(client, I2C_SPM_BASE, offset);
+	if (err < 0)
+		return err;
+
+	err = i2c_smbus_write_i2c_block_data(client, 0, SPM_BLOCK_SIZE, buffer);
+	if (err < 0)
+		return err;
+
+	err = i2c_smbus_write_byte_data(client, I2C_SPM_CFG, I2C_SPM_CFG_OFF);
+	if (err < 0)
+		return err;
+
+	err = spm_wait(client);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static ssize_t sx8634_spm_load(struct sx8634 *sx)
+{
+	loff_t offset;
+	ssize_t err;
+
+	if (sx->spm_dirty != 0)
+		dev_warn(&sx->client->dev, "discarding modified SPM cache\n");
+
+	memset(sx->spm_cache, 0, SPM_SIZE);
+
+	for (offset = 0; offset < SPM_SIZE; offset += SPM_BLOCK_SIZE) {
+		err = spm_read_block(sx->client, offset,
+				sx->spm_cache + offset, SPM_BLOCK_SIZE);
+		if (err < 0) {
+			dev_err(&sx->client->dev, "spm_read_block(): %d\n",
+					err);
+			return err;
+		}
+	}
+
+	sx->spm_dirty = 0;
+
+	return 0;
+}
+
+static ssize_t sx8634_spm_sync(struct sx8634 *sx)
+{
+	int bit;
+
+	for_each_set_bit(bit, &sx->spm_dirty, SPM_NUM_BLOCKS) {
+		loff_t offset = bit * SPM_BLOCK_SIZE;
+		ssize_t err;
+
+		err = spm_write_block(sx->client, offset,
+				sx->spm_cache + offset, SPM_BLOCK_SIZE);
+		if (err < 0) {
+			dev_err(&sx->client->dev, "spm_write_block(): %d\n",
+					err);
+			return err;
+		}
+	}
+
+	sx->spm_dirty = 0;
+
+	return 0;
+}
+
+static int sx8634_spm_read(struct sx8634 *sx, unsigned int offset, u8 *value)
+{
+	if (offset >= SPM_SIZE)
+		return -ENXIO;
+
+	*value = sx->spm_cache[offset];
+
+	return 0;
+}
+
+static int sx8634_spm_write(struct sx8634 *sx, unsigned int offset, u8 value)
+{
+	if (offset >= SPM_SIZE)
+		return -ENXIO;
+
+	sx->spm_dirty |= BIT(offset / SPM_BLOCK_SIZE);
+	sx->spm_cache[offset] = value;
+
+	return 0;
+}
+
+static int sx8634_reset(struct sx8634 *sx)
+{
+	unsigned int retries = 32;
+	int err;
+
+	err = i2c_smbus_write_byte_data(sx->client, I2C_SOFT_RESET, 0xde);
+	if (err < 0)
+		return err;
+
+	err = i2c_smbus_write_byte_data(sx->client, I2C_SOFT_RESET, 0x00);
+	if (err < 0)
+		return err;
+
+	do {
+		err = i2c_smbus_read_byte_data(sx->client, I2C_IRQ_SRC);
+		if (err < 0)
+			return err;
+
+		if (err & I2C_IRQ_SRC_READY)
+			break;
+
+		msleep(10);
+	} while (--retries);
+
+	return retries ? 0 : -ETIMEDOUT;
+}
+
+static irqreturn_t sx8634_irq(int irq, void *data)
+{
+	struct sx8634 *sx = data;
+	bool need_sync = false;
+	u8 pending;
+	int err;
+
+	err = i2c_smbus_read_byte_data(sx->client, I2C_IRQ_SRC);
+	if (err < 0) {
+		dev_err(&sx->client->dev, "failed to read IRQ source register: %d\n", err);
+		return IRQ_NONE;
+	}
+
+	pending = err;
+
+	if (pending & I2C_IRQ_SRC_COMPENSATION)
+		dev_dbg(&sx->client->dev, "compensation complete\n");
+
+	if (pending & I2C_IRQ_SRC_BUTTONS) {
+		unsigned long changed;
+		unsigned int cap;
+		u16 status;
+
+		err = i2c_smbus_read_byte_data(sx->client, I2C_CAP_STAT_MSB);
+		if (err < 0) {
+			dev_err(&sx->client->dev, "failed to read MSB: %d\n", err);
+			return IRQ_NONE;
+		}
+
+		status = err << 8;
+
+		err = i2c_smbus_read_byte_data(sx->client, I2C_CAP_STAT_LSB);
+		if (err < 0) {
+			dev_err(&sx->client->dev, "failed to read LSB: %d\n", err);
+			return IRQ_NONE;
+		}
+
+		status |= err;
+
+		changed = status ^ sx->status;
+
+		for_each_set_bit(cap, &changed, SX8634_NUM_CAPS) {
+			unsigned int level = (status & BIT(cap)) ? 1 : 0;
+			input_report_key(sx->input, sx->keycodes[cap], level);
+			need_sync = true;
+		}
+
+		sx->status = status;
+	}
+
+	if (pending & I2C_IRQ_SRC_SLIDER) {
+		u16 position;
+
+		err = i2c_smbus_read_byte_data(sx->client, I2C_SLD_POS_MSB);
+		if (err < 0) {
+			dev_err(&sx->client->dev, "failed to read MSB: %d\n",
+					err);
+			return IRQ_NONE;
+		}
+
+		position = err << 8;
+
+		err = i2c_smbus_read_byte_data(sx->client, I2C_SLD_POS_LSB);
+		if (err < 0) {
+			dev_err(&sx->client->dev, "failed to read LSB: %d\n",
+					err);
+			return IRQ_NONE;
+		}
+
+		position |= err;
+
+		input_report_abs(sx->input, ABS_MISC, position);
+	}
+
+	if (need_sync || (pending & I2C_IRQ_SRC_SLIDER))
+		input_sync(sx->input);
+
+	if (pending & I2C_IRQ_SRC_GPI)
+		dev_dbg(&sx->client->dev, "%s(): GPI event\n", __func__);
+
+	if (pending & I2C_IRQ_SRC_SPM)
+		dev_dbg(&sx->client->dev, "%s(): SPM event\n", __func__);
+
+	if (pending & I2C_IRQ_SRC_NVM)
+		dev_dbg(&sx->client->dev, "%s(): NVM event\n", __func__);
+
+	if (pending & I2C_IRQ_SRC_READY)
+		dev_dbg(&sx->client->dev, "%s(): ready event\n", __func__);
+
+	return IRQ_HANDLED;
+}
+
+static int sx8634_set_mode(struct sx8634 *sx, unsigned int cap, enum sx8634_cap_mode mode)
+{
+	u8 value = 0;
+	int err;
+
+	if ((cap >= SX8634_NUM_CAPS) || (mode == SX8634_CAP_MODE_RESERVED))
+		return -EINVAL;
+
+	err = sx8634_spm_read(sx, SPM_CAP_MODE(cap), &value);
+	if (err < 0)
+		return err;
+
+	value &= ~SPM_CAP_MODE_MASK_SHIFTED(cap);
+	value |= (mode & SPM_CAP_MODE_MASK) << SPM_CAP_MODE_SHIFT(cap);
+
+	err = sx8634_spm_write(sx, SPM_CAP_MODE(cap), value);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int sx8634_set_sensitivity(struct sx8634 *sx, unsigned int cap,
+		u8 sensitivity)
+{
+	u8 value = 0;
+	int err = 0;
+
+	if (cap >= SX8634_NUM_CAPS)
+		return -EINVAL;
+
+	err = sx8634_spm_read(sx, SPM_CAP_SENS(cap), &value);
+	if (err < 0)
+		return err;
+
+	value &= ~SPM_CAP_SENS_MASK_SHIFTED(cap);
+	value |= (sensitivity & SPM_CAP_SENS_MASK) << SPM_CAP_SENS_SHIFT(cap);
+
+	err = sx8634_spm_write(sx, SPM_CAP_SENS(cap), value);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int sx8634_set_threshold(struct sx8634 *sx, unsigned int cap,
+		u8 threshold)
+{
+	int err;
+
+	if (cap >= SX8634_NUM_CAPS)
+		return -EINVAL;
+
+	err = sx8634_spm_write(sx, SPM_CAP_THRESHOLD(cap), threshold);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int sx8634_setup(struct sx8634 *sx, struct sx8634_platform_data *pdata)
+{
+	bool slider = false;
+	unsigned int i;
+	int err;
+
+	err = sx8634_reset(sx);
+	if (err < 0)
+		return err;
+
+	err = sx8634_spm_load(sx);
+	if (err < 0)
+		return err;
+
+	/* disable all capacitive sensors */
+	for (i = 0; i < SX8634_NUM_CAPS; i++) {
+		err = sx8634_set_mode(sx, i, SX8634_CAP_MODE_DISABLED);
+		if (err < 0)
+			return err;
+	}
+
+	err = sx8634_spm_sync(sx);
+	if (err < 0)
+		return err;
+
+	err = sx8634_spm_load(sx);
+	if (err < 0)
+		return err;
+
+	/* configure capacitive sensor parameters */
+	for (i = 0; i < SX8634_NUM_CAPS; i++) {
+		struct sx8634_cap *cap = &pdata->caps[i];
+
+		err = sx8634_set_sensitivity(sx, i, cap->sensitivity);
+		if (err < 0)
+			dev_err(&sx->client->dev, "%s failed: %d\n",
+					"sx8634_set_sensitivity()", err);
+
+		err = sx8634_set_threshold(sx, i, cap->threshold);
+		if (err < 0)
+			dev_err(&sx->client->dev, "%s failed: %d\n",
+					"sx8634_set_threshold()", err);
+	}
+
+	err = sx8634_spm_sync(sx);
+	if (err < 0)
+		return err;
+
+	err = sx8634_spm_load(sx);
+	if (err < 0)
+		return err;
+
+	/* enable individual cap sensitivity */
+	err = sx8634_spm_write(sx, SPM_CAP_MODE_MISC, 0x04);
+	if (err < 0)
+		return err;
+
+	/* enable capacitive sensors */
+	for (i = 0; i < SX8634_NUM_CAPS; i++) {
+		struct sx8634_cap *cap = &pdata->caps[i];
+
+		if (cap->mode == SX8634_CAP_MODE_BUTTON) {
+			input_set_capability(sx->input, EV_KEY, cap->keycode);
+			sx->keycodes[i] = cap->keycode;
+		}
+
+		if (cap->mode == SX8634_CAP_MODE_SLIDER) {
+			if (slider)
+				sx->slider_max += SLD_POS_STEP;
+
+			slider = true;
+		}
+
+		err = sx8634_set_mode(sx, i, cap->mode);
+		if (err < 0)
+			dev_err(&sx->client->dev, "%s failed: %d\n",
+					"sx8634_set_mode()", err);
+	}
+
+	err = sx8634_spm_sync(sx);
+	if (err < 0)
+		return err;
+
+	sx->input->id.bustype = BUS_I2C;
+	sx->input->id.product = 0;
+	sx->input->id.version = 0;
+	sx->input->name = "sx8634";
+	sx->input->dev.parent = &sx->client->dev;
+
+	/* setup slider */
+	if (slider) {
+		input_set_abs_params(sx->input, ABS_MISC, 0, sx->slider_max,
+				0, 0);
+		input_set_capability(sx->input, EV_ABS, ABS_MISC);
+	}
+
+	return 0;
+}
+
+static ssize_t sx8634_spm_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct sx8634 *sx = i2c_get_clientdata(client);
+	ssize_t len = 0;
+	size_t i, j;
+	int err;
+
+	err = sx8634_spm_load(sx);
+	if (err < 0)
+		return err;
+
+	for (i = 0; i < SPM_SIZE; i += SPM_BLOCK_SIZE) {
+		const char *prefix = "";
+
+		for (j = 0; j < SPM_BLOCK_SIZE; j++) {
+			len += sprintf(buf + len, "%s%02x", prefix,
+					sx->spm_cache[i + j]);
+			prefix = " ";
+		}
+
+		len += sprintf(buf + len, "\n");
+	}
+
+	return len;
+}
+
+static DEVICE_ATTR(spm, 0664, sx8634_spm_show, NULL);
+
+static struct attribute *sx8634_attributes[] = {
+	&dev_attr_spm.attr,
+	NULL
+};
+
+static const struct attribute_group sx8634_attr_group = {
+	.attrs = sx8634_attributes,
+};
+
+static int sx8634_parse_dt(struct device *dev, struct sx8634_platform_data *pdata)
+{
+	struct device_node *node = dev->of_node;
+	struct device_node *child = NULL;
+	u32 sensitivity_def = 0x00;
+	u32 threshold_def = 0xa0;
+	int err;
+
+	if (!node)
+		return -ENODEV;
+
+	memset(pdata, 0, sizeof(*pdata));
+
+	err = of_property_read_u32(node, "threshold", &threshold_def);
+	if (err < 0) {
+	}
+
+	if (threshold_def > SPM_CAP_THRESHOLD_MAX) {
+		dev_info(dev, "invalid threshold: %u, using %u\n",
+				threshold_def, SPM_CAP_THRESHOLD_MAX);
+		threshold_def = SPM_CAP_THRESHOLD_MAX;
+	}
+
+	err = of_property_read_u32(node, "sensitivity", &sensitivity_def);
+	if (err < 0) {
+	}
+
+	if (sensitivity_def > SPM_CAP_SENS_MAX) {
+		dev_info(dev, "invalid sensitivity: %u, using %u\n",
+				sensitivity_def, SPM_CAP_SENS_MAX);
+		sensitivity_def = SPM_CAP_SENS_MAX;
+	}
+
+	while ((child = of_get_next_child(node, child))) {
+		u32 sensitivity = sensitivity_def;
+		u32 threshold = threshold_def;
+		struct sx8634_cap *cap;
+		u32 keycode;
+		u32 index;
+
+		err = of_property_read_u32(child, "reg", &index);
+		if (err < 0) {
+		}
+
+		if (index >= SX8634_NUM_CAPS) {
+			dev_err(dev, "invalid cap index: %u\n", index);
+			continue;
+		}
+
+		cap = &pdata->caps[index];
+
+		err = of_property_read_u32(child, "threshold", &threshold);
+		if (err < 0) {
+		}
+
+		cap->threshold = threshold;
+
+		err = of_property_read_u32(child, "sensitivity", &sensitivity);
+		if (err < 0) {
+		}
+
+		cap->sensitivity = sensitivity;
+
+		err = of_property_read_u32(child, "linux,code", &keycode);
+		if (err == 0) {
+			cap->mode = SX8634_CAP_MODE_BUTTON;
+			cap->keycode = keycode;
+		} else {
+			cap->mode = SX8634_CAP_MODE_SLIDER;
+		}
+	}
+
+	pdata->power_gpio = of_get_named_gpio(node, "power-gpios", 0);
+
+	return 0;
+}
+
+static int __devinit sx8634_i2c_probe(struct i2c_client *client,
+				      const struct i2c_device_id *id)
+{
+	struct sx8634_platform_data *pdata = client->dev.platform_data;
+	struct device_node *node = client->dev.of_node;
+	struct sx8634_platform_data defpdata;
+	struct sx8634 *sx;
+	int err = 0;
+
+	if (IS_ENABLED(CONFIG_OF) && node) {
+		client->irq = irq_of_parse_and_map(node, 0);
+		if (client->irq == NO_IRQ)
+			return -EPROBE_DEFER;
+	}
+
+	if (!pdata) {
+		if (!IS_ENABLED(CONFIG_OF))
+			return -ENODEV;
+
+		err = sx8634_parse_dt(&client->dev, &defpdata);
+		if (err < 0)
+			return err;
+
+		pdata = &defpdata;
+	}
+
+	sx = devm_kzalloc(&client->dev, sizeof(*sx), GFP_KERNEL);
+	if (!sx)
+		return -ENOMEM;
+
+	sx->spm_cache = devm_kzalloc(&client->dev, SPM_SIZE, GFP_KERNEL);
+	if (!sx->spm_cache)
+		return -ENOMEM;
+
+	sx->input = input_allocate_device();
+	if (!sx->input)
+		return -ENOMEM;
+
+	sx->power_gpio = pdata->power_gpio;
+	sx->client = client;
+
+	if (gpio_is_valid(sx->power_gpio)) {
+		err = gpio_request(sx->power_gpio, "sx8634 power");
+		if (err < 0) {
+			dev_err(&client->dev,
+				"failed to request power GPIO#%u: %d\n",
+				sx->power_gpio, err);
+			goto free_input_device;
+		}
+
+		err = gpio_direction_output(sx->power_gpio, 1);
+		if (err < 0) {
+			dev_err(&client->dev, "failed to enable power: %d\n",
+				err);
+			goto free_power_gpio;
+		}
+
+		msleep(150);
+	}
+
+	err = sx8634_setup(sx, pdata);
+	if (err < 0)
+		goto free_power_gpio;
+
+	err = sysfs_create_group(&client->dev.kobj, &sx8634_attr_group);
+	if (err < 0)
+		goto free_power_gpio;
+
+	err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+					sx8634_irq, IRQF_ONESHOT, "sx8634",
+					sx);
+	if (err < 0) {
+		dev_err(&client->dev, "can't allocate IRQ#%d\n", client->irq);
+		goto remove_sysfs;
+	}
+
+	/* clear interrupts */
+	err = i2c_smbus_read_byte_data(client, I2C_IRQ_SRC);
+	if (err < 0) {
+		dev_err(&client->dev, "can't clear interrupts: %d\n", err);
+		goto remove_sysfs;
+	}
+
+	err = input_register_device(sx->input);
+	if (err < 0)
+		goto remove_sysfs;
+
+	i2c_set_clientdata(client, sx);
+
+	return 0;
+
+remove_sysfs:
+	sysfs_remove_group(&client->dev.kobj, &sx8634_attr_group);
+free_power_gpio:
+	if (gpio_is_valid(sx->power_gpio)) {
+		gpio_direction_output(sx->power_gpio, 0);
+		gpio_free(sx->power_gpio);
+	}
+free_input_device:
+	input_free_device(sx->input);
+	return err;
+}
+
+static int __devexit sx8634_i2c_remove(struct i2c_client *client)
+{
+	struct sx8634 *sx = i2c_get_clientdata(client);
+
+	input_unregister_device(sx->input);
+	sysfs_remove_group(&client->dev.kobj, &sx8634_attr_group);
+
+	if (gpio_is_valid(sx->power_gpio)) {
+		gpio_direction_output(sx->power_gpio, 0);
+		gpio_free(sx->power_gpio);
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int sx8634_i2c_suspend(struct device *dev)
+{
+	return 0;
+}
+
+static int sx8634_i2c_resume(struct device *dev)
+{
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(sx8634_i2c_pm, sx8634_i2c_suspend, sx8634_i2c_resume);
+
+static const struct i2c_device_id sx8634_i2c_ids[] = {
+	{ "sx8634", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, sx8634_i2c_ids);
+
+#ifdef CONFIG_OF
+static const struct of_device_id sx8634_of_match[] = {
+	{ .compatible = "smtc,sx8634", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, sx8634_of_match);
+#endif
+
+static struct i2c_driver sx8634_driver = {
+	.driver = {
+		.name = "sx8634",
+		.owner = THIS_MODULE,
+		.pm = &sx8634_i2c_pm,
+		.of_match_table = of_match_ptr(sx8634_of_match),
+	},
+	.probe = sx8634_i2c_probe,
+	.remove = __devexit_p(sx8634_i2c_remove),
+	.id_table = sx8634_i2c_ids,
+};
+module_i2c_driver(sx8634_driver);
+
+MODULE_AUTHOR("Thierry Reding <thierry.reding@avionic-design.de>");
+MODULE_DESCRIPTION("Semtech SX8634 Controller Driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/input/sx8634.h b/include/linux/input/sx8634.h
new file mode 100644
index 0000000..9b371fe
--- /dev/null
+++ b/include/linux/input/sx8634.h
@@ -0,0 +1,33 @@ 
+/*
+ * Copyright (C) 2011-2012 Avionic Design GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __LINUX_INPUT_SX8634_H__
+#define __LINUX_INPUT_SX8634_H__
+
+#define SX8634_NUM_CAPS 12
+
+enum sx8634_cap_mode {
+	SX8634_CAP_MODE_DISABLED,
+	SX8634_CAP_MODE_BUTTON,
+	SX8634_CAP_MODE_SLIDER,
+	SX8634_CAP_MODE_RESERVED
+};
+
+struct sx8634_cap {
+	enum sx8634_cap_mode mode;
+	unsigned short keycode;
+	u8 sensitivity;
+	u8 threshold;
+};
+
+struct sx8634_platform_data {
+	struct sx8634_cap caps[SX8634_NUM_CAPS];
+	int power_gpio;
+};
+
+#endif