From 0bc8b02b3f6ea77df20f7c7770d19261159d662d Mon Sep 17 00:00:00 2001 From: Thomas Kunze Date: Tue, 6 Oct 2009 22:10:51 +0200 Subject: [PATCH 12/15] add collie touchscreen driver Conflicts: drivers/input/touchscreen/Kconfig drivers/input/touchscreen/Makefile drivers/mfd/Makefile --- drivers/input/touchscreen/Kconfig | 6 + drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/collie-ts.c | 449 +++++++++++++++++++++++++++++++++ drivers/mfd/Makefile | 1 - include/linux/mfd/ucb1x00.h | 3 + 5 files changed, 459 insertions(+), 1 deletions(-) create mode 100644 drivers/input/touchscreen/collie-ts.c diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 72e2712..bcd73a6 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -296,6 +296,12 @@ config TOUCHSCREEN_ATMEL_TSADCC To compile this driver as a module, choose M here: the module will be called atmel_tsadcc. +config TOUCHSCREEN_COLLIE_TS + tristate "Touchscreen collie support" + depends on MCP_UCB1200 && INPUT && !MCP_UCB1200_TS + help + Driver for touchscreen on collie - sharp sl-5500. + config TOUCHSCREEN_UCB1400 tristate "Philips UCB1400 touchscreen" depends on AC97_BUS diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 3e1c5e0..a24d32c 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) += touchit213.o obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o obj-$(CONFIG_TOUCHSCREEN_TSC2007) += tsc2007.o +obj-$(CONFIG_TOUCHSCREEN_COLLIE_TS) += collie-ts.o obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o obj-$(CONFIG_TOUCHSCREEN_WACOM_W8001) += wacom_w8001.o obj-$(CONFIG_TOUCHSCREEN_WM97XX) += wm97xx-ts.o diff --git a/drivers/input/touchscreen/collie-ts.c b/drivers/input/touchscreen/collie-ts.c new file mode 100644 index 0000000..c7c0272 --- /dev/null +++ b/drivers/input/touchscreen/collie-ts.c @@ -0,0 +1,449 @@ +/* + * Touchscreen driver for UCB1x00-based touchscreens + * + * Copyright (C) 2001 Russell King, All Rights Reserved. + * Copyright (C) 2005 Pavel Machek + * + * 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. + * + * 21-Jan-2002 : + * + * Added support for synchronous A/D mode. This mode is useful to + * avoid noise induced in the touchpanel by the LCD, provided that + * the UCB1x00 has a valid LCD sync signal routed to its ADCSYNC pin. + * It is important to note that the signal connected to the ADCSYNC + * pin should provide pulses even when the LCD is blanked, otherwise + * a pen touch needed to unblank the LCD will never be read. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +struct ucb1x00_ts { + struct input_dev *idev; + struct ucb1x00 *ucb; + + wait_queue_head_t irq_wait; + struct task_struct *rtask; + u16 x_res; + u16 y_res; + + unsigned int adcsync:1; +}; + +static int adcsync; + +/********************************** + + ................ + . . = 340 + . . + . ^. + . ^. + . ^. + . ^. + . . + . X. = 10 + . <<<<<<<< Y . + ................ + . Sharp =200 + . . + . - O - . + . . + ................ + +**********************************/ + + +static inline void ucb1x00_ts_evt_add(struct ucb1x00_ts *ts, u16 pressure, u16 x, u16 y) +{ + struct input_dev *idev = ts->idev; + + input_report_abs(idev, ABS_X, x); + input_report_abs(idev, ABS_Y, y); + input_report_abs(idev, ABS_PRESSURE, pressure); + input_report_key(idev, BTN_TOUCH, 1); + input_sync(idev); +} + +static inline void ucb1x00_ts_event_release(struct ucb1x00_ts *ts) +{ + struct input_dev *idev = ts->idev; + + input_report_abs(idev, ABS_PRESSURE, 0); + input_report_key(idev, BTN_TOUCH, 0); + input_sync(idev); +} + +/* + * Switch to interrupt mode. This set touchscreen to interrupt + * mode, so that chip is able to send interrupt. + */ +static inline void ucb1x00_ts_mode_int(struct ucb1x00_ts *ts) +{ + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | + UCB_TS_CR_MODE_INT); +} + +/* + * Switch to pressure mode, and read pressure. We don't need to wait + * here, since both plates are being driven. + * + * set_read_pressure() in sharp code + */ +static inline void ucb1x00_ts_read_pressure(struct ucb1x00_ts *ts) +{ + ucb1x00_io_write(ts->ucb, COLLIE_TC35143_GPIO_TBL_CHK, 0); + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSPX_POW | UCB_TS_CR_TSMX_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + ucb1x00_reg_write(ts->ucb, UCB_ADC_CR, ts->ucb->adc_cr | + UCB_ADC_INP_AD2 | + UCB_ADC_SYNC_ENA); + udelay(100); + ucb1x00_reg_write(ts->ucb, UCB_ADC_CR, ts->ucb->adc_cr | + UCB_ADC_INP_AD2 | + UCB_ADC_SYNC_ENA | UCB_ADC_START); +} + +/* + * Switch to X position mode and measure Y plate. We switch the plate + * configuration in pressure mode, then switch to position mode. This + * gives a faster response time. Even so, we need to wait about 55us + * for things to stabilise. + */ +static inline void ucb1x00_ts_read_xpos(struct ucb1x00_ts *ts) +{ + ucb1x00_io_write(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK); + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + + ucb1x00_reg_write(ts->ucb, UCB_ADC_CR, ts->ucb->adc_cr | + UCB_ADC_INP_TSPY | UCB_ADC_SYNC_ENA); + udelay(100); + ucb1x00_reg_write(ts->ucb, UCB_ADC_CR, ts->ucb->adc_cr | + UCB_ADC_INP_TSPY | UCB_ADC_SYNC_ENA | + UCB_ADC_START); +} + +/* + * Switch to Y position mode and measure X plate. We switch the plate + * configuration in pressure mode, then switch to position mode. This + * gives a faster response time. Even so, we need to wait about 55us + * for things to stabilise. + */ +static inline void ucb1x00_ts_read_ypos(struct ucb1x00_ts *ts) +{ + ucb1x00_io_write(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK); + + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + + ucb1x00_reg_write(ts->ucb, UCB_ADC_CR, ts->ucb->adc_cr | + UCB_ADC_INP_TSPX | UCB_ADC_SYNC_ENA); + udelay(100); + ucb1x00_reg_write(ts->ucb, UCB_ADC_CR, ts->ucb->adc_cr | + UCB_ADC_INP_TSPX | UCB_ADC_SYNC_ENA | + UCB_ADC_START); +} + +/* + * Switch to X plate resistance mode. Set MX to ground, PX to + * supply. Measure current. + */ +static inline unsigned int ucb1x00_ts_read_xres(struct ucb1x00_ts *ts) +{ + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + return ucb1x00_adc_read(ts->ucb, 0, ts->adcsync); +} + +/* + * Switch to Y plate resistance mode. Set MY to ground, PY to + * supply. Measure current. + */ +static inline unsigned int ucb1x00_ts_read_yres(struct ucb1x00_ts *ts) +{ + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + return ucb1x00_adc_read(ts->ucb, 0, ts->adcsync); +} + +/* + * This is a RT kernel thread that handles the ADC accesses + * (mainly so we can use semaphores in the UCB1200 core code + * to serialise accesses to the ADC). + */ +static int ucb1x00_thread(void *_ts) +{ + struct ucb1x00_ts *ts = _ts; + struct task_struct *tsk = current; + DECLARE_WAITQUEUE(wait, tsk); + int state; + + /* + * We could run as a real-time thread. However, thus far + * this doesn't seem to be necessary. + */ + + add_wait_queue(&ts->irq_wait, &wait); + + while (!kthread_should_stop()) { + unsigned int data[3]; + + for (state=0; state<3; state++) { + + ucb1x00_adc_enable(ts->ucb); + ucb1x00_enable_irq(ts->ucb, UCB_IRQ_ADC, UCB_FALLING); + switch (state) { + /* Order matters here; last measurement seems to be more noisy then the + rest, and we care about pressure least */ + case 2: ucb1x00_ts_read_pressure(ts); + break; + case 0: ucb1x00_ts_read_ypos(ts); + break; + case 1: ucb1x00_ts_read_xpos(ts); + break; + } + /* wait for adc */ + try_to_freeze(); + schedule_timeout(1000 * HZ); + ucb1x00_disable_irq(ts->ucb, UCB_IRQ_ADC, UCB_FALLING); + data[state] = UCB_ADC_DAT(ucb1x00_reg_read(ts->ucb, UCB_ADC_DATA)); + ucb1x00_adc_disable(ts->ucb); + } + + /* If not pressed any more, try to sleep! */ + if (data[2] < 300) { + set_task_state(tsk, TASK_INTERRUPTIBLE); + ucb1x00_enable_irq(ts->ucb, UCB_IRQ_TSPX, UCB_RISING); + ucb1x00_ts_mode_int(ts); + ucb1x00_disable(ts->ucb); + ucb1x00_ts_event_release(ts); + try_to_freeze(); + schedule_timeout(1000 * HZ); + ucb1x00_disable_irq(ts->ucb, UCB_IRQ_TSPX, UCB_RISING); + ucb1x00_enable(ts->ucb); + } else { + ucb1x00_ts_evt_add(ts, data[2], data[1], data[0]); + } + ucb1x00_disable(ts->ucb); + msleep(20); + ucb1x00_enable(ts->ucb); + } + + remove_wait_queue(&ts->irq_wait, &wait); + + ts->rtask = NULL; + return 0; +} + +/* + * We only detect touch screen _touches_ with this interrupt + * handler, and even then we just schedule our task. + */ +static void ucb1x00_ts_irq(int idx, void *id) +{ + struct ucb1x00_ts *ts = id; + wake_up(&ts->irq_wait); +} + +static void ucb1x00_adc_irq(int idx, void *id) +{ + struct ucb1x00_ts *ts = id; + wake_up(&ts->irq_wait); +} + +static int ucb1x00_ts_open(struct input_dev *idev) +{ + struct ucb1x00_ts *ts = input_get_drvdata(idev); + int ret = 0; + + BUG_ON(ts->rtask); + + init_waitqueue_head(&ts->irq_wait); + + ret = ucb1x00_hook_irq(ts->ucb, UCB_IRQ_TSPX, ucb1x00_ts_irq, ts); + if (ret < 0) + return ret; + + ret = ucb1x00_hook_irq(ts->ucb, UCB_IRQ_ADC, ucb1x00_adc_irq, ts); + if (ret < 0) { + ucb1x00_free_irq(ts->ucb, UCB_IRQ_TSPX, ts); + return ret; + } + + ucb1x00_enable_irq(ts->ucb, UCB_IRQ_TSPX, UCB_RISING); + + /* + * If we do this at all, we should allow the user to + * measure and read the X and Y resistance at any time. + */ + ucb1x00_adc_enable(ts->ucb); + ts->x_res = ucb1x00_ts_read_xres(ts); + ts->y_res = ucb1x00_ts_read_yres(ts); + ucb1x00_adc_disable(ts->ucb); + + if (machine_is_collie()) { + ucb1x00_io_set_dir(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK); + } + + ts->rtask = kthread_run(ucb1x00_thread, ts, "ktsd"); + if (!IS_ERR(ts->rtask)) { + ret = 0; + } else { + ucb1x00_free_irq(ts->ucb, UCB_IRQ_TSPX, ts); + ts->rtask = NULL; + ret = -EFAULT; + } + + return ret; +} + +/* + * Release touchscreen resources. Disable IRQs. + */ +static void ucb1x00_ts_close(struct input_dev *idev) +{ + struct ucb1x00_ts *ts = input_get_drvdata(idev); + + if (ts->rtask) + kthread_stop(ts->rtask); + + ucb1x00_enable(ts->ucb); + ucb1x00_free_irq(ts->ucb, UCB_IRQ_TSPX, ts); + ucb1x00_free_irq(ts->ucb, UCB_IRQ_ADC, ts); + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, 0); + ucb1x00_disable(ts->ucb); +} + +#ifdef CONFIG_PM +static int ucb1x00_ts_resume(struct ucb1x00_dev *dev) +{ + struct ucb1x00_ts *ts = dev->priv; + + if (ts->rtask != NULL) { + /* + * Restart the TS thread to ensure the + * TS interrupt mode is set up again + * after sleep. + */ + wake_up(&ts->irq_wait); + } + return 0; +} +#else +#define ucb1x00_ts_resume NULL +#endif + + +/* + * Initialisation. + */ +static int ucb1x00_ts_add(struct ucb1x00_dev *dev) +{ + struct ucb1x00_ts *ts; + struct input_dev *idev; + int err; + + ts = kzalloc(sizeof(struct ucb1x00_ts), GFP_KERNEL); + idev = input_allocate_device(); + if (!ts || !idev) { + err = -ENOMEM; + goto fail; + } + + ts->ucb = dev->ucb; + ts->idev = idev; + ts->adcsync = adcsync ? UCB_SYNC : UCB_NOSYNC; + + input_set_drvdata(idev, ts); + idev->name = "Touchscreen panel"; + idev->id.product = ts->ucb->id; + idev->open = ucb1x00_ts_open; + idev->close = ucb1x00_ts_close; + + __set_bit(EV_ABS, idev->evbit); + __set_bit(ABS_X, idev->absbit); + __set_bit(ABS_Y, idev->absbit); + __set_bit(ABS_PRESSURE, idev->absbit); + + input_set_abs_params(ts->idev, ABS_X, 0, 450, 0, 0); + input_set_abs_params(ts->idev, ABS_Y, 200, 800, 0, 0); + input_set_abs_params(ts->idev, ABS_PRESSURE, 400, 800, 0, 0); + + + err = input_register_device(idev); + if (err) + goto fail; + + dev->priv = ts; + + return 0; + + fail: + input_free_device(idev); + kfree(ts); + return err; +} + +static void ucb1x00_ts_remove(struct ucb1x00_dev *dev) +{ + struct ucb1x00_ts *ts = dev->priv; + + input_unregister_device(ts->idev); + kfree(ts); +} + +static struct ucb1x00_driver ucb1x00_ts_driver = { + .add = ucb1x00_ts_add, + .remove = ucb1x00_ts_remove, + .resume = ucb1x00_ts_resume, +}; + +static int __init ucb1x00_ts_init(void) +{ + return ucb1x00_register_driver(&ucb1x00_ts_driver); +} + +static void __exit ucb1x00_ts_exit(void) +{ + ucb1x00_unregister_driver(&ucb1x00_ts_driver); +} + +module_param(adcsync, int, 0444); +module_init(ucb1x00_ts_init); +module_exit(ucb1x00_ts_exit); + +MODULE_AUTHOR("Russell King "); +MODULE_DESCRIPTION("UCB1x00 touchscreen driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 6f8a9a1..184a8a9 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -32,7 +32,6 @@ obj-$(CONFIG_MCP) += mcp-core.o obj-$(CONFIG_MCP_SA11X0) += mcp-sa11x0.o obj-$(CONFIG_MCP_UCB1200) += ucb1x00-core.o obj-$(CONFIG_MCP_UCB1200_TS) += ucb1x00-ts.o - ifeq ($(CONFIG_SA1100_ASSABET),y) obj-$(CONFIG_MCP_UCB1200) += ucb1x00-assabet.o endif diff --git a/include/linux/mfd/ucb1x00.h b/include/linux/mfd/ucb1x00.h index aa9c378..b13171e 100644 --- a/include/linux/mfd/ucb1x00.h +++ b/include/linux/mfd/ucb1x00.h @@ -37,7 +37,10 @@ #define UCB_IE_TCLIP (1 << 14) #define UCB_IE_ACLIP (1 << 15) +/* UCB1200 irqs */ +#define UCB_IRQ_ADC 11 #define UCB_IRQ_TSPX 12 +#define UCB_IRQ_TSMX 13 #define UCB_TC_A 0x05 #define UCB_TC_A_LOOP (1 << 7) /* UCB1200 */ -- 1.6.0.4