From 9e0d71c4a6247d88d3b772f6b05bcaa39711a937 Mon Sep 17 00:00:00 2001 From: Thomas Kunze Date: Tue, 10 Feb 2009 19:31:25 +0100 Subject: [PATCH 12/23] move ucb1200-ts driver Move the touchscreen driver to drivers/input/touchscreen where touchscreen drivers belong. Conflicts: drivers/input/touchscreen/Makefile drivers/mfd/Kconfig drivers/mfd/Makefile Conflicts: drivers/mfd/Kconfig drivers/mfd/Makefile --- drivers/input/touchscreen/Kconfig | 7 + drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/ucb1x00-ts.c | 438 ++++++++++++++++++++++++++++++++ drivers/mfd/Kconfig | 3 - drivers/mfd/Makefile | 3 +- drivers/mfd/ucb1x00-ts.c | 438 -------------------------------- 6 files changed, 447 insertions(+), 443 deletions(-) create mode 100644 drivers/input/touchscreen/ucb1x00-ts.c delete mode 100644 drivers/mfd/ucb1x00-ts.c diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 3d1ab8f..3ac8cd6 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -221,6 +221,13 @@ config TOUCHSCREEN_ATMEL_TSADCC To compile this driver as a module, choose M here: the module will be called atmel_tsadcc. +config TOUCHSCREEN_UCB1200_TS + tristate "Philips UCB1200 touchscreen" + depends on MCP_UCB1200 + help + This enabled support for the Pilips UCB1200 touchscreen interface + and compatible. + config TOUCHSCREEN_UCB1400 tristate "Philips UCB1400 touchscreen" depends on AC97_BUS diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 15cf290..77ba930 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_TOUCHSCREEN_PENMOUNT) += penmount.o obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) += touchit213.o obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o +obj-$(CONFIG_TOUCHSCREEN_UCB1200_TS) += ucb1x00-ts.o obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o obj-$(CONFIG_TOUCHSCREEN_WM97XX) += wm97xx-ts.o wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705) += wm9705.o diff --git a/drivers/input/touchscreen/ucb1x00-ts.c b/drivers/input/touchscreen/ucb1x00-ts.c new file mode 100644 index 0000000..b5feae9 --- /dev/null +++ b/drivers/input/touchscreen/ucb1x00-ts.c @@ -0,0 +1,438 @@ +/* + * 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 + + + +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 restart:1; + unsigned int adcsync:1; +}; + +static int adcsync; + +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_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_sync(idev); +} + +/* + * Switch to interrupt mode. + */ +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. + */ +static inline unsigned int ucb1x00_ts_read_pressure(struct ucb1x00_ts *ts) +{ + if (machine_is_collie()) { + 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); + + udelay(55); + + return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_AD2, ts->adcsync); + } else { + 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_PRES | UCB_TS_CR_BIAS_ENA); + + return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPY, ts->adcsync); + } +} + +/* + * 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 unsigned int ucb1x00_ts_read_xpos(struct ucb1x00_ts *ts) +{ + if (machine_is_collie()) + ucb1x00_io_write(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK); + else { + 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); + 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); + } + 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); + + udelay(55); + + return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPY, ts->adcsync); +} + +/* + * 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 unsigned int ucb1x00_ts_read_ypos(struct ucb1x00_ts *ts) +{ + if (machine_is_collie()) + ucb1x00_io_write(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK); + else { + 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); + 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); + } + + 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); + + udelay(55); + + return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPX, ts->adcsync); +} + +/* + * 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); +} + +static inline int ucb1x00_ts_pen_down(struct ucb1x00_ts *ts) +{ + unsigned int val = ucb1x00_reg_read(ts->ucb, UCB_TS_CR); + + if (machine_is_collie()) + return (!(val & (UCB_TS_CR_TSPX_LOW))); + else + return (val & (UCB_TS_CR_TSPX_LOW | UCB_TS_CR_TSMX_LOW)); +} + +/* + * 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; + DECLARE_WAITQUEUE(wait, current); + int valid = 0; + + set_freezable(); + add_wait_queue(&ts->irq_wait, &wait); + while (!kthread_should_stop()) { + unsigned int x, y, p; + signed long timeout; + + ts->restart = 0; + + ucb1x00_adc_enable(ts->ucb); + + x = ucb1x00_ts_read_xpos(ts); + y = ucb1x00_ts_read_ypos(ts); + p = ucb1x00_ts_read_pressure(ts); + + /* + * Switch back to interrupt mode. + */ + ucb1x00_ts_mode_int(ts); + ucb1x00_adc_disable(ts->ucb); + + msleep(10); + + ucb1x00_enable(ts->ucb); + + + if (ucb1x00_ts_pen_down(ts)) { + set_current_state(TASK_INTERRUPTIBLE); + + ucb1x00_enable_irq(ts->ucb, UCB_IRQ_TSPX, machine_is_collie() ? UCB_RISING : UCB_FALLING); + ucb1x00_disable(ts->ucb); + + /* + * If we spat out a valid sample set last time, + * spit out a "pen off" sample here. + */ + if (valid) { + ucb1x00_ts_event_release(ts); + valid = 0; + } + + timeout = MAX_SCHEDULE_TIMEOUT; + } else { + ucb1x00_disable(ts->ucb); + + /* + * Filtering is policy. Policy belongs in user + * space. We therefore leave it to user space + * to do any filtering they please. + */ + if (!ts->restart) { + ucb1x00_ts_evt_add(ts, p, x, y); + valid = 1; + } + + set_current_state(TASK_INTERRUPTIBLE); + timeout = HZ / 100; + } + + try_to_freeze(); + + schedule_timeout(timeout); + } + + 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; + + ucb1x00_disable_irq(ts->ucb, UCB_IRQ_TSPX, UCB_FALLING); + 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) + goto out; + + /* + * 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); + + 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; + } + + out: + 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_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. + */ + ts->restart = 1; + 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; + + 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_drvdata(idev, ts); + + 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/Kconfig b/drivers/mfd/Kconfig index 2572773..bbc137d 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -172,8 +172,5 @@ config MCP_UCB1200 tristate "Support for UCB1200 / UCB1300" depends on MCP -config MCP_UCB1200_TS - tristate "Touchscreen interface support" - depends on MCP_UCB1200 && INPUT endmenu diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 9a5ad8a..4981aff 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -24,11 +24,10 @@ obj-$(CONFIG_MFD_CORE) += mfd-core.o 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 obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o -obj-$(CONFIG_PMIC_DA903X) += da903x.o \ No newline at end of file +obj-$(CONFIG_PMIC_DA903X) += da903x.o diff --git a/drivers/mfd/ucb1x00-ts.c b/drivers/mfd/ucb1x00-ts.c deleted file mode 100644 index b5feae9..0000000 --- a/drivers/mfd/ucb1x00-ts.c +++ /dev/null @@ -1,438 +0,0 @@ -/* - * 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 - - - -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 restart:1; - unsigned int adcsync:1; -}; - -static int adcsync; - -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_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_sync(idev); -} - -/* - * Switch to interrupt mode. - */ -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. - */ -static inline unsigned int ucb1x00_ts_read_pressure(struct ucb1x00_ts *ts) -{ - if (machine_is_collie()) { - 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); - - udelay(55); - - return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_AD2, ts->adcsync); - } else { - 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_PRES | UCB_TS_CR_BIAS_ENA); - - return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPY, ts->adcsync); - } -} - -/* - * 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 unsigned int ucb1x00_ts_read_xpos(struct ucb1x00_ts *ts) -{ - if (machine_is_collie()) - ucb1x00_io_write(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK); - else { - 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); - 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); - } - 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); - - udelay(55); - - return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPY, ts->adcsync); -} - -/* - * 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 unsigned int ucb1x00_ts_read_ypos(struct ucb1x00_ts *ts) -{ - if (machine_is_collie()) - ucb1x00_io_write(ts->ucb, 0, COLLIE_TC35143_GPIO_TBL_CHK); - else { - 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); - 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); - } - - 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); - - udelay(55); - - return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPX, ts->adcsync); -} - -/* - * 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); -} - -static inline int ucb1x00_ts_pen_down(struct ucb1x00_ts *ts) -{ - unsigned int val = ucb1x00_reg_read(ts->ucb, UCB_TS_CR); - - if (machine_is_collie()) - return (!(val & (UCB_TS_CR_TSPX_LOW))); - else - return (val & (UCB_TS_CR_TSPX_LOW | UCB_TS_CR_TSMX_LOW)); -} - -/* - * 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; - DECLARE_WAITQUEUE(wait, current); - int valid = 0; - - set_freezable(); - add_wait_queue(&ts->irq_wait, &wait); - while (!kthread_should_stop()) { - unsigned int x, y, p; - signed long timeout; - - ts->restart = 0; - - ucb1x00_adc_enable(ts->ucb); - - x = ucb1x00_ts_read_xpos(ts); - y = ucb1x00_ts_read_ypos(ts); - p = ucb1x00_ts_read_pressure(ts); - - /* - * Switch back to interrupt mode. - */ - ucb1x00_ts_mode_int(ts); - ucb1x00_adc_disable(ts->ucb); - - msleep(10); - - ucb1x00_enable(ts->ucb); - - - if (ucb1x00_ts_pen_down(ts)) { - set_current_state(TASK_INTERRUPTIBLE); - - ucb1x00_enable_irq(ts->ucb, UCB_IRQ_TSPX, machine_is_collie() ? UCB_RISING : UCB_FALLING); - ucb1x00_disable(ts->ucb); - - /* - * If we spat out a valid sample set last time, - * spit out a "pen off" sample here. - */ - if (valid) { - ucb1x00_ts_event_release(ts); - valid = 0; - } - - timeout = MAX_SCHEDULE_TIMEOUT; - } else { - ucb1x00_disable(ts->ucb); - - /* - * Filtering is policy. Policy belongs in user - * space. We therefore leave it to user space - * to do any filtering they please. - */ - if (!ts->restart) { - ucb1x00_ts_evt_add(ts, p, x, y); - valid = 1; - } - - set_current_state(TASK_INTERRUPTIBLE); - timeout = HZ / 100; - } - - try_to_freeze(); - - schedule_timeout(timeout); - } - - 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; - - ucb1x00_disable_irq(ts->ucb, UCB_IRQ_TSPX, UCB_FALLING); - 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) - goto out; - - /* - * 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); - - 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; - } - - out: - 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_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. - */ - ts->restart = 1; - 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; - - 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_drvdata(idev, ts); - - 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"); -- 1.5.6.5