[Patch 08/10] input: Touchscreen driver for the PCAP

Subsystems: the rest · Maintainer: Linus Torvalds

STALE6682d

8 messages, 3 authors, 2008-07-11 · open the first message on its own page

[Patch 08/10] input: Touchscreen driver for the PCAP

From: stefan@datenfreihafen.org
Date: 2008-07-07 18:40:08

Touchscreen driver based on the MFD PCAP available on the EZX mobile phones.

Signed-off-by: Daniel Ribeiro <redacted>

PATCH FOLLOWS
KernelVersion: 2.6-arm-git pxa branch

Index: linux-2.6-arm/drivers/input/touchscreen/Kconfig
===================================================================
--- linux-2.6-arm.orig/drivers/input/touchscreen/Kconfig
+++ linux-2.6-arm/drivers/input/touchscreen/Kconfig
@@ -316,4 +316,13 @@
 	bool "GoTop Super_Q2/GogoPen/PenPower tablet device support" if EMBEDDED
 	depends on TOUCHSCREEN_USB_COMPOSITE
 
+config TOUCHSCREEN_PCAP
+	tristate "Motorola PCAP touchscreen"
+	depends on EZX_PCAP
+	help
+	  Say Y here if you have a Motorola EZX telephone and
+	  want to support the built-in touchscreen.
+
+	  If unsure, say N.
+
 endif
Index: linux-2.6-arm/drivers/input/touchscreen/pcap_ts.c
===================================================================
--- /dev/null
+++ linux-2.6-arm/drivers/input/touchscreen/pcap_ts.c
@@ -0,0 +1,316 @@
+/*
+ * pcap_ts.c - Touchscreen driver for Motorola PCAP2 based touchscreen as found
+ * 	       in the EZX phone platform.
+ *
+ *  Copyright (C) 2006 Harald Welte <laforge@openezx.org>
+ *  Copyright (C) 2007 Daniel Ribeiro <wyrm@openezx.org>
+ *
+ *  Based on information found in the original Motorola 2.4.x ezx-ts.c driver.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/pm.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/mfd/ezx-pcap.h>
+
+#include <asm/arch/hardware.h>
+#include <asm/arch/pxa-regs.h>
+
+/* PCAP_ADC_TS_M modes */
+#define POSITION_X_MEASUREMENT	0
+#define POSITION_XY_MEASUREMENT	1
+#define PRESSURE_MEASUREMENT	2
+#define PLATE_X_MEASUREMENT	3
+#define PLATE_Y_MEASUREMENT	4
+#define STANDBY_MODE		5
+#define NONTS_MODE		6
+
+struct pcap_ts {
+	int irq_xy;
+	int irq_touch;
+	struct input_dev *input;
+	struct timer_list timer;
+	u_int16_t x, y;
+	u_int16_t pressure;
+	u_int8_t read_state;
+};
+
+#define X_AXIS_MIN	0
+#define X_AXIS_MAX	1023
+
+#define Y_AXIS_MAX	X_AXIS_MAX
+#define Y_AXIS_MIN	X_AXIS_MIN
+
+#define PRESSURE_MAX	X_AXIS_MAX
+#define PRESSURE_MIN	X_AXIS_MIN
+
+/* if we try to read faster, pressure reading becomes unreliable */
+#define SAMPLE_INTERVAL		(HZ/50)
+
+
+static void pcap_ts_mode(struct pcap_ts *pcap_ts, u_int32_t mode)
+{
+	u_int32_t tmp;
+
+	pcap_ts->read_state = mode;
+	ezx_pcap_read(PCAP_REG_ADC, &tmp);
+	tmp &= ~PCAP_ADC_TS_M_MASK;
+	tmp |= ((mode << PCAP_ADC_TS_M_SHIFT) & PCAP_ADC_TS_M_MASK);
+	ezx_pcap_write(PCAP_REG_ADC, tmp);
+}
+
+/* issue a XY read command to the ADC of PCAP2.  Well get an ADCDONE interrupt
+ * once the result of the conversion is available */
+static void pcap_ts_start_xy_read(struct pcap_ts *pcap_ts)
+{
+	u_int32_t tmp;
+
+	ezx_pcap_read(PCAP_REG_ADC, &tmp);
+	tmp &= ~(PCAP_ADC_RAND | PCAP_ADC_ADA1_MASK | PCAP_ADC_ADA2_MASK);
+	tmp |= (PCAP_ADC_ADEN | PCAP_ADC_AD_SEL1 |
+		PCAP_ADC_AD_SEL2 | (5 << PCAP_ADC_ADA1_SHIFT) |
+					(3 << PCAP_ADC_ADA2_SHIFT));
+	ezx_pcap_write(PCAP_REG_ADC, tmp);
+
+	ezx_pcap_write(PCAP_REG_ADR, PCAP_ADR_ASC);
+}
+
+/* read the XY result from the ADC of PCAP2 */
+static void pcap_ts_get_xy_value(struct pcap_ts *pcap_ts)
+{
+	u_int32_t tmp;
+
+	ezx_pcap_read(PCAP_REG_ADR, &tmp);
+
+	if (pcap_ts->read_state == POSITION_XY_MEASUREMENT) {
+		pcap_ts->x = (tmp & PCAP_ADR_ADD1_MASK) >>
+					PCAP_ADR_ADD1_SHIFT;
+		pcap_ts->y = (tmp & PCAP_ADR_ADD2_MASK) >>
+					PCAP_ADR_ADD2_SHIFT;
+	} else {
+		pcap_ts->pressure = (tmp & PCAP_ADR_ADD2_MASK) >>
+						PCAP_ADR_ADD2_SHIFT;
+	}
+}
+
+/* PCAP2 interrupts us when ADC conversion result is available */
+static irqreturn_t pcap_ts_irq_xy(int irq, void *dev_id)
+{
+	struct pcap_ts *pcap_ts = dev_id;
+
+	pcap_ts_get_xy_value(pcap_ts);
+	switch (pcap_ts->read_state) {
+	case PRESSURE_MEASUREMENT:
+		if (!(pcap_ts->pressure >= PRESSURE_MAX ||
+				pcap_ts->pressure <= PRESSURE_MIN)) {
+			/* pen has been touched down */
+			input_report_key(pcap_ts->input, BTN_TOUCH, 1);
+			input_report_abs(pcap_ts->input, ABS_PRESSURE,
+							pcap_ts->pressure);
+		}
+		/* switch state machine into coordinate read mode */
+		pcap_ts_mode(pcap_ts, POSITION_XY_MEASUREMENT);
+		pcap_ts_start_xy_read(pcap_ts);
+		break;
+	case POSITION_XY_MEASUREMENT:
+		if (pcap_ts->x <= X_AXIS_MIN || pcap_ts->x >= X_AXIS_MAX ||
+		    pcap_ts->y <= Y_AXIS_MIN || pcap_ts->y >= Y_AXIS_MAX) {
+			/* pen has been released */
+			input_report_key(pcap_ts->input, BTN_TOUCH, 0);
+			input_report_abs(pcap_ts->input, ABS_PRESSURE, 0);
+
+			/* no need for timer, we'll get interrupted with
+			 * next touch down event */
+			del_timer(&pcap_ts->timer);
+
+			/* ask PCAP2 to interrupt us if touch event happens
+			 * again */
+			pcap_ts_mode(pcap_ts, STANDBY_MODE);
+			enable_irq(pcap_ts->irq_touch);
+		} else {
+			input_report_abs(pcap_ts->input, ABS_X, pcap_ts->x);
+			input_report_abs(pcap_ts->input, ABS_Y, pcap_ts->y);
+
+			/* switch back to pressure read mode */
+			pcap_ts_mode(pcap_ts, PRESSURE_MEASUREMENT);
+			mod_timer(&pcap_ts->timer, jiffies + SAMPLE_INTERVAL);
+		}
+		input_sync(pcap_ts->input);
+		break;
+	default:
+		break;
+	}
+	return IRQ_HANDLED;
+}
+
+/* PCAP2 interrupts us on pen down/up */
+static irqreturn_t pcap_ts_irq_touch(int irq, void *dev_id)
+{
+	struct pcap_ts *pcap_ts = dev_id;
+	/* mask Touchscreen interrupt bit, prevents further touch events
+	 * from being reported to us until we're finished with reading
+	 * both pressure and x/y from ADC */
+	disable_irq(pcap_ts->irq_touch);
+
+	pcap_ts_mode(pcap_ts, PRESSURE_MEASUREMENT);
+	pcap_ts_start_xy_read(pcap_ts);
+	return IRQ_HANDLED;
+}
+
+static void pcap_ts_timer_fn(unsigned long data)
+{
+	struct pcap_ts *pcap_ts = (struct pcap_ts *) data;
+
+	pcap_ts_start_xy_read(pcap_ts);
+}
+
+static int __init ezxts_probe(struct platform_device *pdev)
+{
+	struct pcap_ts *pcap_ts;
+	struct input_dev *input_dev;
+	int err = -ENOMEM;
+
+	pcap_ts = kzalloc(sizeof(*pcap_ts), GFP_KERNEL);
+	input_dev = input_allocate_device();
+	if (!pcap_ts || !input_dev)
+		goto fail;
+
+	pcap_ts->irq_xy = platform_get_irq(pdev, 0);
+	if (pcap_ts->irq_xy < 0) {
+		err = pcap_ts->irq_xy;
+		goto fail;
+	}
+
+	pcap_ts->irq_touch = platform_get_irq(pdev, 1);
+	if (pcap_ts->irq_touch < 0) {
+		err = pcap_ts->irq_touch;
+		goto fail;
+	}
+
+	ezx_pcap_write(PCAP_REG_ADC, 0);
+	ezx_pcap_write(PCAP_REG_ADR, 0);
+	pcap_ts_mode(pcap_ts, STANDBY_MODE);
+
+	err = request_irq(pcap_ts->irq_xy, pcap_ts_irq_xy, IRQF_DISABLED,
+			  "pcap-ts X/Y", pcap_ts);
+	if (err < 0) {
+		printk(KERN_ERR "pcap_ts: can't grab xy irq %d: %d\n",
+		       pcap_ts->irq_xy, err);
+		goto fail;
+	}
+
+	err = request_irq(pcap_ts->irq_touch, pcap_ts_irq_touch, IRQF_DISABLED,
+			  "pcap-ts touch", pcap_ts);
+	if (err < 0) {
+		printk(KERN_ERR "pcap_ts: can't grab touch irq %d: %d\n",
+		       pcap_ts->irq_touch, err);
+		goto fail_xy;
+	}
+
+	pcap_ts->input = input_dev;
+	init_timer(&pcap_ts->timer);
+	pcap_ts->timer.data = (unsigned long) pcap_ts;
+	pcap_ts->timer.function = &pcap_ts_timer_fn;
+
+	platform_set_drvdata(pdev, pcap_ts);
+
+	input_dev->name = "pcap-touchscreen";
+	input_dev->phys = "ezxts/input0";
+	input_dev->id.bustype = BUS_HOST;
+	input_dev->id.vendor = 0x0001;
+	input_dev->id.product = 0x0002;
+	input_dev->id.version = 0x0100;
+	input_dev->dev.parent = &pdev->dev;
+
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+	input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0);
+	input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0);
+	input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN,
+			     PRESSURE_MAX, 0, 0);
+
+	input_register_device(pcap_ts->input);
+
+	return 0;
+
+fail_xy:
+	free_irq(pcap_ts->irq_xy, pcap_ts);
+fail:
+	input_free_device(input_dev);
+	kfree(pcap_ts);
+
+	return err;
+}
+
+static int ezxts_remove(struct platform_device *pdev)
+{
+	struct pcap_ts *pcap_ts = platform_get_drvdata(pdev);
+
+	del_timer_sync(&pcap_ts->timer);
+
+	free_irq(pcap_ts->irq_touch, pcap_ts);
+	free_irq(pcap_ts->irq_xy, pcap_ts);
+
+	input_unregister_device(pcap_ts->input);
+	kfree(pcap_ts);
+
+	return 0;
+}
+
+static int ezxts_suspend(struct platform_device *dev, pm_message_t state)
+{
+	u_int32_t tmp;
+	ezx_pcap_read(PCAP_REG_ADC, &tmp);
+	tmp |= PCAP_ADC_TS_REF_LOWPWR;
+	ezx_pcap_write(PCAP_REG_ADC, tmp);
+	return 0;
+}
+
+static int ezxts_resume(struct platform_device *dev)
+{
+	u_int32_t tmp;
+	ezx_pcap_read(PCAP_REG_ADC, &tmp);
+	tmp &= ~PCAP_ADC_TS_REF_LOWPWR;
+	ezx_pcap_write(PCAP_REG_ADC, tmp);
+	return 0;
+}
+
+
+static struct platform_driver ezxts_driver = {
+	.probe		= ezxts_probe,
+	.remove		= ezxts_remove,
+	.suspend	= ezxts_suspend,
+	.resume		= ezxts_resume,
+	.driver		= {
+		.name	= "pcap-ts",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init ezxts_init(void)
+{
+	return platform_driver_register(&ezxts_driver);
+}
+
+static void __exit ezxts_exit(void)
+{
+	platform_driver_unregister(&ezxts_driver);
+}
+
+module_init(ezxts_init);
+module_exit(ezxts_exit);
+
+MODULE_DESCRIPTION("Motorola PCAP2 touchscreen driver");
+MODULE_AUTHOR("Harald Welte <laforge@openezx.org>");
+MODULE_LICENSE("GPL");
Index: linux-2.6-arm/drivers/input/touchscreen/Makefile
===================================================================
--- linux-2.6-arm.orig/drivers/input/touchscreen/Makefile
+++ linux-2.6-arm/drivers/input/touchscreen/Makefile
@@ -26,3 +26,4 @@
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712)	+= wm9712.o
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9713)	+= wm9713.o
 obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE)	+= mainstone-wm97xx.o
+obj-$(CONFIG_TOUCHSCREEN_PCAP)		+= pcap_ts.o
Index: linux-2.6-arm/arch/arm/mach-pxa/ezx.c
===================================================================
--- linux-2.6-arm.orig/arch/arm/mach-pxa/ezx.c
+++ linux-2.6-arm/arch/arm/mach-pxa/ezx.c
@@ -244,6 +244,30 @@
 	.setpower       = ezx_mci_setpower,
 	.exit           = ezx_mci_exit,
 };
+/* PCAP_TS */
+struct resource pcap_ts_resources[] = {
+	[0] = {
+		.start          = EZX_IRQ_ADCDONE,
+		.end            = EZX_IRQ_ADCDONE,
+		.flags          = IORESOURCE_IRQ,
+	},
+	[1] = {
+		.start          = EZX_IRQ_TS,
+		.end            = EZX_IRQ_TS,
+		.flags          = IORESOURCE_IRQ,
+	}
+};
+
+struct platform_device pcap_ts_device = {
+	.name           = "pcap-ts",
+	.id             = -1,
+	.dev        = {
+		.parent = &ezx_pcap_device.dev,
+	},
+	.num_resources  = ARRAY_SIZE(pcap_ts_resources),
+	.resource       = pcap_ts_resources,
+};
+
 
 static struct platform_device *devices[] __initdata = {
 	&ezx_backlight_device,
@@ -294,6 +318,14 @@
 	else
 		set_pxa_fb_info(&ezx_fb_info_2);
 	platform_device_register(&ezx_pcap_device);
+	/* A780 and E680 AP is connected to PCAP port 2 */
+	if (machine_is_ezx_a780() || machine_is_ezx_e680()) {
+		pcap_ts_resources[0].start = EZX_IRQ_ADCDONE2;
+		pcap_ts_resources[0].end = EZX_IRQ_ADCDONE2;
+	}
+	/* A910 and E2 dont have a touchscreen */
+	if (!(machine_is_ezx_a910() || machine_is_ezx_e2()))
+		platform_device_register(&pcap_ts_device);
 	pxa_set_mci_info(&ezx_mci_platform_data);
 	platform_add_devices(devices, ARRAY_SIZE(devices));
 }
-- 

-------------------------------------------------------------------
List admin: http://lists.arm.linux.org.uk/mailman/listinfo/linux-arm-kernel
FAQ:        http://www.arm.linux.org.uk/mailinglists/faq.php
Etiquette:  http://www.arm.linux.org.uk/mailinglists/etiquette.php

Re: [Patch 08/10] input: Touchscreen driver for the PCAP

From: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Date: 2008-07-07 20:13:34

Hi Stefan,

On Mon, Jul 07, 2008 at 08:40:08PM +0200, stefan@datenfreihafen.org wrote:
quoted hunk
+
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+	input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0);
+	input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0);
+	input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN,
+			     PRESSURE_MAX, 0, 0);
+
+	input_register_device(pcap_ts->input);
Please add error handling here, otherwise looks good.

Thanks.

-- 
Dmitry

Re: [Patch 08/10] input: Touchscreen driver for the PCAP

From: Stefan Schmidt <stefan@datenfreihafen.org>
Date: 2008-07-07 20:17:56

Hello.

On Mon, 2008-07-07 at 16:13, Dmitry Torokhov wrote:
On Mon, Jul 07, 2008 at 08:40:08PM +0200, stefan@datenfreihafen.org wrote:
quoted
+
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+	input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0);
+	input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0);
+	input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN,
+			     PRESSURE_MAX, 0, 0);
+
+	input_register_device(pcap_ts->input);
Please add error handling here, otherwise looks good.
Ok.New patch somes tomorrow. Need sleep now.
Thanks.
Thanks for the feedback.

regards
Stefan Schmidt

Re: [Patch 08/10] input: Touchscreen driver for the PCAP

From: Eric Miao <hidden>
Date: 2008-07-08 07:40:50

Stefan Schmidt wrote:
Hello.

On Mon, 2008-07-07 at 16:13, Dmitry Torokhov wrote:
quoted
On Mon, Jul 07, 2008 at 08:40:08PM +0200, stefan@datenfreihafen.org wrote:
quoted
+
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+	input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0);
+	input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0);
+	input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN,
+			     PRESSURE_MAX, 0, 0);
+
+	input_register_device(pcap_ts->input);
Please add error handling here, otherwise looks good.
Ok.New patch somes tomorrow. Need sleep now.
Well, one small nit: I'm not a language lawyer, but I don't think usage of
u_int32_t/u_int16_t/u_int8_t is encourage nowadays, use uin32_t/uint16_t/uint8_t
or u32/u16/u8.

Note: I'm not going to start an argument on which is the correct one to
use, so it's totally up to you.

Re: [Patch 08/10] input: Touchscreen driver for the PCAP

From: Stefan Schmidt <stefan@datenfreihafen.org>
Date: 2008-07-08 20:20:12

Hello.

On Tue, 2008-07-08 at 15:40, Eric Miao wrote:
Well, one small nit: I'm not a language lawyer, but I don't think usage of
u_int32_t/u_int16_t/u_int8_t is encourage nowadays, use uin32_t/uint16_t/uint8_t
or u32/u16/u8.

Note: I'm not going to start an argument on which is the correct one to
use, so it's totally up to you.
I would like to discuss this with Daniel and then submit a patch for all this
files to make them consistent. Doing this now would make life harder as some are
already acked, I need to take care with different maintainers, etc.

Hope this is ok with you.

regards
Stefan Schmidt

Re: [Patch 08/10] input: Touchscreen driver for the PCAP

From: Stefan Schmidt <stefan@datenfreihafen.org>
Date: 2008-07-08 20:22:22

Hello.

On Mon, 2008-07-07 at 16:13, Dmitry Torokhov wrote:
On Mon, Jul 07, 2008 at 08:40:08PM +0200, stefan@datenfreihafen.org wrote:
quoted
+
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+	input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0);
+	input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0);
+	input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN,
+			     PRESSURE_MAX, 0, 0);
+
+	input_register_device(pcap_ts->input);
Please add error handling here, otherwise looks good.
Added, how about this one?


Subject: [@num@/@total@] input: Touchscreen driver for the PCAP
Cc: <redacted>

Touchscreen driver based on the MFD PCAP available on the EZX mobile phones.

Signed-off-by: Daniel Ribeiro <redacted>

PATCH FOLLOWS
KernelVersion: 2.6-arm-git pxa branch

Index: linux-2.6-arm/drivers/input/touchscreen/Kconfig
===================================================================
--- linux-2.6-arm.orig/drivers/input/touchscreen/Kconfig
+++ linux-2.6-arm/drivers/input/touchscreen/Kconfig
@@ -316,4 +316,13 @@
 	bool "GoTop Super_Q2/GogoPen/PenPower tablet device support" if EMBEDDED
 	depends on TOUCHSCREEN_USB_COMPOSITE
 
+config TOUCHSCREEN_PCAP
+	tristate "Motorola PCAP touchscreen"
+	depends on EZX_PCAP
+	help
+	  Say Y here if you have a Motorola EZX telephone and
+	  want to support the built-in touchscreen.
+
+	  If unsure, say N.
+
 endif
Index: linux-2.6-arm/drivers/input/touchscreen/pcap_ts.c
===================================================================
--- /dev/null
+++ linux-2.6-arm/drivers/input/touchscreen/pcap_ts.c
@@ -0,0 +1,320 @@
+/*
+ * pcap_ts.c - Touchscreen driver for Motorola PCAP2 based touchscreen as found
+ * 	       in the EZX phone platform.
+ *
+ *  Copyright (C) 2006 Harald Welte <laforge@openezx.org>
+ *  Copyright (C) 2007 Daniel Ribeiro <drwyrm@gmail.com>
+ *
+ *  Based on information found in the original Motorola 2.4.x ezx-ts.c driver.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/pm.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/mfd/ezx-pcap.h>
+
+#include <asm/arch/hardware.h>
+#include <asm/arch/pxa-regs.h>
+
+/* PCAP_ADC_TS_M modes */
+#define POSITION_X_MEASUREMENT	0
+#define POSITION_XY_MEASUREMENT	1
+#define PRESSURE_MEASUREMENT	2
+#define PLATE_X_MEASUREMENT	3
+#define PLATE_Y_MEASUREMENT	4
+#define STANDBY_MODE		5
+#define NONTS_MODE		6
+
+struct pcap_ts {
+	int irq_xy;
+	int irq_touch;
+	struct input_dev *input;
+	struct timer_list timer;
+	u_int16_t x, y;
+	u_int16_t pressure;
+	u_int8_t read_state;
+};
+
+#define X_AXIS_MIN	0
+#define X_AXIS_MAX	1023
+
+#define Y_AXIS_MAX	X_AXIS_MAX
+#define Y_AXIS_MIN	X_AXIS_MIN
+
+#define PRESSURE_MAX	X_AXIS_MAX
+#define PRESSURE_MIN	X_AXIS_MIN
+
+/* if we try to read faster, pressure reading becomes unreliable */
+#define SAMPLE_INTERVAL		(HZ/50)
+
+
+static void pcap_ts_mode(struct pcap_ts *pcap_ts, u_int32_t mode)
+{
+	u_int32_t tmp;
+
+	pcap_ts->read_state = mode;
+	ezx_pcap_read(PCAP_REG_ADC, &tmp);
+	tmp &= ~PCAP_ADC_TS_M_MASK;
+	tmp |= ((mode << PCAP_ADC_TS_M_SHIFT) & PCAP_ADC_TS_M_MASK);
+	ezx_pcap_write(PCAP_REG_ADC, tmp);
+}
+
+/* issue a XY read command to the ADC of PCAP2.  Well get an ADCDONE interrupt
+ * once the result of the conversion is available */
+static void pcap_ts_start_xy_read(struct pcap_ts *pcap_ts)
+{
+	u_int32_t tmp;
+
+	ezx_pcap_read(PCAP_REG_ADC, &tmp);
+	tmp &= ~(PCAP_ADC_RAND | PCAP_ADC_ADA1_MASK | PCAP_ADC_ADA2_MASK);
+	tmp |= (PCAP_ADC_ADEN | PCAP_ADC_AD_SEL1 |
+		PCAP_ADC_AD_SEL2 | (5 << PCAP_ADC_ADA1_SHIFT) |
+					(3 << PCAP_ADC_ADA2_SHIFT));
+	ezx_pcap_write(PCAP_REG_ADC, tmp);
+
+	ezx_pcap_write(PCAP_REG_ADR, PCAP_ADR_ASC);
+}
+
+/* read the XY result from the ADC of PCAP2 */
+static void pcap_ts_get_xy_value(struct pcap_ts *pcap_ts)
+{
+	u_int32_t tmp;
+
+	ezx_pcap_read(PCAP_REG_ADR, &tmp);
+
+	if (pcap_ts->read_state == POSITION_XY_MEASUREMENT) {
+		pcap_ts->x = (tmp & PCAP_ADR_ADD1_MASK) >>
+					PCAP_ADR_ADD1_SHIFT;
+		pcap_ts->y = (tmp & PCAP_ADR_ADD2_MASK) >>
+					PCAP_ADR_ADD2_SHIFT;
+	} else {
+		pcap_ts->pressure = (tmp & PCAP_ADR_ADD2_MASK) >>
+						PCAP_ADR_ADD2_SHIFT;
+	}
+}
+
+/* PCAP2 interrupts us when ADC conversion result is available */
+static irqreturn_t pcap_ts_irq_xy(int irq, void *dev_id)
+{
+	struct pcap_ts *pcap_ts = dev_id;
+
+	pcap_ts_get_xy_value(pcap_ts);
+	switch (pcap_ts->read_state) {
+	case PRESSURE_MEASUREMENT:
+		if (!(pcap_ts->pressure >= PRESSURE_MAX ||
+				pcap_ts->pressure <= PRESSURE_MIN)) {
+			/* pen has been touched down */
+			input_report_key(pcap_ts->input, BTN_TOUCH, 1);
+			input_report_abs(pcap_ts->input, ABS_PRESSURE,
+							pcap_ts->pressure);
+		}
+		/* switch state machine into coordinate read mode */
+		pcap_ts_mode(pcap_ts, POSITION_XY_MEASUREMENT);
+		pcap_ts_start_xy_read(pcap_ts);
+		break;
+	case POSITION_XY_MEASUREMENT:
+		if (pcap_ts->x <= X_AXIS_MIN || pcap_ts->x >= X_AXIS_MAX ||
+		    pcap_ts->y <= Y_AXIS_MIN || pcap_ts->y >= Y_AXIS_MAX) {
+			/* pen has been released */
+			input_report_key(pcap_ts->input, BTN_TOUCH, 0);
+			input_report_abs(pcap_ts->input, ABS_PRESSURE, 0);
+
+			/* no need for timer, we'll get interrupted with
+			 * next touch down event */
+			del_timer(&pcap_ts->timer);
+
+			/* ask PCAP2 to interrupt us if touch event happens
+			 * again */
+			pcap_ts_mode(pcap_ts, STANDBY_MODE);
+			enable_irq(pcap_ts->irq_touch);
+		} else {
+			input_report_abs(pcap_ts->input, ABS_X, pcap_ts->x);
+			input_report_abs(pcap_ts->input, ABS_Y, pcap_ts->y);
+
+			/* switch back to pressure read mode */
+			pcap_ts_mode(pcap_ts, PRESSURE_MEASUREMENT);
+			mod_timer(&pcap_ts->timer, jiffies + SAMPLE_INTERVAL);
+		}
+		input_sync(pcap_ts->input);
+		break;
+	default:
+		break;
+	}
+	return IRQ_HANDLED;
+}
+
+/* PCAP2 interrupts us on pen down/up */
+static irqreturn_t pcap_ts_irq_touch(int irq, void *dev_id)
+{
+	struct pcap_ts *pcap_ts = dev_id;
+	/* mask Touchscreen interrupt bit, prevents further touch events
+	 * from being reported to us until we're finished with reading
+	 * both pressure and x/y from ADC */
+	disable_irq(pcap_ts->irq_touch);
+
+	pcap_ts_mode(pcap_ts, PRESSURE_MEASUREMENT);
+	pcap_ts_start_xy_read(pcap_ts);
+	return IRQ_HANDLED;
+}
+
+static void pcap_ts_timer_fn(unsigned long data)
+{
+	struct pcap_ts *pcap_ts = (struct pcap_ts *) data;
+
+	pcap_ts_start_xy_read(pcap_ts);
+}
+
+static int __init ezxts_probe(struct platform_device *pdev)
+{
+	struct pcap_ts *pcap_ts;
+	struct input_dev *input_dev;
+	int err = -ENOMEM;
+
+	pcap_ts = kzalloc(sizeof(*pcap_ts), GFP_KERNEL);
+	input_dev = input_allocate_device();
+	if (!pcap_ts || !input_dev)
+		goto fail;
+
+	pcap_ts->irq_xy = platform_get_irq(pdev, 0);
+	if (pcap_ts->irq_xy < 0) {
+		err = pcap_ts->irq_xy;
+		goto fail;
+	}
+
+	pcap_ts->irq_touch = platform_get_irq(pdev, 1);
+	if (pcap_ts->irq_touch < 0) {
+		err = pcap_ts->irq_touch;
+		goto fail;
+	}
+
+	ezx_pcap_write(PCAP_REG_ADC, 0);
+	ezx_pcap_write(PCAP_REG_ADR, 0);
+	pcap_ts_mode(pcap_ts, STANDBY_MODE);
+
+	err = request_irq(pcap_ts->irq_xy, pcap_ts_irq_xy, IRQF_DISABLED,
+			  "pcap-ts X/Y", pcap_ts);
+	if (err < 0) {
+		printk(KERN_ERR "pcap_ts: can't grab xy irq %d: %d\n",
+		       pcap_ts->irq_xy, err);
+		goto fail;
+	}
+
+	err = request_irq(pcap_ts->irq_touch, pcap_ts_irq_touch, IRQF_DISABLED,
+			  "pcap-ts touch", pcap_ts);
+	if (err < 0) {
+		printk(KERN_ERR "pcap_ts: can't grab touch irq %d: %d\n",
+		       pcap_ts->irq_touch, err);
+		goto fail_xy;
+	}
+
+	pcap_ts->input = input_dev;
+	init_timer(&pcap_ts->timer);
+	pcap_ts->timer.data = (unsigned long) pcap_ts;
+	pcap_ts->timer.function = &pcap_ts_timer_fn;
+
+	platform_set_drvdata(pdev, pcap_ts);
+
+	input_dev->name = "pcap-touchscreen";
+	input_dev->phys = "ezxts/input0";
+	input_dev->id.bustype = BUS_HOST;
+	input_dev->id.vendor = 0x0001;
+	input_dev->id.product = 0x0002;
+	input_dev->id.version = 0x0100;
+	input_dev->dev.parent = &pdev->dev;
+
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+	input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0);
+	input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0);
+	input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN,
+			     PRESSURE_MAX, 0, 0);
+
+	err = input_register_device(pcap_ts->input);
+	if (err)
+		goto fail_touch;
+
+	return 0;
+
+fail_touch:
+	free_irq(pcap_ts->irq_touch, pcap_ts);
+fail_xy:
+	free_irq(pcap_ts->irq_xy, pcap_ts);
+fail:
+	input_free_device(input_dev);
+	kfree(pcap_ts);
+
+	return err;
+}
+
+static int ezxts_remove(struct platform_device *pdev)
+{
+	struct pcap_ts *pcap_ts = platform_get_drvdata(pdev);
+
+	del_timer_sync(&pcap_ts->timer);
+
+	free_irq(pcap_ts->irq_touch, pcap_ts);
+	free_irq(pcap_ts->irq_xy, pcap_ts);
+
+	input_unregister_device(pcap_ts->input);
+	kfree(pcap_ts);
+
+	return 0;
+}
+
+static int ezxts_suspend(struct platform_device *dev, pm_message_t state)
+{
+	u_int32_t tmp;
+	ezx_pcap_read(PCAP_REG_ADC, &tmp);
+	tmp |= PCAP_ADC_TS_REF_LOWPWR;
+	ezx_pcap_write(PCAP_REG_ADC, tmp);
+	return 0;
+}
+
+static int ezxts_resume(struct platform_device *dev)
+{
+	u_int32_t tmp;
+	ezx_pcap_read(PCAP_REG_ADC, &tmp);
+	tmp &= ~PCAP_ADC_TS_REF_LOWPWR;
+	ezx_pcap_write(PCAP_REG_ADC, tmp);
+	return 0;
+}
+
+
+static struct platform_driver ezxts_driver = {
+	.probe		= ezxts_probe,
+	.remove		= ezxts_remove,
+	.suspend	= ezxts_suspend,
+	.resume		= ezxts_resume,
+	.driver		= {
+		.name	= "pcap-ts",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init ezxts_init(void)
+{
+	return platform_driver_register(&ezxts_driver);
+}
+
+static void __exit ezxts_exit(void)
+{
+	platform_driver_unregister(&ezxts_driver);
+}
+
+module_init(ezxts_init);
+module_exit(ezxts_exit);
+
+MODULE_DESCRIPTION("Motorola PCAP2 touchscreen driver");
+MODULE_AUTHOR("Harald Welte <laforge@openezx.org>");
+MODULE_LICENSE("GPL");
Index: linux-2.6-arm/drivers/input/touchscreen/Makefile
===================================================================
--- linux-2.6-arm.orig/drivers/input/touchscreen/Makefile
+++ linux-2.6-arm/drivers/input/touchscreen/Makefile
@@ -26,3 +26,4 @@
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712)	+= wm9712.o
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9713)	+= wm9713.o
 obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE)	+= mainstone-wm97xx.o
+obj-$(CONFIG_TOUCHSCREEN_PCAP)		+= pcap_ts.o
Index: linux-2.6-arm/arch/arm/mach-pxa/ezx.c
===================================================================
--- linux-2.6-arm.orig/arch/arm/mach-pxa/ezx.c
+++ linux-2.6-arm/arch/arm/mach-pxa/ezx.c
@@ -245,6 +245,30 @@
 	.setpower       = ezx_mci_setpower,
 	.exit           = ezx_mci_exit,
 };
+/* PCAP_TS */
+struct resource pcap_ts_resources[] = {
+	[0] = {
+		.start          = EZX_IRQ_ADCDONE,
+		.end            = EZX_IRQ_ADCDONE,
+		.flags          = IORESOURCE_IRQ,
+	},
+	[1] = {
+		.start          = EZX_IRQ_TS,
+		.end            = EZX_IRQ_TS,
+		.flags          = IORESOURCE_IRQ,
+	}
+};
+
+struct platform_device pcap_ts_device = {
+	.name           = "pcap-ts",
+	.id             = -1,
+	.dev        = {
+		.parent = &ezx_pcap_device.dev,
+	},
+	.num_resources  = ARRAY_SIZE(pcap_ts_resources),
+	.resource       = pcap_ts_resources,
+};
+
 
 static struct platform_device *devices[] __initdata = {
 	&ezx_pcap_device,
@@ -294,11 +318,17 @@
 	if (machine_is_ezx_a780() || machine_is_ezx_e680()) {
 		set_pxa_fb_info(&ezx_fb_info_1);
 		ezx_pcap_platform_data.config |= CS_INVERTED;
+		pcap_ts_resources[0].start = EZX_IRQ_ADCDONE2;
+		pcap_ts_resources[0].end = EZX_IRQ_ADCDONE2;
 	} else {
 		set_pxa_fb_info(&ezx_fb_info_2);
 	}
 	platform_add_devices(devices, ARRAY_SIZE(devices));
 	pxa_set_mci_info(&ezx_mci_platform_data);
+
+	/* A910 and E2 dont have a touchscreen */
+	if (!(machine_is_ezx_a910() || machine_is_ezx_e2()))
+		platform_device_register(&pcap_ts_device);
 }
 
 static void __init ezx_fixup(struct machine_desc *desc, struct tag *tags,

Re: [Patch 08/10] input: Touchscreen driver for the PCAP

From: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Date: 2008-07-10 14:24:15

Hi Stefan,

On Tue, Jul 08, 2008 at 10:26:22PM +0200, Stefan Schmidt wrote:
Hello.

On Mon, 2008-07-07 at 16:13, Dmitry Torokhov wrote:
quoted
On Mon, Jul 07, 2008 at 08:40:08PM +0200, stefan@datenfreihafen.org wrote:
quoted
+
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+	input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0);
+	input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0);
+	input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN,
+			     PRESSURE_MAX, 0, 0);
+
+	input_register_device(pcap_ts->input);
Please add error handling here, otherwise looks good.
Added, how about this one?
Looks better, more comments though.
+static int __init ezxts_probe(struct platform_device *pdev)
This should be __devinit.
quoted hunk
+{
+	struct pcap_ts *pcap_ts;
+	struct input_dev *input_dev;
+	int err = -ENOMEM;
+
+	pcap_ts = kzalloc(sizeof(*pcap_ts), GFP_KERNEL);
+	input_dev = input_allocate_device();
+	if (!pcap_ts || !input_dev)
+		goto fail;
+
+	pcap_ts->irq_xy = platform_get_irq(pdev, 0);
+	if (pcap_ts->irq_xy < 0) {
+		err = pcap_ts->irq_xy;
+		goto fail;
+	}
+
+	pcap_ts->irq_touch = platform_get_irq(pdev, 1);
+	if (pcap_ts->irq_touch < 0) {
+		err = pcap_ts->irq_touch;
+		goto fail;
+	}
+
+	ezx_pcap_write(PCAP_REG_ADC, 0);
+	ezx_pcap_write(PCAP_REG_ADR, 0);
+	pcap_ts_mode(pcap_ts, STANDBY_MODE);
+
OK, so if I understand this correctly we make the hardware fully ready
here...
quoted hunk
+	err = request_irq(pcap_ts->irq_xy, pcap_ts_irq_xy, IRQF_DISABLED,
+			  "pcap-ts X/Y", pcap_ts);
+	if (err < 0) {
+		printk(KERN_ERR "pcap_ts: can't grab xy irq %d: %d\n",
+		       pcap_ts->irq_xy, err);
+		goto fail;
+	}
+
+	err = request_irq(pcap_ts->irq_touch, pcap_ts_irq_touch, IRQF_DISABLED,
+			  "pcap-ts touch", pcap_ts);
+	if (err < 0) {
+		printk(KERN_ERR "pcap_ts: can't grab touch irq %d: %d\n",
+		       pcap_ts->irq_touch, err);
+		goto fail_xy;
+	}
+
And request IRQ.. At this point in time IRQ may come up if pen is
touching the screen...
quoted hunk
+	pcap_ts->input = input_dev;
+	init_timer(&pcap_ts->timer);
+	pcap_ts->timer.data = (unsigned long) pcap_ts;
+	pcap_ts->timer.function = &pcap_ts_timer_fn;
But the timer is not setup yet... This may cause problems.
quoted hunk
+
+static int ezxts_remove(struct platform_device *pdev)
+{
__devexit?
quoted hunk
+	struct pcap_ts *pcap_ts = platform_get_drvdata(pdev);
+
+	del_timer_sync(&pcap_ts->timer);
+
We delete the timer but if IRQ comes up right here we'll reactivate it
again. Is there a way to disable the hardware? Something like putting it
into low power mode like you do in suspend to make sure it does not
generate IRQs? I guess you can just disable_irq before trying to delete
the timer.
quoted hunk
+	free_irq(pcap_ts->irq_touch, pcap_ts);
+	free_irq(pcap_ts->irq_xy, pcap_ts);
+
+	input_unregister_device(pcap_ts->input);
+	kfree(pcap_ts);
+
+	return 0;
+}
+
+static int ezxts_suspend(struct platform_device *dev, pm_message_t state)
+static int ezxts_resume(struct platform_device *dev)
Guard with CONFIG_PM?
quoted hunk
+static struct platform_driver ezxts_driver = {
+	.probe		= ezxts_probe,
+	.remove		= ezxts_remove,
__devexit_p()
+	.suspend	= ezxts_suspend,
+	.resume		= ezxts_resume,
Guard with CONFIG_PM here as well.
quoted hunk
+	.driver		= {
+		.name	= "pcap-ts",
+		.owner	= THIS_MODULE,
+	},
+};
+
-- 
Dmitry

Re: [Patch 08/10] input: Touchscreen driver for the PCAP

From: Stefan Schmidt <stefan@datenfreihafen.org>
Date: 2008-07-11 15:19:47

Hello.

On Thu, 2008-07-10 at 10:23, Dmitry Torokhov wrote:
On Tue, Jul 08, 2008 at 10:26:22PM +0200, Stefan Schmidt wrote:

Looks better, more comments though.
Which are very much welcome.
quoted
+static int __init ezxts_probe(struct platform_device *pdev)
This should be __devinit.
Done
quoted
+{
+	struct pcap_ts *pcap_ts;
+	struct input_dev *input_dev;
+	int err = -ENOMEM;
+
+	pcap_ts = kzalloc(sizeof(*pcap_ts), GFP_KERNEL);
+	input_dev = input_allocate_device();
+	if (!pcap_ts || !input_dev)
+		goto fail;
+
+	pcap_ts->irq_xy = platform_get_irq(pdev, 0);
+	if (pcap_ts->irq_xy < 0) {
+		err = pcap_ts->irq_xy;
+		goto fail;
+	}
+
+	pcap_ts->irq_touch = platform_get_irq(pdev, 1);
+	if (pcap_ts->irq_touch < 0) {
+		err = pcap_ts->irq_touch;
+		goto fail;
+	}
+
+	ezx_pcap_write(PCAP_REG_ADC, 0);
+	ezx_pcap_write(PCAP_REG_ADR, 0);
+	pcap_ts_mode(pcap_ts, STANDBY_MODE);
+
OK, so if I understand this correctly we make the hardware fully ready
here...
Yes
quoted
+	err = request_irq(pcap_ts->irq_xy, pcap_ts_irq_xy, IRQF_DISABLED,
+			  "pcap-ts X/Y", pcap_ts);
+	if (err < 0) {
+		printk(KERN_ERR "pcap_ts: can't grab xy irq %d: %d\n",
+		       pcap_ts->irq_xy, err);
+		goto fail;
+	}
+
+	err = request_irq(pcap_ts->irq_touch, pcap_ts_irq_touch, IRQF_DISABLED,
+			  "pcap-ts touch", pcap_ts);
+	if (err < 0) {
+		printk(KERN_ERR "pcap_ts: can't grab touch irq %d: %d\n",
+		       pcap_ts->irq_touch, err);
+		goto fail_xy;
+	}
+
And request IRQ.. At this point in time IRQ may come up if pen is
touching the screen...
Yes
quoted
+	pcap_ts->input = input_dev;
+	init_timer(&pcap_ts->timer);
+	pcap_ts->timer.data = (unsigned long) pcap_ts;
+	pcap_ts->timer.function = &pcap_ts_timer_fn;
But the timer is not setup yet... This may cause problems.
Good point. Moved the timer code block before request_irq.
quoted
+
+static int ezxts_remove(struct platform_device *pdev)
+{
__devexit?
Done
quoted
+	struct pcap_ts *pcap_ts = platform_get_drvdata(pdev);
+
+	del_timer_sync(&pcap_ts->timer);
+
We delete the timer but if IRQ comes up right here we'll reactivate it
again. Is there a way to disable the hardware? Something like putting it
into low power mode like you do in suspend to make sure it does not
generate IRQs? I guess you can just disable_irq before trying to delete
the timer.
Sadly the chip does not support this. At least as far as we know, because
Motorola does not give us any docs about it. In low power mode it still reacts
on IRQs. To avoid the disable_irq we just reorder it to delete the pointer after
the free_irq. Should do the same job, right?
quoted
+	free_irq(pcap_ts->irq_touch, pcap_ts);
+	free_irq(pcap_ts->irq_xy, pcap_ts);
+
+	input_unregister_device(pcap_ts->input);
+	kfree(pcap_ts);
+
+	return 0;
+}
+
quoted
+static int ezxts_suspend(struct platform_device *dev, pm_message_t state)
+static int ezxts_resume(struct platform_device *dev)
Guard with CONFIG_PM?
Done
quoted
+static struct platform_driver ezxts_driver = {
+	.probe		= ezxts_probe,
+	.remove		= ezxts_remove,
__devexit_p()
Done
quoted
+	.suspend	= ezxts_suspend,
+	.resume		= ezxts_resume,
Guard with CONFIG_PM here as well.
Done

How about this version?


Subject: [@num@/@total@] input: Touchscreen driver for the PCAP
Cc: <redacted>

Touchscreen driver based on the MFD PCAP available on the EZX mobile phones.

Signed-off-by: Daniel Ribeiro <redacted>

PATCH FOLLOWS
KernelVersion: 2.6-arm-git pxa branch

Index: linux-2.6-arm/drivers/input/touchscreen/Kconfig
===================================================================
--- linux-2.6-arm.orig/drivers/input/touchscreen/Kconfig
+++ linux-2.6-arm/drivers/input/touchscreen/Kconfig
@@ -316,4 +316,13 @@
 	bool "GoTop Super_Q2/GogoPen/PenPower tablet device support" if EMBEDDED
 	depends on TOUCHSCREEN_USB_COMPOSITE
 
+config TOUCHSCREEN_PCAP
+	tristate "Motorola PCAP touchscreen"
+	depends on EZX_PCAP
+	help
+	  Say Y here if you have a Motorola EZX telephone and
+	  want to support the built-in touchscreen.
+
+	  If unsure, say N.
+
 endif
Index: linux-2.6-arm/drivers/input/touchscreen/pcap_ts.c
===================================================================
--- /dev/null
+++ linux-2.6-arm/drivers/input/touchscreen/pcap_ts.c
@@ -0,0 +1,323 @@
+/*
+ * pcap_ts.c - Touchscreen driver for Motorola PCAP2 based touchscreen as found
+ * 	       in the EZX phone platform.
+ *
+ *  Copyright (C) 2006 Harald Welte <laforge@openezx.org>
+ *  Copyright (C) 2007 Daniel Ribeiro <drwyrm@gmail.com>
+ *
+ *  Based on information found in the original Motorola 2.4.x ezx-ts.c driver.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/pm.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/mfd/ezx-pcap.h>
+
+#include <asm/arch/hardware.h>
+#include <asm/arch/pxa-regs.h>
+
+/* PCAP_ADC_TS_M modes */
+#define POSITION_X_MEASUREMENT	0
+#define POSITION_XY_MEASUREMENT	1
+#define PRESSURE_MEASUREMENT	2
+#define PLATE_X_MEASUREMENT	3
+#define PLATE_Y_MEASUREMENT	4
+#define STANDBY_MODE		5
+#define NONTS_MODE		6
+
+struct pcap_ts {
+	int irq_xy;
+	int irq_touch;
+	struct input_dev *input;
+	struct timer_list timer;
+	u16 x, y;
+	u16 pressure;
+	u8 read_state;
+};
+
+#define X_AXIS_MIN	0
+#define X_AXIS_MAX	1023
+
+#define Y_AXIS_MAX	X_AXIS_MAX
+#define Y_AXIS_MIN	X_AXIS_MIN
+
+#define PRESSURE_MAX	X_AXIS_MAX
+#define PRESSURE_MIN	X_AXIS_MIN
+
+/* if we try to read faster, pressure reading becomes unreliable */
+#define SAMPLE_INTERVAL		(HZ/50)
+
+
+static void pcap_ts_mode(struct pcap_ts *pcap_ts, u32 mode)
+{
+	u32 tmp;
+
+	pcap_ts->read_state = mode;
+	ezx_pcap_read(PCAP_REG_ADC, &tmp);
+	tmp &= ~PCAP_ADC_TS_M_MASK;
+	tmp |= ((mode << PCAP_ADC_TS_M_SHIFT) & PCAP_ADC_TS_M_MASK);
+	ezx_pcap_write(PCAP_REG_ADC, tmp);
+}
+
+/* issue a XY read command to the ADC of PCAP2.  Well get an ADCDONE interrupt
+ * once the result of the conversion is available */
+static void pcap_ts_start_xy_read(struct pcap_ts *pcap_ts)
+{
+	u32 tmp;
+
+	ezx_pcap_read(PCAP_REG_ADC, &tmp);
+	tmp &= ~(PCAP_ADC_RAND | PCAP_ADC_ADA1_MASK | PCAP_ADC_ADA2_MASK);
+	tmp |= (PCAP_ADC_ADEN | PCAP_ADC_AD_SEL1 |
+		PCAP_ADC_AD_SEL2 | (5 << PCAP_ADC_ADA1_SHIFT) |
+					(3 << PCAP_ADC_ADA2_SHIFT));
+	ezx_pcap_write(PCAP_REG_ADC, tmp);
+
+	ezx_pcap_write(PCAP_REG_ADR, PCAP_ADR_ASC);
+}
+
+/* read the XY result from the ADC of PCAP2 */
+static void pcap_ts_get_xy_value(struct pcap_ts *pcap_ts)
+{
+	u32 tmp;
+
+	ezx_pcap_read(PCAP_REG_ADR, &tmp);
+
+	if (pcap_ts->read_state == POSITION_XY_MEASUREMENT) {
+		pcap_ts->x = (tmp & PCAP_ADR_ADD1_MASK) >>
+					PCAP_ADR_ADD1_SHIFT;
+		pcap_ts->y = (tmp & PCAP_ADR_ADD2_MASK) >>
+					PCAP_ADR_ADD2_SHIFT;
+	} else {
+		pcap_ts->pressure = (tmp & PCAP_ADR_ADD2_MASK) >>
+						PCAP_ADR_ADD2_SHIFT;
+	}
+}
+
+/* PCAP2 interrupts us when ADC conversion result is available */
+static irqreturn_t pcap_ts_irq_xy(int irq, void *dev_id)
+{
+	struct pcap_ts *pcap_ts = dev_id;
+
+	pcap_ts_get_xy_value(pcap_ts);
+	switch (pcap_ts->read_state) {
+	case PRESSURE_MEASUREMENT:
+		if (!(pcap_ts->pressure >= PRESSURE_MAX ||
+				pcap_ts->pressure <= PRESSURE_MIN)) {
+			/* pen has been touched down */
+			input_report_key(pcap_ts->input, BTN_TOUCH, 1);
+			input_report_abs(pcap_ts->input, ABS_PRESSURE,
+							pcap_ts->pressure);
+		}
+		/* switch state machine into coordinate read mode */
+		pcap_ts_mode(pcap_ts, POSITION_XY_MEASUREMENT);
+		pcap_ts_start_xy_read(pcap_ts);
+		break;
+	case POSITION_XY_MEASUREMENT:
+		if (pcap_ts->x <= X_AXIS_MIN || pcap_ts->x >= X_AXIS_MAX ||
+		    pcap_ts->y <= Y_AXIS_MIN || pcap_ts->y >= Y_AXIS_MAX) {
+			/* pen has been released */
+			input_report_key(pcap_ts->input, BTN_TOUCH, 0);
+			input_report_abs(pcap_ts->input, ABS_PRESSURE, 0);
+
+			/* no need for timer, we'll get interrupted with
+			 * next touch down event */
+			del_timer(&pcap_ts->timer);
+
+			/* ask PCAP2 to interrupt us if touch event happens
+			 * again */
+			pcap_ts_mode(pcap_ts, STANDBY_MODE);
+			enable_irq(pcap_ts->irq_touch);
+		} else {
+			input_report_abs(pcap_ts->input, ABS_X, pcap_ts->x);
+			input_report_abs(pcap_ts->input, ABS_Y, pcap_ts->y);
+
+			/* switch back to pressure read mode */
+			pcap_ts_mode(pcap_ts, PRESSURE_MEASUREMENT);
+			mod_timer(&pcap_ts->timer, jiffies + SAMPLE_INTERVAL);
+		}
+		input_sync(pcap_ts->input);
+		break;
+	default:
+		break;
+	}
+	return IRQ_HANDLED;
+}
+
+/* PCAP2 interrupts us on pen down/up */
+static irqreturn_t pcap_ts_irq_touch(int irq, void *dev_id)
+{
+	struct pcap_ts *pcap_ts = dev_id;
+	/* mask Touchscreen interrupt bit, prevents further touch events
+	 * from being reported to us until we're finished with reading
+	 * both pressure and x/y from ADC */
+	disable_irq(pcap_ts->irq_touch);
+
+	pcap_ts_mode(pcap_ts, PRESSURE_MEASUREMENT);
+	pcap_ts_start_xy_read(pcap_ts);
+	return IRQ_HANDLED;
+}
+
+static void pcap_ts_timer_fn(unsigned long data)
+{
+	struct pcap_ts *pcap_ts = (struct pcap_ts *) data;
+
+	pcap_ts_start_xy_read(pcap_ts);
+}
+
+static int __devinit ezxts_probe(struct platform_device *pdev)
+{
+	struct pcap_ts *pcap_ts;
+	struct input_dev *input_dev;
+	int err = -ENOMEM;
+
+	pcap_ts = kzalloc(sizeof(*pcap_ts), GFP_KERNEL);
+	input_dev = input_allocate_device();
+	if (!pcap_ts || !input_dev)
+		goto fail;
+
+	pcap_ts->irq_xy = platform_get_irq(pdev, 0);
+	if (pcap_ts->irq_xy < 0) {
+		err = pcap_ts->irq_xy;
+		goto fail;
+	}
+
+	pcap_ts->irq_touch = platform_get_irq(pdev, 1);
+	if (pcap_ts->irq_touch < 0) {
+		err = pcap_ts->irq_touch;
+		goto fail;
+	}
+
+	ezx_pcap_write(PCAP_REG_ADC, 0);
+	ezx_pcap_write(PCAP_REG_ADR, 0);
+	pcap_ts_mode(pcap_ts, STANDBY_MODE);
+
+	init_timer(&pcap_ts->timer);
+	pcap_ts->timer.data = (unsigned long) pcap_ts;
+	pcap_ts->timer.function = &pcap_ts_timer_fn;
+
+	pcap_ts->input = input_dev;
+	platform_set_drvdata(pdev, pcap_ts);
+
+	input_dev->name = "pcap-touchscreen";
+	input_dev->phys = "ezxts/input0";
+	input_dev->id.bustype = BUS_HOST;
+	input_dev->id.vendor = 0x0001;
+	input_dev->id.product = 0x0002;
+	input_dev->id.version = 0x0100;
+	input_dev->dev.parent = &pdev->dev;
+
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+	input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0);
+	input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0);
+	input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN,
+			     PRESSURE_MAX, 0, 0);
+
+	err = request_irq(pcap_ts->irq_xy, pcap_ts_irq_xy, IRQF_DISABLED,
+			  "pcap-ts X/Y", pcap_ts);
+	if (err < 0) {
+		printk(KERN_ERR "pcap_ts: can't grab xy irq %d: %d\n",
+		       pcap_ts->irq_xy, err);
+		goto fail;
+	}
+
+	err = request_irq(pcap_ts->irq_touch, pcap_ts_irq_touch, IRQF_DISABLED,
+			  "pcap-ts touch", pcap_ts);
+	if (err < 0) {
+		printk(KERN_ERR "pcap_ts: can't grab touch irq %d: %d\n",
+		       pcap_ts->irq_touch, err);
+		goto fail_xy;
+	}
+
+	err = input_register_device(pcap_ts->input);
+	if (err)
+		goto fail_touch;
+
+	return 0;
+
+fail_touch:
+	free_irq(pcap_ts->irq_touch, pcap_ts);
+fail_xy:
+	free_irq(pcap_ts->irq_xy, pcap_ts);
+fail:
+	input_free_device(input_dev);
+	kfree(pcap_ts);
+
+	return err;
+}
+
+static int __devexit ezxts_remove(struct platform_device *pdev)
+{
+	struct pcap_ts *pcap_ts = platform_get_drvdata(pdev);
+
+	free_irq(pcap_ts->irq_touch, pcap_ts);
+	free_irq(pcap_ts->irq_xy, pcap_ts);
+
+	del_timer_sync(&pcap_ts->timer);
+
+	input_unregister_device(pcap_ts->input);
+	kfree(pcap_ts);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int ezxts_suspend(struct platform_device *dev, pm_message_t state)
+{
+	u32 tmp;
+	ezx_pcap_read(PCAP_REG_ADC, &tmp);
+	tmp |= PCAP_ADC_TS_REF_LOWPWR;
+	ezx_pcap_write(PCAP_REG_ADC, tmp);
+	return 0;
+}
+
+static int ezxts_resume(struct platform_device *dev)
+{
+	u32 tmp;
+	ezx_pcap_read(PCAP_REG_ADC, &tmp);
+	tmp &= ~PCAP_ADC_TS_REF_LOWPWR;
+	ezx_pcap_write(PCAP_REG_ADC, tmp);
+	return 0;
+}
+#endif
+
+static struct platform_driver ezxts_driver = {
+	.probe		= ezxts_probe,
+	.remove		= __devexit_p(ezxts_remove),
+#ifdef CONFIG_PM
+	.suspend	= ezxts_suspend,
+	.resume		= ezxts_resume,
+#endif
+	.driver		= {
+		.name	= "pcap-ts",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init ezxts_init(void)
+{
+	return platform_driver_register(&ezxts_driver);
+}
+
+static void __exit ezxts_exit(void)
+{
+	platform_driver_unregister(&ezxts_driver);
+}
+
+module_init(ezxts_init);
+module_exit(ezxts_exit);
+
+MODULE_DESCRIPTION("Motorola PCAP2 touchscreen driver");
+MODULE_AUTHOR("Harald Welte <laforge@openezx.org>");
+MODULE_LICENSE("GPL");
Index: linux-2.6-arm/drivers/input/touchscreen/Makefile
===================================================================
--- linux-2.6-arm.orig/drivers/input/touchscreen/Makefile
+++ linux-2.6-arm/drivers/input/touchscreen/Makefile
@@ -26,3 +26,4 @@
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712)	+= wm9712.o
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9713)	+= wm9713.o
 obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE)	+= mainstone-wm97xx.o
+obj-$(CONFIG_TOUCHSCREEN_PCAP)		+= pcap_ts.o
Index: linux-2.6-arm/arch/arm/mach-pxa/ezx.c
===================================================================
--- linux-2.6-arm.orig/arch/arm/mach-pxa/ezx.c
+++ linux-2.6-arm/arch/arm/mach-pxa/ezx.c
@@ -239,6 +239,30 @@
 	GPIO56_USB_P3_4,	/* ICL_VMOUT */
 	GPIO113_USB_P3_3,	/* /ICL_VMIN */
 };
+/* PCAP_TS */
+struct resource pcap_ts_resources[] = {
+	[0] = {
+		.start          = EZX_IRQ_ADCDONE,
+		.end            = EZX_IRQ_ADCDONE,
+		.flags          = IORESOURCE_IRQ,
+	},
+	[1] = {
+		.start          = EZX_IRQ_TS,
+		.end            = EZX_IRQ_TS,
+		.flags          = IORESOURCE_IRQ,
+	}
+};
+
+struct platform_device pcap_ts_device = {
+	.name           = "pcap-ts",
+	.id             = -1,
+	.dev        = {
+		.parent = &ezx_pcap_device.dev,
+	},
+	.num_resources  = ARRAY_SIZE(pcap_ts_resources),
+	.resource       = pcap_ts_resources,
+};
+
 
 static void __init ezx_init(void)
 {
@@ -248,11 +272,17 @@
 		set_pxa_fb_info(&ezx_fb_info_1);
 		ezx_pcap_platform_data.config |=
 			(PCAP_SECOND_PORT | PCAP_CS_INVERTED);
+		pcap_ts_resources[0].start = EZX_IRQ_ADCDONE2;
+		pcap_ts_resources[0].end = EZX_IRQ_ADCDONE2;
 	} else {
 		set_pxa_fb_info(&ezx_fb_info_2);
 	}
 	pxa_set_mci_info(&ezx_mci_platform_data);
 	platform_add_devices(devices, ARRAY_SIZE(devices));
+
+	/* A910 and E2 dont have a touchscreen */
+	if (!(machine_is_ezx_a910() || machine_is_ezx_e2()))
+		platform_device_register(&pcap_ts_device);
 }
 
 static void __init ezx_fixup(struct machine_desc *desc, struct tag *tags,
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help