From patchwork Sun Dec 13 20:39:14 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Cory Maccarrone X-Patchwork-Id: 67047 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter.kernel.org (8.14.2/8.14.2) with ESMTP id nBDKdW9V010029 for ; Sun, 13 Dec 2009 20:39:32 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753001AbZLMUjc (ORCPT ); Sun, 13 Dec 2009 15:39:32 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1753018AbZLMUjb (ORCPT ); Sun, 13 Dec 2009 15:39:31 -0500 Received: from mail-pw0-f42.google.com ([209.85.160.42]:57825 "HELO mail-pw0-f42.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with SMTP id S1752899AbZLMUja (ORCPT ); Sun, 13 Dec 2009 15:39:30 -0500 Received: by pwj9 with SMTP id 9so1545747pwj.21 for ; Sun, 13 Dec 2009 12:39:29 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:received:received:from:to:cc:subject:date :message-id:x-mailer; bh=Ji1TUq62aNO1TJOpUWV828tND/d7XdSwQ+ETYaZveOk=; b=BXy+YgrHIGGyliAHYWZ6tDoh3z65VxzDo5QtCTYYPOVmspax7BKYz5o02JcCO2Xnyq fRUWAr3OljzU8Y0tNOWsmal4z1anmLSniWn4REbiHiSdFAhLbi3UqnJuLhkQZKVoKozD YfRWOd5IrSdyJCxJjWvP2v5iUSmUj37Z8aa4g= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=from:to:cc:subject:date:message-id:x-mailer; b=V1crLmGqdcHm6wxBn4UNQWZ60bbkpY7Xht4KjLCaKDyYmkzK1XDO0h0+MgSg8U/wwh eSx0mKI2Mmw28Rf/BKyY9hKmfz0sFDZj8xp24evHpq/5DB75YlR4XOsdyMFMo2yQVzUB IWLnCQWV+2jxwI9LEAGz5nAmxjbdtWPF+wIsI= Received: by 10.142.247.42 with SMTP id u42mr2512437wfh.259.1260736769478; Sun, 13 Dec 2009 12:39:29 -0800 (PST) Received: from localhost (97-126-116-159.tukw.qwest.net [97.126.116.159]) by mx.google.com with ESMTPS id 23sm4352756pzk.12.2009.12.13.12.39.28 (version=TLSv1/SSLv3 cipher=RC4-MD5); Sun, 13 Dec 2009 12:39:28 -0800 (PST) From: Cory Maccarrone To: linux-input@vger.kernel.org, spi-devel-general@lists.sourceforge.net Cc: Cory Maccarrone Subject: [PATCH] Input: Add tsc2046 touchscreen driver Date: Sun, 13 Dec 2009 12:39:14 -0800 Message-Id: <1260736754-374-1-git-send-email-darkstar6262@gmail.com> X-Mailer: git-send-email 1.6.3.3 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 8cc453c..1f3273b 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -501,6 +501,20 @@ config TOUCHSCREEN_TOUCHIT213 To compile this driver as a module, choose M here: the module will be called touchit213. +config TOUCHSCREEN_TSC2046 + tristate "TSC2046 based touchscreens" + depends on SPI_MASTER + help + Say Y here if you have a touchscreen interface using the + TI TSC 2046 controller, and your board-specific initialization + code includes that in its table of SPI devices. Also make + sure the proper SPI controller is selected. + + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called tsc2046. + config TOUCHSCREEN_TSC2007 tristate "TSC2007 based touchscreens" depends on I2C diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 15fa62c..3c413f7 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -30,6 +30,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_TSC2046) += tsc2046.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/tsc2046.c b/drivers/input/touchscreen/tsc2046.c new file mode 100644 index 0000000..e45d251 --- /dev/null +++ b/drivers/input/touchscreen/tsc2046.c @@ -0,0 +1,740 @@ +/* + * 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 +#include + +#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) + +#define MIN_ABS_X 528 +#define MAX_ABS_X 3760 +#define MIN_ABS_Y 624 +#define MAX_ABS_Y 3760 + +/* The TSC2046 operates at a maximum speed of 2MHz */ +#define TSC2046_HZ 2000000 + +#define CONV_DATA(d1, d2) \ + (((d1 & 0x7f) << 5) | ((d2 >> 11) & 0x1f)) + +/* Convenience macros for pointercal calibration calculation */ +#define PA ts->pointercal[0] +#define PB ts->pointercal[1] +#define PC ts->pointercal[2] +#define PD ts->pointercal[3] +#define PE ts->pointercal[4] +#define PF ts->pointercal[5] +#define PS ts->pointercal[6] + +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; + u16 old_x; + u16 old_y; + u16 old_pressure; + int sample_cnt; + + int ignore_last : 1; + u16 x_plate_ohm; + int max_pressure; + int touch_pressure; + int pressure_limit; + + int pointercal[7]; + u16 x_res; + u16 y_res; + + u16 irq_enabled:1; + u16 pen_down:1; + u16 disabled:1; + u16 pending:1; + u16 use_pointercal:1; + + s16 dav_gpio; + int (*get_pendown_state)(void); + int irq; +}; + +struct tsc2046 { + struct spi_device *spi; + struct tsc2046_ts *ts; +}; + +static const u16 tsc2046_ts_cmd_data[] = { + START_BYTE, X_CMDWD, Y_CMDWD, Z1_CMDWD, Z2_CMDWD, +}; + +static int tsc2046_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 tsc2046_update_pen_state(struct tsc2046 *tsc, int x, int y, int pressure) +{ + struct tsc2046_ts *ts = tsc->ts; + + if (x <= ts->old_x - 4 || x >= ts->old_x + 4 || + y <= ts->old_y - 4 || y >= ts->old_y + 4 || + pressure <= ts->old_pressure - 4 || pressure >= ts->old_pressure + 4) + { + if (pressure) { + if (!ts->use_pointercal || (ts->use_pointercal && + x >= 0 && + x <= ts->x_res - 1 && + y >= 0 && + y <= ts->y_res - 1)) + { + input_report_abs(ts->idev, ABS_X, x); + input_report_abs(ts->idev, ABS_Y, y); + input_report_abs(ts->idev, ABS_PRESSURE, 1); + input_report_key(ts->idev, BTN_TOUCH, 1); + ts->pen_down = 1; + } else { + input_report_abs(ts->idev, ABS_PRESSURE, 0); + input_report_key(ts->idev, BTN_TOUCH, 0); + ts->pen_down = 0; + } + } else { + input_report_abs(ts->idev, ABS_PRESSURE, 0); + input_report_key(ts->idev, BTN_TOUCH, 0); + ts->pen_down = 0; + } + + ts->old_x = x; + ts->old_y = y; + ts->old_pressure = pressure; + + input_sync(ts->idev); + +#ifdef VERBOSE + dev_dbg(&tsc->spi->dev, "x %4d y %4d p %4d\n", x, y, pressure); +#endif + } +} + +/* + * 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; + int xN, yN; + + 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 (ts->use_pointercal) { + /* Transform pen state according to calibration parameters. This + * algorithm is the same used by tslib and Qt to do 7-value + * screen transforms. See: + * + * http://doc.trolltech.com/4.3/qwscalibratedmousehandler.html#transform + */ + xN = ((PA * x) + (PB * y) + PC) / PS; + yN = ((PD * x) + (PE * y) + PF) / PS; + + x = (xN < 0) ? 0 : (xN > ts->x_res ? ts->x_res - 1 : (unsigned int)xN); + y = (yN < 0) ? 0 : (yN > ts->y_res ? ts->y_res - 1 : (unsigned int)yN); + } + + /* 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) + tsc2046_update_pen_state(tsc, ts->x, ts->y, ts->p); + ts->x = x; + ts->y = y; + ts->p = pressure; + } else + tsc2046_update_pen_state(tsc, x, y, pressure); + ts->sample_cnt++; + } + + mod_timer(&ts->timer, + jiffies + msecs_to_jiffies(TSC2046_TS_SCAN_TIME)); +} + +static int tsc2046_is_pen_down(struct tsc2046_ts *ts) +{ + return ts->pen_down; +} + +static int tsc2046_get_pendown_state(struct tsc2046_ts *ts) +{ + if (ts->get_pendown_state) + return ts->get_pendown_state(); + + return !gpio_get_value(ts->dav_gpio); +} + +/* + * 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 = tsc2046_get_pendown_state(ts); + if (!ndav || tsc2046_device_suspended(&tsc->spi->dev)) { + /* Pen has been lifted */ + if (!tsc2046_device_suspended(&tsc->spi->dev) && + !ts->irq_enabled) + { + ts->irq_enabled = 1; + enable_irq(ts->irq); + } + tsc2046_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_nosync(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->rx_buf = &ts->data; + x->len = 2; + spi_message_add_tail(x, m); + + m->complete = tsc2046_ts_rx; + m->context = tsc; +} + +/* Attribute functions */ + +static ssize_t tsc2046_ts_use_pointercal_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->use_pointercal); +} + +static ssize_t tsc2046_ts_use_pointercal_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) + ts->use_pointercal = 1; + else + ts->use_pointercal = 0; + + spin_unlock_irq(&ts->lock); + + return count; +} + +static DEVICE_ATTR(use_pointercal, 0644, tsc2046_ts_use_pointercal_show, tsc2046_ts_use_pointercal_store); + +static ssize_t tsc2046_ts_pointercal_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, "%d %d %d %d %d %d %d\n", PA, PB, PC, PD, PE, PF, PS); +} + +static ssize_t tsc2046_ts_pointercal_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; + unsigned int index = 0, offset = 0; + + spin_lock_irq(&ts->lock); + + for (index = 0; index < 7; index++) { + /* Grab the numbers */ + i = simple_strtol(buf + offset, &endp, 10); + ts->pointercal[index] = i; + + /* Skip the space */ + offset = endp - buf + 1; + } + + spin_unlock_irq(&ts->lock); + + return count; +} + +static DEVICE_ATTR(pointercal, 0644, tsc2046_ts_pointercal_show, tsc2046_ts_pointercal_store); + +static ssize_t tsc2046_ts_screen_res_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 %u\n", ts->x_res, ts->y_res); +} + +static ssize_t tsc2046_ts_screen_res_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; + unsigned int i, offset = 0; + + spin_lock_irq(&ts->lock); + + /* Grab the x res */ + i = simple_strtoul(buf, &endp, 10); + ts->x_res = i; + + /* Skip the space */ + offset = endp - buf + 1; + + /* Grab the y res */ + i = simple_strtoul(buf + offset, &endp, 10); + ts->y_res = i; + + /* Update the device parameters */ + input_set_abs_params(ts->idev, ABS_X, 0, ts->x_res - 1, 0, 0); + input_set_abs_params(ts->idev, ABS_Y, 0, ts->y_res - 1, 0, 0); + + spin_unlock_irq(&ts->lock); + + return count; +} + +static DEVICE_ATTR(screen_res, 0644, tsc2046_ts_screen_res_show, tsc2046_ts_screen_res_store); + +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", tsc2046_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); + +/* Init functions */ + +static int __devinit tsc2046_probe(struct spi_device *spi) +{ + struct tsc2046 *tsc; + struct tsc2046_ts *ts; + struct tsc2046_platform_data *pdata = spi->dev.platform_data; + struct input_dev *idev; + 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); + + if (spi->irq < 0) { + dev_err(&tsc->spi->dev, "need DAV IRQ/GPIO"); + return -EINVAL; + } + + if (!pdata->get_pendown_state && !gpio_is_valid(pdata->gpio_pendown)) { + dev_err(&spi->dev, "no get_pendown_state nor gpio_pendown?\n"); + return -EINVAL; + } + + ts = kzalloc(sizeof(*ts), GFP_KERNEL); + if (ts == NULL) + return -ENOMEM; + tsc->ts = ts; + + if (pdata->get_pendown_state) + ts->get_pendown_state = pdata->get_pendown_state; + else { + ts->dav_gpio = pdata->gpio_pendown; + r = gpio_request(ts->dav_gpio, dev_name(&tsc->spi->dev)); + if (r < 0) { + dev_err(&tsc->spi->dev, "unable to get DAV GPIO"); + goto err1; + } + gpio_direction_input(ts->dav_gpio); + } + + ts->irq = spi->irq; + + 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", dev_name(&tsc->spi->dev)); + 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); + + /* X and Y res default to 240x320 */ + ts->x_res = pdata->ts_x_res ? : 240; + ts->y_res = pdata->ts_y_res ? : 320; + + input_set_abs_params(idev, ABS_X, 0, ts->x_res - 1, 0, 0); + input_set_abs_params(idev, ABS_Y, 0, ts->y_res - 1, 0, 0); + input_set_abs_params(idev, ABS_PRESSURE, 0, 1, 0, 0); + + set_bit(BTN_TOUCH, idev->keybit); + + r = request_irq(ts->irq, tsc2046_ts_irq_handler, + IRQF_SAMPLE_RANDOM | IRQF_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); + ts->irq_enabled = 1; + + /* Create the various driver attributes */ + 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; + if (device_create_file(&tsc->spi->dev, &dev_attr_use_pointercal) < 0) + goto err6; + if (device_create_file(&tsc->spi->dev, &dev_attr_pointercal) < 0) + goto err7; + if (device_create_file(&tsc->spi->dev, &dev_attr_screen_res) < 0) + goto err8; + + r = input_register_device(idev); + if (r < 0) { + dev_err(&tsc->spi->dev, "can't register touchscreen device\n"); + goto err9; + } + + /* kick off a transaction to enable pen interrupts */ + spi_async(tsc->spi, &ts->read_msg); + + return 0; +err9: + device_remove_file(&tsc->spi->dev, &dev_attr_screen_res); +err8: + device_remove_file(&tsc->spi->dev, &dev_attr_pointercal); +err7: + device_remove_file(&tsc->spi->dev, &dev_attr_use_pointercal); +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: + if (ts->dav_gpio) + gpio_free(ts->dav_gpio); +err1: + kfree(ts); + return r; +} + +static int __devexit tsc2046_remove(struct spi_device *spi) +{ + struct tsc2046 *tsc = dev_get_drvdata(&spi->dev); + struct tsc2046_ts *ts = tsc->ts; + unsigned long flags; + + dev_dbg(&tsc->spi->dev, "%s\n", __FUNCTION__); + + 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); + device_remove_file(&tsc->spi->dev, &dev_attr_use_pointercal); + device_remove_file(&tsc->spi->dev, &dev_attr_pointercal); + device_remove_file(&tsc->spi->dev, &dev_attr_screen_res); + + free_irq(ts->irq, tsc); + input_unregister_device(ts->idev); + + if (ts->dav_gpio) + gpio_free(ts->dav_gpio); + + kfree(ts); + 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(KERN_INFO "TSC2046 driver initializing\n"); + + return spi_register_driver(&tsc2046_driver); +} + +static void __exit tsc2046_exit(void) +{ + spi_unregister_driver(&tsc2046_driver); +} + +module_init(tsc2046_init); +module_exit(tsc2046_exit); + +MODULE_AUTHOR("Kevin Hilman "); +MODULE_LICENSE("GPL"); + diff --git a/include/linux/input/tsc2046.h b/include/linux/input/tsc2046.h new file mode 100644 index 0000000..acb8fed --- /dev/null +++ b/include/linux/input/tsc2046.h @@ -0,0 +1,26 @@ +#ifndef _LINUX_INPUT_TSC2046_H +#define _LINUX_INPUT_TSC2046_H + +#include +#include + +struct tsc2046_platform_data { + int (*get_pendown_state)(void); + int gpio_pendown; + 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. */ + u32 ts_x_res; + u32 ts_y_res; + unsigned ts_ignore_last : 1; + +}; + +#endif + +