From: Kevin Hilman Date: Fri, 4 May 2007 23:35:00 +0000 (-0700) Subject: SPI: TSC2046 touchscreen support X-Git-Tag: v2.6.21-omap1 X-Git-Url: http://www.pilppa.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=78e73e07e27783951ba64da4a3012b84b43b7b90;p=linux-2.6-omap-h63xx.git SPI: TSC2046 touchscreen support This is a rework of the tsc2046 driver from TI's tree, based on the tsc2301 driver and using the new SPI framework. Signed-off-by: Kevin Hilman Signed-off-by: Tony Lindgren --- diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index caac4d425b7..5d534bee3aa 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -165,4 +165,10 @@ config TOUCHSCREEN_TSC2301 help Say Y here for if you are using the touchscreen features of TSC2301. +config TOUCHSCREEN_TSC2046 + tristate "TSC2046 touchscreen support" + default MACH_OMAP2430SDP + help + Say Y here for if you are using the touchscreen features of TSC2046 + endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index ee27e0b1e2d..4cf6b755b73 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -19,3 +19,4 @@ obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o obj-$(CONFIG_TOUCHSCREEN_TSC2102) += tsc2102_ts.o obj-$(CONFIG_TOUCHSCREEN_OMAP) += omap/ obj-$(CONFIG_TOUCHSCREEN_TSC2301) += tsc2301_ts.o +obj-$(CONFIG_TOUCHSCREEN_TSC2046) += tsc2046_ts.o diff --git a/drivers/input/touchscreen/tsc2046_ts.c b/drivers/input/touchscreen/tsc2046_ts.c new file mode 100644 index 00000000000..a56ae5a891e --- /dev/null +++ b/drivers/input/touchscreen/tsc2046_ts.c @@ -0,0 +1,555 @@ +/* + * TSC2046 Touchscreen driver + * + * Author: Kevin Hilman, MontaVista Software, Inc. + * + * Communication details from original TI driver + * Copyright (C) 2004-2005 Texas Instruments, Inc. + * + * Structure based heavily on TSC2301 driver + * Copyright (C) 2005-2006 Nokia Corporation + * + * 2007 (c) MontaVista Software, Inc. This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + * + */ + +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_ARCH_OMAP +#include +#endif + +#include + +/* TSC2046 commands */ +#define START_BYTE 0x8000 +#define X_CMDWD 0xd300 +#define Y_CMDWD 0x9300 +#define Z1_CMDWD 0xb300 +#define Z2_CMDWD 0xc300 + +#define TSC2046_TS_SCAN_TIME 1 +#define MAX_12BIT ((1 << 12) - 1) + +struct tsc2046_ts { + struct input_dev *idev; + char phys[32]; + struct timer_list timer; + spinlock_t lock; + + struct spi_transfer read_xfer[3]; + struct spi_message read_msg; + struct spi_message enable_msg; + u16 data[5]; + + u16 x; + u16 y; + u16 p; + int sample_cnt; + + int ignore_last : 1; + u16 x_plate_ohm; + int max_pressure; + int touch_pressure; + int pressure_limit; + + u16 irq_enabled:1; + u16 pen_down:1; + u16 disabled:1; + u16 pending:1; + + s16 dav_gpio; + int irq; +}; + +static const u16 tsc2046_ts_cmd_data[] = { + START_BYTE, X_CMDWD, Y_CMDWD, Z1_CMDWD, Z2_CMDWD, +}; + +static int device_suspended(struct device *dev) +{ + struct tsc2046 *tsc = dev_get_drvdata(dev); + return dev->power.power_state.event != PM_EVENT_ON || tsc->ts->disabled; +} + +static void update_pen_state(struct tsc2046 *tsc, int x, int y, int pressure) +{ + struct tsc2046_ts *ts = tsc->ts; + int sync = 0; + + if (pressure) { + input_report_abs(ts->idev, ABS_X, x); + input_report_abs(ts->idev, ABS_Y, y); + input_report_abs(ts->idev, ABS_PRESSURE, pressure); + if (!ts->pen_down) + input_report_key(ts->idev, BTN_TOUCH, 1); + sync = 1; + } else if (ts->pen_down) { + input_report_abs(ts->idev, ABS_PRESSURE, 0); + input_report_key(ts->idev, BTN_TOUCH, 0); + sync = 1; + } + + if (sync) + input_sync(ts->idev); + + ts->pen_down = pressure ? 1 : 0; + +#ifdef VERBOSE + dev_dbg(&tsc->spi->dev, "x %4d y %4d p %4d\n", x, y, pressure); +#endif +} + +#define CONV_DATA(d1, d2) \ + (((d1 & 0x7f) << 5) | ((d2 >> 11) & 0x1f)) + +/* + * This procedure is called by the SPI framework after the coordinates + * have been read from TSC2046 + */ +static void tsc2046_ts_rx(void *arg) +{ + struct tsc2046 *tsc = arg; + struct tsc2046_ts *ts = tsc->ts; + unsigned int x, y, z1, z2, pressure; + + x = CONV_DATA(ts->data[2], ts->data[3]); + y = CONV_DATA(ts->data[1], ts->data[2]); + z1 = CONV_DATA(ts->data[3], ts->data[4]); + z2 = CONV_DATA(ts->data[4], 0); + + if (z1) { + pressure = ts->x_plate_ohm * x; + pressure /= 4096; + pressure *= z2 - z1; + pressure /= z1; + } else + pressure = 0; + + /* If pressure value is above a preset limit (pen is barely + * touching the screen) we can't trust the coordinate values. + */ + if (pressure < ts->pressure_limit && x < MAX_12BIT && y < MAX_12BIT) { + ts->pressure_limit = ts->max_pressure; + if (ts->ignore_last) { + if (ts->sample_cnt) + update_pen_state(tsc, ts->x, ts->y, ts->p); + ts->x = x; + ts->y = y; + ts->p = pressure; + } else + update_pen_state(tsc, x, y, pressure); + ts->sample_cnt++; + } + + mod_timer(&ts->timer, + jiffies + msecs_to_jiffies(TSC2046_TS_SCAN_TIME)); +} + +static int is_pen_down(struct tsc2046_ts *ts) +{ + return ts->pen_down; +} + +/* + * Timer is called every TSC2046_TS_SCAN_TIME when the pen is down + */ +static void tsc2046_ts_timer(unsigned long arg) +{ + struct tsc2046 *tsc = (void *) arg; + struct tsc2046_ts *ts = tsc->ts; + unsigned long flags; + int ndav; + int r; + + spin_lock_irqsave(&ts->lock, flags); + ndav = omap_get_gpio_datain(ts->dav_gpio); + if (ndav || device_suspended(&tsc->spi->dev)) { + /* Pen has been lifted */ + if (!device_suspended(&tsc->spi->dev) && + !ts->irq_enabled) { + ts->irq_enabled = 1; + enable_irq(ts->irq); + } + update_pen_state(tsc, 0, 0, 0); + ts->pending = 0; + spin_unlock_irqrestore(&ts->lock, flags); + + } else { + ts->pen_down = 1; + spin_unlock_irqrestore(&ts->lock, flags); + + r = spi_async(tsc->spi, &ts->read_msg); + if (r) + dev_err(&tsc->spi->dev, "ts: spi_async() failed"); + } +} + +/* + * This interrupt is called when pen is down and first coordinates are + * available. That is indicated by a falling edge on DEV line. IRQ is + * disabled here because while the pen is down the coordinates are + * read by a timer. + */ +static irqreturn_t tsc2046_ts_irq_handler(int irq, void *dev_id) +{ + struct tsc2046 *tsc = dev_id; + struct tsc2046_ts *ts = tsc->ts; + unsigned long flags; + + spin_lock_irqsave(&ts->lock, flags); + if (ts->irq_enabled) { + ts->irq_enabled = 0; + disable_irq(ts->irq); + ts->pending = 1; + ts->pressure_limit = ts->touch_pressure; + ts->sample_cnt = 0; + mod_timer(&ts->timer, + jiffies + msecs_to_jiffies(TSC2046_TS_SCAN_TIME)); + } + spin_unlock_irqrestore(&ts->lock, flags); + + return IRQ_HANDLED; +} + +/* Must be called with ts->lock held */ +static void tsc2046_ts_disable(struct tsc2046 *tsc) +{ + struct tsc2046_ts *ts = tsc->ts; + + if (ts->disabled) + return; + + ts->disabled = 1; + if (!ts->pending) { + ts->irq_enabled = 0; + disable_irq(ts->irq); + } else { + while (ts->pending) { + spin_unlock_irq(&ts->lock); + msleep(1); + spin_lock_irq(&ts->lock); + } + } +} + +static void tsc2046_ts_enable(struct tsc2046 *tsc) +{ + struct tsc2046_ts *ts = tsc->ts; + + if (!ts->disabled) + return; + + ts->disabled = 0; + ts->irq_enabled = 1; + enable_irq(ts->irq); +} + +#ifdef CONFIG_PM +int tsc2046_ts_suspend(struct tsc2046 *tsc) +{ + struct tsc2046_ts *ts = tsc->ts; + + spin_lock_irq(&ts->lock); + tsc2046_ts_disable(tsc); + spin_unlock_irq(&ts->lock); + + return 0; +} + +void tsc2046_ts_resume(struct tsc2046 *tsc) +{ + struct tsc2046_ts *ts = tsc->ts; + + spin_lock_irq(&ts->lock); + tsc2046_ts_enable(tsc); + spin_unlock_irq(&ts->lock); +} +#endif + +static void tsc2046_ts_setup_spi_xfer(struct tsc2046 *tsc) +{ + struct tsc2046_ts *ts = tsc->ts; + struct spi_message *m = &ts->read_msg; + struct spi_transfer *x = &ts->read_xfer[1]; + + spi_message_init(m); + + /* read and write data in one transaction */ + x->tx_buf = &tsc2046_ts_cmd_data; + x->rx_buf = &ts->data; + x->len = 10; + spi_message_add_tail(x, m); + + /* send another START_BYTE to (re)enable pen interrupts */ + x++; + x->tx_buf = &tsc2046_ts_cmd_data[0]; + x->len = 2; + spi_message_add_tail(x, m); + + m->complete = tsc2046_ts_rx; + m->context = tsc; +} + +static ssize_t tsc2046_ts_pen_down_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tsc2046 *tsc = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", is_pen_down(tsc->ts)); +} + +static DEVICE_ATTR(pen_down, S_IRUGO, tsc2046_ts_pen_down_show, NULL); + +static ssize_t tsc2046_ts_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsc2046 *tsc = dev_get_drvdata(dev); + struct tsc2046_ts *ts = tsc->ts; + + return sprintf(buf, "%u\n", ts->disabled); +} + +static ssize_t tsc2046_ts_disable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tsc2046 *tsc = dev_get_drvdata(dev); + struct tsc2046_ts *ts = tsc->ts; + char *endp; + int i; + + i = simple_strtoul(buf, &endp, 10); + spin_lock_irq(&ts->lock); + + if (i) + tsc2046_ts_disable(tsc); + else + tsc2046_ts_enable(tsc); + + spin_unlock_irq(&ts->lock); + + return count; +} + +static DEVICE_ATTR(disable_ts, 0664, tsc2046_ts_disable_show, + tsc2046_ts_disable_store); + +int __devinit tsc2046_ts_init(struct tsc2046 *tsc, + struct tsc2046_platform_data *pdata) +{ + struct tsc2046_ts *ts; + struct input_dev *idev; + int dav_gpio, r; + + if (pdata->dav_gpio < 0) { + dev_err(&tsc->spi->dev, "need DAV GPIO"); + return -EINVAL; + } + dav_gpio = pdata->dav_gpio; + + ts = kzalloc(sizeof(*ts), GFP_KERNEL); + if (ts == NULL) + return -ENOMEM; + tsc->ts = ts; + + ts->dav_gpio = dav_gpio; +#ifdef CONFIG_ARCH_OMAP + r = omap_request_gpio(dav_gpio); + if (r < 0) { + dev_err(&tsc->spi->dev, "unable to get DAV GPIO"); + goto err1; + } + omap_set_gpio_direction(dav_gpio, 1); + if (pdata->gpio_debounce) { + omap_set_gpio_debounce(tsc->gpio, 1); + omap_set_gpio_debounce_time(tsc->gpio, pdata->gpio_debounce); + } + + ts->irq = OMAP_GPIO_IRQ(dav_gpio); +#endif + init_timer(&ts->timer); + ts->timer.data = (unsigned long) tsc; + ts->timer.function = tsc2046_ts_timer; + + spin_lock_init(&ts->lock); + + ts->x_plate_ohm = pdata->ts_x_plate_ohm ? : 280; + ts->max_pressure= pdata->ts_max_pressure ? : MAX_12BIT; + ts->touch_pressure = pdata->ts_touch_pressure ? : ts->max_pressure; + ts->ignore_last = pdata->ts_ignore_last; + + idev = input_allocate_device(); + if (idev == NULL) { + r = -ENOMEM; + goto err2; + } + idev->name = "TSC2046 touchscreen"; + snprintf(ts->phys, sizeof(ts->phys), + "%s/input-ts", tsc->spi->dev.bus_id); + idev->phys = ts->phys; + + idev->evbit[0] = BIT(EV_ABS) | BIT(EV_KEY); + idev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE); + ts->idev = idev; + + tsc2046_ts_setup_spi_xfer(tsc); + + /* These parameters should perhaps be configurable? */ + input_set_abs_params(idev, ABS_X, 0, 4096, 0, 0); + input_set_abs_params(idev, ABS_Y, 0, 4096, 0, 0); + input_set_abs_params(idev, ABS_PRESSURE, 0, 1024, 0, 0); + + ts->irq_enabled = 1; + r = request_irq(ts->irq, tsc2046_ts_irq_handler, + SA_SAMPLE_RANDOM | SA_TRIGGER_FALLING, + "tsc2046-ts", tsc); + if (r < 0) { + dev_err(&tsc->spi->dev, "unable to get DAV IRQ"); + goto err3; + } + set_irq_wake(ts->irq, 1); + + if (device_create_file(&tsc->spi->dev, &dev_attr_pen_down) < 0) + goto err4; + if (device_create_file(&tsc->spi->dev, &dev_attr_disable_ts) < 0) + goto err5; + + r = input_register_device(idev); + if (r < 0) { + dev_err(&tsc->spi->dev, "can't register touchscreen device\n"); + goto err6; + } + + /* kick off a transaction to enable pen interrupts */ + spi_async(tsc->spi, &ts->read_msg); + + return 0; +err6: + device_remove_file(&tsc->spi->dev, &dev_attr_disable_ts); +err5: + device_remove_file(&tsc->spi->dev, &dev_attr_pen_down); +err4: + free_irq(ts->irq, tsc); +err3: + input_free_device(idev); +err2: +#ifdef CONFIG_ARCH_OMAP + omap_free_gpio(dav_gpio); +#endif + err1: + kfree(ts); + return r; +} +EXPORT_SYMBOL(tsc2046_ts_init); + +void __devexit tsc2046_ts_exit(struct tsc2046 *tsc) +{ + struct tsc2046_ts *ts = tsc->ts; + unsigned long flags; + + spin_lock_irqsave(&ts->lock, flags); + tsc2046_ts_disable(tsc); + spin_unlock_irqrestore(&ts->lock, flags); + + device_remove_file(&tsc->spi->dev, &dev_attr_disable_ts); + device_remove_file(&tsc->spi->dev, &dev_attr_pen_down); + + free_irq(ts->irq, tsc); + input_unregister_device(ts->idev); + +#ifdef CONFIG_ARCH_OMAP + omap_free_gpio(ts->dav_gpio); +#endif + kfree(ts); +} +EXPORT_SYMBOL(tsc2046_ts_exit); + + +static int __devinit tsc2046_probe(struct spi_device *spi) +{ + struct tsc2046 *tsc; + struct tsc2046_platform_data *pdata = spi->dev.platform_data; + int r = -ENODEV; + + dev_dbg(&spi->dev, "%s\n", __FUNCTION__); + + if (!pdata) { + dev_dbg(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + tsc = kzalloc(sizeof(*tsc), GFP_KERNEL); + if (tsc == NULL) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, tsc); + tsc->spi = spi; + spi->dev.power.power_state = PMSG_ON; + + spi->mode = SPI_MODE_1; + spi->bits_per_word = 16; + + /* The max speed might've been defined by the board-specific + * struct */ + if (!spi->max_speed_hz) + spi->max_speed_hz = TSC2046_HZ; + spi_setup(spi); + + r = tsc2046_ts_init(tsc, pdata); + if (r) + goto err1; + + return 0; + err1: + kfree(tsc); + return r; +} + +static int __devexit tsc2046_remove(struct spi_device *spi) +{ + struct tsc2046 *tsc = dev_get_drvdata(&spi->dev); + + dev_dbg(&tsc->spi->dev, "%s\n", __FUNCTION__); + + tsc2046_ts_exit(tsc); + kfree(tsc); + + return 0; +} + +static struct spi_driver tsc2046_driver = { + .driver = { + .name = "tsc2046", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = tsc2046_probe, + .remove = __devexit_p(tsc2046_remove), +}; + +static int __init tsc2046_init(void) +{ + printk("TSC2046 driver initializing\n"); + + return spi_register_driver(&tsc2046_driver); +} +module_init(tsc2046_init); + +static void __exit tsc2046_exit(void) +{ + spi_unregister_driver(&tsc2046_driver); +} +module_exit(tsc2046_exit); + +MODULE_AUTHOR("Kevin Hilman "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/spi/tsc2046.h b/include/linux/spi/tsc2046.h new file mode 100644 index 00000000000..aca712f979e --- /dev/null +++ b/include/linux/spi/tsc2046.h @@ -0,0 +1,60 @@ +#ifndef _LINUX_SPI_TSC2046_H +#define _LINUX_SPI_TSC2046_H + +#include +#include + +struct tsc2046_platform_data { + s16 dav_gpio; + s16 gpio_debounce; + u16 ts_x_plate_ohm; + u32 ts_max_pressure; /* Samples with bigger pressure value will + be ignored, since the corresponding X, Y + values are unreliable */ + u32 ts_touch_pressure;/* Pressure limit until we report a + touch event. After that we switch + to ts_max_pressure. */ + unsigned ts_ignore_last : 1; + +}; + +struct tsc2046_ts; + +struct tsc2046 { + struct spi_device *spi; + int gpio; + + struct tsc2046_ts *ts; +}; + +/* The TSC2046 operates at a maximum speed of 2MHz */ +#define TSC2046_HZ 2000000 + +#define TSC2046_DECL_MOD(module) \ +extern int tsc2046_##module##_init(struct tsc2046 *tsc, \ + struct tsc2046_platform_data *pdata); \ +extern void tsc2046_##module##_exit(struct tsc2046 *tsc); \ +extern int tsc2046_##module##_suspend(struct tsc2046 *tsc); \ +extern void tsc2046_##module##_resume(struct tsc2046 *tsc); + +#define TSC2046_DECL_EMPTY_MOD(module) \ +static inline int tsc2046_##module##_init(struct tsc2046 *tsc, \ + struct tsc2046_platform_data *pdata) \ +{ \ + return 0; \ +} \ +static inline void tsc2046_##module##_exit(struct tsc2046 *tsc) {} \ +static inline int tsc2046_##module##_suspend(struct tsc2046 *tsc) \ +{ \ + return 0; \ +} \ +static inline void tsc2046_##module##_resume(struct tsc2046 *tsc) {} + +#if defined(CONFIG_TOUCHSCREEN_TSC2046) || \ + defined(CONFIG_TOUCHSCREEN_TSC2046_MODULE) +TSC2046_DECL_MOD(ts) +#else +TSC2046_DECL_EMPTY_MOD(ts) +#endif + +#endif