public inbox for linux-omap@vger.kernel.org
 help / color / mirror / Atom feed
* Common code for TSC 2101 and 2102
@ 2007-06-04 13:44 andrzej zaborowski
  2007-07-13  6:46 ` Trilok Soni
  2007-07-15 20:18 ` David Brownell
  0 siblings, 2 replies; 17+ messages in thread
From: andrzej zaborowski @ 2007-06-04 13:44 UTC (permalink / raw)
  To: Linux OMAP, David Brownell

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

Hi,
  here's a proposition of a common driver for TSC 2101 and 2102 based
on the previous 2102 code - the difference between the two chips'
registers turns out to be minimal, not considering the audio part.
2101 provides a superset of 2102 functionality for the most part.

I will appreciate if anyone with a board using a TSC2101 could test
how this works for them, and even more if they can fix the parts that
possibly don't work. The part that needs to go into the board file is
similar to that in arch/arm/mach-omap1/board-palmte.c. Audio is not
expected to work, but touchscreen and hwmon parts work for me.

I've put the sensors code back in drivers/hwmon to conform with the
convention that options like CONFIG_HWMON don't affect code outside
drivers/hwmon and for general consistency. As an experiment I use a
workqueue this time.

Cheers,
Andrzej

[-- Attachment #2: 0003-TSC210x-driver-using-the-SPI-framework.txt --]
[-- Type: text/plain, Size: 56041 bytes --]

From 54b60e8e33d288c5fa9644fe9f48cdb8582173f0 Mon Sep 17 00:00:00 2001
From: Andrzej Zaborowski <balrog@zabor.org>
Date: Mon, 4 Jun 2007 16:14:01 +0200
Subject: [PATCH] TSC210x driver using the SPI framework.

This is a rework of the TSC2102 driver from linux-omap-2.6 taking TSC2101
into account with the goal of using only a single driver.
---
 drivers/hwmon/Kconfig                  |   12 +
 drivers/hwmon/Makefile                 |    1 +
 drivers/hwmon/tsc210x_sensors.c        |  256 +++++++
 drivers/input/touchscreen/Kconfig      |   14 +
 drivers/input/touchscreen/Makefile     |    1 +
 drivers/input/touchscreen/tsc210x_ts.c |  153 ++++
 drivers/spi/Kconfig                    |   14 +-
 drivers/spi/Makefile                   |    1 +
 drivers/spi/tsc210x.c                  | 1181 ++++++++++++++++++++++++++++++++
 include/linux/spi/tsc210x.h            |  224 ++++++
 10 files changed, 1856 insertions(+), 1 deletions(-)

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 4d1cb5b..35ba068 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -642,6 +642,18 @@ config SENSORS_APPLESMC
 	  Say Y here if you have an applicable laptop and want to experience
 	  the awesome power of applesmc.
 
+config SENSORS_TSC210X
+	tristate "TI TSC2101/2102 battery & temperature sensors"
+	depends on HWMON && SPI_MASTER
+	select SPI_TSC210X
+	help
+	  Say Y if your board has a TSC210X chip and you want to
+	  have its battery state, auxiliary input and/or temperature
+	  sensors exported through hwmon.
+
+	  This driver can also be built as a module.  In this case
+	  the module will be called tsc210x_sensors.
+
 config HWMON_DEBUG_CHIP
 	bool "Hardware Monitoring Chip debugging messages"
 	default n
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index cfaf338..b436e10 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -58,6 +58,7 @@ obj-$(CONFIG_SENSORS_VT1211)	+= vt1211.o
 obj-$(CONFIG_SENSORS_VT8231)	+= vt8231.o
 obj-$(CONFIG_SENSORS_W83627EHF)	+= w83627ehf.o
 obj-$(CONFIG_SENSORS_W83L785TS)	+= w83l785ts.o
+obj-$(CONFIG_SENSORS_TSC210X)	+= tsc210x_sensors.o
 
 ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y)
 EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/hwmon/tsc210x_sensors.c b/drivers/hwmon/tsc210x_sensors.c
new file mode 100644
index 0000000..91cdd2f
--- /dev/null
+++ b/drivers/hwmon/tsc210x_sensors.c
@@ -0,0 +1,256 @@
+/*
+ * drivers/hwmon/tsc210x_sensors.c
+ *
+ * hwmon interface for TSC210X sensors
+ *
+ * Copyright (c) 2005-2007 Andrzej Zaborowski  <balrog@zabor.org>
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/autoconf.h>
+#ifdef CONFIG_APM
+# include <linux/apm-emulation.h>
+#endif
+
+#include <linux/spi/tsc210x.h>
+
+struct tsc210x_hwmon {
+	int bat[2], aux[2], temp[2];
+
+	struct class_device *dev;
+	struct tsc210x_config *pdata;
+#ifdef CONFIG_APM
+	spinlock_t apm_lock;
+#endif
+};
+
+#ifdef CONFIG_APM
+# define apm_lock()	spin_lock(&hwmon->apm_lock)
+# define apm_unlock()	spin_unlock(&hwmon->apm_lock)
+#else
+# define apm_lock()
+# define apm_unlock()
+#endif
+
+static void tsc210x_ports(struct tsc210x_hwmon *hwmon, int bat[], int aux[])
+{
+	apm_lock();
+	hwmon->bat[0] = bat[0];
+	hwmon->bat[1] = bat[1];
+	hwmon->aux[0] = aux[0];
+	hwmon->aux[1] = aux[1];
+	apm_unlock();
+}
+
+static void tsc210x_temp1(struct tsc210x_hwmon *hwmon, int temp)
+{
+	apm_lock();
+	hwmon->temp[0] = temp;
+	apm_unlock();
+}
+
+static void tsc210x_temp2(struct tsc210x_hwmon *hwmon, int temp)
+{
+	apm_lock();
+	hwmon->temp[1] = temp;
+	apm_unlock();
+}
+
+#define TSC210X_INPUT(devname, field)	\
+static ssize_t tsc_show_ ## devname(struct device *dev,	\
+		struct device_attribute *devattr, char *buf)	\
+{	\
+	struct tsc210x_hwmon *hwmon = (struct tsc210x_hwmon *)	\
+		platform_get_drvdata(to_platform_device(dev));	\
+	return sprintf(buf, "%i\n", hwmon->field);	\
+}	\
+static DEVICE_ATTR(devname ## _input, S_IRUGO, tsc_show_ ## devname, NULL);
+
+TSC210X_INPUT(in0, bat[0])
+TSC210X_INPUT(in1, bat[1])
+TSC210X_INPUT(in2, aux[0])
+TSC210X_INPUT(in3, aux[1])
+TSC210X_INPUT(in4, temp[0])
+TSC210X_INPUT(in5, temp[1])
+
+static ssize_t tsc_show_temp1(struct device *dev,
+		struct device_attribute *devattr, char *buf)
+{
+	struct tsc210x_hwmon *hwmon = (struct tsc210x_hwmon *)
+		platform_get_drvdata(to_platform_device(dev));
+	int t1, t2;
+	int diff, value;
+
+	t1 = hwmon->temp[0];
+	t2 = hwmon->temp[1];
+
+	/*
+	 * Use method #2 (differential) to calculate current temperature.
+	 * The difference between TEMP2 and TEMP1 input values is
+	 * multiplied by a constant to obtain current temperature.
+	 * To find this constant we use the values measured at 25 C as
+	 * thermometer calibration data.
+	 *
+	 * 298150 is 25 degrees Celcius represented in Kelvins and
+	 * multiplied by 1000 for fixed point precision (273.15 + 25).
+	 * 273150 is zero degrees Celcius.
+	 */
+	diff = hwmon->pdata->temp_at25c[1] - hwmon->pdata->temp_at25c[0];
+	BUG_ON(diff == 0);
+	value = (t2 - t1) * 298150 / diff;	/* This is in Kelvins now */
+
+	value -= 273150;			/* Celcius millidegree */
+	return sprintf(buf, "%i\n", value);
+}
+static DEVICE_ATTR(temp1_input, S_IRUGO, tsc_show_temp1, NULL);
+
+#ifdef CONFIG_APM
+static struct tsc210x_hwmon *apm_hwmon;
+
+static void tsc210x_get_power_status(struct apm_power_info *info)
+{
+	struct tsc210x_hwmon *hwmon = apm_hwmon;
+	apm_lock();
+	hwmon->pdata->apm_report(info, hwmon->bat);
+	apm_unlock();
+}
+#endif
+
+static int tsc210x_hwmon_probe(struct platform_device *pdev)
+{
+	struct tsc210x_hwmon *hwmon;
+	struct tsc210x_config *pdata = pdev->dev.platform_data;
+	int status = 0;
+
+	hwmon = (struct tsc210x_hwmon *)
+		kzalloc(sizeof(struct tsc210x_hwmon), GFP_KERNEL);
+	if (!hwmon) {
+		printk(KERN_ERR "%s: allocation failed\n", __FUNCTION__);
+		return -ENOMEM;
+	}
+
+	hwmon->dev = hwmon_device_register(&pdev->dev);
+	if (IS_ERR(hwmon->dev)) {
+		kfree(hwmon);
+		printk(KERN_ERR "%s: Class registration failed\n",
+				__FUNCTION__);
+		return PTR_ERR(hwmon->dev);
+	}
+
+	hwmon->pdata = pdata;
+
+#ifdef CONFIG_APM
+	spin_lock_init(&hwmon->apm_lock);
+
+	if (pdata->apm_report) {
+		apm_hwmon = hwmon;
+		apm_get_power_status = tsc210x_get_power_status;
+	}
+#endif
+
+	platform_set_drvdata(pdev, hwmon);
+
+	if (pdata->monitor & (TSC_BAT1 | TSC_BAT2 | TSC_AUX1 | TSC_AUX2))
+		status |= tsc210x_ports_cb(pdev->dev.parent,
+				(tsc210x_ports_t) tsc210x_ports, hwmon);
+	if (pdata->monitor & TSC_TEMP) {
+		status |= tsc210x_temp1_cb(pdev->dev.parent,
+				(tsc210x_temp_t) tsc210x_temp1, hwmon);
+		status |= tsc210x_temp2_cb(pdev->dev.parent,
+				(tsc210x_temp_t) tsc210x_temp2, hwmon);
+	}
+
+	if (status) {
+		tsc210x_ports_cb(pdev->dev.parent, 0, 0);
+		tsc210x_temp1_cb(pdev->dev.parent, 0, 0);
+		tsc210x_temp2_cb(pdev->dev.parent, 0, 0);
+		platform_set_drvdata(pdev, 0);
+#ifdef CONFIG_APM
+		if (pdata->apm_report)
+			apm_get_power_status = 0;
+#endif
+		hwmon_device_unregister(hwmon->dev);
+		kfree(hwmon);
+		return status;
+	}
+
+	if (pdata->monitor & TSC_BAT1)
+		status |= device_create_file(&pdev->dev, &dev_attr_in0_input);
+	if (pdata->monitor & TSC_BAT2)
+		status |= device_create_file(&pdev->dev, &dev_attr_in1_input);
+	if (pdata->monitor & TSC_AUX1)
+		status |= device_create_file(&pdev->dev, &dev_attr_in2_input);
+	if (pdata->monitor & TSC_AUX2)
+		status |= device_create_file(&pdev->dev, &dev_attr_in3_input);
+	if (pdata->monitor & TSC_TEMP) {
+		status |= device_create_file(&pdev->dev, &dev_attr_in4_input);
+		status |= device_create_file(&pdev->dev, &dev_attr_in5_input);
+		status |= device_create_file(&pdev->dev, &dev_attr_temp1_input);
+	}
+	if (status)	/* Not fatal */
+		printk(KERN_ERR "%s: Creating one or more "
+				"attribute files failed\n", __FUNCTION__);
+
+	return 0;
+}
+
+static int tsc210x_hwmon_remove(struct platform_device *pdev)
+{
+	struct tsc210x_hwmon *dev = platform_get_drvdata(pdev);
+
+	tsc210x_ports_cb(pdev->dev.parent, 0, 0);
+	tsc210x_temp1_cb(pdev->dev.parent, 0, 0);
+	tsc210x_temp2_cb(pdev->dev.parent, 0, 0);
+	platform_set_drvdata(pdev, 0);
+#ifdef CONFIG_APM
+	if (dev->pdata->apm_report)
+		apm_get_power_status = 0;
+#endif
+	hwmon_device_unregister(dev->dev);
+	kfree(dev);
+	return 0;
+}
+
+static struct platform_driver tsc210x_hwmon_driver = {
+	.probe 		= tsc210x_hwmon_probe,
+	.remove 	= tsc210x_hwmon_remove,
+	/* Nothing to do on suspend/resume */
+	.driver		= {
+		.name	= "tsc210x-hwmon",
+	},
+};
+
+static int __init tsc210x_hwmon_init(void)
+{
+	return platform_driver_register(&tsc210x_hwmon_driver);
+}
+
+static void __exit tsc210x_hwmon_exit(void)
+{
+	platform_driver_unregister(&tsc210x_hwmon_driver);
+}
+
+module_init(tsc210x_hwmon_init);
+module_exit(tsc210x_hwmon_exit);
+
+MODULE_AUTHOR("Andrzej Zaborowski");
+MODULE_DESCRIPTION("hwmon driver for TI TSC210x-connected sensors.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 58fe796..f51d0e1 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -164,6 +164,20 @@ config TOUCHSCREEN_UCB1400
 	  To compile this driver as a module, choose M here: the
 	  module will be called ucb1400_ts.
 
+config TOUCHSCREEN_TSC210X
+	tristate "TSC 2101/2102 based touchscreens"
+	depends on SPI_MASTER
+	select SPI_TSC210X
+	help
+	  Say Y here if you have a touchscreen interface using a
+	  TI TSC 210x controller, and your board-specific initialisation
+	  code includes that in its table of SPI devices.
+
+	  If unsure, say N (but it's safe to say "Y").
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called tsc210x_ts.
+
 config TOUCHSCREEN_TSC2301
 	tristate "TSC2301 touchscreen support"
 	depends on SPI_TSC2301
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 5489b7e..e1eb7e6 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -19,5 +19,6 @@ obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN)	+= touchwin.o
 obj-$(CONFIG_TOUCHSCREEN_UCB1400)	+= ucb1400_ts.o
 obj-$(CONFIG_TOUCHSCREEN_TSC2102)	+= tsc2102_ts.o
 obj-$(CONFIG_TOUCHSCREEN_OMAP)	+= omap/
+obj-$(CONFIG_TOUCHSCREEN_TSC210X)	+= tsc210x_ts.o
 obj-$(CONFIG_TOUCHSCREEN_TSC2301)	+= tsc2301_ts.o
 obj-$(CONFIG_TOUCHSCREEN_TSC2046)	+= tsc2046_ts.o
diff --git a/drivers/input/touchscreen/tsc210x_ts.c b/drivers/input/touchscreen/tsc210x_ts.c
new file mode 100644
index 0000000..09c6937
--- /dev/null
+++ b/drivers/input/touchscreen/tsc210x_ts.c
@@ -0,0 +1,153 @@
+/*
+ * input/touchscreen/tsc210x_ts.c
+ *
+ * Touchscreen input device driver for the TSC 2101/2102 chips.
+ *
+ * Copyright (c) 2006-2007 Andrzej Zaborowski  <balrog@zabor.org>
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+
+#include <linux/spi/tsc210x.h>
+
+static void tsc210x_touch(struct input_dev *dev, int touching)
+{
+	if (!touching) {
+		input_report_abs(dev, ABS_X, 0);
+		input_report_abs(dev, ABS_Y, 0);
+		input_report_abs(dev, ABS_PRESSURE, 0);
+		input_sync(dev);
+	}
+
+	input_report_key(dev, BTN_TOUCH, touching);
+}
+
+static void tsc210x_coords(struct input_dev *dev, int x, int y, int z1, int z2)
+{
+	int p;
+
+	/* Calculate the touch resistance a la equation #1 */
+	if (z1 != 0)
+		p = x * (z2 - z1) / (z1 << 4);
+	else
+		p = 1;
+
+	input_report_abs(dev, ABS_X, x);
+	input_report_abs(dev, ABS_Y, y);
+	input_report_abs(dev, ABS_PRESSURE, p);
+	input_sync(dev);
+}
+
+static int tsc210x_ts_probe(struct platform_device *pdev)
+{
+	int status;
+	struct input_dev *dev;
+
+	dev = input_allocate_device();
+	if (!dev)
+		return -ENOMEM;
+
+	status = tsc210x_touch_cb(pdev->dev.parent,
+			(tsc210x_touch_t) tsc210x_touch, dev);
+	if (status) {
+		input_free_device(dev);
+		return status;
+	}
+
+	status = tsc210x_coords_cb(pdev->dev.parent,
+			(tsc210x_coords_t) tsc210x_coords, dev);
+	if (status) {
+		tsc210x_touch_cb(pdev->dev.parent, 0, 0);
+		input_free_device(dev);
+		return status;
+	}
+
+	dev->name = "TSC210x Touchscreen";
+	dev->cdev.dev = &pdev->dev;
+	dev->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS);
+	dev->keybit[LONG(BTN_TOUCH)] |= BIT(BTN_TOUCH);
+	dev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE);
+	dev->phys = "tsc210x/input0";
+	dev->id.bustype = BUS_HOST;
+	dev->id.vendor = 0x0001;
+	dev->id.product = 0x2100;
+	dev->id.version = 0x0001;
+
+	status = input_register_device(dev);
+	if (status) {
+		tsc210x_coords_cb(pdev->dev.parent, 0, 0);
+		tsc210x_touch_cb(pdev->dev.parent, 0, 0);
+		input_free_device(dev);
+		return status;
+	}
+
+	platform_set_drvdata(pdev, dev);
+	printk(KERN_INFO "TSC210x touchscreen initialised\n");
+	return 0;
+}
+
+static int tsc210x_ts_remove(struct platform_device *pdev)
+{
+	struct input_dev *dev = (struct input_dev *)
+		platform_get_drvdata(pdev);
+
+	tsc210x_touch_cb(pdev->dev.parent, 0, 0);
+	tsc210x_coords_cb(pdev->dev.parent, 0, 0);
+	platform_set_drvdata(pdev, 0);
+	input_unregister_device(dev);
+	input_free_device(dev);
+
+	return 0;
+}
+
+static struct platform_driver tsc210x_ts_driver = {
+	.probe 		= tsc210x_ts_probe,
+	.remove 	= tsc210x_ts_remove,
+	/* Nothing to do on suspend/resume */
+	.driver		= {
+		.name	= "tsc210x-ts",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init tsc210x_ts_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&tsc210x_ts_driver);
+	if (ret)
+		return -ENODEV;
+
+	return 0;
+}
+
+static void __exit tsc210x_ts_exit(void)
+{
+	platform_driver_unregister(&tsc210x_ts_driver);
+}
+
+module_init(tsc210x_ts_init);
+module_exit(tsc210x_ts_exit);
+
+MODULE_AUTHOR("Andrzej Zaborowski");
+MODULE_DESCRIPTION("Touchscreen input driver for TI TSC2101/2102.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 6fd6a47..dfe8018 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -197,7 +197,19 @@ config SPI_TSC2102
        ---help---
          Say Y here if you want support for the TSC2102 chip.  It
 	 will be needed for the touchscreen driver on some boards.
-	 
+
+config SPI_TSC210X
+	depends on SPI_MASTER && EXPERIMENTAL
+	tristate "TSC2101/TSC2102 chips support"
+	help
+	  Say Y here if you want support for the TSC210x chips.  It
+	  will be needed for the touchscreen driver on some boards.
+
+	  Note that the device has to be present in the board's SPI
+	  devices table for this driver to load.  This driver doesn't
+	  automatically enable touchscreen, sensors or audio
+	  functionality - enable these in their respective menus.
+
 config SPI_TSC2301
 	tristate "TSC2301 driver"
 	depends on SPI_MASTER
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index bbb3ca9..f5dabed 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_SPI_AT25)		+= at25.o
 obj-$(CONFIG_SPI_SPIDEV)	+= spidev.o
 obj-$(CONFIG_SPI_TSC2101)	+= tsc2101.o
 obj-$(CONFIG_SPI_TSC2102)	+= tsc2102.o
+obj-$(CONFIG_SPI_TSC210X)	+= tsc210x.o
 obj-$(CONFIG_SPI_TSC2301)	+= tsc2301.o
 tsc2301-objs			:= tsc2301-core.o
 tsc2301-$(CONFIG_SPI_TSC2301_AUDIO)	+= tsc2301-mixer.o
diff --git a/drivers/spi/tsc210x.c b/drivers/spi/tsc210x.c
new file mode 100644
index 0000000..6ecf7cf
--- /dev/null
+++ b/drivers/spi/tsc210x.c
@@ -0,0 +1,1181 @@
+/*
+ * drivers/spi/tsc210x.c
+ *
+ * TSC2101/2102 interface driver.
+ *
+ * Copyright (c) 2005-2007 Andrzej Zaborowski  <balrog@zabor.org>
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/suspend.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/completion.h>
+#include <linux/autoconf.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/tsc210x.h>
+
+/* Bit field definitions for chip registers */
+
+/* Scan X, Y, Z1, Z2, chip controlled, 12-bit, 16 samples, 500 usec */
+#define TSC210X_ADC_TS_CONTROL		0x8bf4
+/* Scan BAT1, BAT2, AUX1, AUX2, 12-bit, 16 samples, 500 usec */
+#define TSC210X_ADC_SCAN_CONTROL	0x2ff4
+/* Scan TEMP1, 12-bit, 16 samples, 500 usec */
+#define TSC210X_ADC_T1_CONTROL		0x2bf4
+/* Scan TEMP2, 12-bit, 16 samples, 500 usec */
+#define TSC210X_ADC_T2_CONTROL		0x33f4
+/* PINT/DAV acts as DAV */
+#define TSC210X_ADC_DAV			0x4000
+/* Internal reference, 100 usec delay, 1.25 V reference */
+#define TSC210X_ADC_INT_REF		0x0016
+/* External reference, 100 usec delay, 1.25 V reference */
+#define TSC210X_ADC_EXT_REF		0x0002
+/* 84 usec precharge time, 32 usec sense time */
+#define TSC210X_CONFIG_TIMES		0x0008
+/* The reset sequence */
+#define TSC210X_RESET			0xbb00
+/* Pen Status bit */
+#define TSC210X_ADC_PSTCM		(1 << 15)
+/* A/D Status bit */
+#define TSC210X_ADC_ADST		(1 << 14)
+/* (At least) One of X, Y, Z1, Z2 contains data */
+#define TSC210X_TS_DAV			0x0780
+/* (At least) One of BAT1, BAT2, AUX1, AUX2 contains data */
+#define TSC210X_PS_DAV			0x0078
+/* TEMP1 contains data */
+#define TSC210X_T1_DAV			0x0004
+/* TEMP2 contains data */
+#define TSC210X_T2_DAV			0x0002
+#define TSC2101_DAC_ON			0x0000
+#define TSC2101_DAC_OFF			0xe7fc
+#define TSC2102_DAC_ON			0x3ba0
+#define TSC2102_DAC_OFF			0xafa0
+#define TSC210X_FS44K			(1 << 13)
+#define TSC210X_PLL1_OFF		0x0000
+#define TSC210X_PLL1_44K		0x811c
+#define TSC210X_PLL1_48K		0x8120
+#define TSC210X_PLL2_44K		(5462 << 2)
+#define TSC210X_PLL2_48K		(1920 << 2)
+#define TSC210X_SLVMS			(1 << 11)
+#define TSC210X_DEEMPF			(1 << 0)
+#define TSC2102_BASSBC			(1 << 1)
+#define TSC210X_KEYCLICK_OFF		0x0000
+
+#define CS_CHANGE(val)			0
+
+struct tsc210x_spi_req {
+	struct spi_device *dev;
+	uint16_t command, data;
+	struct spi_message message;
+};
+
+struct tsc210x_dev {
+	enum tsc_type {
+		tsc2101,
+		tsc2102,
+	} kind;
+	struct tsc210x_config *pdata;
+	struct clk *bclk_ck;
+
+	struct workqueue_struct *queue;
+	struct delayed_work ts_worker;		/* Poll-wait for PEN UP */
+	struct delayed_work sensor_worker;	/* Scan the ADC inputs */
+	spinlock_t queue_lock;
+	struct completion data_avail;
+	tsc210x_touch_t touch_cb;
+	void *touch_cb_ctx;
+	tsc210x_coords_t coords_cb;
+	void *coords_cb_ctx;
+	tsc210x_ports_t ports_cb;
+	void *ports_cb_ctx;
+	tsc210x_temp_t temp1_cb;
+	void *temp2_cb_ctx;
+	tsc210x_temp_t temp2_cb;
+	void *temp1_cb_ctx;
+
+	struct spi_device *spi;
+	struct spi_transfer *transfers;
+	struct tsc210x_spi_req req_adc;
+	struct tsc210x_spi_req req_status;
+	struct tsc210x_spi_req req_mode;
+	struct tsc210x_spi_req req_stop;
+
+	int pendown;
+	int flushing;			/* Queue flush in progress */
+	uint16_t status, adc_data[4];
+	int bat[2], aux[2], temp[2];
+};
+
+static struct {
+	unsigned int ts_msecs;		/* Interval for .ts_timer */
+	unsigned int mode_msecs;	/* Interval for .mode_timer */
+} settings;
+
+module_param_named(touch_check_msecs, settings.ts_msecs, uint, 0);
+MODULE_PARM_DESC(touch_check_msecs, "Pen-up polling interval in msecs");
+
+module_param_named(sensor_scan_msecs, settings.mode_msecs, uint, 0);
+MODULE_PARM_DESC(sensor_scan_msecs, "Temperature & battery scan interval");
+
+void tsc210x_write_sync(struct tsc210x_dev *dev,
+		int page, u8 address, u16 data)
+{
+	static struct tsc210x_spi_req req;
+	static struct spi_transfer transfer[2];
+	int ret;
+
+	spi_message_init(&req.message);
+
+	/* Address */
+	req.command = (page << 11) | (address << 5);
+	transfer[0].tx_buf = &req.command;
+	transfer[0].rx_buf = 0;
+	transfer[0].len = 2;
+	spi_message_add_tail(&transfer[0], &req.message);
+
+	/* Data */
+	transfer[1].tx_buf = &data;
+	transfer[1].rx_buf = 0;
+	transfer[1].len = 2;
+	transfer[1].cs_change = CS_CHANGE(1);
+	spi_message_add_tail(&transfer[1], &req.message);
+
+	ret = spi_sync(dev->spi, &req.message);
+	if (!ret && req.message.status)
+		ret = req.message.status;
+
+	if (ret)
+		printk(KERN_ERR "%s: error %i in SPI request\n",
+				__FUNCTION__, ret);
+}
+
+void tsc210x_reads_sync(struct tsc210x_dev *dev,
+		int page, u8 startaddress, u16 *data, int numregs)
+{
+	static struct tsc210x_spi_req req;
+	static struct spi_transfer transfer[6];
+	int ret, i, j;
+
+	BUG_ON(numregs + 1 > ARRAY_SIZE(transfer));
+
+	spi_message_init(&req.message);
+	i = 0;
+	j = 0;
+
+	/* Address */
+	req.command = 0x8000 | (page << 11) | (startaddress << 5);
+	transfer[i].tx_buf = &req.command;
+	transfer[i].rx_buf = 0;
+	transfer[i].len = 2;
+	spi_message_add_tail(&transfer[i ++], &req.message);
+
+	/* Data */
+	while (j < numregs) {
+		transfer[i].tx_buf = 0;
+		transfer[i].rx_buf = &data[j ++];
+		transfer[i].len = 2;
+		transfer[i].cs_change = CS_CHANGE(j == numregs);
+		spi_message_add_tail(&transfer[i ++], &req.message);
+	}
+
+	ret = spi_sync(dev->spi, &req.message);
+	if (!ret && req.message.status)
+		ret = req.message.status;
+
+	if (ret)
+		printk(KERN_ERR "%s: error %i in SPI request\n",
+				__FUNCTION__, ret);
+}
+
+uint16_t tsc210x_read_sync(struct tsc210x_dev *dev, int page, uint8_t address)
+{
+	uint16_t ret;
+	tsc210x_reads_sync(dev, page, address, &ret, 1);
+	return ret;
+}
+
+static void tsc210x_submit_async(struct tsc210x_spi_req *spi)
+{
+	int ret;
+
+	ret = spi_async(spi->dev, &spi->message);
+	if (ret)
+		printk(KERN_ERR "%s: error %i in SPI request\n",
+				__FUNCTION__, ret);
+}
+
+static void tsc210x_request_alloc(struct tsc210x_dev *dev,
+		struct tsc210x_spi_req *spi, int direction,
+		int page, u8 startaddress, int numregs, uint16_t *data,
+		void (*complete)(struct tsc210x_dev *context),
+		struct spi_transfer **transfer)
+{
+	spi->dev = dev->spi;
+
+	if (direction == 1)	/* Write */
+		numregs = 2;
+	else			/* Read */
+		numregs += 1;
+
+	spi_message_init(&spi->message);
+	spi->message.complete = (void (*)(void *)) complete;
+	spi->message.context = dev;
+
+	/* Address */
+	spi->command = (page << 11) | (startaddress << 5);
+	if (direction != 1)
+		spi->command |= 1 << 15;
+
+	(*transfer)->tx_buf = &spi->command;
+	(*transfer)->rx_buf = 0;
+	(*transfer)->len = 2;
+	spi_message_add_tail((*transfer) ++, &spi->message);
+
+	/* Data */
+	while (-- numregs) {
+		if (direction == 1)
+			(*transfer)->tx_buf = &spi->data;
+		else
+			(*transfer)->rx_buf = data ++;
+		(*transfer)->len = 2;
+		(*transfer)->cs_change = CS_CHANGE(numregs != 1);
+		spi_message_add_tail((*transfer) ++, &spi->message);
+	}
+}
+
+#define tsc210x_cb_register_func(cb, cb_t)	\
+int tsc210x_ ## cb(struct device *dev, cb_t handler, void *context)	\
+{	\
+	struct tsc210x_dev *tsc = (struct tsc210x_dev *)	\
+		platform_get_drvdata(to_platform_device(dev));	\
+	\
+	/* Lock the module */	\
+	if (handler && !tsc->cb)	\
+		if (!try_module_get(THIS_MODULE)) {	\
+			printk(KERN_INFO "Failed to get TSC module\n");	\
+		}	\
+	if (!handler && tsc->cb)	\
+		module_put(THIS_MODULE);	\
+	\
+	tsc->cb = handler;	\
+	tsc->cb ## _ctx = context;	\
+	return 0;	\
+}
+
+tsc210x_cb_register_func(touch_cb, tsc210x_touch_t)
+tsc210x_cb_register_func(coords_cb, tsc210x_coords_t)
+tsc210x_cb_register_func(ports_cb, tsc210x_ports_t)
+tsc210x_cb_register_func(temp1_cb, tsc210x_temp_t)
+tsc210x_cb_register_func(temp2_cb, tsc210x_temp_t)
+
+#ifdef DEBUG
+static void tsc210x_print_dav(void)
+{
+	u16 status = tsc210x_read_sync(dev, TSC210X_TS_STATUS_CTRL);
+	if (status & 0x0fff)
+		printk("TSC210x: data in");
+	if (status & 0x0400)
+		printk(" X");
+	if (status & 0x0200)
+		printk(" Y");
+	if (status & 0x0100)
+		printk(" Z1");
+	if (status & 0x0080)
+		printk(" Z2");
+	if (status & 0x0040)
+		printk(" BAT1");
+	if (status & 0x0020)
+		printk(" BAT2");
+	if (status & 0x0010)
+		printk(" AUX1");
+	if (status & 0x0008)
+		printk(" AUX2");
+	if (status & 0x0004)
+		printk(" TEMP1");
+	if (status & 0x0002)
+		printk(" TEMP2");
+	if (status & 0x0001)
+		printk(" KP");
+	if (status & 0x0fff)
+		printk(".\n");
+}
+#endif
+
+static void tsc210x_complete_dummy(struct tsc210x_dev *dev)
+{
+}
+
+static inline void tsc210x_touchscreen_mode(struct tsc210x_dev *dev)
+{
+	/* Scan X, Y, Z1, Z2, chip controlled, 12-bit, 16 samples, 500 usec */
+	dev->req_mode.data = TSC210X_ADC_TS_CONTROL;
+	tsc210x_submit_async(&dev->req_mode);
+}
+
+static inline void tsc210x_portscan_mode(struct tsc210x_dev *dev)
+{
+	/* Scan BAT1, BAT2, AUX1, AUX2, 12-bit, 16 samples, 500 usec */
+	dev->req_mode.data = TSC210X_ADC_SCAN_CONTROL;
+	tsc210x_submit_async(&dev->req_mode);
+}
+
+static inline void tsc210x_temp1_mode(struct tsc210x_dev *dev)
+{
+	/* Scan TEMP1, 12-bit, 16 samples, 500 usec */
+	dev->req_mode.data = TSC210X_ADC_T1_CONTROL;
+	tsc210x_submit_async(&dev->req_mode);
+}
+
+static inline void tsc210x_temp2_mode(struct tsc210x_dev *dev)
+{
+	/* Scan TEMP2, 12-bit, 16 samples, 500 usec */
+	dev->req_mode.data = TSC210X_ADC_T2_CONTROL;
+	tsc210x_submit_async(&dev->req_mode);
+}
+
+/* Abort current conversion if any */
+static void tsc210x_new_mode(struct tsc210x_dev *dev)
+{
+	dev->req_stop.data = TSC210X_ADC_ADST;
+	tsc210x_submit_async(&dev->req_stop);
+}
+
+static void tsc210x_queue_scan(struct tsc210x_dev *dev)
+{
+	if (dev->pdata->monitor)
+		if (!queue_delayed_work(dev->queue,
+					&dev->sensor_worker,
+					msecs_to_jiffies(settings.mode_msecs)))
+			printk(KERN_ERR "%s: can't queue measurements\n",
+					__FUNCTION__);
+}
+
+static void tsc210x_queue_penup(struct tsc210x_dev *dev)
+{
+	if (!queue_delayed_work(dev->queue,
+				&dev->ts_worker,
+				msecs_to_jiffies(settings.ts_msecs)))
+		printk(KERN_ERR "%s: can't queue pen-up poll\n",
+				__FUNCTION__);
+}
+
+static void tsc210x_status_report(struct tsc210x_dev *dev)
+{
+	/*
+	 * Read all converted data from corresponding registers
+	 * so that the ADC can move on to a new conversion.
+	 */
+	if (dev->status & TSC210X_TS_DAV) {
+		if (!dev->pendown && !dev->flushing) {
+			dev->pendown = 1;
+			if (dev->touch_cb)
+				dev->touch_cb(dev->touch_cb_ctx, 1);
+
+			tsc210x_queue_penup(dev);
+		}
+
+		tsc210x_submit_async(&dev->req_adc);
+	}
+
+	if (dev->status & (TSC210X_PS_DAV | TSC210X_T1_DAV |TSC210X_T2_DAV))
+		complete(&dev->data_avail);
+}
+
+static void tsc210x_data_report(struct tsc210x_dev *dev)
+{
+	uint16_t adc_data[4];
+
+	if (dev->status & TSC210X_PS_DAV) {
+		tsc210x_reads_sync(dev, TSC210X_TS_BAT1, adc_data, 4);
+
+		dev->bat[0] = adc_data[0];
+		dev->bat[1] = adc_data[1];
+		dev->aux[0] = adc_data[2];
+		dev->aux[1] = adc_data[3];
+		if (dev->ports_cb)
+			dev->ports_cb(dev->ports_cb_ctx, dev->bat, dev->aux);
+	}
+
+	if (dev->status & TSC210X_T1_DAV) {
+		dev->temp[0] = tsc210x_read_sync(dev, TSC210X_TS_TEMP1);
+
+		if (dev->temp1_cb)
+			dev->temp1_cb(dev->temp1_cb_ctx, dev->temp[0]);
+	}
+
+	if (dev->status & TSC210X_T2_DAV) {
+		dev->temp[1] = tsc210x_read_sync(dev, TSC210X_TS_TEMP2);
+
+		if (dev->temp2_cb)
+			dev->temp2_cb(dev->temp2_cb_ctx, dev->temp[1]);
+	}
+}
+
+static void tsc210x_coords_report(struct tsc210x_dev *dev)
+{
+	if (dev->coords_cb)
+		dev->coords_cb(dev->coords_cb_ctx,
+				dev->adc_data[0], dev->adc_data[1],
+				dev->adc_data[2], dev->adc_data[3]);
+}
+
+/*
+ * There are at least three ways to check for pen-up:
+ *	- the PINT/DAV pin state,
+ *	- reading PSTCM bit in ADC Control register (D15, offset 0x00),
+ *	- reading ADST bit in ADC Control register (D14, offset 0x00),
+ *		ADC idle would indicate no screen touch.
+ * Unfortunately none of them seems to be 100% accurate and you will
+ * find they are totally inconsistent, i.e. you get to see any arbitrary
+ * combination of values in these three bits.  So we will busy-wait
+ * for a moment when the latter two indicate a pen-up, using a queue,
+ * before we report a pen-up.
+ */
+static void tsc210x_pressure(struct work_struct *work)
+{
+	struct tsc210x_dev *dev =
+		container_of(work, struct tsc210x_dev, ts_worker.work);
+	uint16_t adc_status;
+
+	BUG_ON(!dev->pendown);
+
+	adc_status = tsc210x_read_sync(dev, TSC210X_TS_ADC_CTRL);
+
+	if ((adc_status & TSC210X_ADC_PSTCM) ||
+			!(adc_status & TSC210X_ADC_ADST)) {
+		tsc210x_queue_penup(dev);
+	} else {
+		dev->pendown = 0;
+		if (dev->touch_cb)
+			dev->touch_cb(dev->touch_cb_ctx, 0);
+	}
+}
+
+static void tsc210x_wait_data(struct tsc210x_dev *dev)
+{
+	wait_for_completion(&dev->data_avail);
+
+	tsc210x_data_report(dev);
+}
+
+static void tsc210x_input_scan(struct work_struct *work)
+{
+	struct tsc210x_dev *dev = (struct tsc210x_dev *)
+		container_of(work, struct tsc210x_dev, sensor_worker.work);
+
+	tsc210x_new_mode(dev);
+
+	if (dev->pdata->monitor &
+			(TSC_BAT1 | TSC_BAT2 | TSC_AUX1 | TSC_AUX2)) {
+		tsc210x_portscan_mode(dev);
+		tsc210x_wait_data(dev);
+	}
+
+	if (dev->pdata->monitor & TSC_TEMP) {
+		tsc210x_temp1_mode(dev);
+		tsc210x_wait_data(dev);
+
+		tsc210x_temp2_mode(dev);
+		tsc210x_wait_data(dev);
+	}
+
+	tsc210x_touchscreen_mode(dev);
+
+	spin_lock(&dev->queue_lock);
+	if (!dev->flushing)
+		tsc210x_queue_scan(dev);
+	spin_unlock(&dev->queue_lock);
+}
+
+/* ADC has finished a new conversion for us.  */
+static irqreturn_t tsc210x_handler(int irq, void *dev_id)
+{
+	struct tsc210x_dev *dev = (struct tsc210x_dev *) dev_id;
+
+	/* See what data became available.  */
+	tsc210x_submit_async(&dev->req_status);
+
+	return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_SOUND
+/*
+ * Volume level values should be in the range [0, 127].
+ * Higher values mean lower volume.
+ */
+void tsc210x_set_dac_volume(struct device *dev,
+		uint8_t left_ch, uint8_t right_ch)
+{
+	struct tsc210x_dev *tsc = (struct tsc210x_dev *)
+		platform_get_drvdata(to_platform_device(dev));
+	u16 val;
+	if (tsc->kind == tsc2102) {
+		/* All 0's or all 1's */
+		if (left_ch == 0x00 || left_ch == 0x7f)
+			left_ch ^= 0x7f;
+		if (right_ch == 0x00 || right_ch == 0x7f)
+			right_ch ^= 0x7f;
+	}
+
+	val = tsc210x_read_sync(tsc, TSC210X_DAC_GAIN_CTRL);
+
+	val &= 0x8080;	/* Preserve mute-bits */
+	val |= (left_ch << 8) | right_ch;
+
+	tsc210x_write_sync(tsc, TSC210X_DAC_GAIN_CTRL, val);
+}
+
+void tsc210x_set_dac_mute(struct device *dev, int left_ch, int right_ch)
+{
+	struct tsc210x_dev *tsc = (struct tsc210x_dev *)
+		platform_get_drvdata(to_platform_device(dev));
+	u16 val;
+
+	val = tsc210x_read_sync(tsc, TSC210X_DAC_GAIN_CTRL);
+
+	val &= 0x7f7f;	/* Preserve volume settings */
+	val |= (left_ch << 15) | (right_ch << 7);
+
+	tsc210x_write_sync(tsc, TSC210X_DAC_GAIN_CTRL, val);
+}
+
+void tsc210x_get_dac_mute(struct device *dev, int *left_ch, int *right_ch)
+{
+	struct tsc210x_dev *tsc = (struct tsc210x_dev *)
+		platform_get_drvdata(to_platform_device(dev));
+	u16 val;
+
+	val = tsc210x_read_sync(tsc, TSC210X_DAC_GAIN_CTRL);
+
+	*left_ch = !!(val & (1 << 15));
+	*right_ch = !!(val & (1 << 7));
+}
+
+void tsc210x_set_deemphasis(struct device *dev, int enable)
+{
+	struct tsc210x_dev *tsc = (struct tsc210x_dev *)
+		platform_get_drvdata(to_platform_device(dev));
+	u16 val;
+	val = tsc210x_read_sync(tsc, TSC210X_POWER_CTRL);
+
+	if (enable)
+		val &= ~TSC210X_DEEMPF;
+	else
+		val |= TSC210X_DEEMPF;
+
+	tsc210x_write_sync(tsc, TSC210X_POWER_CTRL, val);
+}
+
+void tsc2102_set_bassboost(struct device *dev, int enable)
+{
+	struct tsc210x_dev *tsc = (struct tsc210x_dev *)
+		platform_get_drvdata(to_platform_device(dev));
+	u16 val;
+	val = tsc210x_read_sync(tsc, TSC210X_POWER_CTRL);
+
+	if (enable)
+		val &= ~TSC2102_BASSBC;
+	else
+		val |= TSC2102_BASSBC;
+
+	tsc210x_write_sync(tsc, TSC210X_POWER_CTRL, val);
+}
+
+/*	{rate, dsor, fsref}	*/
+static const struct tsc210x_rate_info_s tsc2101_rates[] = {
+	/* Fsref / 6.0 */
+	{7350,	7,	1},
+	{8000,	7,	0},
+	/* Fsref / 5.5 */
+	{8018,	6,	1},
+	{8727,	6,	0},
+	/* Fsref / 5.0 */
+	{8820,	5,	1},
+	{9600,	5,	0},
+	/* Fsref / 4.0 */
+	{11025,	4,	1},
+	{12000,	4,	0},
+	/* Fsref / 3.0 */
+	{14700,	3,	1},
+	{16000,	3,	0},
+	/* Fsref / 2.0 */
+	{22050,	2,	1},
+	{24000,	2,	0},
+	/* Fsref / 1.5 */
+	{29400,	1,	1},
+	{32000,	1,	0},
+	/* Fsref */
+	{44100,	0,	1},
+	{48000,	0,	0},
+
+	{0,	0, 	0},
+};
+
+/*	{rate, dsor, fsref}	*/
+static const struct tsc210x_rate_info_s tsc2102_rates[] = {
+	/* Fsref / 6.0 */
+	{7350,	63,	1},
+	{8000,	63,	0},
+	/* Fsref / 6.0 */
+	{7350,	54,	1},
+	{8000,	54,	0},
+	/* Fsref / 5.0 */
+	{8820,	45,	1},
+	{9600,	45,	0},
+	/* Fsref / 4.0 */
+	{11025,	36,	1},
+	{12000,	36,	0},
+	/* Fsref / 3.0 */
+	{14700,	27,	1},
+	{16000,	27,	0},
+	/* Fsref / 2.0 */
+	{22050,	18,	1},
+	{24000,	18,	0},
+	/* Fsref / 1.5 */
+	{29400,	9,	1},
+	{32000,	9,	0},
+	/* Fsref */
+	{44100,	0,	1},
+	{48000,	0,	0},
+
+	{0,	0, 	0},
+};
+
+int tsc210x_set_rate(struct device *dev, int rate)
+{
+	struct tsc210x_dev *tsc = (struct tsc210x_dev *)
+		platform_get_drvdata(to_platform_device(dev));
+	int i;
+	uint16_t val;
+	const struct tsc210x_rate_info_s *rates;
+
+	if (tsc->kind == tsc2101)
+		rates = tsc2101_rates;
+	else
+		rates = tsc2102_rates;
+
+	for (i = 0; rates[i].sample_rate; i ++)
+		if (rates[i].sample_rate == rate)
+			break;
+	if (rates[i].sample_rate == 0) {
+		printk(KERN_ERR "Unknown sampling rate %i.0 Hz\n", rate);
+		return -EINVAL;
+	}
+
+	if (tsc->kind == tsc2101) {
+		val = tsc210x_read_sync(tsc, TSC210X_AUDIO1_CTRL) &
+			~((7 << 3) | (7 << 0));
+		val |= rates[i].divisor << 3;
+		val |= rates[i].divisor << 0;
+	} else
+		val = rates[i].divisor;
+
+	tsc210x_write_sync(tsc, TSC210X_AUDIO1_CTRL, val);
+
+	val = tsc210x_read_sync(tsc, TSC210X_AUDIO3_CTRL);
+
+	if (tsc2102_rates[i].fs_44k) {
+		tsc210x_write_sync(tsc,
+				TSC210X_AUDIO3_CTRL, val | TSC210X_FS44K);
+		/* Enable Phase-locked-loop, set up clock dividers */
+		tsc210x_write_sync(tsc, TSC210X_PLL1_CTRL, TSC210X_PLL1_44K);
+		tsc210x_write_sync(tsc, TSC210X_PLL2_CTRL, TSC210X_PLL2_44K);
+	} else {
+		tsc210x_write_sync(tsc,
+				TSC210X_AUDIO3_CTRL, val & ~TSC210X_FS44K);
+		/* Enable Phase-locked-loop, set up clock dividers */
+		tsc210x_write_sync(tsc, TSC210X_PLL1_CTRL, TSC210X_PLL1_48K);
+		tsc210x_write_sync(tsc, TSC210X_PLL2_CTRL, TSC210X_PLL2_48K);
+	}
+
+	return 0;
+}
+
+/*
+ * Perform basic set-up with default values and power the DAC/ADC on.
+ */
+void tsc210x_dac_power(struct device *dev, int on)
+{
+	struct tsc210x_dev *tsc = (struct tsc210x_dev *)
+		platform_get_drvdata(to_platform_device(dev));
+
+	if (on) {
+		/* 16-bit words, DSP mode, sample at Fsref */
+		tsc210x_write_sync(tsc,
+				TSC210X_AUDIO1_CTRL, 0x0100);
+		/* Keyclicks off, soft-stepping at normal rate */
+		tsc210x_write_sync(tsc,
+				TSC210X_AUDIO2_CTRL, TSC210X_KEYCLICK_OFF);
+		/* 44.1 kHz Fsref, continuous transfer mode, master DAC */
+		tsc210x_write_sync(tsc,
+				TSC210X_AUDIO3_CTRL, 0x2000);
+		/* Soft-stepping enabled, 1 dB MIX AGC hysteresis */
+		tsc210x_write_sync(tsc,
+				TSC210X_AUDIO4_CTRL, 0x0000);
+
+		/* PLL generates 44.1 kHz */
+		tsc210x_write_sync(tsc,
+				TSC210X_PLL1_CTRL, TSC210X_PLL1_44K);
+		tsc210x_write_sync(tsc,
+				TSC210X_PLL2_CTRL, TSC210X_PLL2_44K);
+
+		/* Codec & DAC power up, virtual ground disabled */
+		tsc210x_write_sync(tsc,
+				TSC210X_POWER_CTRL, (tsc->kind == tsc2101) ?
+				TSC2101_DAC_ON : TSC2102_DAC_ON);
+	} else {
+		/* All off */
+		tsc210x_write_sync(tsc,
+				TSC210X_AUDIO2_CTRL, TSC210X_KEYCLICK_OFF);
+		tsc210x_write_sync(tsc,
+				TSC210X_PLL1_CTRL, TSC210X_PLL1_OFF);
+#if 0
+		tsc210x_write_sync(tsc,
+				TSC210X_POWER_CTRL, (tsc->kind == tsc2101) ?
+				TSC2102_DAC_OFF : TSC2102_DAC_OFF);
+#endif
+	}
+}
+
+void tsc210x_set_i2s_master(struct device *dev, int state)
+{
+	struct tsc210x_dev *tsc = (struct tsc210x_dev *)
+		platform_get_drvdata(to_platform_device(dev));
+	uint16_t val;
+
+	val = tsc210x_read_sync(tsc, TSC210X_AUDIO3_CTRL);
+
+	if (state)
+		tsc210x_write_sync(tsc, TSC210X_AUDIO3_CTRL,
+				val | TSC210X_SLVMS);
+	else
+		tsc210x_write_sync(tsc, TSC210X_AUDIO3_CTRL,
+				val & ~TSC210X_SLVMS);
+}
+#endif	/* CONFIG_SOUND */
+
+static int tsc210x_configure(struct tsc210x_dev *dev)
+{
+	/* Reset the chip */
+	tsc210x_write_sync(dev, TSC210X_TS_RESET_CTRL, TSC210X_RESET);
+
+	/* Reference mode */
+	if (dev->pdata->use_internal)
+		tsc210x_write_sync(dev,
+				TSC210X_TS_REF_CTRL, TSC210X_ADC_INT_REF);
+	else
+		tsc210x_write_sync(dev,
+				TSC210X_TS_REF_CTRL, TSC210X_ADC_EXT_REF);
+
+	/* Precharge and sense delays, pen touch detection on */
+	tsc210x_write_sync(dev, TSC210X_TS_CONFIG_CTRL, TSC210X_CONFIG_TIMES);
+
+	/* PINT/DAV acts as DAV */
+	tsc210x_write_sync(dev, TSC210X_TS_STATUS_CTRL, TSC210X_ADC_DAV);
+
+	tsc210x_queue_scan(dev);
+	return 0;
+}
+
+/*
+ * Retrieves chip revision.  Should be always 1.
+ */
+int tsc210x_get_revision(struct tsc210x_dev *dev)
+{
+	return tsc210x_read_sync(dev, TSC210X_AUDIO3_CTRL) & 7;
+}
+
+void tsc210x_keyclick(struct tsc210x_dev *dev,
+		int amplitude, int freq, int length)
+{
+	u16 val;
+	val = tsc210x_read_sync(dev, TSC210X_AUDIO2_CTRL);
+	val &= 0x800f;
+
+	/* Set amplitude */
+	switch (amplitude) {
+	case 1:
+		val |= 4 << 12;
+		break;
+	case 2:
+		val |= 7 << 12;
+		break;
+	default:
+		break;
+	}
+
+	/* Frequency */
+	val |= (freq & 0x7) << 8;
+
+	/* Round to nearest supported length */
+	if (dev->kind == tsc2101)
+		val = (min(length - 1, 31) >> 1) << 4;
+	else {
+		if (length > 20)
+			val |= 4 << 4;
+		else if (length > 6)
+			val |= 3 << 4;
+		else if (length > 4)
+			val |= 2 << 4;
+		else if (length > 2)
+			val |= 1 << 4;
+	}
+
+	/* Enable keyclick */
+	val |= 0x8000;
+
+	tsc210x_write_sync(dev, TSC210X_AUDIO2_CTRL, val);
+}
+
+#ifdef CONFIG_PM
+/*
+ * Suspend the chip.
+ */
+static int
+tsc210x_suspend(struct spi_device *spi, pm_message_t state)
+{
+	struct tsc210x_dev *dev = dev_get_drvdata(&spi->dev);
+
+	if (!dev)
+		return -ENODEV;
+
+	/* Stop the inputs scan loop */
+	spin_lock(&dev->queue_lock);
+	dev->flushing = 1;
+	cancel_delayed_work(&dev->sensor_worker);
+	spin_unlock(&dev->queue_lock);
+	flush_workqueue(dev->queue);
+
+	/* Wait until pen-up happens */
+	while (dev->pendown)
+		flush_workqueue(dev->queue);
+
+	/* Abort current conversion and power down the ADC */
+	tsc210x_write_sync(dev, TSC210X_TS_ADC_CTRL, TSC210X_ADC_ADST);
+
+	dev->spi->dev.power.power_state = state;
+
+	return 0;
+}
+
+/*
+ * Resume chip operation.
+ */
+static int tsc210x_resume(struct spi_device *spi)
+{
+	struct tsc210x_dev *dev = dev_get_drvdata(&spi->dev);
+	int err;
+
+	if (!dev)
+		return 0;
+
+	dev->spi->dev.power.power_state = PMSG_ON;
+
+	spin_lock(&dev->queue_lock);
+	err = tsc210x_configure(dev);
+
+	dev->flushing = 0;
+	spin_unlock(&dev->queue_lock);
+
+	return err;
+}
+#else
+#define tsc210x_suspend	NULL
+#define tsc210x_resume	NULL
+#endif
+
+static struct platform_device tsc210x_ts_device = {
+	.name 		= "tsc210x-ts",
+	.id 		= -1,
+};
+
+static struct platform_device tsc210x_hwmon_device = {
+	.name 		= "tsc210x-hwmon",
+	.id 		= -1,
+};
+
+static struct platform_device tsc210x_alsa_device = {
+	.name 		= "tsc210x-alsa",
+	.id 		= -1,
+};
+
+static int tsc210x_probe(struct spi_device *spi, enum tsc_type type)
+{
+	struct tsc210x_config *pdata = spi->dev.platform_data;
+	struct spi_transfer *spi_buffer;
+	struct tsc210x_dev *dev;
+	int err = 0;
+
+	if (!pdata) {
+		printk(KERN_ERR "TSC210x: Platform data not supplied\n");
+		return -ENOENT;
+	}
+
+	if (!spi->irq) {
+		printk(KERN_ERR "TSC210x: Invalid irq value\n");
+		return -EINVAL;
+	}
+
+	dev = (struct tsc210x_dev *)
+		kzalloc(sizeof(struct tsc210x_dev), GFP_KERNEL);
+	if (!dev) {
+		printk(KERN_ERR "TSC210x: No memory\n");
+		return -ENOMEM;
+	}
+
+	dev->pdata = pdata;
+	dev->pendown = 0;
+	dev->spi = spi;
+	dev->kind = type;
+	dev->queue = create_singlethread_workqueue(spi->dev.driver->name);
+	if (!dev->queue) {
+		printk(KERN_ERR "TSC210x: Can't make a workqueue\n");
+		err = -ENOMEM;
+		goto err_queue;
+	}
+
+	spin_lock_init(&dev->queue_lock);
+	init_completion(&dev->data_avail);
+
+	/* Allocate enough struct spi_transfer's for all requests */
+	spi_buffer = kzalloc(sizeof(struct spi_transfer) * 16, GFP_KERNEL);
+	if (!spi_buffer) {
+		printk(KERN_ERR "TSC210x: No memory for SPI buffers\n");
+		err = -ENOMEM;
+		goto err_buffers;
+	}
+
+	dev->transfers = spi_buffer;
+	tsc210x_request_alloc(dev, &dev->req_adc, 0,
+			TSC210X_TS_X, 4, dev->adc_data,
+			tsc210x_coords_report, &spi_buffer);
+	tsc210x_request_alloc(dev, &dev->req_status, 0,
+			TSC210X_TS_STATUS_CTRL, 1, &dev->status,
+			tsc210x_status_report, &spi_buffer);
+	tsc210x_request_alloc(dev, &dev->req_mode, 1,
+			TSC210X_TS_ADC_CTRL, 1, 0,
+			tsc210x_complete_dummy, &spi_buffer);
+	tsc210x_request_alloc(dev, &dev->req_stop, 1,
+			TSC210X_TS_ADC_CTRL, 1, 0,
+			tsc210x_complete_dummy, &spi_buffer);
+
+	if (pdata->bclk) {
+		/* Get the BCLK */
+		dev->bclk_ck = clk_get(&spi->dev, pdata->bclk);
+		if (IS_ERR(dev->bclk_ck)) {
+			err = PTR_ERR(dev->bclk_ck);
+			printk(KERN_ERR "Unable to get '%s': %i\n",
+					pdata->bclk, err);
+			goto err_clk;
+		}
+
+		clk_enable(dev->bclk_ck);
+	}
+
+	INIT_DELAYED_WORK(&dev->ts_worker, tsc210x_pressure);
+	INIT_DELAYED_WORK(&dev->sensor_worker, tsc210x_input_scan);
+
+	/* Setup the communication bus */
+	dev_set_drvdata(&spi->dev, dev);
+	spi->dev.power.power_state = PMSG_ON;
+	spi->mode = SPI_MODE_1;
+	spi->bits_per_word = 16;
+	err = spi_setup(spi);
+	if (err)
+		goto err_spi;
+
+	/* Now try to detect the chip, make first contact */
+	if (tsc210x_get_revision(dev) != 0x1) {
+		printk(KERN_ERR "No TI %s chip found!\n",
+				spi->dev.driver->name);
+		err = -ENODEV;
+		goto err_spi;
+	}
+
+	err = tsc210x_configure(dev);
+	if (err)
+		goto err_spi;
+
+	/* We want no interrupts before configuration succeeds.  */
+	spin_lock(&dev->queue_lock);
+	dev->flushing = 1;
+
+	if (request_irq(spi->irq, tsc210x_handler, IRQF_SAMPLE_RANDOM |
+				IRQF_TRIGGER_FALLING, spi->dev.driver->name,
+				dev)) {
+		printk(KERN_ERR "Could not allocate touchscreen IRQ!\n");
+		err = -EINVAL;
+		goto err_irq;
+	}
+
+	/* Register subdevices controlled by the TSC 2101/2102 */
+	tsc210x_ts_device.dev.platform_data = dev;
+	tsc210x_ts_device.dev.parent = &spi->dev;
+	err = platform_device_register(&tsc210x_ts_device);
+	if (err)
+		goto err_irq;
+
+	tsc210x_hwmon_device.dev.platform_data = pdata;
+	tsc210x_hwmon_device.dev.parent = &spi->dev;
+	err = platform_device_register(&tsc210x_hwmon_device);
+	if (err)
+		goto err_hwmon;
+
+	tsc210x_alsa_device.dev.platform_data = pdata->alsa_config;
+	tsc210x_alsa_device.dev.parent = &spi->dev;
+	err = platform_device_register(&tsc210x_alsa_device);
+	if (err)
+		goto err_alsa;
+
+	dev->flushing = 0;
+	spin_unlock(&dev->queue_lock);
+	return 0;
+
+err_alsa:
+	platform_device_unregister(&tsc210x_hwmon_device);
+err_hwmon:
+	platform_device_unregister(&tsc210x_ts_device);
+err_irq:
+	spin_unlock(&dev->queue_lock);
+err_spi:
+	dev_set_drvdata(&spi->dev, NULL);
+	clk_disable(dev->bclk_ck);
+	clk_put(dev->bclk_ck);
+err_clk:
+	kfree(dev->transfers);
+err_buffers:
+	destroy_workqueue(dev->queue);
+err_queue:
+	kfree(dev);
+	return err;
+}
+
+static int tsc2101_probe(struct spi_device *spi)
+{
+	return tsc210x_probe(spi, tsc2101);
+}
+
+static int tsc2102_probe(struct spi_device *spi)
+{
+	return tsc210x_probe(spi, tsc2102);
+}
+
+static int tsc210x_remove(struct spi_device *spi)
+{
+	struct tsc210x_dev *dev = dev_get_drvdata(&spi->dev);
+
+	/* Stop the inputs scan loop */
+	spin_lock(&dev->queue_lock);
+	dev->flushing = 1;
+	cancel_delayed_work(&dev->sensor_worker);
+	spin_unlock(&dev->queue_lock);
+	flush_workqueue(dev->queue);
+
+	/* Wait for pen-up */
+	while (dev->pendown)
+		flush_workqueue(dev->queue);
+
+	/* Abort current conversion and power down the ADC */
+	tsc210x_write_sync(dev, TSC210X_TS_ADC_CTRL, TSC210X_ADC_ADST);
+
+	destroy_workqueue(dev->queue);
+
+	platform_device_unregister(&tsc210x_ts_device);
+	platform_device_unregister(&tsc210x_hwmon_device);
+	platform_device_unregister(&tsc210x_alsa_device);
+
+	dev_set_drvdata(&spi->dev, NULL);
+
+	/* Release the BCLK */
+	clk_disable(dev->bclk_ck);
+	clk_put(dev->bclk_ck);
+
+	kfree(dev->transfers);
+	kfree(dev);
+
+	return 0;
+}
+
+static struct spi_driver tsc2101_driver = {
+	.probe		= tsc2101_probe,
+	.remove		= tsc210x_remove,
+	.suspend	= tsc210x_suspend,
+	.resume		= tsc210x_resume,
+	.driver		= {
+		.name	= "tsc2101",
+		.owner	= THIS_MODULE,
+		.bus	= &spi_bus_type,
+	},
+};
+
+static struct spi_driver tsc2102_driver = {
+	.probe		= tsc2102_probe,
+	.remove		= tsc210x_remove,
+	.suspend	= tsc210x_suspend,
+	.resume		= tsc210x_resume,
+	.driver		= {
+		.name	= "tsc2102",
+		.owner	= THIS_MODULE,
+		.bus	= &spi_bus_type,
+	},
+};
+
+static char __initdata banner[] = KERN_INFO "TI TSC210x driver initializing\n";
+
+static int __init tsc210x_init(void)
+{
+	int err;
+	printk(banner);
+
+	settings.ts_msecs = 20;
+	settings.mode_msecs = 1000;
+
+	err = spi_register_driver(&tsc2101_driver);
+	if (err != 0)
+		return err;
+
+	err = spi_register_driver(&tsc2102_driver);
+	if (err != 0)
+		spi_unregister_driver(&tsc2101_driver);
+
+	return err;
+}
+
+static void __exit tsc210x_exit(void)
+{
+	spi_unregister_driver(&tsc2101_driver);
+	spi_unregister_driver(&tsc2102_driver);
+}
+
+module_init(tsc210x_init);
+module_exit(tsc210x_exit);
+
+EXPORT_SYMBOL(tsc210x_read_sync);
+EXPORT_SYMBOL(tsc210x_reads_sync);
+EXPORT_SYMBOL(tsc210x_write_sync);
+EXPORT_SYMBOL(tsc210x_keyclick);
+
+MODULE_AUTHOR("Andrzej Zaborowski");
+MODULE_DESCRIPTION("Interface driver for TI TSC210x chips.");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/spi/tsc210x.h b/include/linux/spi/tsc210x.h
new file mode 100644
index 0000000..de5977c
--- /dev/null
+++ b/include/linux/spi/tsc210x.h
@@ -0,0 +1,224 @@
+/*
+ * include/linux/spi/tsc2102.h
+ *
+ * TI TSC2101/2102 control register definitions.
+ *
+ * Copyright (c) 2005-2007 Andrzej Zaborowski  <balrog@zabor.org>
+ *
+ * This package is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __LINUX_SPI_TSC210X_H
+#define __LINUX_SPI_TSC210X_H
+
+struct apm_power_info;
+struct tsc210x_config {
+	int use_internal;	/* Use internal reference voltage */
+	uint32_t monitor;	/* What inputs are relevant */
+	int temp_at25c[2];	/* Thermometer calibration data */
+	void (*apm_report)(struct apm_power_info *info, int battery[]);
+				/* Report status to APM based on battery[] */
+	void *alsa_config;	/* .platform_data for the ALSA device */
+	const char *mclk;	/* Optional: bclk name */
+	const char *bclk;	/* Optional: mclk name */
+};
+
+#define TSC_BAT1	(1 << 0)
+#define TSC_BAT2	(1 << 1)
+#define TSC_AUX1	(1 << 2)
+#define TSC_AUX2	(1 << 3)
+#define TSC_TEMP	(1 << 4)
+
+#define TSC_AUX		TSC_AUX1
+#define TSC_VBAT	TSC_BAT1
+
+struct tsc210x_dev;
+extern u16 tsc210x_read_sync(struct tsc210x_dev *dev, int page, u8 address);
+extern void tsc210x_reads_sync(struct tsc210x_dev *dev, int page,
+		u8 startaddress, u16 *data, int numregs);
+extern void tsc210x_write_sync(struct tsc210x_dev *dev, int page,
+		u8 address, u16 data);
+
+typedef void (*tsc210x_touch_t)(void *context, int touching);
+typedef void (*tsc210x_coords_t)(void *context, int x, int y, int z1, int z2);
+typedef void (*tsc210x_ports_t)(void *context, int bat[], int aux[]);
+typedef void (*tsc210x_temp_t)(void *context, int temp);
+extern int tsc210x_touch_cb(struct device *dev,
+		tsc210x_touch_t handler, void *context);
+extern int tsc210x_coords_cb(struct device *dev,
+		tsc210x_coords_t handler, void *context);
+extern int tsc210x_ports_cb(struct device *dev,
+		tsc210x_ports_t handler, void *context);
+extern int tsc210x_temp1_cb(struct device *dev,
+		tsc210x_temp_t handler, void *context);
+extern int tsc210x_temp2_cb(struct device *dev,
+		tsc210x_temp_t handler, void *context);
+
+#ifdef CONFIG_SOUND
+extern void tsc210x_set_dac_volume(struct device *dev,
+		uint8_t left_ch, uint8_t right_ch);
+extern void tsc210x_set_dac_mute(struct device *dev,
+		int left_ch, int right_ch);
+extern void tsc210x_get_dac_mute(struct device *dev,
+		int *left_ch, int *right_ch);
+extern void tsc210x_dac_power(struct device *dev, int on);
+extern int tsc210x_set_rate(struct device *dev, int rate);
+extern void tsc210x_set_i2s_master(struct device *dev, int state);
+extern void tsc210x_set_deemphasis(struct device *dev, int enable);
+extern void tsc2102_set_bassboost(struct device *dev, int enable);
+#endif
+
+/*
+ * Emit a short keyclick typically in order to give feedback to
+ * user on specific events.
+ *
+ * amplitude must be between 0 (lowest) and 2 (highest).
+ * freq must be between 0 (corresponds to 62.5 Hz) and 7 (8 kHz).
+ * length should be between 2 and 32 periods.
+ *
+ * This function sleeps but for a period unrelated to the length of
+ * the sound, i.e. returning doesn't indicate that the sound has
+ * finished.
+ */
+extern void tsc210x_keyclick(struct tsc210x_dev *dev,
+		int amplitude, int freq, int length);
+
+/* Page 0, Touch Screen & Keypad Data registers */
+#define TSC210X_TS_X			0, 0x00
+#define TSC210X_TS_Y			0, 0x01
+#define TSC210X_TS_Z1			0, 0x02
+#define TSC210X_TS_Z2			0, 0x03
+#define TSC210X_TS_BAT1			0, 0x05
+#define TSC2102_TS_BAT2			0, 0x06
+#define TSC210X_TS_AUX1			0, 0x07
+#define TSC2101_TS_AUX2			0, 0x08
+#define TSC210X_TS_TEMP1		0, 0x09
+#define TSC210X_TS_TEMP2		0, 0x0a
+
+/* Page 1, Touch Screen & Keypad Control registers */
+#define TSC210X_TS_ADC_CTRL		1, 0x00
+#define TSC210X_TS_STATUS_CTRL		1, 0x01
+#define TSC2101_TS_BUFFER_CTRL		1, 0x02
+#define TSC210X_TS_REF_CTRL		1, 0x03
+#define TSC210X_TS_RESET_CTRL		1, 0x04
+#define TSC210X_TS_CONFIG_CTRL		1, 0x05
+#define TSC2101_TS_TEMPMAX_CTRL		1, 0x06
+#define TSC2101_TS_TEMPMIN_CTRL		1, 0x07
+#define TSC2101_TS_AUX1MAX_CTRL		1, 0x08
+#define TSC2101_TS_AUX1MIN_CTRL		1, 0x09
+#define TSC2101_TS_AUX2MAX_CTRL		1, 0x0a
+#define TSC2101_TS_AUX2MIN_CTRL		1, 0x0b
+#define TSC2101_TS_MCONFIG_CTRL		1, 0x0c
+#define TSC2101_TS_DELAY_CTRL		1, 0x0d
+
+/* Page 2, Audio Control registers */
+#define TSC210X_AUDIO1_CTRL		2, 0x00
+#define TSC2101_HEADSET_GAIN_CTRL	2, 0x01
+#define TSC210X_DAC_GAIN_CTRL		2, 0x02
+#define TSC2101_MIXER_GAIN_CTRL		2, 0x03
+#define TSC210X_AUDIO2_CTRL		2, 0x04
+#define TSC210X_POWER_CTRL		2, 0x05
+#define TSC210X_AUDIO3_CTRL		2, 0x06
+#define TSC210X_LCH_BASS_BOOST_N0	2, 0x07
+#define TSC210X_LCH_BASS_BOOST_N1	2, 0x08
+#define TSC210X_LCH_BASS_BOOST_N2	2, 0x09
+#define TSC210X_LCH_BASS_BOOST_N3	2, 0x0a
+#define TSC210X_LCH_BASS_BOOST_N4	2, 0x0b
+#define TSC210X_LCH_BASS_BOOST_N5	2, 0x0c
+#define TSC210X_LCH_BASS_BOOST_D1	2, 0x0d
+#define TSC210X_LCH_BASS_BOOST_D2	2, 0x0e
+#define TSC210X_LCH_BASS_BOOST_D4	2, 0x0f
+#define TSC210X_LCH_BASS_BOOST_D5	2, 0x10
+#define TSC210X_RCH_BASS_BOOST_N0	2, 0x11
+#define TSC210X_RCH_BASS_BOOST_N1	2, 0x12
+#define TSC210X_RCH_BASS_BOOST_N2	2, 0x13
+#define TSC210X_RCH_BASS_BOOST_N3	2, 0x14
+#define TSC210X_RCH_BASS_BOOST_N4	2, 0x15
+#define TSC210X_RCH_BASS_BOOST_N5	2, 0x16
+#define TSC210X_RCH_BASS_BOOST_D1	2, 0x17
+#define TSC210X_RCH_BASS_BOOST_D2	2, 0x18
+#define TSC210X_RCH_BASS_BOOST_D4	2, 0x19
+#define TSC210X_RCH_BASS_BOOST_D5	2, 0x1a
+#define TSC210X_PLL1_CTRL		2, 0x1b
+#define TSC210X_PLL2_CTRL		2, 0x1c
+#define TSC210X_AUDIO4_CTRL		2, 0x1d
+#define TSC2101_HANDSET_GAIN_CTRL	2, 0x1e
+#define TSC2101_CELL_GAIN_CTRL		2, 0x1f
+#define TSC2101_AUIDO5_CTRL		2, 0x20
+#define TSC2101_AUDIO6_CTRL		2, 0x21
+#define TSC2101_AUDIO7_CTRL		2, 0x22
+#define TSC2101_GPIO_CTRL		2, 0x23
+#define TSC2101_IN_AGC_CTRL		2, 0x24
+#define TSC2101_POWER_STATUS		2, 0x25
+#define TSC2101_MIX_AGC_CTRL		2, 0x26
+#define TSC2101_CELL_AGC_CTRL		2, 0x27
+
+/* Field masks for Audio Control 1 */
+#define AC1_WLEN(ARG)			(((ARG) & 0x03) << 10)
+#define AC1_DATFM(ARG)			(((ARG) & 0x03) << 8)
+#define AC1_DACFS(ARG)			((ARG) & 0x3f)
+
+/* Field masks for TSC2102_DAC_GAIN_CTRL */
+#define DGC_DALMU			(1 << 15)
+#define DGC_DALVL(ARG)			(((ARG) & 0x7f) << 8)
+#define DGC_DARMU			(1 << 7)
+#define DGC_DARVL(ARG)			(((ARG) & 0x7f))
+
+/* Field formats for TSC210X_AUDIO2_CTRL */
+#define AC2_KCLEN			(1 << 15)
+#define AC2_KCLAC(ARG)			(((ARG) & 0x07) << 12)
+#define AC2_KCLFRQ(ARG)			(((ARG) & 0x07) << 8)
+#define AC2_KCLLN(ARG)			(((ARG) & 0x0f) << 4)
+#define AC2_DLGAF			(1 << 3)
+#define AC2_DRGAF			(1 << 2)
+#define AC2_DASTC			(1 << 1)
+
+/* Field masks for TSC210X_DAC_POWER_CTRL */
+#define CPC_PWDNC			(1 << 15)
+#define CPC_DAODRC			(1 << 12)
+#define CPC_DAPWDN			(1 << 10)
+#define CPC_VGPWDN			(1 << 8)
+#define CPC_DAPWDF			(1 << 6)
+#define CPC_BASSBC			(1 << 1)
+#define CPC_DEEMPF			(0x01)
+
+/* Field masks for TSC210X_AUDIO3_CTRL */
+#define AC3_DMSVOL(ARG)			(((ARG) & 0x03) << 14)
+#define AC3_REFFS			(1 << 13)
+#define AC3_DAXFM			(1 << 12)
+#define AC3_SLVMS			(1 << 11)
+#define AC3_DALOVF			(1 << 7)
+#define AC3_DAROVF			(1 << 6)
+#define AC3_REVID(ARG)			(((ARG) & 0x07))
+
+/* Field masks for TSC210X_PLL1_CTRL */
+#define PLL1_PLLEN			(1 << 15)
+#define PLL1_Q_VAL(ARG)			(((ARG) & 0x0f) << 11)
+#define PLL1_P_VAL(ARG)			(((ARG) & 0x07) << 8)
+#define PLL1_I_VAL(ARG)			(((ARG) & 0x3f) << 2)
+
+/* Field masks for TSC210X_PLL2_CTRL */
+#define PLL2_D_VAL(ARG)			(((ARG) & 0x3fff) << 2)
+
+/* Field masks for TSC210X_AUDIO4_CTRL */
+#define AC4_DASTPD			(1 << 14)
+
+struct tsc210x_rate_info_s {
+	u16 sample_rate;
+	u8 divisor;
+	u8 fs_44k;	/* 44.1 kHz Fsref if 1, 48 kHz if 0 */
+};
+
+#endif	/* __LINUX_SPI_TSC210X_H */
-- 
1.4.4.3


[-- Attachment #3: Type: text/plain, Size: 0 bytes --]



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

* Re: Common code for TSC 2101 and 2102
  2007-06-04 13:44 Common code for TSC 2101 and 2102 andrzej zaborowski
@ 2007-07-13  6:46 ` Trilok Soni
  2007-07-13  7:10   ` Trilok Soni
  2007-07-13 18:38   ` Common code for TSC 2101 and 2102 David Brownell
  2007-07-15 20:18 ` David Brownell
  1 sibling, 2 replies; 17+ messages in thread
From: Trilok Soni @ 2007-07-13  6:46 UTC (permalink / raw)
  To: andrzej zaborowski; +Cc: Linux OMAP

Hi All,

On 6/4/07, andrzej zaborowski <balrogg@gmail.com> wrote:
> Hi,
>   here's a proposition of a common driver for TSC 2101 and 2102 based
> on the previous 2102 code - the difference between the two chips'
> registers turns out to be minimal, not considering the audio part.
> 2101 provides a superset of 2102 functionality for the most part.

Anybody on list who can review this approach. We need to get this
thing sorted out and have alsa-audio ASoC support on 2420 H4 and
finally in mainline.

>
> I will appreciate if anyone with a board using a TSC2101 could test
> how this works for them, and even more if they can fix the parts that
> possibly don't work. The part that needs to go into the board file is
> similar to that in arch/arm/mach-omap1/board-palmte.c. Audio is not
> expected to work, but touchscreen and hwmon parts work for me.

I will try to review this patch, and not sure when I can test it
touchscreen part on H4.


-- 
--Trilok Soni

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

* Re: Common code for TSC 2101 and 2102
  2007-07-13  6:46 ` Trilok Soni
@ 2007-07-13  7:10   ` Trilok Soni
  2007-07-13 19:29     ` andrzej zaborowski
  2007-07-13 18:38   ` Common code for TSC 2101 and 2102 David Brownell
  1 sibling, 1 reply; 17+ messages in thread
From: Trilok Soni @ 2007-07-13  7:10 UTC (permalink / raw)
  To: andrzej zaborowski; +Cc: Linux OMAP

Hi Andrej,

On 7/13/07, Trilok Soni <soni.trilok@gmail.com> wrote:
> Hi All,
>
> On 6/4/07, andrzej zaborowski <balrogg@gmail.com> wrote:
> > Hi,
> >   here's a proposition of a common driver for TSC 2101 and 2102 based
> > on the previous 2102 code - the difference between the two chips'
> > registers turns out to be minimal, not considering the audio part.
> > 2101 provides a superset of 2102 functionality for the most part.
>
> Anybody on list who can review this approach. We need to get this
> thing sorted out and have alsa-audio ASoC support on 2420 H4 and
> finally in mainline.
>
> >
> > I will appreciate if anyone with a board using a TSC2101 could test
> > how this works for them, and even more if they can fix the parts that
> > possibly don't work. The part that needs to go into the board file is
> > similar to that in arch/arm/mach-omap1/board-palmte.c. Audio is not
> > expected to work, but touchscreen and hwmon parts work for me.
>
> I will try to review this patch, and not sure when I can test it
> touchscreen part on H4.

First cut output of checkpatch, if everyone agrees on your approach,
you can add changes suggested below:

spinlock_t definition without comment
#102: FILE: drivers/hwmon/tsc210x_sensors.c:41:
+       spinlock_t apm_lock;

no space before that '++' (ctx:WxO)
#815: FILE: drivers/spi/tsc210x.c:260:
+                       (*transfer)->rx_buf = data ++;
                                                   ^

printk() should include KERN_ facility level
#852: FILE: drivers/spi/tsc210x.c:297:
+               printk("TSC210x: data in");

need consistent spacing around '|' (ctx:WxV)
#956: FILE: drivers/spi/tsc210x.c:401:
+       if (dev->status & (TSC210X_PS_DAV | TSC210X_T1_DAV |TSC210X_T2_DAV))
                                                           ^

#if 0 -- if this code redundant remove it
#1308: FILE: drivers/spi/tsc210x.c:753:
+#if 0

printk() should include KERN_ facility level
#1704: FILE: drivers/spi/tsc210x.c:1149:
+       printk(banner);

EXPORT_SYMBOL(foo); should immediately follow its function/variable
#1729: FILE: drivers/spi/tsc210x.c:1174:
+EXPORT_SYMBOL(tsc210x_read_sync);

EXPORT_SYMBOL(foo); should immediately follow its function/variable
#1730: FILE: drivers/spi/tsc210x.c:1175:
+EXPORT_SYMBOL(tsc210x_reads_sync);

EXPORT_SYMBOL(foo); should immediately follow its function/variable
#1731: FILE: drivers/spi/tsc210x.c:1176:
+EXPORT_SYMBOL(tsc210x_write_sync);

EXPORT_SYMBOL(foo); should immediately follow its function/variable
#1732: FILE: drivers/spi/tsc210x.c:1177:
+EXPORT_SYMBOL(tsc210x_keyclick);

do not add new typedefs
#1796: FILE: include/linux/spi/tsc210x.h:54:
+typedef void (*tsc210x_touch_t)(void *context, int touching);

do not add new typedefs
#1797: FILE: include/linux/spi/tsc210x.h:55:
+typedef void (*tsc210x_coords_t)(void *context, int x, int y, int z1, int z2);

do not add new typedefs
#1798: FILE: include/linux/spi/tsc210x.h:56:
+typedef void (*tsc210x_ports_t)(void *context, int bat[], int aux[]);

do not add new typedefs
#1799: FILE: include/linux/spi/tsc210x.h:57:
+typedef void (*tsc210x_temp_t)(void *context, int temp);

Missing Signed-off-by: line(s)
Your patch has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.

-- 
--Trilok Soni

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

* Re: Common code for TSC 2101 and 2102
  2007-07-13  6:46 ` Trilok Soni
  2007-07-13  7:10   ` Trilok Soni
@ 2007-07-13 18:38   ` David Brownell
  1 sibling, 0 replies; 17+ messages in thread
From: David Brownell @ 2007-07-13 18:38 UTC (permalink / raw)
  To: Trilok Soni; +Cc: Linux OMAP

On Thursday 12 July 2007, Trilok Soni wrote:
> Hi All,
> 
> On 6/4/07, andrzej zaborowski <balrogg@gmail.com> wrote:
> > Hi,
> >   here's a proposition of a common driver for TSC 2101 and 2102 based
> > on the previous 2102 code - the difference between the two chips'
> > registers turns out to be minimal, not considering the audio part.
> > 2101 provides a superset of 2102 functionality for the most part.
> 
> Anybody on list who can review this approach. We need to get this
> thing sorted out and have alsa-audio ASoC support on 2420 H4 and
> finally in mainline.

ISTR my first reaction was positive ... but I've had other things
ahead of it in my review-and-test queue.  They're nearly done.


> > I will appreciate if anyone with a board using a TSC2101 could test
> > how this works for them, and even more if they can fix the parts that
> > possibly don't work. The part that needs to go into the board file is
> > similar to that in arch/arm/mach-omap1/board-palmte.c. Audio is not
> > expected to work, but touchscreen and hwmon parts work for me.
> 
> I will try to review this patch, and not sure when I can test it
> touchscreen part on H4.
> 
> 
> -- 
> --Trilok Soni
> 

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

* Re: Common code for TSC 2101 and 2102
  2007-07-13  7:10   ` Trilok Soni
@ 2007-07-13 19:29     ` andrzej zaborowski
  2007-07-13 20:04       ` David Brownell
  0 siblings, 1 reply; 17+ messages in thread
From: andrzej zaborowski @ 2007-07-13 19:29 UTC (permalink / raw)
  To: Trilok Soni; +Cc: Linux OMAP

Hi,
thanks for this review.

After GUADEC 07, I should have access to a Palm Tungsten T3 and should
be able to test the TSC2101 in it. Using ASOC is also on my TODO list
for a long time, but unfortunately that's also for after GUADEC.

On 13/07/07, Trilok Soni <soni.trilok@gmail.com> wrote:
> Hi Andrej,
>
> On 7/13/07, Trilok Soni <soni.trilok@gmail.com> wrote:
> > Hi All,
> >
> > On 6/4/07, andrzej zaborowski <balrogg@gmail.com> wrote:
> > > Hi,
> > >   here's a proposition of a common driver for TSC 2101 and 2102 based
> > > on the previous 2102 code - the difference between the two chips'
> > > registers turns out to be minimal, not considering the audio part.
> > > 2101 provides a superset of 2102 functionality for the most part.
> >
> > Anybody on list who can review this approach. We need to get this
> > thing sorted out and have alsa-audio ASoC support on 2420 H4 and
> > finally in mainline.
> >
> > >
> > > I will appreciate if anyone with a board using a TSC2101 could test
> > > how this works for them, and even more if they can fix the parts that
> > > possibly don't work. The part that needs to go into the board file is
> > > similar to that in arch/arm/mach-omap1/board-palmte.c. Audio is not
> > > expected to work, but touchscreen and hwmon parts work for me.
> >
> > I will try to review this patch, and not sure when I can test it
> > touchscreen part on H4.
>
> First cut output of checkpatch, if everyone agrees on your approach,
> you can add changes suggested below:

Uh, should have done that myself before sending, sorry.

>
> spinlock_t definition without comment
> #102: FILE: drivers/hwmon/tsc210x_sensors.c:41:
> +       spinlock_t apm_lock;
>
> no space before that '++' (ctx:WxO)
> #815: FILE: drivers/spi/tsc210x.c:260:
> +                       (*transfer)->rx_buf = data ++;
>                                                    ^
>
> printk() should include KERN_ facility level
> #852: FILE: drivers/spi/tsc210x.c:297:
> +               printk("TSC210x: data in");
>
> need consistent spacing around '|' (ctx:WxV)
> #956: FILE: drivers/spi/tsc210x.c:401:
> +       if (dev->status & (TSC210X_PS_DAV | TSC210X_T1_DAV |TSC210X_T2_DAV))
>                                                            ^
>
> #if 0 -- if this code redundant remove it
> #1308: FILE: drivers/spi/tsc210x.c:753:
> +#if 0
>
> printk() should include KERN_ facility level
> #1704: FILE: drivers/spi/tsc210x.c:1149:
> +       printk(banner);
>
> EXPORT_SYMBOL(foo); should immediately follow its function/variable
> #1729: FILE: drivers/spi/tsc210x.c:1174:
> +EXPORT_SYMBOL(tsc210x_read_sync);
>
> EXPORT_SYMBOL(foo); should immediately follow its function/variable
> #1730: FILE: drivers/spi/tsc210x.c:1175:
> +EXPORT_SYMBOL(tsc210x_reads_sync);
>
> EXPORT_SYMBOL(foo); should immediately follow its function/variable
> #1731: FILE: drivers/spi/tsc210x.c:1176:
> +EXPORT_SYMBOL(tsc210x_write_sync);
>
> EXPORT_SYMBOL(foo); should immediately follow its function/variable
> #1732: FILE: drivers/spi/tsc210x.c:1177:
> +EXPORT_SYMBOL(tsc210x_keyclick);
>
> do not add new typedefs
> #1796: FILE: include/linux/spi/tsc210x.h:54:
> +typedef void (*tsc210x_touch_t)(void *context, int touching);
>
> do not add new typedefs
> #1797: FILE: include/linux/spi/tsc210x.h:55:
> +typedef void (*tsc210x_coords_t)(void *context, int x, int y, int z1, int z2);
>
> do not add new typedefs
> #1798: FILE: include/linux/spi/tsc210x.h:56:
> +typedef void (*tsc210x_ports_t)(void *context, int bat[], int aux[]);
>
> do not add new typedefs
> #1799: FILE: include/linux/spi/tsc210x.h:57:
> +typedef void (*tsc210x_temp_t)(void *context, int temp);

I wonder what is used instead.

>
> Missing Signed-off-by: line(s)
> Your patch has style problems, please review.  If any of these errors
> are false positives report them to the maintainer, see
> CHECKPATCH in MAINTAINERS.
>
> --
> --Trilok Soni
>

Cheers,
Andrew

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

* Re: Common code for TSC 2101 and 2102
  2007-07-13 19:29     ` andrzej zaborowski
@ 2007-07-13 20:04       ` David Brownell
  2007-08-10  7:29         ` Tony Lindgren
  0 siblings, 1 reply; 17+ messages in thread
From: David Brownell @ 2007-07-13 20:04 UTC (permalink / raw)
  To: andrzej zaborowski; +Cc: Linux OMAP

On Friday 13 July 2007, andrzej zaborowski wrote:

> > do not add new typedefs
> > #1796: FILE: include/linux/spi/tsc210x.h:54:
> > +typedef void (*tsc210x_touch_t)(void *context, int touching);
> >
> > do not add new typedefs
> > #1797: FILE: include/linux/spi/tsc210x.h:55:
> > +typedef void (*tsc210x_coords_t)(void *context, int x, int y, int z1, int z2);
> >
> > do not add new typedefs
> > #1798: FILE: include/linux/spi/tsc210x.h:56:
> > +typedef void (*tsc210x_ports_t)(void *context, int bat[], int aux[]);
> >
> > do not add new typedefs
> > #1799: FILE: include/linux/spi/tsc210x.h:57:
> > +typedef void (*tsc210x_temp_t)(void *context, int temp);
> 
> I wonder what is used instead.

I think the "no new typedefs" rule gets relaxed for function
call prototypes.  But to answer your question:  it's usually
more clear to just put the function prototype into the relevant
type declarator than to define a new type.

- Dave

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

* Re: Common code for TSC 2101 and 2102
  2007-06-04 13:44 Common code for TSC 2101 and 2102 andrzej zaborowski
  2007-07-13  6:46 ` Trilok Soni
@ 2007-07-15 20:18 ` David Brownell
  1 sibling, 0 replies; 17+ messages in thread
From: David Brownell @ 2007-07-15 20:18 UTC (permalink / raw)
  To: andrzej zaborowski; +Cc: Linux OMAP

When I built this:

ERROR: "tsc210x_coords_cb" [drivers/input/touchscreen/tsc210x_ts.ko] undefined!
ERROR: "tsc210x_touch_cb" [drivers/input/touchscreen/tsc210x_ts.ko] undefined!
ERROR: "tsc210x_temp2_cb" [drivers/hwmon/tsc210x_sensors.ko] undefined!
ERROR: "tsc210x_temp1_cb" [drivers/hwmon/tsc210x_sensors.ko] undefined!
ERROR: "tsc210x_ports_cb" [drivers/hwmon/tsc210x_sensors.ko] undefined!

with

CONFIG_TOUCHSCREEN_TSC210X=m
CONFIG_SPI_TSC210X=m
CONFIG_SENSORS_TSC210X=m

Same with SPI_TSC210X=y ... problem went away when everything
was statically linked.

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

* Re: Common code for TSC 2101 and 2102
  2007-07-13 20:04       ` David Brownell
@ 2007-08-10  7:29         ` Tony Lindgren
  2007-08-11  3:04           ` David Brownell
  0 siblings, 1 reply; 17+ messages in thread
From: Tony Lindgren @ 2007-08-10  7:29 UTC (permalink / raw)
  To: David Brownell; +Cc: Linux OMAP

* David Brownell <david-b@pacbell.net> [070713 13:05]:
> On Friday 13 July 2007, andrzej zaborowski wrote:
> 
> > > do not add new typedefs
> > > #1796: FILE: include/linux/spi/tsc210x.h:54:
> > > +typedef void (*tsc210x_touch_t)(void *context, int touching);
> > >
> > > do not add new typedefs
> > > #1797: FILE: include/linux/spi/tsc210x.h:55:
> > > +typedef void (*tsc210x_coords_t)(void *context, int x, int y, int z1, int z2);
> > >
> > > do not add new typedefs
> > > #1798: FILE: include/linux/spi/tsc210x.h:56:
> > > +typedef void (*tsc210x_ports_t)(void *context, int bat[], int aux[]);
> > >
> > > do not add new typedefs
> > > #1799: FILE: include/linux/spi/tsc210x.h:57:
> > > +typedef void (*tsc210x_temp_t)(void *context, int temp);
> > 
> > I wonder what is used instead.
> 
> I think the "no new typedefs" rule gets relaxed for function
> call prototypes.  But to answer your question:  it's usually
> more clear to just put the function prototype into the relevant
> type declarator than to define a new type.

I'll be pushing Andrzej's patch today.

Tony

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

* Re: Common code for TSC 2101 and 2102
  2007-08-10  7:29         ` Tony Lindgren
@ 2007-08-11  3:04           ` David Brownell
  2007-08-13  7:05             ` Tony Lindgren
  0 siblings, 1 reply; 17+ messages in thread
From: David Brownell @ 2007-08-11  3:04 UTC (permalink / raw)
  To: Tony Lindgren; +Cc: Linux OMAP

On Friday 10 August 2007, Tony Lindgren wrote:
> I'll be pushing Andrzej's patch today.

I saw it ... it was after I've finally started to dig
out from under my pile of patches going upstream, and
even fired up an OMAP build again!  :)

Glad to see this.  Now I'll have to actually take
more of a real look at it.  Presumably I'll have a
few updates, and then will feel it's time to start
pushing three patches upstream from this big one:

 - drivers/spi/tsc210x.c core, which I can handle;
 - drivers/hwmon/tsc210x_sensors.c, through hwmon;
 - drivers/input/touchscreen/tsc210x_ts.c, through input;
 - ... and later sound/soc/.../something.c, TBD.

I figure an initial upstream merge should included
one or both of { touchscreen, hwmon } in addition
to the core.  Hitting 2.6.24-early should be possible.

- Dave

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

* Re: Common code for TSC 2101 and 2102
  2007-08-11  3:04           ` David Brownell
@ 2007-08-13  7:05             ` Tony Lindgren
  2007-08-14  5:34               ` David Brownell
                                 ` (2 more replies)
  0 siblings, 3 replies; 17+ messages in thread
From: Tony Lindgren @ 2007-08-13  7:05 UTC (permalink / raw)
  To: David Brownell; +Cc: Linux OMAP

* David Brownell <david-b@pacbell.net> [070810 20:05]:
> On Friday 10 August 2007, Tony Lindgren wrote:
> > I'll be pushing Andrzej's patch today.
> 
> I saw it ... it was after I've finally started to dig
> out from under my pile of patches going upstream, and
> even fired up an OMAP build again!  :)

Cool :)

> Glad to see this.  Now I'll have to actually take
> more of a real look at it.  Presumably I'll have a
> few updates, and then will feel it's time to start
> pushing three patches upstream from this big one:
> 
>  - drivers/spi/tsc210x.c core, which I can handle;
>  - drivers/hwmon/tsc210x_sensors.c, through hwmon;
>  - drivers/input/touchscreen/tsc210x_ts.c, through input;
>  - ... and later sound/soc/.../something.c, TBD.
> 
> I figure an initial upstream merge should included
> one or both of { touchscreen, hwmon } in addition
> to the core.  Hitting 2.6.24-early should be possible.

Great!

Tony

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

* Re: Common code for TSC 2101 and 2102
  2007-08-13  7:05             ` Tony Lindgren
@ 2007-08-14  5:34               ` David Brownell
  2007-09-23 22:37                 ` andrzej zaborowski
  2007-08-14  5:38               ` [patch 2.6.23-rc2-omap1] tsc210x cleanup David Brownell
  2007-08-14  5:40               ` [patch 2.6.23-rc2-omap1] H4 support for tsc210x David Brownell
  2 siblings, 1 reply; 17+ messages in thread
From: David Brownell @ 2007-08-14  5:34 UTC (permalink / raw)
  To: Tony Lindgren; +Cc: Linux OMAP

On Monday 13 August 2007, Tony Lindgren wrote:
> * David Brownell <david-b@pacbell.net> [070810 20:05]:
> > On Friday 10 August 2007, Tony Lindgren wrote:
> > > I'll be pushing Andrzej's patch today.
> > 
> > I saw it ... it was after I've finally started to dig
> > out from under my pile of patches going upstream, and
> > even fired up an OMAP build again!  :)
> 
> Cool :)

Following this will be several patches, two related to
this driver:

 - H4 board support, using TSC2101 (not TSC2102)
 - A biggish tsc210x cleanup patch, with some bugfixes

The main thing I'd call a bug in the tsc210x code is the
hwmon support not reporting milliVolts.  Easily enough
solved after passing some more info down the hwmon code
via platform_data:  actual VREF, chip type, precision.

For boards using external VREF, that value should be in
the tsc210x chip platform data.  (And Andrzej, I'm
curious why you settled on VREF=1.25V vs 2.50V ... not
that it's necessarily an issue, just "why".  A comment
in the code might be good.)

Somewhat related, I think the TSC setup code should
likely hold off until it can fetch calibration data
from EEPROM or somesuch ... temperature readings are
not all that useful until that data is available.
Alternatively, do like I did in ads7846:  punt it all
to userspace, just provide the raw milliVolt readings
and expect someone else to compute temperature.

I only did really basic touchscreen testing, to verify
that data came out of /dev/input/event1.  Not to check
that it was "good" data.  I suspect there's some work
yet to be done there.

- Dave

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

* [patch 2.6.23-rc2-omap1] tsc210x cleanup
  2007-08-13  7:05             ` Tony Lindgren
  2007-08-14  5:34               ` David Brownell
@ 2007-08-14  5:38               ` David Brownell
  2007-08-15 10:54                 ` Tony Lindgren
  2007-08-14  5:40               ` [patch 2.6.23-rc2-omap1] H4 support for tsc210x David Brownell
  2 siblings, 1 reply; 17+ messages in thread
From: David Brownell @ 2007-08-14  5:38 UTC (permalink / raw)
  To: Tony Lindgren, andrzej zaborowski; +Cc: Linux OMAP

This is mostly cleanup of the tsc210x patch, but some bugs were fixed so now
it works on tsc2101 too.  Also, a few issues are now noted in the code:

 Tool-reported:
  - Address checkpatch.pl issues in the original patch
  - And also "sparse" issues

 Previously wrong:
  - Cope with CONFIG_SOUND_MODULE
  - Register accessor routines will now return error codes
  - ... many callers now abort cleanly after errors
  - Don't depend on seeing only a rev #1 tsc2102 chip!
  - Add missing EXPORT_SYMBOL declarations

 Style issues:
  - BUG_ON() is strongly to be avoided
  - So are macros that capture variables
  - And needless casting from void pointers
  - And type punning
  - Use dev_*() for messaging where practical
  - Use u16 not uint16_t, etc
  - Various other whitespace issues
  - Avoid __FUNCTION__
  - Single-lines for file-top comments; no whole paths

 Object code size and Other cleanup:
  - Compile most strings out unless -DDEBUG is configured (saving space)
  - Move some code into exit sections (then it may well vanish, saving space)
  - Add some header comments

 Open issues:
  - Hwmon needs to know VREF and the chip type, and to scale its values
  - Upstream push should exclude audio until some version is working
  - Audio will needs to export symbols to modules, too
  - Lots of the I/O calls (especially audio) still don't handle errors
  - How could board init code get board-specific temp calibration data?
 
Also the Kconfig is more open-ended; tsc210x might in the future include the
tsc2100 and tsc2111, it doesn't need to be limited to tsc2101 and tsc2102.

Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
---
 drivers/hwmon/Kconfig                  |    4 
 drivers/hwmon/tsc210x_sensors.c        |  140 ++++++++----
 drivers/input/touchscreen/Kconfig      |    4 
 drivers/input/touchscreen/tsc210x_ts.c |   65 +++--
 drivers/spi/Kconfig                    |   13 -
 drivers/spi/tsc210x.c                  |  366 +++++++++++++++++++--------------
 include/linux/spi/tsc210x.h            |   33 +-
 7 files changed, 381 insertions(+), 244 deletions(-)

--- h4.orig/drivers/hwmon/Kconfig	2007-08-13 15:40:13.000000000 -0700
+++ h4/drivers/hwmon/Kconfig	2007-08-13 15:40:31.000000000 -0700
@@ -685,11 +685,11 @@ config SENSORS_APPLESMC
 	  the awesome power of applesmc.
 
 config SENSORS_TSC210X
-	tristate "TI TSC2101/2102 battery & temperature sensors"
+	tristate "TI TSC210x battery & temperature sensors"
 	depends on HWMON && SPI_MASTER
 	select SPI_TSC210X
 	help
-	  Say Y if your board has a TSC210X chip and you want to
+	  Say Y if your board has a TSC210x chip and you want to
 	  have its battery state, auxiliary input and/or temperature
 	  sensors exported through hwmon.
 
--- h4.orig/drivers/hwmon/tsc210x_sensors.c	2007-08-13 15:40:13.000000000 -0700
+++ h4/drivers/hwmon/tsc210x_sensors.c	2007-08-13 21:59:18.000000000 -0700
@@ -1,7 +1,5 @@
 /*
- * drivers/hwmon/tsc210x_sensors.c
- *
- * hwmon interface for TSC210X sensors
+ * tsc210x_sensors.c - hwmon interface to TI TSC210x sensors
  *
  * Copyright (c) 2005-2007 Andrzej Zaborowski  <balrog@zabor.org>
  *
@@ -32,54 +30,92 @@
 
 #include <linux/spi/tsc210x.h>
 
+
+/*
+ * TI TSC210x chips include an ADC that's shared between various
+ * sensors (temperature, battery, vAUX, etc) and the touchscreen.
+ * This driver packages access to the non-touchscreen sensors
+ * available on a given board.
+ */
+
 struct tsc210x_hwmon {
 	int bat[2], aux[2], temp[2];
 
 	struct class_device *dev;
 	struct tsc210x_config *pdata;
 #ifdef CONFIG_APM
+	/* prevent APM from colliding with normal hwmon accessors */
 	spinlock_t apm_lock;
 #endif
 };
 
 #ifdef CONFIG_APM
-# define apm_lock()	spin_lock(&hwmon->apm_lock)
-# define apm_unlock()	spin_unlock(&hwmon->apm_lock)
+# define apm_lock(h)	spin_lock(&(h)->apm_lock)
+# define apm_unlock(h)	spin_unlock(&(h)->apm_lock)
 #else
-# define apm_lock()
-# define apm_unlock()
+# define apm_lock(h)	do { } while (0)
+# define apm_unlock(h)	do { } while (0)
 #endif
 
-static void tsc210x_ports(struct tsc210x_hwmon *hwmon, int bat[], int aux[])
+static void tsc210x_ports(void *context, int bat[], int aux[])
 {
-	apm_lock();
+	struct tsc210x_hwmon	*hwmon = context;
+
+	apm_lock(hwmon);
+
+	/* FIXME for tsc2101 and tsc2111, battery voltage is:
+	 *	VBAT = (5 * VREF * (bat[x])) / (2 ^ bits)
+	 * For tsc2100 and tsc2102, use "6" not "5"; that formula ignores
+	 * an external 100-300 Ohm resistor making the right value be just
+	 * a bit over 5 (or 6).
+	 *
+	 * FIXME the vAUX measurements need scaling too, but in that case
+	 * there's no *internal* voltage divider so just scale to VREF.
+	 *
+	 *  --> This code needs to know VREF, the VBAT multiplier, and
+	 *	the precision.  For now, assume VREF 1.25V and 12 bits.
+	 *	When an external reference is used, it normally won't
+	 *	match the 1.25V (or 2.5V) values supported internally...
+	 *
+	 *  --> Output units should become milliVolts; currently they are
+	 *	dimensionless...
+	 */
 	hwmon->bat[0] = bat[0];
 	hwmon->bat[1] = bat[1];
+
 	hwmon->aux[0] = aux[0];
 	hwmon->aux[1] = aux[1];
-	apm_unlock();
+
+	apm_unlock(hwmon);
 }
 
-static void tsc210x_temp1(struct tsc210x_hwmon *hwmon, int temp)
+/* FIXME temp sensors also need scaling so values are milliVolts...
+ * temperature (given calibration data) should be millidegrees C.
+ */
+
+static void tsc210x_temp1(void *context, int temp)
 {
-	apm_lock();
+	struct tsc210x_hwmon	*hwmon = context;
+
+	apm_lock(hwmon);
 	hwmon->temp[0] = temp;
-	apm_unlock();
+	apm_unlock(hwmon);
 }
 
-static void tsc210x_temp2(struct tsc210x_hwmon *hwmon, int temp)
+static void tsc210x_temp2(void *context, int temp)
 {
-	apm_lock();
+	struct tsc210x_hwmon	*hwmon = context;
+
+	apm_lock(hwmon);
 	hwmon->temp[1] = temp;
-	apm_unlock();
+	apm_unlock(hwmon);
 }
 
 #define TSC210X_INPUT(devname, field)	\
 static ssize_t tsc_show_ ## devname(struct device *dev,	\
 		struct device_attribute *devattr, char *buf)	\
 {	\
-	struct tsc210x_hwmon *hwmon = (struct tsc210x_hwmon *)	\
-		platform_get_drvdata(to_platform_device(dev));	\
+	struct tsc210x_hwmon *hwmon = dev_get_drvdata(dev);	\
 	return sprintf(buf, "%i\n", hwmon->field);	\
 }	\
 static DEVICE_ATTR(devname ## _input, S_IRUGO, tsc_show_ ## devname, NULL);
@@ -94,13 +130,11 @@ TSC210X_INPUT(in5, temp[1])
 static ssize_t tsc_show_temp1(struct device *dev,
 		struct device_attribute *devattr, char *buf)
 {
-	struct tsc210x_hwmon *hwmon = (struct tsc210x_hwmon *)
-		platform_get_drvdata(to_platform_device(dev));
-	int t1, t2;
-	int diff, value;
-
-	t1 = hwmon->temp[0];
-	t2 = hwmon->temp[1];
+	struct tsc210x_hwmon *hwmon = dev_get_drvdata(dev);
+	int t1 = hwmon->temp[0];
+	int t2 = hwmon->temp[1];
+	int diff;
+	int value;
 
 	/*
 	 * Use method #2 (differential) to calculate current temperature.
@@ -114,7 +148,6 @@ static ssize_t tsc_show_temp1(struct dev
 	 * 273150 is zero degrees Celcius.
 	 */
 	diff = hwmon->pdata->temp_at25c[1] - hwmon->pdata->temp_at25c[0];
-	BUG_ON(diff == 0);
 	value = (t2 - t1) * 298150 / diff;	/* This is in Kelvins now */
 
 	value -= 273150;			/* Celcius millidegree */
@@ -128,9 +161,10 @@ static struct tsc210x_hwmon *apm_hwmon;
 static void tsc210x_get_power_status(struct apm_power_info *info)
 {
 	struct tsc210x_hwmon *hwmon = apm_hwmon;
-	apm_lock();
+
+	apm_lock(hwmon);
 	hwmon->pdata->apm_report(info, hwmon->bat);
-	apm_unlock();
+	apm_unlock(hwmon);
 }
 #endif
 
@@ -143,15 +177,14 @@ static int tsc210x_hwmon_probe(struct pl
 	hwmon = (struct tsc210x_hwmon *)
 		kzalloc(sizeof(struct tsc210x_hwmon), GFP_KERNEL);
 	if (!hwmon) {
-		printk(KERN_ERR "%s: allocation failed\n", __FUNCTION__);
+		dev_dbg(&pdev->dev, "allocation failed\n");
 		return -ENOMEM;
 	}
 
 	hwmon->dev = hwmon_device_register(&pdev->dev);
 	if (IS_ERR(hwmon->dev)) {
 		kfree(hwmon);
-		printk(KERN_ERR "%s: Class registration failed\n",
-				__FUNCTION__);
+		dev_dbg(&pdev->dev, "registration failed\n");
 		return PTR_ERR(hwmon->dev);
 	}
 
@@ -170,19 +203,19 @@ static int tsc210x_hwmon_probe(struct pl
 
 	if (pdata->monitor & (TSC_BAT1 | TSC_BAT2 | TSC_AUX1 | TSC_AUX2))
 		status |= tsc210x_ports_cb(pdev->dev.parent,
-				(tsc210x_ports_t) tsc210x_ports, hwmon);
+				tsc210x_ports, hwmon);
 	if (pdata->monitor & TSC_TEMP) {
 		status |= tsc210x_temp1_cb(pdev->dev.parent,
-				(tsc210x_temp_t) tsc210x_temp1, hwmon);
+				tsc210x_temp1, hwmon);
 		status |= tsc210x_temp2_cb(pdev->dev.parent,
-				(tsc210x_temp_t) tsc210x_temp2, hwmon);
+				tsc210x_temp2, hwmon);
 	}
 
 	if (status) {
-		tsc210x_ports_cb(pdev->dev.parent, 0, 0);
-		tsc210x_temp1_cb(pdev->dev.parent, 0, 0);
-		tsc210x_temp2_cb(pdev->dev.parent, 0, 0);
-		platform_set_drvdata(pdev, 0);
+		tsc210x_ports_cb(pdev->dev.parent, NULL, NULL);
+		tsc210x_temp1_cb(pdev->dev.parent, NULL, NULL);
+		tsc210x_temp2_cb(pdev->dev.parent, NULL, NULL);
+		platform_set_drvdata(pdev, NULL);
 #ifdef CONFIG_APM
 		if (pdata->apm_report)
 			apm_get_power_status = 0;
@@ -203,23 +236,28 @@ static int tsc210x_hwmon_probe(struct pl
 	if (pdata->monitor & TSC_TEMP) {
 		status |= device_create_file(&pdev->dev, &dev_attr_in4_input);
 		status |= device_create_file(&pdev->dev, &dev_attr_in5_input);
-		status |= device_create_file(&pdev->dev, &dev_attr_temp1_input);
+
+		if ((pdata->temp_at25c[1] - pdata->temp_at25c[0]) == 0)
+			dev_warn(&pdev->dev, "No temp calibration data.\n");
+		else
+			status |= device_create_file(&pdev->dev,
+						&dev_attr_temp1_input);
 	}
 	if (status)	/* Not fatal */
-		printk(KERN_ERR "%s: Creating one or more "
-				"attribute files failed\n", __FUNCTION__);
+		dev_dbg(&pdev->dev, "Creating one or more "
+				"attribute files failed\n");
 
 	return 0;
 }
 
-static int tsc210x_hwmon_remove(struct platform_device *pdev)
+static int __exit tsc210x_hwmon_remove(struct platform_device *pdev)
 {
 	struct tsc210x_hwmon *dev = platform_get_drvdata(pdev);
 
-	tsc210x_ports_cb(pdev->dev.parent, 0, 0);
-	tsc210x_temp1_cb(pdev->dev.parent, 0, 0);
-	tsc210x_temp2_cb(pdev->dev.parent, 0, 0);
-	platform_set_drvdata(pdev, 0);
+	tsc210x_ports_cb(pdev->dev.parent, NULL, NULL);
+	tsc210x_temp1_cb(pdev->dev.parent, NULL, NULL);
+	tsc210x_temp2_cb(pdev->dev.parent, NULL, NULL);
+	platform_set_drvdata(pdev, NULL);
 #ifdef CONFIG_APM
 	if (dev->pdata->apm_report)
 		apm_get_power_status = 0;
@@ -230,8 +268,8 @@ static int tsc210x_hwmon_remove(struct p
 }
 
 static struct platform_driver tsc210x_hwmon_driver = {
-	.probe 		= tsc210x_hwmon_probe,
-	.remove 	= tsc210x_hwmon_remove,
+	.probe		= tsc210x_hwmon_probe,
+	.remove		= __exit_p(tsc210x_hwmon_remove),
 	/* Nothing to do on suspend/resume */
 	.driver		= {
 		.name	= "tsc210x-hwmon",
@@ -240,15 +278,17 @@ static struct platform_driver tsc210x_hw
 
 static int __init tsc210x_hwmon_init(void)
 {
+	/* can't use driver_probe() here since the parent device
+	 * gets registered "late"
+	 */
 	return platform_driver_register(&tsc210x_hwmon_driver);
 }
+module_init(tsc210x_hwmon_init);
 
 static void __exit tsc210x_hwmon_exit(void)
 {
 	platform_driver_unregister(&tsc210x_hwmon_driver);
 }
-
-module_init(tsc210x_hwmon_init);
 module_exit(tsc210x_hwmon_exit);
 
 MODULE_AUTHOR("Andrzej Zaborowski");
--- h4.orig/drivers/input/touchscreen/Kconfig	2007-08-13 15:40:13.000000000 -0700
+++ h4/drivers/input/touchscreen/Kconfig	2007-08-13 15:40:31.000000000 -0700
@@ -193,12 +193,12 @@ config TOUCHSCREEN_TSC2102
 	  module will be called tsc2102_ts.
 
 config TOUCHSCREEN_TSC210X
-	tristate "TSC 2101/2102 based touchscreens"
+	tristate "TI TSC210x based touchscreens"
 	depends on SPI_MASTER
 	select SPI_TSC210X
 	help
 	  Say Y here if you have a touchscreen interface using a
-	  TI TSC 210x controller, and your board-specific initialisation
+	  TI TSC210x controller, and your board-specific initialisation
 	  code includes that in its table of SPI devices.
 
 	  If unsure, say N (but it's safe to say "Y").
--- h4.orig/drivers/input/touchscreen/tsc210x_ts.c	2007-08-13 15:40:13.000000000 -0700
+++ h4/drivers/input/touchscreen/tsc210x_ts.c	2007-08-13 22:00:03.000000000 -0700
@@ -1,7 +1,5 @@
 /*
- * input/touchscreen/tsc210x_ts.c
- *
- * Touchscreen input device driver for the TSC 2101/2102 chips.
+ * tsc210x_ts.c - touchscreen input device for TI TSC210x chips
  *
  * Copyright (c) 2006-2007 Andrzej Zaborowski  <balrog@zabor.org>
  *
@@ -29,8 +27,23 @@
 
 #include <linux/spi/tsc210x.h>
 
-static void tsc210x_touch(struct input_dev *dev, int touching)
+
+/*
+ * The sensor ADC on tsc210x chips is most often used with the smart
+ * touchscreen controller.   Those controllers can be made to improve
+ * sample quality directly by multi-sampling and by taking the mean or
+ * median of various numbers of samples.  They also take X, Y, and
+ * pressure measurements automatically, so this driver has relatively
+ * little to do.
+ *
+ * There are a few chips in this family that don't have quite the same
+ * touchscreen interface, e.g. no "median" mode.
+ */
+
+static void tsc210x_touch(void *context, int touching)
 {
+	struct input_dev *dev = context;
+
 	if (!touching) {
 		input_report_abs(dev, ABS_X, 0);
 		input_report_abs(dev, ABS_Y, 0);
@@ -41,8 +54,9 @@ static void tsc210x_touch(struct input_d
 	input_report_key(dev, BTN_TOUCH, touching);
 }
 
-static void tsc210x_coords(struct input_dev *dev, int x, int y, int z1, int z2)
+static void tsc210x_coords(void *context, int x, int y, int z1, int z2)
 {
+	struct input_dev *dev = context;
 	int p;
 
 	/* Calculate the touch resistance a la equation #1 */
@@ -66,17 +80,15 @@ static int tsc210x_ts_probe(struct platf
 	if (!dev)
 		return -ENOMEM;
 
-	status = tsc210x_touch_cb(pdev->dev.parent,
-			(tsc210x_touch_t) tsc210x_touch, dev);
+	status = tsc210x_touch_cb(pdev->dev.parent, tsc210x_touch, dev);
 	if (status) {
 		input_free_device(dev);
 		return status;
 	}
 
-	status = tsc210x_coords_cb(pdev->dev.parent,
-			(tsc210x_coords_t) tsc210x_coords, dev);
+	status = tsc210x_coords_cb(pdev->dev.parent, tsc210x_coords, dev);
 	if (status) {
-		tsc210x_touch_cb(pdev->dev.parent, 0, 0);
+		tsc210x_touch_cb(pdev->dev.parent, NULL, NULL);
 		input_free_device(dev);
 		return status;
 	}
@@ -94,8 +106,8 @@ static int tsc210x_ts_probe(struct platf
 
 	status = input_register_device(dev);
 	if (status) {
-		tsc210x_coords_cb(pdev->dev.parent, 0, 0);
-		tsc210x_touch_cb(pdev->dev.parent, 0, 0);
+		tsc210x_coords_cb(pdev->dev.parent, NULL, NULL);
+		tsc210x_touch_cb(pdev->dev.parent, NULL, NULL);
 		input_free_device(dev);
 		return status;
 	}
@@ -105,14 +117,13 @@ static int tsc210x_ts_probe(struct platf
 	return 0;
 }
 
-static int tsc210x_ts_remove(struct platform_device *pdev)
+static int __exit tsc210x_ts_remove(struct platform_device *pdev)
 {
-	struct input_dev *dev = (struct input_dev *)
-		platform_get_drvdata(pdev);
+	struct input_dev *dev = platform_get_drvdata(pdev);
 
-	tsc210x_touch_cb(pdev->dev.parent, 0, 0);
-	tsc210x_coords_cb(pdev->dev.parent, 0, 0);
-	platform_set_drvdata(pdev, 0);
+	tsc210x_touch_cb(pdev->dev.parent, NULL, NULL);
+	tsc210x_coords_cb(pdev->dev.parent, NULL, NULL);
+	platform_set_drvdata(pdev, NULL);
 	input_unregister_device(dev);
 	input_free_device(dev);
 
@@ -120,8 +131,8 @@ static int tsc210x_ts_remove(struct plat
 }
 
 static struct platform_driver tsc210x_ts_driver = {
-	.probe 		= tsc210x_ts_probe,
-	.remove 	= tsc210x_ts_remove,
+	.probe		= tsc210x_ts_probe,
+	.remove		= __exit_p(tsc210x_ts_remove),
 	/* Nothing to do on suspend/resume */
 	.driver		= {
 		.name	= "tsc210x-ts",
@@ -131,21 +142,17 @@ static struct platform_driver tsc210x_ts
 
 static int __init tsc210x_ts_init(void)
 {
-	int ret;
-
-	ret = platform_driver_register(&tsc210x_ts_driver);
-	if (ret)
-		return -ENODEV;
-
-	return 0;
+	/* can't use driver_probe() here since the parent device
+	 * gets registered "late"
+	 */
+	return platform_driver_register(&tsc210x_ts_driver);
 }
+module_init(tsc210x_ts_init);
 
 static void __exit tsc210x_ts_exit(void)
 {
 	platform_driver_unregister(&tsc210x_ts_driver);
 }
-
-module_init(tsc210x_ts_init);
 module_exit(tsc210x_ts_exit);
 
 MODULE_AUTHOR("Andrzej Zaborowski");
--- h4.orig/drivers/spi/Kconfig	2007-08-13 15:40:13.000000000 -0700
+++ h4/drivers/spi/Kconfig	2007-08-13 15:40:31.000000000 -0700
@@ -229,10 +229,17 @@ config SPI_TSC2102
 
 config SPI_TSC210X
 	depends on SPI_MASTER && EXPERIMENTAL
-	tristate "TSC2101/TSC2102 chips support"
+	tristate "TI TSC210x (TSC2101/TSC2102) support"
 	help
-	  Say Y here if you want support for the TSC210x chips.  It
-	  will be needed for the touchscreen driver on some boards.
+	  Say Y here if you want support for the TSC210x chips.  Some
+	  boards use these for touchscreen and audio support.
+
+	  These are members of a family of highly integrated PDA analog
+	  interface circuit.  They include a 12-bit ADC used for battery,
+	  temperature, touchscreen, and other sensors.  They also have
+	  an audio DAC and amplifier, and in some models an audio ADC.
+	  The audio support is highly chip-specific, but most of the
+	  sensor support works the same.
 
 	  Note that the device has to be present in the board's SPI
 	  devices table for this driver to load.  This driver doesn't
--- h4.orig/drivers/spi/tsc210x.c	2007-08-13 15:40:13.000000000 -0700
+++ h4/drivers/spi/tsc210x.c	2007-08-13 22:00:16.000000000 -0700
@@ -1,7 +1,5 @@
 /*
- * drivers/spi/tsc210x.c
- *
- * TSC2101/2102 interface driver.
+ * tsc210x.c - TSC2101/2102/... driver core
  *
  * Copyright (c) 2005-2007 Andrzej Zaborowski  <balrog@zabor.org>
  *
@@ -35,6 +33,13 @@
 #include <linux/spi/spi.h>
 #include <linux/spi/tsc210x.h>
 
+
+/* NOTE:  It should be straightforward to make this driver framework handle
+ * tsc2100 and tsc2111 chips, and maybe others too.  The main differences
+ * are in the audio codec capabilities, but there are also some differences
+ * in how the various sensors (including touchscreen) are handled.
+ */
+
 /* Bit field definitions for chip registers */
 
 /* Scan X, Y, Z1, Z2, chip controlled, 12-bit, 16 samples, 500 usec */
@@ -86,7 +91,8 @@
 
 struct tsc210x_spi_req {
 	struct spi_device *dev;
-	uint16_t command, data;
+	u16 command;
+	u16 data;
 	struct spi_message message;
 };
 
@@ -103,14 +109,19 @@ struct tsc210x_dev {
 	struct delayed_work sensor_worker;	/* Scan the ADC inputs */
 	spinlock_t queue_lock;
 	struct completion data_avail;
+
 	tsc210x_touch_t touch_cb;
 	void *touch_cb_ctx;
+
 	tsc210x_coords_t coords_cb;
 	void *coords_cb_ctx;
+
 	tsc210x_ports_t ports_cb;
 	void *ports_cb_ctx;
+
 	tsc210x_temp_t temp1_cb;
 	void *temp2_cb_ctx;
+
 	tsc210x_temp_t temp2_cb;
 	void *temp1_cb_ctx;
 
@@ -123,7 +134,8 @@ struct tsc210x_dev {
 
 	int pendown;
 	int flushing;			/* Queue flush in progress */
-	uint16_t status, adc_data[4];
+	u16 status;
+	u16 adc_data[4];
 	int bat[2], aux[2], temp[2];
 };
 
@@ -138,7 +150,7 @@ MODULE_PARM_DESC(touch_check_msecs, "Pen
 module_param_named(sensor_scan_msecs, settings.mode_msecs, uint, 0);
 MODULE_PARM_DESC(sensor_scan_msecs, "Temperature & battery scan interval");
 
-void tsc210x_write_sync(struct tsc210x_dev *dev,
+int tsc210x_write_sync(struct tsc210x_dev *dev,
 		int page, u8 address, u16 data)
 {
 	static struct tsc210x_spi_req req;
@@ -150,13 +162,13 @@ void tsc210x_write_sync(struct tsc210x_d
 	/* Address */
 	req.command = (page << 11) | (address << 5);
 	transfer[0].tx_buf = &req.command;
-	transfer[0].rx_buf = 0;
+	transfer[0].rx_buf = NULL;
 	transfer[0].len = 2;
 	spi_message_add_tail(&transfer[0], &req.message);
 
 	/* Data */
 	transfer[1].tx_buf = &data;
-	transfer[1].rx_buf = 0;
+	transfer[1].rx_buf = NULL;
 	transfer[1].len = 2;
 	transfer[1].cs_change = CS_CHANGE(1);
 	spi_message_add_tail(&transfer[1], &req.message);
@@ -164,20 +176,22 @@ void tsc210x_write_sync(struct tsc210x_d
 	ret = spi_sync(dev->spi, &req.message);
 	if (!ret && req.message.status)
 		ret = req.message.status;
-
 	if (ret)
-		printk(KERN_ERR "%s: error %i in SPI request\n",
-				__FUNCTION__, ret);
+		dev_dbg(&dev->spi->dev, "write_sync --> %d\n", ret);
+
+	return ret;
 }
+EXPORT_SYMBOL(tsc210x_write_sync);
 
-void tsc210x_reads_sync(struct tsc210x_dev *dev,
+int tsc210x_reads_sync(struct tsc210x_dev *dev,
 		int page, u8 startaddress, u16 *data, int numregs)
 {
 	static struct tsc210x_spi_req req;
 	static struct spi_transfer transfer[6];
 	int ret, i, j;
 
-	BUG_ON(numregs + 1 > ARRAY_SIZE(transfer));
+	if (numregs + 1 > ARRAY_SIZE(transfer))
+		return -EINVAL;
 
 	spi_message_init(&req.message);
 	i = 0;
@@ -186,13 +200,13 @@ void tsc210x_reads_sync(struct tsc210x_d
 	/* Address */
 	req.command = 0x8000 | (page << 11) | (startaddress << 5);
 	transfer[i].tx_buf = &req.command;
-	transfer[i].rx_buf = 0;
+	transfer[i].rx_buf = NULL;
 	transfer[i].len = 2;
 	spi_message_add_tail(&transfer[i ++], &req.message);
 
 	/* Data */
 	while (j < numregs) {
-		transfer[i].tx_buf = 0;
+		transfer[i].tx_buf = NULL;
 		transfer[i].rx_buf = &data[j ++];
 		transfer[i].len = 2;
 		transfer[i].cs_change = CS_CHANGE(j == numregs);
@@ -202,18 +216,23 @@ void tsc210x_reads_sync(struct tsc210x_d
 	ret = spi_sync(dev->spi, &req.message);
 	if (!ret && req.message.status)
 		ret = req.message.status;
-
 	if (ret)
-		printk(KERN_ERR "%s: error %i in SPI request\n",
-				__FUNCTION__, ret);
+		dev_dbg(&dev->spi->dev, "reads_sync --> %d\n", ret);
+
+	return ret;
 }
+EXPORT_SYMBOL(tsc210x_reads_sync);
 
-uint16_t tsc210x_read_sync(struct tsc210x_dev *dev, int page, uint8_t address)
+int tsc210x_read_sync(struct tsc210x_dev *dev, int page, u8 address)
 {
-	uint16_t ret;
-	tsc210x_reads_sync(dev, page, address, &ret, 1);
-	return ret;
+	u16 ret;
+	int status;
+
+	status = tsc210x_reads_sync(dev, page, address, &ret, 1);
+	return status ? : ret;
 }
+EXPORT_SYMBOL(tsc210x_read_sync);
+
 
 static void tsc210x_submit_async(struct tsc210x_spi_req *spi)
 {
@@ -221,13 +240,13 @@ static void tsc210x_submit_async(struct 
 
 	ret = spi_async(spi->dev, &spi->message);
 	if (ret)
-		printk(KERN_ERR "%s: error %i in SPI request\n",
+		dev_dbg(&spi->dev->dev, "%s: error %i in SPI request\n",
 				__FUNCTION__, ret);
 }
 
 static void tsc210x_request_alloc(struct tsc210x_dev *dev,
 		struct tsc210x_spi_req *spi, int direction,
-		int page, u8 startaddress, int numregs, uint16_t *data,
+		int page, u8 startaddress, int numregs, u16 *data,
 		void (*complete)(struct tsc210x_dev *context),
 		struct spi_transfer **transfer)
 {
@@ -248,7 +267,7 @@ static void tsc210x_request_alloc(struct
 		spi->command |= 1 << 15;
 
 	(*transfer)->tx_buf = &spi->command;
-	(*transfer)->rx_buf = 0;
+	(*transfer)->rx_buf = NULL;
 	(*transfer)->len = 2;
 	spi_message_add_tail((*transfer) ++, &spi->message);
 
@@ -257,7 +276,7 @@ static void tsc210x_request_alloc(struct
 		if (direction == 1)
 			(*transfer)->tx_buf = &spi->data;
 		else
-			(*transfer)->rx_buf = data ++;
+			(*transfer)->rx_buf = data++;
 		(*transfer)->len = 2;
 		(*transfer)->cs_change = CS_CHANGE(numregs != 1);
 		spi_message_add_tail((*transfer) ++, &spi->message);
@@ -267,13 +286,12 @@ static void tsc210x_request_alloc(struct
 #define tsc210x_cb_register_func(cb, cb_t)	\
 int tsc210x_ ## cb(struct device *dev, cb_t handler, void *context)	\
 {	\
-	struct tsc210x_dev *tsc = (struct tsc210x_dev *)	\
-		platform_get_drvdata(to_platform_device(dev));	\
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);	\
 	\
 	/* Lock the module */	\
 	if (handler && !tsc->cb)	\
 		if (!try_module_get(THIS_MODULE)) {	\
-			printk(KERN_INFO "Failed to get TSC module\n");	\
+			dev_err(dev, "Failed to get TSC module\n");	\
 		}	\
 	if (!handler && tsc->cb)	\
 		module_put(THIS_MODULE);	\
@@ -281,7 +299,8 @@ int tsc210x_ ## cb(struct device *dev, c
 	tsc->cb = handler;	\
 	tsc->cb ## _ctx = context;	\
 	return 0;	\
-}
+} \
+EXPORT_SYMBOL(tsc210x_ ## cb);
 
 tsc210x_cb_register_func(touch_cb, tsc210x_touch_t)
 tsc210x_cb_register_func(coords_cb, tsc210x_coords_t)
@@ -290,35 +309,30 @@ tsc210x_cb_register_func(temp1_cb, tsc21
 tsc210x_cb_register_func(temp2_cb, tsc210x_temp_t)
 
 #ifdef DEBUG
-static void tsc210x_print_dav(void)
+static void tsc210x_print_dav(struct tsc210x_dev *dev)
 {
-	u16 status = tsc210x_read_sync(dev, TSC210X_TS_STATUS_CTRL);
-	if (status & 0x0fff)
-		printk("TSC210x: data in");
-	if (status & 0x0400)
-		printk(" X");
-	if (status & 0x0200)
-		printk(" Y");
-	if (status & 0x0100)
-		printk(" Z1");
-	if (status & 0x0080)
-		printk(" Z2");
-	if (status & 0x0040)
-		printk(" BAT1");
-	if (status & 0x0020)
-		printk(" BAT2");
-	if (status & 0x0010)
-		printk(" AUX1");
-	if (status & 0x0008)
-		printk(" AUX2");
-	if (status & 0x0004)
-		printk(" TEMP1");
-	if (status & 0x0002)
-		printk(" TEMP2");
-	if (status & 0x0001)
-		printk(" KP");
-	if (status & 0x0fff)
-		printk(".\n");
+	int status = tsc210x_read_sync(dev, TSC210X_TS_STATUS_CTRL);
+
+	if (status < 0) {
+		dev_dbg(&dev->spi->dev, "status %d\n", status);
+		return;
+	}
+
+	if (!(status & 0x0fff))
+		return;
+
+	dev_dbg(&dev->spi->dev, "data in %s%s%s%s%s%s%s%s%s%s%s\n",
+		(status & 0x0400) ? " X" : "",
+		(status & 0x0200) ? " Y" : "",
+		(status & 0x0100) ? " Z1" : "",
+		(status & 0x0080) ? " Z2" : "",
+		(status & 0x0040) ? " BAT1" : "",
+		(status & 0x0020) ? " BAT2" : "",
+		(status & 0x0010) ? " AUX1" : "",
+		(status & 0x0008) ? " AUX2" : "",
+		(status & 0x0004) ? " TEMP1" : "",
+		(status & 0x0002) ? " TEMP2" : "",
+		(status & 0x0001) ? " KP" : "");
 }
 #endif
 
@@ -365,18 +379,20 @@ static void tsc210x_queue_scan(struct ts
 {
 	if (dev->pdata->monitor)
 		if (!queue_delayed_work(dev->queue,
-					&dev->sensor_worker,
-					msecs_to_jiffies(settings.mode_msecs)))
-			printk(KERN_ERR "%s: can't queue measurements\n",
+				&dev->sensor_worker,
+				msecs_to_jiffies(settings.mode_msecs)))
+			dev_err(&dev->spi->dev,
+					"%s: can't queue measurements\n",
 					__FUNCTION__);
 }
 
 static void tsc210x_queue_penup(struct tsc210x_dev *dev)
 {
 	if (!queue_delayed_work(dev->queue,
-				&dev->ts_worker,
-				msecs_to_jiffies(settings.ts_msecs)))
-		printk(KERN_ERR "%s: can't queue pen-up poll\n",
+			&dev->ts_worker,
+			msecs_to_jiffies(settings.ts_msecs)))
+		dev_err(&dev->spi->dev,
+				"%s: can't queue pen-up poll\n",
 				__FUNCTION__);
 }
 
@@ -398,16 +414,17 @@ static void tsc210x_status_report(struct
 		tsc210x_submit_async(&dev->req_adc);
 	}
 
-	if (dev->status & (TSC210X_PS_DAV | TSC210X_T1_DAV |TSC210X_T2_DAV))
+	if (dev->status & (TSC210X_PS_DAV | TSC210X_T1_DAV | TSC210X_T2_DAV))
 		complete(&dev->data_avail);
 }
 
 static void tsc210x_data_report(struct tsc210x_dev *dev)
 {
-	uint16_t adc_data[4];
+	u16 adc_data[4];
 
 	if (dev->status & TSC210X_PS_DAV) {
 		tsc210x_reads_sync(dev, TSC210X_TS_BAT1, adc_data, 4);
+		/* NOTE: reads_sync() could fail */
 
 		dev->bat[0] = adc_data[0];
 		dev->bat[1] = adc_data[1];
@@ -420,14 +437,14 @@ static void tsc210x_data_report(struct t
 	if (dev->status & TSC210X_T1_DAV) {
 		dev->temp[0] = tsc210x_read_sync(dev, TSC210X_TS_TEMP1);
 
-		if (dev->temp1_cb)
+		if (dev->temp[0] >= 0 && dev->temp1_cb)
 			dev->temp1_cb(dev->temp1_cb_ctx, dev->temp[0]);
 	}
 
 	if (dev->status & TSC210X_T2_DAV) {
 		dev->temp[1] = tsc210x_read_sync(dev, TSC210X_TS_TEMP2);
 
-		if (dev->temp2_cb)
+		if (dev->temp[1] >= 0 && dev->temp2_cb)
 			dev->temp2_cb(dev->temp2_cb_ctx, dev->temp[1]);
 	}
 }
@@ -456,16 +473,20 @@ static void tsc210x_pressure(struct work
 {
 	struct tsc210x_dev *dev =
 		container_of(work, struct tsc210x_dev, ts_worker.work);
-	uint16_t adc_status;
+	int adc_status;
 
-	BUG_ON(!dev->pendown);
+	WARN_ON(!dev->pendown);
 
 	adc_status = tsc210x_read_sync(dev, TSC210X_TS_ADC_CTRL);
+	if (adc_status < 0) {
+		dev_dbg(&dev->spi->dev, "pressure, err %d\n", adc_status);
+		return;
+	}
 
-	if ((adc_status & TSC210X_ADC_PSTCM) ||
-			!(adc_status & TSC210X_ADC_ADST)) {
+	if ((adc_status & TSC210X_ADC_PSTCM) != 0
+			|| !(adc_status & TSC210X_ADC_ADST))
 		tsc210x_queue_penup(dev);
-	} else {
+	else {
 		dev->pendown = 0;
 		if (dev->touch_cb)
 			dev->touch_cb(dev->touch_cb_ctx, 0);
@@ -519,17 +540,26 @@ static irqreturn_t tsc210x_handler(int i
 	return IRQ_HANDLED;
 }
 
-#ifdef CONFIG_SOUND
+#if defined(CONFIG_SOUND) || defined(CONFIG_SOUND_MODULE)
+
+/*
+ * FIXME the audio support shouldn't be included in upstream patches
+ * until it's ready.  They might be better as utility functions linked
+ * with a chip-specific tsc21xx audio module ... e.g. chips with input
+ * channels need more, as will ones with multiple output channels and
+ * so on.  Each of these functions should probably return a fault code,
+ * and will need to be exported so the sound drier can be modular.
+ */
+
 /*
  * Volume level values should be in the range [0, 127].
  * Higher values mean lower volume.
  */
-void tsc210x_set_dac_volume(struct device *dev,
-		uint8_t left_ch, uint8_t right_ch)
+void tsc210x_set_dac_volume(struct device *dev, u8 left_ch, u8 right_ch)
 {
-	struct tsc210x_dev *tsc = (struct tsc210x_dev *)
-		platform_get_drvdata(to_platform_device(dev));
-	u16 val;
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);
+	int val;
+
 	if (tsc->kind == tsc2102) {
 		/* All 0's or all 1's */
 		if (left_ch == 0x00 || left_ch == 0x7f)
@@ -539,34 +569,46 @@ void tsc210x_set_dac_volume(struct devic
 	}
 
 	val = tsc210x_read_sync(tsc, TSC210X_DAC_GAIN_CTRL);
+	if (val < 0) {
+		dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val);
+		return;
+	}
 
 	val &= 0x8080;	/* Preserve mute-bits */
 	val |= (left_ch << 8) | right_ch;
 
 	tsc210x_write_sync(tsc, TSC210X_DAC_GAIN_CTRL, val);
+	/* NOTE: write_sync() could fail */
 }
 
 void tsc210x_set_dac_mute(struct device *dev, int left_ch, int right_ch)
 {
-	struct tsc210x_dev *tsc = (struct tsc210x_dev *)
-		platform_get_drvdata(to_platform_device(dev));
-	u16 val;
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);
+	int val;
 
 	val = tsc210x_read_sync(tsc, TSC210X_DAC_GAIN_CTRL);
+	if (val < 0) {
+		dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val);
+		return;
+	}
 
 	val &= 0x7f7f;	/* Preserve volume settings */
 	val |= (left_ch << 15) | (right_ch << 7);
 
 	tsc210x_write_sync(tsc, TSC210X_DAC_GAIN_CTRL, val);
+	/* NOTE: write_sync() could fail */
 }
 
 void tsc210x_get_dac_mute(struct device *dev, int *left_ch, int *right_ch)
 {
-	struct tsc210x_dev *tsc = (struct tsc210x_dev *)
-		platform_get_drvdata(to_platform_device(dev));
-	u16 val;
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);
+	int val;
 
 	val = tsc210x_read_sync(tsc, TSC210X_DAC_GAIN_CTRL);
+	if (val < 0) {
+		dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val);
+		return;
+	}
 
 	*left_ch = !!(val & (1 << 15));
 	*right_ch = !!(val & (1 << 7));
@@ -574,10 +616,14 @@ void tsc210x_get_dac_mute(struct device 
 
 void tsc210x_set_deemphasis(struct device *dev, int enable)
 {
-	struct tsc210x_dev *tsc = (struct tsc210x_dev *)
-		platform_get_drvdata(to_platform_device(dev));
-	u16 val;
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);
+	int val;
+
 	val = tsc210x_read_sync(tsc, TSC210X_POWER_CTRL);
+	if (val < 0) {
+		dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val);
+		return;
+	}
 
 	if (enable)
 		val &= ~TSC210X_DEEMPF;
@@ -585,14 +631,19 @@ void tsc210x_set_deemphasis(struct devic
 		val |= TSC210X_DEEMPF;
 
 	tsc210x_write_sync(tsc, TSC210X_POWER_CTRL, val);
+	/* NOTE: write_sync() could fail */
 }
 
 void tsc2102_set_bassboost(struct device *dev, int enable)
 {
-	struct tsc210x_dev *tsc = (struct tsc210x_dev *)
-		platform_get_drvdata(to_platform_device(dev));
-	u16 val;
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);
+	int val;
+
 	val = tsc210x_read_sync(tsc, TSC210X_POWER_CTRL);
+	if (val < 0) {
+		dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val);
+		return;
+	}
 
 	if (enable)
 		val &= ~TSC2102_BASSBC;
@@ -600,6 +651,7 @@ void tsc2102_set_bassboost(struct device
 		val |= TSC2102_BASSBC;
 
 	tsc210x_write_sync(tsc, TSC210X_POWER_CTRL, val);
+	/* NOTE: write_sync() could fail */
 }
 
 /*	{rate, dsor, fsref}	*/
@@ -629,7 +681,7 @@ static const struct tsc210x_rate_info_s 
 	{44100,	0,	1},
 	{48000,	0,	0},
 
-	{0,	0, 	0},
+	{0,	0,	0},
 };
 
 /*	{rate, dsor, fsref}	*/
@@ -659,15 +711,14 @@ static const struct tsc210x_rate_info_s 
 	{44100,	0,	1},
 	{48000,	0,	0},
 
-	{0,	0, 	0},
+	{0,	0,	0},
 };
 
 int tsc210x_set_rate(struct device *dev, int rate)
 {
-	struct tsc210x_dev *tsc = (struct tsc210x_dev *)
-		platform_get_drvdata(to_platform_device(dev));
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);
 	int i;
-	uint16_t val;
+	int val;
 	const struct tsc210x_rate_info_s *rates;
 
 	if (tsc->kind == tsc2101)
@@ -679,21 +730,30 @@ int tsc210x_set_rate(struct device *dev,
 		if (rates[i].sample_rate == rate)
 			break;
 	if (rates[i].sample_rate == 0) {
-		printk(KERN_ERR "Unknown sampling rate %i.0 Hz\n", rate);
+		dev_err(dev, "Unknown sampling rate %i.0 Hz\n", rate);
 		return -EINVAL;
 	}
 
 	if (tsc->kind == tsc2101) {
-		val = tsc210x_read_sync(tsc, TSC210X_AUDIO1_CTRL) &
-			~((7 << 3) | (7 << 0));
+		val = tsc210x_read_sync(tsc, TSC210X_AUDIO1_CTRL);
+		if (val < 0) {
+			dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val);
+			return val;
+		}
+		val &= ~((7 << 3) | (7 << 0));
 		val |= rates[i].divisor << 3;
 		val |= rates[i].divisor << 0;
 	} else
 		val = rates[i].divisor;
 
 	tsc210x_write_sync(tsc, TSC210X_AUDIO1_CTRL, val);
+	/* NOTE: write_sync() could fail */
 
 	val = tsc210x_read_sync(tsc, TSC210X_AUDIO3_CTRL);
+	if (val < 0) {
+		dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val);
+		return val;
+	}
 
 	if (tsc2102_rates[i].fs_44k) {
 		tsc210x_write_sync(tsc,
@@ -717,9 +777,9 @@ int tsc210x_set_rate(struct device *dev,
  */
 void tsc210x_dac_power(struct device *dev, int on)
 {
-	struct tsc210x_dev *tsc = (struct tsc210x_dev *)
-		platform_get_drvdata(to_platform_device(dev));
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);
 
+	/* NOTE: write_sync() could fail */
 	if (on) {
 		/* 16-bit words, DSP mode, sample at Fsref */
 		tsc210x_write_sync(tsc,
@@ -760,12 +820,16 @@ void tsc210x_dac_power(struct device *de
 
 void tsc210x_set_i2s_master(struct device *dev, int state)
 {
-	struct tsc210x_dev *tsc = (struct tsc210x_dev *)
-		platform_get_drvdata(to_platform_device(dev));
-	uint16_t val;
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);
+	int val;
 
 	val = tsc210x_read_sync(tsc, TSC210X_AUDIO3_CTRL);
+	if (val < 0) {
+		dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val);
+		return;
+	}
 
+	/* NOTE: write_sync() could fail */
 	if (state)
 		tsc210x_write_sync(tsc, TSC210X_AUDIO3_CTRL,
 				val | TSC210X_SLVMS);
@@ -777,6 +841,8 @@ void tsc210x_set_i2s_master(struct devic
 
 static int tsc210x_configure(struct tsc210x_dev *dev)
 {
+	/* NOTE: write_sync() could fail */
+
 	/* Reset the chip */
 	tsc210x_write_sync(dev, TSC210X_TS_RESET_CTRL, TSC210X_RESET);
 
@@ -798,19 +864,17 @@ static int tsc210x_configure(struct tsc2
 	return 0;
 }
 
-/*
- * Retrieves chip revision.  Should be always 1.
- */
-int tsc210x_get_revision(struct tsc210x_dev *dev)
-{
-	return tsc210x_read_sync(dev, TSC210X_AUDIO3_CTRL) & 7;
-}
-
 void tsc210x_keyclick(struct tsc210x_dev *dev,
 		int amplitude, int freq, int length)
 {
-	u16 val;
+	int val;
+
 	val = tsc210x_read_sync(dev, TSC210X_AUDIO2_CTRL);
+	if (val < 0) {
+		dev_dbg(&dev->spi->dev, "%s, err %d\n",
+				__FUNCTION__, val);
+		return;
+	}
 	val &= 0x800f;
 
 	/* Set amplitude */
@@ -845,8 +909,10 @@ void tsc210x_keyclick(struct tsc210x_dev
 	/* Enable keyclick */
 	val |= 0x8000;
 
+	/* NOTE: write_sync() could fail */
 	tsc210x_write_sync(dev, TSC210X_AUDIO2_CTRL, val);
 }
+EXPORT_SYMBOL(tsc210x_keyclick);
 
 #ifdef CONFIG_PM
 /*
@@ -873,8 +939,7 @@ tsc210x_suspend(struct spi_device *spi, 
 
 	/* Abort current conversion and power down the ADC */
 	tsc210x_write_sync(dev, TSC210X_TS_ADC_CTRL, TSC210X_ADC_ADST);
-
-	dev->spi->dev.power.power_state = state;
+	/* NOTE: write_sync() could fail */
 
 	return 0;
 }
@@ -890,8 +955,6 @@ static int tsc210x_resume(struct spi_dev
 	if (!dev)
 		return 0;
 
-	dev->spi->dev.power.power_state = PMSG_ON;
-
 	spin_lock(&dev->queue_lock);
 	err = tsc210x_configure(dev);
 
@@ -905,19 +968,20 @@ static int tsc210x_resume(struct spi_dev
 #define tsc210x_resume	NULL
 #endif
 
+/* REVISIT don't make these static */
 static struct platform_device tsc210x_ts_device = {
-	.name 		= "tsc210x-ts",
-	.id 		= -1,
+	.name		= "tsc210x-ts",
+	.id		= -1,
 };
 
 static struct platform_device tsc210x_hwmon_device = {
-	.name 		= "tsc210x-hwmon",
-	.id 		= -1,
+	.name		= "tsc210x-hwmon",
+	.id		= -1,
 };
 
 static struct platform_device tsc210x_alsa_device = {
-	.name 		= "tsc210x-alsa",
-	.id 		= -1,
+	.name		= "tsc210x-alsa",
+	.id		= -1,
 };
 
 static int tsc210x_probe(struct spi_device *spi, enum tsc_type type)
@@ -925,22 +989,23 @@ static int tsc210x_probe(struct spi_devi
 	struct tsc210x_config *pdata = spi->dev.platform_data;
 	struct spi_transfer *spi_buffer;
 	struct tsc210x_dev *dev;
+	int reg;
 	int err = 0;
 
 	if (!pdata) {
-		printk(KERN_ERR "TSC210x: Platform data not supplied\n");
+		dev_dbg(&spi->dev, "Platform data not supplied\n");
 		return -ENOENT;
 	}
 
 	if (!spi->irq) {
-		printk(KERN_ERR "TSC210x: Invalid irq value\n");
+		dev_dbg(&spi->dev, "Invalid irq value\n");
 		return -EINVAL;
 	}
 
 	dev = (struct tsc210x_dev *)
 		kzalloc(sizeof(struct tsc210x_dev), GFP_KERNEL);
 	if (!dev) {
-		printk(KERN_ERR "TSC210x: No memory\n");
+		dev_dbg(&spi->dev, "No memory\n");
 		return -ENOMEM;
 	}
 
@@ -950,7 +1015,7 @@ static int tsc210x_probe(struct spi_devi
 	dev->kind = type;
 	dev->queue = create_singlethread_workqueue(spi->dev.driver->name);
 	if (!dev->queue) {
-		printk(KERN_ERR "TSC210x: Can't make a workqueue\n");
+		dev_dbg(&spi->dev, "Can't make a workqueue\n");
 		err = -ENOMEM;
 		goto err_queue;
 	}
@@ -961,7 +1026,7 @@ static int tsc210x_probe(struct spi_devi
 	/* Allocate enough struct spi_transfer's for all requests */
 	spi_buffer = kzalloc(sizeof(struct spi_transfer) * 16, GFP_KERNEL);
 	if (!spi_buffer) {
-		printk(KERN_ERR "TSC210x: No memory for SPI buffers\n");
+		dev_dbg(&spi->dev, "No memory for SPI buffers\n");
 		err = -ENOMEM;
 		goto err_buffers;
 	}
@@ -974,10 +1039,10 @@ static int tsc210x_probe(struct spi_devi
 			TSC210X_TS_STATUS_CTRL, 1, &dev->status,
 			tsc210x_status_report, &spi_buffer);
 	tsc210x_request_alloc(dev, &dev->req_mode, 1,
-			TSC210X_TS_ADC_CTRL, 1, 0,
+			TSC210X_TS_ADC_CTRL, 1, NULL,
 			tsc210x_complete_dummy, &spi_buffer);
 	tsc210x_request_alloc(dev, &dev->req_stop, 1,
-			TSC210X_TS_ADC_CTRL, 1, 0,
+			TSC210X_TS_ADC_CTRL, 1, NULL,
 			tsc210x_complete_dummy, &spi_buffer);
 
 	if (pdata->bclk) {
@@ -985,7 +1050,7 @@ static int tsc210x_probe(struct spi_devi
 		dev->bclk_ck = clk_get(&spi->dev, pdata->bclk);
 		if (IS_ERR(dev->bclk_ck)) {
 			err = PTR_ERR(dev->bclk_ck);
-			printk(KERN_ERR "Unable to get '%s': %i\n",
+			dev_dbg(&spi->dev, "Unable to get '%s': %i\n",
 					pdata->bclk, err);
 			goto err_clk;
 		}
@@ -998,20 +1063,36 @@ static int tsc210x_probe(struct spi_devi
 
 	/* Setup the communication bus */
 	dev_set_drvdata(&spi->dev, dev);
-	spi->dev.power.power_state = PMSG_ON;
 	spi->mode = SPI_MODE_1;
 	spi->bits_per_word = 16;
 	err = spi_setup(spi);
 	if (err)
 		goto err_spi;
 
-	/* Now try to detect the chip, make first contact */
-	if (tsc210x_get_revision(dev) != 0x1) {
-		printk(KERN_ERR "No TI %s chip found!\n",
-				spi->dev.driver->name);
-		err = -ENODEV;
+	/* Now try to detect the chip, make first contact.  These chips
+	 * don't self-identify, but we can expect that the status register
+	 * reports the ADC is idle and use that as a sanity check.  (It'd
+	 * be even better if we did a soft reset first...)
+	 */
+	reg = tsc210x_read_sync(dev, TSC210X_TS_ADC_CTRL);
+	if (reg < 0) {
+		err = reg;
+		dev_dbg(&dev->spi->dev, "adc_ctrl, err %d\n", err);
 		goto err_spi;
 	}
+	if (!(reg & (1 << 14))) {
+		err = -EIO;
+		dev_dbg(&dev->spi->dev, "adc_ctrl, busy? - %04x\n", reg);
+		goto err_spi;
+	}
+
+	reg = tsc210x_read_sync(dev, TSC210X_AUDIO3_CTRL);
+	if (reg < 0) {
+		err = reg;
+		dev_dbg(&dev->spi->dev, "revision, err %d\n", err);
+		goto err_spi;
+	}
+	dev_info(&spi->dev, "rev %d, irq %d\n", reg & 0x0007, spi->irq);
 
 	err = tsc210x_configure(dev);
 	if (err)
@@ -1024,7 +1105,7 @@ static int tsc210x_probe(struct spi_devi
 	if (request_irq(spi->irq, tsc210x_handler, IRQF_SAMPLE_RANDOM |
 				IRQF_TRIGGER_FALLING, spi->dev.driver->name,
 				dev)) {
-		printk(KERN_ERR "Could not allocate touchscreen IRQ!\n");
+		dev_dbg(&spi->dev, "Could not allocate touchscreen IRQ!\n");
 		err = -EINVAL;
 		goto err_irq;
 	}
@@ -1098,6 +1179,7 @@ static int tsc210x_remove(struct spi_dev
 
 	/* Abort current conversion and power down the ADC */
 	tsc210x_write_sync(dev, TSC210X_TS_ADC_CTRL, TSC210X_ADC_ADST);
+	/* NOTE: write_sync() could fail */
 
 	destroy_workqueue(dev->queue);
 
@@ -1161,21 +1243,15 @@ static int __init tsc210x_init(void)
 
 	return err;
 }
+module_init(tsc210x_init);
 
 static void __exit tsc210x_exit(void)
 {
 	spi_unregister_driver(&tsc2101_driver);
 	spi_unregister_driver(&tsc2102_driver);
 }
-
-module_init(tsc210x_init);
 module_exit(tsc210x_exit);
 
-EXPORT_SYMBOL(tsc210x_read_sync);
-EXPORT_SYMBOL(tsc210x_reads_sync);
-EXPORT_SYMBOL(tsc210x_write_sync);
-EXPORT_SYMBOL(tsc210x_keyclick);
-
 MODULE_AUTHOR("Andrzej Zaborowski");
 MODULE_DESCRIPTION("Interface driver for TI TSC210x chips.");
 MODULE_LICENSE("GPL");
--- h4.orig/include/linux/spi/tsc210x.h	2007-08-13 15:40:13.000000000 -0700
+++ h4/include/linux/spi/tsc210x.h	2007-08-13 22:10:26.000000000 -0700
@@ -24,15 +24,16 @@
 #define __LINUX_SPI_TSC210X_H
 
 struct apm_power_info;
+
 struct tsc210x_config {
 	int use_internal;	/* Use internal reference voltage */
-	uint32_t monitor;	/* What inputs are relevant */
+	u32 monitor;		/* What inputs are wired on this board */
 	int temp_at25c[2];	/* Thermometer calibration data */
 	void (*apm_report)(struct apm_power_info *info, int battery[]);
 				/* Report status to APM based on battery[] */
 	void *alsa_config;	/* .platform_data for the ALSA device */
-	const char *mclk;	/* Optional: bclk name */
-	const char *bclk;	/* Optional: mclk name */
+	const char *mclk;	/* Optional: mclk name */
+	const char *bclk;	/* Optional: bclk name */
 };
 
 #define TSC_BAT1	(1 << 0)
@@ -45,16 +46,25 @@ struct tsc210x_config {
 #define TSC_VBAT	TSC_BAT1
 
 struct tsc210x_dev;
-extern u16 tsc210x_read_sync(struct tsc210x_dev *dev, int page, u8 address);
-extern void tsc210x_reads_sync(struct tsc210x_dev *dev, int page,
+
+/* Drivers for tsc210x components like touchscreen, sensor, and audio
+ * are packaged as platform drivers which can issue synchronous register
+ * acceses, and may also register a callback to process their particular
+ * type of data when that data is automatically sampled.  The platform
+ * device is a child of the TSC spi device.
+ */
+
+extern int tsc210x_read_sync(struct tsc210x_dev *dev, int page, u8 address);
+extern int tsc210x_reads_sync(struct tsc210x_dev *dev, int page,
 		u8 startaddress, u16 *data, int numregs);
-extern void tsc210x_write_sync(struct tsc210x_dev *dev, int page,
+extern int tsc210x_write_sync(struct tsc210x_dev *dev, int page,
 		u8 address, u16 data);
 
 typedef void (*tsc210x_touch_t)(void *context, int touching);
 typedef void (*tsc210x_coords_t)(void *context, int x, int y, int z1, int z2);
 typedef void (*tsc210x_ports_t)(void *context, int bat[], int aux[]);
 typedef void (*tsc210x_temp_t)(void *context, int temp);
+
 extern int tsc210x_touch_cb(struct device *dev,
 		tsc210x_touch_t handler, void *context);
 extern int tsc210x_coords_cb(struct device *dev,
@@ -66,13 +76,10 @@ extern int tsc210x_temp1_cb(struct devic
 extern int tsc210x_temp2_cb(struct device *dev,
 		tsc210x_temp_t handler, void *context);
 
-#ifdef CONFIG_SOUND
-extern void tsc210x_set_dac_volume(struct device *dev,
-		uint8_t left_ch, uint8_t right_ch);
-extern void tsc210x_set_dac_mute(struct device *dev,
-		int left_ch, int right_ch);
-extern void tsc210x_get_dac_mute(struct device *dev,
-		int *left_ch, int *right_ch);
+#if defined(CONFIG_SOUND) || defined(CONFIG_SOUND_MODULE)
+extern void tsc210x_set_dac_volume(struct device *dev, u8 left, u8 right);
+extern void tsc210x_set_dac_mute(struct device *dev, int left, int right);
+extern void tsc210x_get_dac_mute(struct device *dev, int *left, int *right);
 extern void tsc210x_dac_power(struct device *dev, int on);
 extern int tsc210x_set_rate(struct device *dev, int rate);
 extern void tsc210x_set_i2s_master(struct device *dev, int state);

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

* [patch 2.6.23-rc2-omap1] H4 support for tsc210x
  2007-08-13  7:05             ` Tony Lindgren
  2007-08-14  5:34               ` David Brownell
  2007-08-14  5:38               ` [patch 2.6.23-rc2-omap1] tsc210x cleanup David Brownell
@ 2007-08-14  5:40               ` David Brownell
  2007-08-14  6:45                 ` David Brownell
  2 siblings, 1 reply; 17+ messages in thread
From: David Brownell @ 2007-08-14  5:40 UTC (permalink / raw)
  To: Tony Lindgren; +Cc: Linux OMAP

H4 board support now lists its tsc210x touchscreen/sensor/codec chip.

Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
 
--- h4.orig/arch/arm/mach-omap2/board-h4.c	2007-08-13 12:32:12.000000000 -0700
+++ h4/arch/arm/mach-omap2/board-h4.c	2007-08-13 15:07:22.000000000 -0700
@@ -22,6 +22,8 @@
 #include <linux/err.h>
 #include <linux/clk.h>
 #include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/tsc210x.h>
 
 #include <asm/hardware.h>
 #include <asm/mach-types.h>
@@ -418,6 +420,33 @@ static struct omap_usb_config h4_usb_con
 #endif
 };
 
+/* ----------------------------------------------------------------------- */
+
+static struct tsc210x_config tsc_platform_data = {
+	.use_internal		= 1,
+	.monitor		= TSC_VBAT | TSC_TEMP,
+	/* REVISIT temp calibration data -- board-specific; from EEPROM? */
+	.mclk			= "sys_clkout",
+};
+
+static struct spi_board_info h4_spi_board_info[] __initdata = {
+	{
+		.modalias	= "tsc2101",
+		.bus_num	= 1,
+		.chip_select	= 0,
+		.mode		= SPI_MODE_1,
+		.irq		= OMAP_GPIO_IRQ(93),
+		.max_speed_hz	= 16000000,
+		.platform_data	= &tsc_platform_data,
+	},
+
+	/* nCS1 -- to lcd board, but unused
+	 * nCS2 -- to WLAN/miniPCI
+	 */
+};
+
+/* ----------------------------------------------------------------------- */
+
 static struct omap_board_config_kernel h4_config[] __initdata = {
 	{ OMAP_TAG_UART,	&h4_uart_config },
 	{ OMAP_TAG_MMC,		&h4_mmc_config },
@@ -550,6 +579,28 @@ static void __init omap_h4_init(void)
 	tusb_evm_setup();
 #endif
 
+	/* defaults seem ok for:
+	 * omap_cfg_reg(U18_24XX_SPI1_SCK);
+	 * omap_cfg_reg(V20_24XX_SPI1_MOSI);
+	 * omap_cfg_reg(T18_24XX_SPI1_MISO);
+	 * omap_cfg_reg(U19_24XX_SPI1_NCS0);
+	 */
+
+	/* TSC2101 */
+	omap_cfg_reg(P20_24XX_GPIO93);
+	gpio_request(93, "tsc_irq");
+	gpio_direction_input(93);
+
+	omap_cfg_reg(W14_24XX_SYS_CLKOUT);	/* mclk */
+	/* defaults seem ok for:
+	 * omap_cfg_reg(Y15_EAC_AC_SCLK);	// bclk
+	 * omap_cfg_reg(R14_EAC_AC_FS);
+	 * omap_cfg_reg(V15_EAC_AC_DOUT);
+	 * omap_cfg_reg(W15_EAC_AC_DIN);
+	 */
+
+	spi_register_board_info(h4_spi_board_info,
+				ARRAY_SIZE(h4_spi_board_info));
 }
 
 static int gpio_exp_init(void)

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

* Re: [patch 2.6.23-rc2-omap1] H4 support for tsc210x
  2007-08-14  5:40               ` [patch 2.6.23-rc2-omap1] H4 support for tsc210x David Brownell
@ 2007-08-14  6:45                 ` David Brownell
  2007-08-15 10:54                   ` Tony Lindgren
  0 siblings, 1 reply; 17+ messages in thread
From: David Brownell @ 2007-08-14  6:45 UTC (permalink / raw)
  To: Tony Lindgren, andrzej zaborowski; +Cc: Linux OMAP

Note that the new tsc210x code needs work yet before it's ready
to handle an H2, even just for the LCD support ... maybe it's
too ambitious to think of getting it upstream in 2.6.23-early.

- Dave

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

* Re: [patch 2.6.23-rc2-omap1] H4 support for tsc210x
  2007-08-14  6:45                 ` David Brownell
@ 2007-08-15 10:54                   ` Tony Lindgren
  0 siblings, 0 replies; 17+ messages in thread
From: Tony Lindgren @ 2007-08-15 10:54 UTC (permalink / raw)
  To: David Brownell; +Cc: Linux OMAP

* David Brownell <david-b@pacbell.net> [070813 23:45]:
> Note that the new tsc210x code needs work yet before it's ready
> to handle an H2, even just for the LCD support ... maybe it's
> too ambitious to think of getting it upstream in 2.6.23-early.

Pushing today anyways.

Tony

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

* Re: [patch 2.6.23-rc2-omap1] tsc210x cleanup
  2007-08-14  5:38               ` [patch 2.6.23-rc2-omap1] tsc210x cleanup David Brownell
@ 2007-08-15 10:54                 ` Tony Lindgren
  0 siblings, 0 replies; 17+ messages in thread
From: Tony Lindgren @ 2007-08-15 10:54 UTC (permalink / raw)
  To: David Brownell; +Cc: Linux OMAP

* David Brownell <david-b@pacbell.net> [070813 23:11]:
> This is mostly cleanup of the tsc210x patch, but some bugs were fixed so now
> it works on tsc2101 too.  Also, a few issues are now noted in the code:
> 
>  Tool-reported:
>   - Address checkpatch.pl issues in the original patch
>   - And also "sparse" issues
> 
>  Previously wrong:
>   - Cope with CONFIG_SOUND_MODULE
>   - Register accessor routines will now return error codes
>   - ... many callers now abort cleanly after errors
>   - Don't depend on seeing only a rev #1 tsc2102 chip!
>   - Add missing EXPORT_SYMBOL declarations
> 
>  Style issues:
>   - BUG_ON() is strongly to be avoided
>   - So are macros that capture variables
>   - And needless casting from void pointers
>   - And type punning
>   - Use dev_*() for messaging where practical
>   - Use u16 not uint16_t, etc
>   - Various other whitespace issues
>   - Avoid __FUNCTION__
>   - Single-lines for file-top comments; no whole paths
> 
>  Object code size and Other cleanup:
>   - Compile most strings out unless -DDEBUG is configured (saving space)
>   - Move some code into exit sections (then it may well vanish, saving space)
>   - Add some header comments
> 
>  Open issues:
>   - Hwmon needs to know VREF and the chip type, and to scale its values
>   - Upstream push should exclude audio until some version is working
>   - Audio will needs to export symbols to modules, too
>   - Lots of the I/O calls (especially audio) still don't handle errors
>   - How could board init code get board-specific temp calibration data?
>  
> Also the Kconfig is more open-ended; tsc210x might in the future include the
> tsc2100 and tsc2111, it doesn't need to be limited to tsc2101 and tsc2102.

Pushing today.

Tony

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

* Re: Common code for TSC 2101 and 2102
  2007-08-14  5:34               ` David Brownell
@ 2007-09-23 22:37                 ` andrzej zaborowski
  0 siblings, 0 replies; 17+ messages in thread
From: andrzej zaborowski @ 2007-09-23 22:37 UTC (permalink / raw)
  To: David Brownell; +Cc: Linux OMAP

Hi, sorry for late reply, I'm just catching up with email now.

On 14/08/2007, David Brownell <david-b@pacbell.net> wrote:
> Following this will be several patches, two related to
> this driver:
>
>  - H4 board support, using TSC2101 (not TSC2102)
>  - A biggish tsc210x cleanup patch, with some bugfixes

Thanks for this work.

>
> For boards using external VREF, that value should be in
> the tsc210x chip platform data.  (And Andrzej, I'm
> curious why you settled on VREF=1.25V vs 2.50V ... not
> that it's necessarily an issue, just "why".  A comment
> in the code might be good.)

Oh, I'm afraid I left it to be made configurable by the first person
who needs VREF of 2.5V, out of laziness. I settled on 1.25V
specifically because that's what PalmOS (the OS that ships with Palm
Tungsten E) uses. The device is a consumer PDA and I have no
schematics or even multimeter access to the chips, and likely I
wouldn't know how to use it if I had.

>
> Somewhat related, I think the TSC setup code should
> likely hold off until it can fetch calibration data
> from EEPROM or somesuch ... temperature readings are
> not all that useful until that data is available.
> Alternatively, do like I did in ads7846:  punt it all
> to userspace, just provide the raw milliVolt readings
> and expect someone else to compute temperature.

The board setup code can defer the TSC setup by registering the
tsc210x device only after all information is available to fill in the
pdata struct.

Regards

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

end of thread, other threads:[~2007-09-23 22:37 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2007-06-04 13:44 Common code for TSC 2101 and 2102 andrzej zaborowski
2007-07-13  6:46 ` Trilok Soni
2007-07-13  7:10   ` Trilok Soni
2007-07-13 19:29     ` andrzej zaborowski
2007-07-13 20:04       ` David Brownell
2007-08-10  7:29         ` Tony Lindgren
2007-08-11  3:04           ` David Brownell
2007-08-13  7:05             ` Tony Lindgren
2007-08-14  5:34               ` David Brownell
2007-09-23 22:37                 ` andrzej zaborowski
2007-08-14  5:38               ` [patch 2.6.23-rc2-omap1] tsc210x cleanup David Brownell
2007-08-15 10:54                 ` Tony Lindgren
2007-08-14  5:40               ` [patch 2.6.23-rc2-omap1] H4 support for tsc210x David Brownell
2007-08-14  6:45                 ` David Brownell
2007-08-15 10:54                   ` Tony Lindgren
2007-07-13 18:38   ` Common code for TSC 2101 and 2102 David Brownell
2007-07-15 20:18 ` David Brownell

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox