diff mbox

Input: wm831x - Add driver for Wolfson WM831x PMIC touchscreen controllers

Message ID 1295964556-1024-1-git-send-email-broonie@opensource.wolfsonmicro.com (mailing list archive)
State New, archived
Headers show

Commit Message

Mark Brown Jan. 25, 2011, 2:09 p.m. UTC
None
diff mbox

Patch

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 0c9f4b1..46656b0 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -423,6 +423,16 @@  config TOUCHSCREEN_UCB1400
 	  To compile this driver as a module, choose M here: the
 	  module will be called ucb1400_ts.
 
+config TOUCHSCREEN_WM831X
+	tristate "Support for WM831x touchscreen controllers"
+	depends on MFD_WM831X
+	help
+	  This enables support for the touchscreen controller on the WM831x
+	  series of PMICs.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called wm831x-ts.
+
 config TOUCHSCREEN_WM97XX
 	tristate "Support for WM97xx AC97 touchscreen controllers"
 	depends on AC97_BUS
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 718bcc8..e8dcfab 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -48,6 +48,7 @@  obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN)	+= touchwin.o
 obj-$(CONFIG_TOUCHSCREEN_TSC2007)	+= tsc2007.o
 obj-$(CONFIG_TOUCHSCREEN_UCB1400)	+= ucb1400_ts.o
 obj-$(CONFIG_TOUCHSCREEN_WACOM_W8001)	+= wacom_w8001.o
+obj-$(CONFIG_TOUCHSCREEN_WM831X)	+= wm831x-ts.o
 obj-$(CONFIG_TOUCHSCREEN_WM97XX)	+= wm97xx-ts.o
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705)	+= wm9705.o
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712)	+= wm9712.o
diff --git a/drivers/input/touchscreen/wm831x-ts.c b/drivers/input/touchscreen/wm831x-ts.c
new file mode 100644
index 0000000..80e25c4
--- /dev/null
+++ b/drivers/input/touchscreen/wm831x-ts.c
@@ -0,0 +1,370 @@ 
+/*
+ * wm831x-ts.c - Touchscreen driver for WM831x PMICs
+ *
+ * Copyright 2011 Wolfson Microelectronics plc.
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/pm.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/irq.h>
+#include <linux/mfd/wm831x/pdata.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+/*
+ * R16424 (0x4028) - Touch Control 1
+ */
+#define WM831X_TCH_ENA                          0x8000  /* TCH_ENA */
+#define WM831X_TCH_CVT_ENA                      0x4000  /* TCH_CVT_ENA */
+#define WM831X_TCH_SLPENA                       0x1000  /* TCH_SLPENA */
+#define WM831X_TCH_Z_ENA                        0x0400  /* TCH_Z_ENA */
+#define WM831X_TCH_Y_ENA                        0x0200  /* TCH_Y_ENA */
+#define WM831X_TCH_X_ENA                        0x0100  /* TCH_X_ENA */
+#define WM831X_TCH_DELAY_MASK                   0x00E0  /* TCH_DELAY - [7:5] */
+#define WM831X_TCH_DELAY_SHIFT                       5  /* TCH_DELAY - [7:5] */
+#define WM831X_TCH_DELAY_WIDTH                       3  /* TCH_DELAY - [7:5] */
+#define WM831X_TCH_RATE_MASK                    0x001F  /* TCH_RATE - [4:0] */
+#define WM831X_TCH_RATE_SHIFT                        0  /* TCH_RATE - [4:0] */
+#define WM831X_TCH_RATE_WIDTH                        5  /* TCH_RATE - [4:0] */
+
+/*
+ * R16425 (0x4029) - Touch Control 2
+ */
+#define WM831X_TCH_PD_WK                        0x2000  /* TCH_PD_WK */
+#define WM831X_TCH_5WIRE                        0x1000  /* TCH_5WIRE */
+#define WM831X_TCH_PDONLY                       0x0800  /* TCH_PDONLY */
+#define WM831X_TCH_ISEL                         0x0100  /* TCH_ISEL */
+#define WM831X_TCH_RPU_MASK                     0x000F  /* TCH_RPU - [3:0] */
+#define WM831X_TCH_RPU_SHIFT                         0  /* TCH_RPU - [3:0] */
+#define WM831X_TCH_RPU_WIDTH                         4  /* TCH_RPU - [3:0] */
+
+/*
+ * R16426-8 (0x402A-C) - Touch Data X/Y/X
+ */
+#define WM831X_TCH_PD                           0x8000  /* TCH_PD1 */
+#define WM831X_TCH_DATA_MASK                    0x0FFF  /* TCH_DATA - [11:0] */
+#define WM831X_TCH_DATA_SHIFT                        0  /* TCH_DATA - [11:0] */
+#define WM831X_TCH_DATA_WIDTH                       12  /* TCH_DATA - [11:0] */
+
+struct wm831x_ts {
+	struct input_dev *input_dev;
+	struct wm831x *wm831x;
+	int data_irq;
+	int pd_irq;
+	bool pressure;
+	bool pen_down;
+};
+
+static irqreturn_t wm831x_ts_data_irq(int irq, void *irq_data)
+{
+	struct wm831x_ts *wm831x_ts = irq_data;
+	struct wm831x *wm831x = wm831x_ts->wm831x;
+	static int data_types[] = { ABS_X, ABS_Y, ABS_PRESSURE };
+	u16 data[3];
+	int count, i, ret;
+
+	wm831x_set_bits(wm831x, WM831X_INTERRUPT_STATUS_1,
+			WM831X_TCHDATA_EINT, WM831X_TCHDATA_EINT);
+
+	if (wm831x_ts->pressure)
+		count = 3;
+	else
+		count = 2;
+
+	ret = wm831x_bulk_read(wm831x, WM831X_TOUCH_DATA_X, count,
+			       data);
+	if (ret != 0) {
+		dev_err(wm831x->dev, "Failed to read touch data: %d\n",
+			ret);
+		return IRQ_NONE;
+	}
+
+	/* We get a pen down reading on every reading, report pen up if any
+	 * individual reading does so.
+	 */
+	wm831x_ts->pen_down = true;
+	for (i = 0; i < count; i++) {
+		if (!(data[i] & WM831X_TCH_PD)) {
+			wm831x_ts->pen_down = false;
+			continue;
+		}
+		input_report_abs(wm831x_ts->input_dev, data_types[i],
+				 data[i] & WM831X_TCH_DATA_MASK);
+	}
+
+	if (!wm831x_ts->pen_down) {
+		disable_irq_nosync(wm831x_ts->data_irq);
+
+		/* Don't need data any more */
+		wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
+				WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA |
+				WM831X_TCH_Z_ENA, 0);
+
+		/* Flush any final samples that arrived while reading */
+		wm831x_set_bits(wm831x, WM831X_INTERRUPT_STATUS_1,
+				WM831X_TCHDATA_EINT, WM831X_TCHDATA_EINT);
+
+		wm831x_bulk_read(wm831x, WM831X_TOUCH_DATA_X, count, data);
+
+		if (wm831x_ts->pressure)
+			input_report_abs(wm831x_ts->input_dev,
+					 ABS_PRESSURE, 0);
+
+		input_report_key(wm831x_ts->input_dev, BTN_TOUCH, 0);
+	}
+
+	input_sync(wm831x_ts->input_dev);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t wm831x_ts_pen_down_irq(int irq, void *irq_data)
+{
+	struct wm831x_ts *wm831x_ts = irq_data;
+	struct wm831x *wm831x = wm831x_ts->wm831x;
+	int ena;
+
+	/* Start collecting data */
+	if (wm831x_ts->pressure)
+		ena = WM831X_TCH_Z_ENA;
+	else
+		ena = 0;
+
+	wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
+			WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA | WM831X_TCH_Z_ENA,
+			WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA | ena);
+
+	input_report_key(wm831x_ts->input_dev, BTN_TOUCH, 1);
+	input_sync(wm831x_ts->input_dev);
+
+	wm831x_set_bits(wm831x, WM831X_INTERRUPT_STATUS_1,
+			WM831X_TCHPD_EINT, WM831X_TCHPD_EINT);
+
+	wm831x_ts->pen_down = true;
+	enable_irq(wm831x_ts->data_irq);
+
+	return IRQ_HANDLED;
+}
+
+static int wm831x_ts_input_open(struct input_dev *idev)
+{
+	struct wm831x_ts *wm831x_ts = input_get_drvdata(idev);
+	struct wm831x *wm831x = wm831x_ts->wm831x;
+
+	wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
+			WM831X_TCH_ENA, WM831X_TCH_ENA);
+
+	wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
+			WM831X_TCH_CVT_ENA, WM831X_TCH_CVT_ENA);
+
+	return 0;
+}
+
+static void wm831x_ts_input_close(struct input_dev *idev)
+{
+	struct wm831x_ts *wm831x_ts = input_get_drvdata(idev);
+	struct wm831x *wm831x = wm831x_ts->wm831x;
+
+	wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
+			WM831X_TCH_ENA | WM831X_TCH_CVT_ENA |
+			WM831X_TCH_X_ENA | WM831X_TCH_Y_ENA |
+			WM831X_TCH_Z_ENA, 0);
+
+	if (wm831x_ts->pen_down)
+		disable_irq_nosync(wm831x_ts->data_irq);
+}
+
+static __devinit int wm831x_ts_probe(struct platform_device *pdev)
+{
+	struct wm831x_ts *wm831x_ts;
+	struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+	struct wm831x_pdata *core_pdata = dev_get_platdata(pdev->dev.parent);
+	struct wm831x_touch_pdata *pdata;
+	struct input_dev *input_dev;
+	int ret;
+
+	wm831x_ts = kzalloc(sizeof(struct wm831x_ts), GFP_KERNEL);
+	if (!wm831x_ts)
+		return -ENOMEM;
+
+	wm831x_ts->wm831x = wm831x;
+
+	if (core_pdata && core_pdata->touch)
+		pdata = core_pdata->touch;
+	else
+		pdata = NULL;
+
+	/* If we have a direct IRQ use it, otherwise use the interrupt
+ 	 * from the WM831x IRQ controller.
+	 */
+	if (pdata && pdata->data_irq)
+		wm831x_ts->data_irq = pdata->data_irq;
+	else
+		wm831x_ts->data_irq = platform_get_irq_byname(pdev, "TCHDATA");
+
+	if (pdata && pdata->pd_irq)
+		wm831x_ts->pd_irq = pdata->pd_irq;
+	else
+		wm831x_ts->pd_irq = platform_get_irq_byname(pdev, "TCHPD");
+
+	if (pdata && pdata->pressure)
+		wm831x_ts->pressure = true;
+	else
+		wm831x_ts->pressure = false;
+
+	/* Five wire touchscreens can't report pressure */
+	if (pdata && pdata->fivewire) {
+		wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
+				WM831X_TCH_5WIRE, WM831X_TCH_5WIRE);
+
+		/* Pressure measurments are not possible for five wire mode */
+		WARN_ON(pdata->pressure && pdata->fivewire);
+		wm831x_ts->pressure = false;
+	} else {
+		wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
+				WM831X_TCH_5WIRE, 0);
+	}
+
+	if (pdata) {
+		switch (pdata->isel) {
+		default:
+			dev_err(&pdev->dev, "Unsupported ISEL setting: %d\n",
+				pdata->isel);
+			/* Fall through */
+		case 200:
+		case 0:
+			wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
+					WM831X_TCH_ISEL, 0);
+			break;
+		case 400:
+			wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
+					WM831X_TCH_ISEL, WM831X_TCH_ISEL);
+			break;
+		}
+	}
+
+	wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_2,
+			WM831X_TCH_PDONLY, 0);
+
+	/* Default to 96 samples/sec */
+	wm831x_set_bits(wm831x, WM831X_TOUCH_CONTROL_1,
+			WM831X_TCH_RATE_MASK, 6);
+
+	ret = request_threaded_irq(wm831x_ts->data_irq, NULL,
+				   wm831x_ts_data_irq,
+				   IRQF_ONESHOT,
+				   "Touchscreen data", wm831x_ts);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to request data IRQ %d: %d\n",
+			wm831x_ts->data_irq, ret);
+		goto err_alloc;
+	}
+	disable_irq(wm831x_ts->data_irq);
+
+	ret = request_threaded_irq(wm831x_ts->pd_irq, NULL,
+				   wm831x_ts_pen_down_irq, IRQF_ONESHOT,
+				   "Touchscreen pen down", wm831x_ts);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to request pen down IRQ %d: %d\n",
+			wm831x_ts->pd_irq, ret);
+		goto err_data_irq;
+	}
+
+	platform_set_drvdata(pdev, wm831x_ts);
+
+	input_dev = input_allocate_device();
+	if (input_dev == NULL) {
+		ret = -ENOMEM;
+		goto err_pd_irq;
+	}
+	wm831x_ts->input_dev = input_dev;
+
+	/* set up touch configuration */
+	input_dev->name = "WM831x touchscreen";
+	input_dev->phys = "wm831x";
+	input_dev->open = wm831x_ts_input_open;
+	input_dev->close = wm831x_ts_input_close;
+
+	__set_bit(EV_ABS, input_dev->evbit);
+	__set_bit(EV_KEY, input_dev->evbit);
+	__set_bit(BTN_TOUCH, input_dev->keybit);
+
+	input_set_abs_params(input_dev, ABS_X, 0, 4095, 5, 0);
+	input_set_abs_params(input_dev, ABS_Y, 0, 4095, 5, 0);
+	if (wm831x_ts->pressure)
+		input_set_abs_params(input_dev, ABS_PRESSURE, 0, 4095, 5, 0);
+
+	input_set_drvdata(input_dev, wm831x_ts);
+	input_dev->dev.parent = &pdev->dev;
+
+	ret = input_register_device(input_dev);
+	if (ret < 0)
+		goto err_dev_alloc;
+
+	return ret;
+
+err_dev_alloc:
+	input_free_device(input_dev);
+err_pd_irq:
+	free_irq(wm831x_ts->pd_irq, wm831x_ts);
+err_data_irq:
+	free_irq(wm831x_ts->data_irq, wm831x_ts);
+err_alloc:
+	kfree(wm831x_ts);
+
+	return ret;
+}
+
+static __devexit int wm831x_ts_remove(struct platform_device *pdev)
+{
+	struct wm831x_ts *wm831x_ts = platform_get_drvdata(pdev);
+
+	free_irq(wm831x_ts->pd_irq, wm831x_ts);
+	free_irq(wm831x_ts->data_irq, wm831x_ts);
+	input_unregister_device(wm831x_ts->input_dev);
+	kfree(wm831x_ts);
+
+	return 0;
+}
+
+static struct platform_driver wm831x_ts_driver = {
+	.driver = {
+		.name = "wm831x-touch",
+		.owner = THIS_MODULE,
+	},
+	.probe = wm831x_ts_probe,
+	.remove = __devexit_p(wm831x_ts_remove),
+};
+
+static int __init wm831x_ts_init(void)
+{
+	return platform_driver_register(&wm831x_ts_driver);
+}
+module_init(wm831x_ts_init);
+
+static void __exit wm831x_ts_exit(void)
+{
+	platform_driver_unregister(&wm831x_ts_driver);
+}
+module_exit(wm831x_ts_exit);
+
+/* Module information */
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("WM831x PMIC touchscreen driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-touch");
diff --git a/include/linux/mfd/wm831x/pdata.h b/include/linux/mfd/wm831x/pdata.h
index 1ae8499..c6746e8 100644
--- a/include/linux/mfd/wm831x/pdata.h
+++ b/include/linux/mfd/wm831x/pdata.h
@@ -81,6 +81,7 @@  struct wm831x_touch_pdata {
 	int rpu;               /** Pen down sensitivity resistor divider */
 	int pressure;          /** Report pressure (boolean) */
 	int data_irq;          /** Touch data ready IRQ */
+	int pd_irq;            /** Touch pendown detect IRQ */
 };
 
 enum wm831x_watchdog_action {