* [RFC] TSC2101 support
@ 2007-01-25 6:51 Kyungmin Park
0 siblings, 0 replies; 7+ messages in thread
From: Kyungmin Park @ 2007-01-25 6:51 UTC (permalink / raw)
To: Syed Mohammed, Khasim; +Cc: Linux-omap-open-source@linux.omap.com
[-- Attachment #1: Type: text/plain, Size: 4105 bytes --]
(Sorry for renaming the subject. Our mail system can't reply the mail to maling list)
Thank you for your opinions.
I agree with your ideas except that we don't want to break the existing drivers. especially OMAP1 TSC2101 drivers.
In the previous time I tried to use omapts.c on OMAP2. but it's not working well. I think interrupt handling and timer is problem.
omapts.c is simple but new implementation is more complicated.
If we break the previous drivers. it's big works and I want to avoid it. So how about do it as below?
First, Add OMAP2 TSC2101
Second, Separate the SPI protocol, tsc2101 drivers, and platform dependent part. e.g., N800 use 16 bit but h4 and apollon use 32 bit
Finally, Merge OMAP1 with OMAP2, if possible
By the way TSC2101, 2102, and 2301 has similar functions. Of course, each chips have its own features. but we can make it common driver if possible
(yes, I know it's another topic)
Thank you,
Kyungmin Park
------- Original Message -------
Sender : Syed Mohammed, Khasim<x0khasim@ti.com>
Date : Jan 25, 2007 15:08
Title : RE: [RFC] TSC2101 support
Kyungmin, thanks for the code.
I was thinking on similar lines as Mika. We should have tsc2101 driver independent of Touchscreen / Audio / battery etc. Basically it should provide (export) read and write APIs using SPI. The TS part (GPIO registration / passing data to input subsystem etc) should be part of touch screen driver. The TS / Audio / Battery drivers should use these exported APIs to talk to TSC2101. There by all TSC2101 dependent drivers can be isolated from using direct SPI calls.
If you agree with this design I can work on this part. But as Mika pointed it will change omapts.c, this is the only concern.
Regards,Khasim
From: linux-omap-open-source-bounces@linux.omap.com on behalf of lamikr
Sent: Wed 1/24/2007 9:13 PM
To: kyungmin.park@samsung.com
Cc: Linux-omap-open-source@linux.omap.com
Subject: Re: [RFC] TSC2101 support
It would replace the h2, h3, h6300 touchscreen driver in
drivers/input/touchscreen/omap/ directory. (omapts.c and ts_hx.c).
But if that can be made to work with h2 and h3, I think it would be fine.
Byteway, what about the support for the temperature, battery and aux
registers that are supported at least by some of the devices?
My understanding is that the reading of temperature, battery and aux
data values that the tsc2101 page0 must be handled in the same driver
than the reading of the touchscreen related info. (one must ask the
tsc2101 to give those values and once ready, tsc2101 sends interrupt and
we must check from the status register (01) from page1 informs whether
those values are readable or whether the interrupt was touchscreen press
related.)
I did that once by hacking the omapts.c ts_hx.c framework but the
resulting code was not pretty due to big changes in omapts.c which broke
all others ts drivers relying to omapts.c Do you have any code for doing
the read of those values with the new driver?
And if the ts is transfered to use SPI framework, maybe the same should
be done also for the omap-alsa-tsc2101. (It is currently relying on to
drivers/ssi/omap-tsc2101.c) I could volunteer in that if I manage to get
my h6300 kernel updated from 2.6.16 to latest...
Mika
Kyungmin Park wrote:
> Hi,
>
> A few days ago, Nokia released the their N800 source code. and I found the TSC2301 source code. it's similar with TSC2101 except keypad, and some registers.
>
> Now the TSC2101 chip is used in H4 and apollon. So I modified the TSC2301 source code to run TSC2101 chip for quick enabling test.
> However the TSC2101 use 32-bit spi protocol instead of 16-bit in TSC2301. So we have to modify spi part for 32-bit. but it's main strutures are same.
>
> In my opinion, the TSC2301 is not merged in omap tree. So if you don't mind we commit TSC2101 first, and after TSC2301 is merged. we make one driver to support both.
>
> I want to listen your opinions what is better approache
>
> Thank you,
> Kyungmin Park
>
[-- Attachment #2: Type: text/plain, Size: 0 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread* [RFC] TSC2101 support @ 2007-01-25 1:05 Kyungmin Park 2007-01-25 3:13 ` lamikr 0 siblings, 1 reply; 7+ messages in thread From: Kyungmin Park @ 2007-01-25 1:05 UTC (permalink / raw) To: Linux-omap-open-source [-- Attachment #1: Type: text/plain, Size: 22054 bytes --] Hi, A few days ago, Nokia released the their N800 source code. and I found the TSC2301 source code. it's similar with TSC2101 except keypad, and some registers. Now the TSC2101 chip is used in H4 and apollon. So I modified the TSC2301 source code to run TSC2101 chip for quick enabling test. However the TSC2101 use 32-bit spi protocol instead of 16-bit in TSC2301. So we have to modify spi part for 32-bit. but it's main strutures are same. In my opinion, the TSC2301 is not merged in omap tree. So if you don't mind we commit TSC2101 first, and after TSC2301 is merged. we make one driver to support both. I want to listen your opinions what is better approache Thank you, Kyungmin Park P.S., Sorry. It's not full patch. It's just for reference :) diff --git a/drivers/spi/omap2_mcspi.c b/drivers/spi/omap2_mcspi.c index b8d0ec0..e3bdbfa 100644 --- a/drivers/spi/omap2_mcspi.c +++ b/drivers/spi/omap2_mcspi.c @@ -471,7 +473,7 @@ static int omap2_mcspi_setup_transfer(struct spi_device *spi, conf = (struct omap2_mcspi_device_config *) spi->controller_data; - if (conf->single_channel == 1) + if (conf && conf->single_channel == 1) omap2_mcspi_set_master_mode(spi, 1); else omap2_mcspi_set_master_mode(spi, 0); diff --git a/drivers/spi/tsc2101-ts.c b/drivers/spi/tsc2101-ts.c new file mode 100644 index 0000000..83b1959 --- /dev/null +++ b/drivers/spi/tsc2101-ts.c @@ -0,0 +1,712 @@ +/* + * TSC2101 touchscreen driver + * + * Copyright (C) 2005-2006 Nokia Corporation + * + * Modified for 32-bit SPI bus by Kyungmin Park + * + * Derived from TSC2301 written by Jarkko Oikarinen, Imre Deak and Juha Yrjola + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> + +#ifdef CONFIG_ARCH_OMAP +#include <asm/arch/gpio.h> +#endif + +#include <linux/spi/tsc2101.h> + +/** + * The touchscreen interface operates as follows: + * + * Initialize: + * Request access to GPIO (DAV) + * tsc2101_dav_irq_handler will trigger when DAV line goes down + * + * 1) Pen is pressed against touchscreeen + * 2) TSC2101 performs AD conversion + * 3) After the conversion is done TSC2101 drives DAV line down + * 4) GPIO IRQ is received and tsc2101_dav_irq_handler is called + * 5) tsc2101_dav_irq_handler sets up tsc2101_ts_timer in TSC2101_TS_SCAN_TIME + * 6) tsc2101_ts_timer disables the irq and requests spi driver + * to read X, Y, Z1 and Z2 + * 7) SPI framework calls tsc2101_ts_rx after the coordinates are read + * 8) tsc2101_ts_rx reports coordinates to input layer and + * sets up tsc2101_ts_timer to be called after TSC2101_TS_SCAN_TIME + * 9) if tsc2101_tx_timer notices that the pen has been lifted, the lift event + * is sent, and irq is again enabled. + */ + + +#define TSC2101_TS_SCAN_TIME 1 + +#define TSC2101_ADCREG_CONVERSION_CTRL_BY_TSC2101 0x8000 +#define TSC2101_ADCREG_CONVERSION_CTRL_BY_HOST 0x0000 + +#define TSC2101_ADCREG_FUNCTION_NONE 0x0000 +#define TSC2101_ADCREG_FUNCTION_XY 0x0400 +#define TSC2101_ADCREG_FUNCTION_XYZ 0x0800 +#define TSC2101_ADCREG_FUNCTION_X 0x0C00 +#define TSC2101_ADCREG_FUNCTION_Y 0x1000 +#define TSC2101_ADCREG_FUNCTION_Z 0x1400 +#define TSC2101_ADCREG_FUNCTION_DAT1 0x1800 +#define TSC2101_ADCREG_FUNCTION_DAT2 0x1C00 +#define TSC2101_ADCREG_FUNCTION_AUX1 0x2000 +#define TSC2101_ADCREG_FUNCTION_AUX2 0x2400 +#define TSC2101_ADCREG_FUNCTION_TEMP 0x2800 + +#define TSC2101_ADCREG_RESOLUTION_8BIT 0x0100 +#define TSC2101_ADCREG_RESOLUTION_10BIT 0x0200 +#define TSC2101_ADCREG_RESOLUTION_12BIT 0x0300 + +#define TSC2101_ADCREG_AVERAGING_NONE 0x0000 +#define TSC2101_ADCREG_AVERAGING_4AVG 0x0040 +#define TSC2101_ADCREG_AVERAGING_8AVG 0x0080 +#define TSC2101_ADCREG_AVERAGING_16AVG 0x00C0 + +#define TSC2101_ADCREG_CLOCK_8MHZ 0x0000 +#define TSC2101_ADCREG_CLOCK_4MHZ 0x0010 +#define TSC2101_ADCREG_CLOCK_2MHZ 0x0020 +#define TSC2101_ADCREG_CLOCK_1MHZ 0x0030 + +#define TSC2101_ADCREG_VOLTAGE_STAB_0US 0x0000 +#define TSC2101_ADCREG_VOLTAGE_STAB_100US 0x0002 +#define TSC2101_ADCREG_VOLTAGE_STAB_500US 0x0004 +#define TSC2101_ADCREG_VOLTAGE_STAB_1MS 0x0006 +#define TSC2101_ADCREG_VOLTAGE_STAB_5MS 0x0008 +#define TSC2101_ADCREG_VOLTAGE_STAB_10MS 0x000A +#define TSC2101_ADCREG_VOLTAGE_STAB_50MS 0x000C +#define TSC2101_ADCREG_VOLTAGE_STAB_100MS 0x000E + +#define TSC2101_ADCREG_STOP_CONVERSION 0x4000 + +#define TSC2101_STATUSREG_DAV 0x4000 +#define TSC2101_PROGREG_DELAY 0x0900 + +#define MAX_12BIT ((1 << 12) - 1) + +struct tsc2101_ts { + struct input_dev *idev; + char phys[32]; + struct timer_list timer; + spinlock_t lock; + + struct spi_transfer read_xfer[2]; + struct spi_message read_msg; + u32 address[4]; + u32 data[4]; + + int hw_avg_max; + u16 x; + u16 y; + u16 p; + int sample_cnt; + + int ignore_last : 1; + u16 x_plate_ohm; + int stab_time; + int max_pressure; + int touch_pressure; + int pressure_limit; + + u16 irq_enabled:1; + u16 pen_down:1; + u16 disabled:1; + u16 pending:1; + + int hw_flags; + + s16 dav_gpio; + int irq; +}; + + +static const u32 tsc2101_ts_read_data = ((0x8000 | TSC2101_REG_X) << 16) ; + +static int tsc2101_ts_check_config(struct tsc2101_ts *ts, int *hw_flags) +{ + int flags; + + flags = 0; + switch (ts->hw_avg_max) { + case 0: + flags |= TSC2101_ADCREG_AVERAGING_NONE; + break; + case 4: + flags |= TSC2101_ADCREG_AVERAGING_4AVG; + break; + case 8: + flags |= TSC2101_ADCREG_AVERAGING_8AVG; + break; + case 16: + flags |= TSC2101_ADCREG_AVERAGING_16AVG; + break; + default: + return -EINVAL; + } + + switch (ts->stab_time) { + case 0: + flags |= TSC2101_ADCREG_VOLTAGE_STAB_0US; + break; + case 100: + flags |= TSC2101_ADCREG_VOLTAGE_STAB_100US; + break; + case 500: + flags |= TSC2101_ADCREG_VOLTAGE_STAB_500US; + break; + case 1000: + flags |= TSC2101_ADCREG_VOLTAGE_STAB_1MS; + break; + case 5000: + flags |= TSC2101_ADCREG_VOLTAGE_STAB_5MS; + break; + case 10000: + flags |= TSC2101_ADCREG_VOLTAGE_STAB_10MS; + break; + case 50000: + flags |= TSC2101_ADCREG_VOLTAGE_STAB_50MS; + break; + case 100000: + flags |= TSC2101_ADCREG_VOLTAGE_STAB_100MS; + break; + default: + return -EINVAL; + } + + *hw_flags = flags; + return 0; +} + +static int tsc2101_ts_configure(struct tsc2101 *tsc, int flags) +{ + struct spi_transfer xfer[6]; + struct spi_transfer *x; + struct spi_message m; + int reg; + u32 val, adc_val; + u32 data[6]; + + /* + * TSC2101-controlled conversions + * 12-bit samples + * continuous X,Y,Z1,Z2 scan mode + * average (mean) 16 samples per coordinate + * 1 MHz internal conversion clock + * 500 usec panel voltage stabilization delay + * => 0x8bf4 + */ + /* Averaging and voltage stabilization settings in flags */ + adc_val = TSC2101_ADCREG_CONVERSION_CTRL_BY_TSC2101 | + TSC2101_ADCREG_FUNCTION_XYZ | + TSC2101_ADCREG_RESOLUTION_12BIT | + TSC2101_ADCREG_CLOCK_1MHZ | + flags; + + /* Now we prepare the command for transferring */ + /* Use internal reference clock */ + reg = TSC2101_REG_REF; + val = 0x0016; + data[0] = (reg << 16) | val; + + reg = TSC2101_REG_CONFIG; + val = 0x0008; + data[1] = (reg << 16) | val; + + reg = TSC2101_REG_BUFFER; + val = 0x0; + data[2] = (reg << 16) | val; + + reg = TSC2101_REG_PROG_DELAY; + val = 0x0900; + data[3] = (reg << 16) | val; + + reg = TSC2101_REG_STATUS; + val = TSC2101_STATUSREG_DAV; + data[4] = (reg << 16) | val; + + reg = TSC2101_REG_ADC; + data[5] = (reg << 16) | adc_val; + + spi_message_init(&m); + m.spi = tsc->spi; + + memset(xfer, 0, sizeof(xfer)); + x = &xfer[0]; + + x->tx_buf = &data[0]; + x->len = 4; + x->cs_change = 1; + spi_message_add_tail(x, &m); + + x++; + x->tx_buf = &data[1]; + x->len = 4; + x->cs_change = 1; + spi_message_add_tail(x, &m); + + x++; + x->tx_buf = &data[2]; + x->len = 4; + spi_message_add_tail(x, &m); + + x++; + x->tx_buf = &data[3]; + x->len = 4; + spi_message_add_tail(x, &m); + + x++; + x->tx_buf = &data[4]; + x->len = 4; + spi_message_add_tail(x, &m); + + x++; + x->tx_buf = &data[5]; + x->len = 4; + spi_message_add_tail(x, &m); + + spi_sync(m.spi, &m); + + return 0; +} + +static void tsc2101_ts_start_scan(struct tsc2101 *tsc) +{ + tsc2101_ts_configure(tsc, tsc->ts->hw_flags); +} + +static void tsc2101_ts_stop_scan(struct tsc2101 *tsc) +{ + tsc2101_ts_configure(tsc, TSC2101_ADCREG_STOP_CONVERSION); +} + +static int device_suspended(struct device *dev) +{ + struct tsc2101 *tsc = dev_get_drvdata(dev); + return dev->power.power_state.event != PM_EVENT_ON || tsc->ts->disabled; +} + +static void update_pen_state(struct tsc2101_ts *ts, int x, int y, int pressure) +{ + 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 +} + +/* + * This procedure is called by the SPI framework after the coordinates + * have been read from TSC2101 + */ +static void tsc2101_ts_rx(void *arg) +{ + struct tsc2101 *tsc = arg; + struct tsc2101_ts *ts = tsc->ts; + unsigned int x, y, z1, z2, pressure; + + x = ts->data[0] & 0xffff; + y = ts->data[1] & 0xffff; + z1 = ts->data[2] & 0xffff; + z2 = ts->data[3] & 0xffff; + + 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(ts, ts->x, ts->y, ts->p); + ts->x = x; + ts->y = y; + ts->p = pressure; + } else + update_pen_state(ts, x, y, pressure); + ts->sample_cnt++; + } + + mod_timer(&ts->timer, + jiffies + msecs_to_jiffies(TSC2101_TS_SCAN_TIME)); +} + +static int is_pen_down(struct tsc2101_ts *ts) +{ + return ts->pen_down; +} + +/* + * Timer is called every TSC2101_TS_SCAN_TIME when the pen is down + */ +static void tsc2101_ts_timer(unsigned long arg) +{ + struct tsc2101 *tsc = (void *) arg; + struct tsc2101_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 = 1; + enable_irq(ts->irq); + } + update_pen_state(ts, 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 tsc2101_ts_irq_handler(int irq, void *dev_id) +{ + struct tsc2101 *tsc = dev_id; + struct tsc2101_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(TSC2101_TS_SCAN_TIME)); + } + spin_unlock_irqrestore(&ts->lock, flags); + + return IRQ_HANDLED; +} + +/* Must be called with ts->lock held */ +static void tsc2101_ts_disable(struct tsc2101 *tsc) +{ + struct tsc2101_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); + } + } + + spin_unlock_irq(&ts->lock); + tsc2101_ts_stop_scan(tsc); + spin_lock_irq(&ts->lock); +} + +static void tsc2101_ts_enable(struct tsc2101 *tsc) +{ + struct tsc2101_ts *ts = tsc->ts; + + if (!ts->disabled) + return; + + ts->disabled = 0; + ts->irq_enabled = 1; + enable_irq(ts->irq); + + spin_unlock_irq(&ts->lock); + tsc2101_ts_start_scan(tsc); + spin_lock_irq(&ts->lock); +} + +#ifdef CONFIG_PM +int tsc2101_ts_suspend(struct tsc2101 *tsc) +{ + struct tsc2101_ts *ts = tsc->ts; + + spin_lock_irq(&ts->lock); + tsc2101_ts_disable(tsc); + spin_unlock_irq(&ts->lock); + + return 0; +} + +void tsc2101_ts_resume(struct tsc2101 *tsc) +{ + struct tsc2101_ts *ts = tsc->ts; + + spin_lock_irq(&ts->lock); + tsc2101_ts_enable(tsc); + spin_unlock_irq(&ts->lock); +} +#endif + +void tsc2101_ts_prep_for_clk_stop(struct tsc2101 *tsc) +{ +} + +void tsc2101_ts_cont_after_clk_stop(struct tsc2101 *tsc) +{ +} + +static void tsc2101_ts_setup_spi_xfer(struct tsc2101 *tsc) +{ + struct tsc2101_ts *ts = tsc->ts; + struct spi_message *m = &ts->read_msg; + struct spi_transfer *x = &ts->read_xfer[0]; + + ts->address[0] = (0x8000 | TSC2101_REG_X) << 16; + ts->address[1] = (0x8000 | TSC2101_REG_Y) << 16; + ts->address[2] = (0x8000 | TSC2101_REG_Z1) << 16; + ts->address[3] = (0x8000 | TSC2101_REG_Z2) << 16; + + spi_message_init(m); + + x->tx_buf = &ts->address; + x->rx_buf = &ts->data; + x->len = 16; + spi_message_add_tail(x, m); + + m->complete = tsc2101_ts_rx; + m->context = tsc; +} + +static ssize_t tsc2101_ts_pen_down_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tsc2101 *tsc = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", is_pen_down(tsc->ts)); +} + +static DEVICE_ATTR(pen_down, S_IRUGO, tsc2101_ts_pen_down_show, NULL); + +static ssize_t tsc2101_ts_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsc2101 *tsc = dev_get_drvdata(dev); + struct tsc2101_ts *ts = tsc->ts; + + return sprintf(buf, "%u\n", ts->disabled); +} + +static ssize_t tsc2101_ts_disable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tsc2101 *tsc = dev_get_drvdata(dev); + struct tsc2101_ts *ts = tsc->ts; + char *endp; + int i; + + i = simple_strtoul(buf, &endp, 10); + spin_lock_irq(&ts->lock); + + if (i) + tsc2101_ts_disable(tsc); + else + tsc2101_ts_enable(tsc); + + spin_unlock_irq(&ts->lock); + + return count; +} + +static DEVICE_ATTR(disable_ts, 0664, tsc2101_ts_disable_show, + tsc2101_ts_disable_store); + +int __devinit tsc2101_ts_init(struct tsc2101 *tsc, + struct tsc2101_platform_data *pdata) +{ + struct tsc2101_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); + ts->irq = OMAP_GPIO_IRQ(dav_gpio); +#endif + init_timer(&ts->timer); + ts->timer.data = (unsigned long) tsc; + ts->timer.function = tsc2101_ts_timer; + + spin_lock_init(&ts->lock); + + ts->x_plate_ohm = pdata->ts_x_plate_ohm ? : 280; + ts->hw_avg_max = pdata->ts_hw_avg; + 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; + ts->stab_time = pdata->ts_stab_time; + + if ((r = tsc2101_ts_check_config(ts, &ts->hw_flags))) { + dev_err(&tsc->spi->dev, "invalid configuration\n"); + goto err2; + } + + idev = input_allocate_device(); + if (idev == NULL) { + r = -ENOMEM; + goto err2; + } + idev->cdev.dev = &tsc->spi->dev; + idev->name = "TSC2101 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; + + tsc2101_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); + + tsc2101_ts_start_scan(tsc); + + ts->irq_enabled = 1; + r = request_irq(ts->irq, tsc2101_ts_irq_handler, + IRQF_SAMPLE_RANDOM | IRQF_TRIGGER_FALLING, + "tsc2101-ts", tsc); + if (r < 0) { + dev_err(&tsc->spi->dev, "unable to get DAV IRQ"); + goto err3; + } + set_irq_wake(ts->irq, 1); + + r = device_create_file(&tsc->spi->dev, &dev_attr_pen_down); + r |= device_create_file(&tsc->spi->dev, &dev_attr_disable_ts); + if (r) + goto err4; + + r = input_register_device(idev); + if (r < 0) { + dev_err(&tsc->spi->dev, "can't register touchscreen device\n"); + goto err5; + } + + return 0; +err5: + device_remove_file(&tsc->spi->dev, &dev_attr_disable_ts); + device_remove_file(&tsc->spi->dev, &dev_attr_pen_down); +err4: + free_irq(ts->irq, tsc); +err3: + tsc2101_ts_stop_scan(tsc); + input_free_device(idev); +err2: +#ifdef CONFIG_ARCH_OMAP + omap_free_gpio(dav_gpio); +#endif +err1: + kfree(ts); + return r; +} + +void __devexit tsc2101_ts_exit(struct tsc2101 *tsc) +{ + struct tsc2101_ts *ts = tsc->ts; + unsigned long flags; + + spin_lock_irqsave(&ts->lock, flags); + tsc2101_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); +} +MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>"); +MODULE_DESCRIPTION("TSC2101 driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/spi/tsc2101.h b/include/linux/spi/tsc2101.h new file mode 100644 index 0000000..70da08a --- /dev/null +++ b/include/linux/spi/tsc2101.h @@ -0,0 +1,62 @@ +#ifndef _LINUX_SPI_TSC2101_H +#define _LINUX_SPI_TSC2101_H + +struct tsc2101_platform_data { + /* + * Touchscreen + */ + s16 dav_gpio; + s16 pen_int_gpio; + u16 ts_x_plate_ohm; + u32 ts_stab_time; /* voltage settling time */ + u8 ts_hw_avg; /* HW assiseted averaging. Can be + 0, 4, 8, 16 samples per reading */ + 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 ts2101_ts; + +struct tsc2101 { + struct spi_device *spi; + + struct tsc2101_ts *ts; +}; + +#define TSC2101_REG(page, addr) (((page) << 11) | ((addr) << 5)) + +/* Page 0, Touchscreen data registers */ +#define TSC2101_REG_X TSC2101_REG(0, 0) +#define TSC2101_REG_Y TSC2101_REG(0, 1) +#define TSC2101_REG_Z1 TSC2101_REG(0, 2) +#define TSC2101_REG_Z2 TSC2101_REG(0, 3) +#define TSC2101_REG_TEMP1 TSC2101_REG(0, 9) +#define TSC2101_REG_TEMP2 TSC2101_REG(0, 10) + +/* Page 1, Tochscreen control registers */ +#define TSC2101_REG_ADC TSC2101_REG(1, 0) +#define TSC2101_REG_STATUS TSC2101_REG(1, 1) +#define TSC2101_REG_BUFFER TSC2101_REG(1, 2) +#define TSC2101_REG_REF TSC2101_REG(1, 3) +#define TSC2101_REG_CONFIG TSC2101_REG(1, 5) +#define TSC2101_REG_PROG_DELAY TSC2101_REG(1, 13) + +/* Page 2, Audio control registers */ +#define TSC2101_REG_AUDIO1 TSC2101_REG(2, 0) +#define TSC2101_REG_DAC_GAIN TSC2101_REG(2, 2) +#define TSC2101_REG_AUDIO2 TSC2101_REG(2, 4) +#define TSC2101_REG_DAC_POWER TSC2101_REG(2, 5) +#define TSC2101_REG_AUDIO3 TSC2101_REG(2, 6) +#define TSC2101_REG_PLL1 TSC2101_REG(2, 27) +#define TSC2101_REG_PLL2 TSC2101_REG(2, 28) +#define TSC2101_REG_AUDIO4 TSC2101_REG(2, 29) + +int tsc2101_ts_init(struct tsc2101 *, struct tsc2101_platform_data *); +void tsc2101_ts_exit(struct tsc2101 *); + +#endif [-- Attachment #2: Type: text/plain, Size: 0 bytes --] ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [RFC] TSC2101 support 2007-01-25 1:05 Kyungmin Park @ 2007-01-25 3:13 ` lamikr 2007-01-25 6:08 ` Syed Mohammed, Khasim 0 siblings, 1 reply; 7+ messages in thread From: lamikr @ 2007-01-25 3:13 UTC (permalink / raw) To: kyungmin.park; +Cc: Linux-omap-open-source It would replace the h2, h3, h6300 touchscreen driver in drivers/input/touchscreen/omap/ directory. (omapts.c and ts_hx.c). But if that can be made to work with h2 and h3, I think it would be fine. Byteway, what about the support for the temperature, battery and aux registers that are supported at least by some of the devices? My understanding is that the reading of temperature, battery and aux data values that the tsc2101 page0 must be handled in the same driver than the reading of the touchscreen related info. (one must ask the tsc2101 to give those values and once ready, tsc2101 sends interrupt and we must check from the status register (01) from page1 informs whether those values are readable or whether the interrupt was touchscreen press related.) I did that once by hacking the omapts.c ts_hx.c framework but the resulting code was not pretty due to big changes in omapts.c which broke all others ts drivers relying to omapts.c Do you have any code for doing the read of those values with the new driver? And if the ts is transfered to use SPI framework, maybe the same should be done also for the omap-alsa-tsc2101. (It is currently relying on to drivers/ssi/omap-tsc2101.c) I could volunteer in that if I manage to get my h6300 kernel updated from 2.6.16 to latest... Mika Kyungmin Park wrote: > Hi, > > A few days ago, Nokia released the their N800 source code. and I found the TSC2301 source code. it's similar with TSC2101 except keypad, and some registers. > > Now the TSC2101 chip is used in H4 and apollon. So I modified the TSC2301 source code to run TSC2101 chip for quick enabling test. > However the TSC2101 use 32-bit spi protocol instead of 16-bit in TSC2301. So we have to modify spi part for 32-bit. but it's main strutures are same. > > In my opinion, the TSC2301 is not merged in omap tree. So if you don't mind we commit TSC2101 first, and after TSC2301 is merged. we make one driver to support both. > > I want to listen your opinions what is better approache > > Thank you, > Kyungmin Park > > P.S., Sorry. It's not full patch. It's just for reference :) > > diff --git a/drivers/spi/omap2_mcspi.c b/drivers/spi/omap2_mcspi.c > index b8d0ec0..e3bdbfa 100644 > --- a/drivers/spi/omap2_mcspi.c > +++ b/drivers/spi/omap2_mcspi.c > @@ -471,7 +473,7 @@ static int omap2_mcspi_setup_transfer(struct spi_device *spi, > > conf = (struct omap2_mcspi_device_config *) spi->controller_data; > > - if (conf->single_channel == 1) > + if (conf && conf->single_channel == 1) > omap2_mcspi_set_master_mode(spi, 1); > else > omap2_mcspi_set_master_mode(spi, 0); > diff --git a/drivers/spi/tsc2101-ts.c b/drivers/spi/tsc2101-ts.c > new file mode 100644 > index 0000000..83b1959 > --- /dev/null > +++ b/drivers/spi/tsc2101-ts.c > @@ -0,0 +1,712 @@ > +/* > + * TSC2101 touchscreen driver > + * > + * Copyright (C) 2005-2006 Nokia Corporation > + * > + * Modified for 32-bit SPI bus by Kyungmin Park > + * > + * Derived from TSC2301 written by Jarkko Oikarinen, Imre Deak and Juha Yrjola > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + * > + */ > + > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/input.h> > +#include <linux/interrupt.h> > +#include <linux/delay.h> > +#include <linux/spi/spi.h> > + > +#ifdef CONFIG_ARCH_OMAP > +#include <asm/arch/gpio.h> > +#endif > + > +#include <linux/spi/tsc2101.h> > + > +/** > + * The touchscreen interface operates as follows: > + * > + * Initialize: > + * Request access to GPIO (DAV) > + * tsc2101_dav_irq_handler will trigger when DAV line goes down > + * > + * 1) Pen is pressed against touchscreeen > + * 2) TSC2101 performs AD conversion > + * 3) After the conversion is done TSC2101 drives DAV line down > + * 4) GPIO IRQ is received and tsc2101_dav_irq_handler is called > + * 5) tsc2101_dav_irq_handler sets up tsc2101_ts_timer in TSC2101_TS_SCAN_TIME > + * 6) tsc2101_ts_timer disables the irq and requests spi driver > + * to read X, Y, Z1 and Z2 > + * 7) SPI framework calls tsc2101_ts_rx after the coordinates are read > + * 8) tsc2101_ts_rx reports coordinates to input layer and > + * sets up tsc2101_ts_timer to be called after TSC2101_TS_SCAN_TIME > + * 9) if tsc2101_tx_timer notices that the pen has been lifted, the lift event > + * is sent, and irq is again enabled. > + */ > + > + > +#define TSC2101_TS_SCAN_TIME 1 > + > +#define TSC2101_ADCREG_CONVERSION_CTRL_BY_TSC2101 0x8000 > +#define TSC2101_ADCREG_CONVERSION_CTRL_BY_HOST 0x0000 > + > +#define TSC2101_ADCREG_FUNCTION_NONE 0x0000 > +#define TSC2101_ADCREG_FUNCTION_XY 0x0400 > +#define TSC2101_ADCREG_FUNCTION_XYZ 0x0800 > +#define TSC2101_ADCREG_FUNCTION_X 0x0C00 > +#define TSC2101_ADCREG_FUNCTION_Y 0x1000 > +#define TSC2101_ADCREG_FUNCTION_Z 0x1400 > +#define TSC2101_ADCREG_FUNCTION_DAT1 0x1800 > +#define TSC2101_ADCREG_FUNCTION_DAT2 0x1C00 > +#define TSC2101_ADCREG_FUNCTION_AUX1 0x2000 > +#define TSC2101_ADCREG_FUNCTION_AUX2 0x2400 > +#define TSC2101_ADCREG_FUNCTION_TEMP 0x2800 > + > +#define TSC2101_ADCREG_RESOLUTION_8BIT 0x0100 > +#define TSC2101_ADCREG_RESOLUTION_10BIT 0x0200 > +#define TSC2101_ADCREG_RESOLUTION_12BIT 0x0300 > + > +#define TSC2101_ADCREG_AVERAGING_NONE 0x0000 > +#define TSC2101_ADCREG_AVERAGING_4AVG 0x0040 > +#define TSC2101_ADCREG_AVERAGING_8AVG 0x0080 > +#define TSC2101_ADCREG_AVERAGING_16AVG 0x00C0 > + > +#define TSC2101_ADCREG_CLOCK_8MHZ 0x0000 > +#define TSC2101_ADCREG_CLOCK_4MHZ 0x0010 > +#define TSC2101_ADCREG_CLOCK_2MHZ 0x0020 > +#define TSC2101_ADCREG_CLOCK_1MHZ 0x0030 > + > +#define TSC2101_ADCREG_VOLTAGE_STAB_0US 0x0000 > +#define TSC2101_ADCREG_VOLTAGE_STAB_100US 0x0002 > +#define TSC2101_ADCREG_VOLTAGE_STAB_500US 0x0004 > +#define TSC2101_ADCREG_VOLTAGE_STAB_1MS 0x0006 > +#define TSC2101_ADCREG_VOLTAGE_STAB_5MS 0x0008 > +#define TSC2101_ADCREG_VOLTAGE_STAB_10MS 0x000A > +#define TSC2101_ADCREG_VOLTAGE_STAB_50MS 0x000C > +#define TSC2101_ADCREG_VOLTAGE_STAB_100MS 0x000E > + > +#define TSC2101_ADCREG_STOP_CONVERSION 0x4000 > + > +#define TSC2101_STATUSREG_DAV 0x4000 > +#define TSC2101_PROGREG_DELAY 0x0900 > + > +#define MAX_12BIT ((1 << 12) - 1) > + > +struct tsc2101_ts { > + struct input_dev *idev; > + char phys[32]; > + struct timer_list timer; > + spinlock_t lock; > + > + struct spi_transfer read_xfer[2]; > + struct spi_message read_msg; > + u32 address[4]; > + u32 data[4]; > + > + int hw_avg_max; > + u16 x; > + u16 y; > + u16 p; > + int sample_cnt; > + > + int ignore_last : 1; > + u16 x_plate_ohm; > + int stab_time; > + int max_pressure; > + int touch_pressure; > + int pressure_limit; > + > + u16 irq_enabled:1; > + u16 pen_down:1; > + u16 disabled:1; > + u16 pending:1; > + > + int hw_flags; > + > + s16 dav_gpio; > + int irq; > +}; > + > + > +static const u32 tsc2101_ts_read_data = ((0x8000 | TSC2101_REG_X) << 16) ; > + > +static int tsc2101_ts_check_config(struct tsc2101_ts *ts, int *hw_flags) > +{ > + int flags; > + > + flags = 0; > + switch (ts->hw_avg_max) { > + case 0: > + flags |= TSC2101_ADCREG_AVERAGING_NONE; > + break; > + case 4: > + flags |= TSC2101_ADCREG_AVERAGING_4AVG; > + break; > + case 8: > + flags |= TSC2101_ADCREG_AVERAGING_8AVG; > + break; > + case 16: > + flags |= TSC2101_ADCREG_AVERAGING_16AVG; > + break; > + default: > + return -EINVAL; > + } > + > + switch (ts->stab_time) { > + case 0: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_0US; > + break; > + case 100: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_100US; > + break; > + case 500: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_500US; > + break; > + case 1000: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_1MS; > + break; > + case 5000: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_5MS; > + break; > + case 10000: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_10MS; > + break; > + case 50000: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_50MS; > + break; > + case 100000: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_100MS; > + break; > + default: > + return -EINVAL; > + } > + > + *hw_flags = flags; > + return 0; > +} > + > +static int tsc2101_ts_configure(struct tsc2101 *tsc, int flags) > +{ > + struct spi_transfer xfer[6]; > + struct spi_transfer *x; > + struct spi_message m; > + int reg; > + u32 val, adc_val; > + u32 data[6]; > + > + /* > + * TSC2101-controlled conversions > + * 12-bit samples > + * continuous X,Y,Z1,Z2 scan mode > + * average (mean) 16 samples per coordinate > + * 1 MHz internal conversion clock > + * 500 usec panel voltage stabilization delay > + * => 0x8bf4 > + */ > + /* Averaging and voltage stabilization settings in flags */ > + adc_val = TSC2101_ADCREG_CONVERSION_CTRL_BY_TSC2101 | > + TSC2101_ADCREG_FUNCTION_XYZ | > + TSC2101_ADCREG_RESOLUTION_12BIT | > + TSC2101_ADCREG_CLOCK_1MHZ | > + flags; > + > + /* Now we prepare the command for transferring */ > + /* Use internal reference clock */ > + reg = TSC2101_REG_REF; > + val = 0x0016; > + data[0] = (reg << 16) | val; > + > + reg = TSC2101_REG_CONFIG; > + val = 0x0008; > + data[1] = (reg << 16) | val; > + > + reg = TSC2101_REG_BUFFER; > + val = 0x0; > + data[2] = (reg << 16) | val; > + > + reg = TSC2101_REG_PROG_DELAY; > + val = 0x0900; > + data[3] = (reg << 16) | val; > + > + reg = TSC2101_REG_STATUS; > + val = TSC2101_STATUSREG_DAV; > + data[4] = (reg << 16) | val; > + > + reg = TSC2101_REG_ADC; > + data[5] = (reg << 16) | adc_val; > + > + spi_message_init(&m); > + m.spi = tsc->spi; > + > + memset(xfer, 0, sizeof(xfer)); > + x = &xfer[0]; > + > + x->tx_buf = &data[0]; > + x->len = 4; > + x->cs_change = 1; > + spi_message_add_tail(x, &m); > + > + x++; > + x->tx_buf = &data[1]; > + x->len = 4; > + x->cs_change = 1; > + spi_message_add_tail(x, &m); > + > + x++; > + x->tx_buf = &data[2]; > + x->len = 4; > + spi_message_add_tail(x, &m); > + > + x++; > + x->tx_buf = &data[3]; > + x->len = 4; > + spi_message_add_tail(x, &m); > + > + x++; > + x->tx_buf = &data[4]; > + x->len = 4; > + spi_message_add_tail(x, &m); > + > + x++; > + x->tx_buf = &data[5]; > + x->len = 4; > + spi_message_add_tail(x, &m); > + > + spi_sync(m.spi, &m); > + > + return 0; > +} > + > +static void tsc2101_ts_start_scan(struct tsc2101 *tsc) > +{ > + tsc2101_ts_configure(tsc, tsc->ts->hw_flags); > +} > + > +static void tsc2101_ts_stop_scan(struct tsc2101 *tsc) > +{ > + tsc2101_ts_configure(tsc, TSC2101_ADCREG_STOP_CONVERSION); > +} > + > +static int device_suspended(struct device *dev) > +{ > + struct tsc2101 *tsc = dev_get_drvdata(dev); > + return dev->power.power_state.event != PM_EVENT_ON || tsc->ts->disabled; > +} > + > +static void update_pen_state(struct tsc2101_ts *ts, int x, int y, int pressure) > +{ > + 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 > +} > + > +/* > + * This procedure is called by the SPI framework after the coordinates > + * have been read from TSC2101 > + */ > +static void tsc2101_ts_rx(void *arg) > +{ > + struct tsc2101 *tsc = arg; > + struct tsc2101_ts *ts = tsc->ts; > + unsigned int x, y, z1, z2, pressure; > + > + x = ts->data[0] & 0xffff; > + y = ts->data[1] & 0xffff; > + z1 = ts->data[2] & 0xffff; > + z2 = ts->data[3] & 0xffff; > + > + 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(ts, ts->x, ts->y, ts->p); > + ts->x = x; > + ts->y = y; > + ts->p = pressure; > + } else > + update_pen_state(ts, x, y, pressure); > + ts->sample_cnt++; > + } > + > + mod_timer(&ts->timer, > + jiffies + msecs_to_jiffies(TSC2101_TS_SCAN_TIME)); > +} > + > +static int is_pen_down(struct tsc2101_ts *ts) > +{ > + return ts->pen_down; > +} > + > +/* > + * Timer is called every TSC2101_TS_SCAN_TIME when the pen is down > + */ > +static void tsc2101_ts_timer(unsigned long arg) > +{ > + struct tsc2101 *tsc = (void *) arg; > + struct tsc2101_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 = 1; > + enable_irq(ts->irq); > + } > + update_pen_state(ts, 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 tsc2101_ts_irq_handler(int irq, void *dev_id) > +{ > + struct tsc2101 *tsc = dev_id; > + struct tsc2101_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(TSC2101_TS_SCAN_TIME)); > + } > + spin_unlock_irqrestore(&ts->lock, flags); > + > + return IRQ_HANDLED; > +} > + > +/* Must be called with ts->lock held */ > +static void tsc2101_ts_disable(struct tsc2101 *tsc) > +{ > + struct tsc2101_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); > + } > + } > + > + spin_unlock_irq(&ts->lock); > + tsc2101_ts_stop_scan(tsc); > + spin_lock_irq(&ts->lock); > +} > + > +static void tsc2101_ts_enable(struct tsc2101 *tsc) > +{ > + struct tsc2101_ts *ts = tsc->ts; > + > + if (!ts->disabled) > + return; > + > + ts->disabled = 0; > + ts->irq_enabled = 1; > + enable_irq(ts->irq); > + > + spin_unlock_irq(&ts->lock); > + tsc2101_ts_start_scan(tsc); > + spin_lock_irq(&ts->lock); > +} > + > +#ifdef CONFIG_PM > +int tsc2101_ts_suspend(struct tsc2101 *tsc) > +{ > + struct tsc2101_ts *ts = tsc->ts; > + > + spin_lock_irq(&ts->lock); > + tsc2101_ts_disable(tsc); > + spin_unlock_irq(&ts->lock); > + > + return 0; > +} > + > +void tsc2101_ts_resume(struct tsc2101 *tsc) > +{ > + struct tsc2101_ts *ts = tsc->ts; > + > + spin_lock_irq(&ts->lock); > + tsc2101_ts_enable(tsc); > + spin_unlock_irq(&ts->lock); > +} > +#endif > + > +void tsc2101_ts_prep_for_clk_stop(struct tsc2101 *tsc) > +{ > +} > + > +void tsc2101_ts_cont_after_clk_stop(struct tsc2101 *tsc) > +{ > +} > + > +static void tsc2101_ts_setup_spi_xfer(struct tsc2101 *tsc) > +{ > + struct tsc2101_ts *ts = tsc->ts; > + struct spi_message *m = &ts->read_msg; > + struct spi_transfer *x = &ts->read_xfer[0]; > + > + ts->address[0] = (0x8000 | TSC2101_REG_X) << 16; > + ts->address[1] = (0x8000 | TSC2101_REG_Y) << 16; > + ts->address[2] = (0x8000 | TSC2101_REG_Z1) << 16; > + ts->address[3] = (0x8000 | TSC2101_REG_Z2) << 16; > + > + spi_message_init(m); > + > + x->tx_buf = &ts->address; > + x->rx_buf = &ts->data; > + x->len = 16; > + spi_message_add_tail(x, m); > + > + m->complete = tsc2101_ts_rx; > + m->context = tsc; > +} > + > +static ssize_t tsc2101_ts_pen_down_show(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct tsc2101 *tsc = dev_get_drvdata(dev); > + > + return sprintf(buf, "%u\n", is_pen_down(tsc->ts)); > +} > + > +static DEVICE_ATTR(pen_down, S_IRUGO, tsc2101_ts_pen_down_show, NULL); > + > +static ssize_t tsc2101_ts_disable_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct tsc2101 *tsc = dev_get_drvdata(dev); > + struct tsc2101_ts *ts = tsc->ts; > + > + return sprintf(buf, "%u\n", ts->disabled); > +} > + > +static ssize_t tsc2101_ts_disable_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct tsc2101 *tsc = dev_get_drvdata(dev); > + struct tsc2101_ts *ts = tsc->ts; > + char *endp; > + int i; > + > + i = simple_strtoul(buf, &endp, 10); > + spin_lock_irq(&ts->lock); > + > + if (i) > + tsc2101_ts_disable(tsc); > + else > + tsc2101_ts_enable(tsc); > + > + spin_unlock_irq(&ts->lock); > + > + return count; > +} > + > +static DEVICE_ATTR(disable_ts, 0664, tsc2101_ts_disable_show, > + tsc2101_ts_disable_store); > + > +int __devinit tsc2101_ts_init(struct tsc2101 *tsc, > + struct tsc2101_platform_data *pdata) > +{ > + struct tsc2101_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); > + ts->irq = OMAP_GPIO_IRQ(dav_gpio); > +#endif > + init_timer(&ts->timer); > + ts->timer.data = (unsigned long) tsc; > + ts->timer.function = tsc2101_ts_timer; > + > + spin_lock_init(&ts->lock); > + > + ts->x_plate_ohm = pdata->ts_x_plate_ohm ? : 280; > + ts->hw_avg_max = pdata->ts_hw_avg; > + 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; > + ts->stab_time = pdata->ts_stab_time; > + > + if ((r = tsc2101_ts_check_config(ts, &ts->hw_flags))) { > + dev_err(&tsc->spi->dev, "invalid configuration\n"); > + goto err2; > + } > + > + idev = input_allocate_device(); > + if (idev == NULL) { > + r = -ENOMEM; > + goto err2; > + } > + idev->cdev.dev = &tsc->spi->dev; > + idev->name = "TSC2101 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; > + > + tsc2101_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); > + > + tsc2101_ts_start_scan(tsc); > + > + ts->irq_enabled = 1; > + r = request_irq(ts->irq, tsc2101_ts_irq_handler, > + IRQF_SAMPLE_RANDOM | IRQF_TRIGGER_FALLING, > + "tsc2101-ts", tsc); > + if (r < 0) { > + dev_err(&tsc->spi->dev, "unable to get DAV IRQ"); > + goto err3; > + } > + set_irq_wake(ts->irq, 1); > + > + r = device_create_file(&tsc->spi->dev, &dev_attr_pen_down); > + r |= device_create_file(&tsc->spi->dev, &dev_attr_disable_ts); > + if (r) > + goto err4; > + > + r = input_register_device(idev); > + if (r < 0) { > + dev_err(&tsc->spi->dev, "can't register touchscreen device\n"); > + goto err5; > + } > + > + return 0; > +err5: > + device_remove_file(&tsc->spi->dev, &dev_attr_disable_ts); > + device_remove_file(&tsc->spi->dev, &dev_attr_pen_down); > +err4: > + free_irq(ts->irq, tsc); > +err3: > + tsc2101_ts_stop_scan(tsc); > + input_free_device(idev); > +err2: > +#ifdef CONFIG_ARCH_OMAP > + omap_free_gpio(dav_gpio); > +#endif > +err1: > + kfree(ts); > + return r; > +} > + > +void __devexit tsc2101_ts_exit(struct tsc2101 *tsc) > +{ > + struct tsc2101_ts *ts = tsc->ts; > + unsigned long flags; > + > + spin_lock_irqsave(&ts->lock, flags); > + tsc2101_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); > +} > +MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>"); > +MODULE_DESCRIPTION("TSC2101 driver"); > +MODULE_LICENSE("GPL"); > diff --git a/include/linux/spi/tsc2101.h b/include/linux/spi/tsc2101.h > new file mode 100644 > index 0000000..70da08a > --- /dev/null > +++ b/include/linux/spi/tsc2101.h > @@ -0,0 +1,62 @@ > +#ifndef _LINUX_SPI_TSC2101_H > +#define _LINUX_SPI_TSC2101_H > + > +struct tsc2101_platform_data { > + /* > + * Touchscreen > + */ > + s16 dav_gpio; > + s16 pen_int_gpio; > + u16 ts_x_plate_ohm; > + u32 ts_stab_time; /* voltage settling time */ > + u8 ts_hw_avg; /* HW assiseted averaging. Can be > + 0, 4, 8, 16 samples per reading */ > + 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 ts2101_ts; > + > +struct tsc2101 { > + struct spi_device *spi; > + > + struct tsc2101_ts *ts; > +}; > + > +#define TSC2101_REG(page, addr) (((page) << 11) | ((addr) << 5)) > + > +/* Page 0, Touchscreen data registers */ > +#define TSC2101_REG_X TSC2101_REG(0, 0) > +#define TSC2101_REG_Y TSC2101_REG(0, 1) > +#define TSC2101_REG_Z1 TSC2101_REG(0, 2) > +#define TSC2101_REG_Z2 TSC2101_REG(0, 3) > +#define TSC2101_REG_TEMP1 TSC2101_REG(0, 9) > +#define TSC2101_REG_TEMP2 TSC2101_REG(0, 10) > + > +/* Page 1, Tochscreen control registers */ > +#define TSC2101_REG_ADC TSC2101_REG(1, 0) > +#define TSC2101_REG_STATUS TSC2101_REG(1, 1) > +#define TSC2101_REG_BUFFER TSC2101_REG(1, 2) > +#define TSC2101_REG_REF TSC2101_REG(1, 3) > +#define TSC2101_REG_CONFIG TSC2101_REG(1, 5) > +#define TSC2101_REG_PROG_DELAY TSC2101_REG(1, 13) > + > +/* Page 2, Audio control registers */ > +#define TSC2101_REG_AUDIO1 TSC2101_REG(2, 0) > +#define TSC2101_REG_DAC_GAIN TSC2101_REG(2, 2) > +#define TSC2101_REG_AUDIO2 TSC2101_REG(2, 4) > +#define TSC2101_REG_DAC_POWER TSC2101_REG(2, 5) > +#define TSC2101_REG_AUDIO3 TSC2101_REG(2, 6) > +#define TSC2101_REG_PLL1 TSC2101_REG(2, 27) > +#define TSC2101_REG_PLL2 TSC2101_REG(2, 28) > +#define TSC2101_REG_AUDIO4 TSC2101_REG(2, 29) > + > +int tsc2101_ts_init(struct tsc2101 *, struct tsc2101_platform_data *); > +void tsc2101_ts_exit(struct tsc2101 *); > + > +#endif > ------------------------------------------------------------------------ > > _______________________________________________ > Linux-omap-open-source mailing list > Linux-omap-open-source@linux.omap.com > http://linux.omap.com/mailman/listinfo/linux-omap-open-source > ^ permalink raw reply [flat|nested] 7+ messages in thread
* RE: [RFC] TSC2101 support 2007-01-25 3:13 ` lamikr @ 2007-01-25 6:08 ` Syed Mohammed, Khasim 2007-01-25 6:26 ` Kondaiah G, Manjunath 0 siblings, 1 reply; 7+ messages in thread From: Syed Mohammed, Khasim @ 2007-01-25 6:08 UTC (permalink / raw) To: lamikr, kyungmin.park; +Cc: Linux-omap-open-source Kyungmin, thanks for the code. I was thinking on similar lines as Mika. We should have tsc2101 driver independent of Touchscreen / Audio / battery etc. Basically it should provide (export) read and write APIs using SPI. The TS part (GPIO registration / passing data to input subsystem etc) should be part of touch screen driver. The TS / Audio / Battery drivers should use these exported APIs to talk to TSC2101. There by all TSC2101 dependent drivers can be isolated from using direct SPI calls. If you agree with this design I can work on this part. But as Mika pointed it will change omapts.c, this is the only concern. Regards, Khasim ________________________________ From: linux-omap-open-source-bounces@linux.omap.com on behalf of lamikr Sent: Wed 1/24/2007 9:13 PM To: kyungmin.park@samsung.com Cc: Linux-omap-open-source@linux.omap.com Subject: Re: [RFC] TSC2101 support It would replace the h2, h3, h6300 touchscreen driver in drivers/input/touchscreen/omap/ directory. (omapts.c and ts_hx.c). But if that can be made to work with h2 and h3, I think it would be fine. Byteway, what about the support for the temperature, battery and aux registers that are supported at least by some of the devices? My understanding is that the reading of temperature, battery and aux data values that the tsc2101 page0 must be handled in the same driver than the reading of the touchscreen related info. (one must ask the tsc2101 to give those values and once ready, tsc2101 sends interrupt and we must check from the status register (01) from page1 informs whether those values are readable or whether the interrupt was touchscreen press related.) I did that once by hacking the omapts.c ts_hx.c framework but the resulting code was not pretty due to big changes in omapts.c which broke all others ts drivers relying to omapts.c Do you have any code for doing the read of those values with the new driver? And if the ts is transfered to use SPI framework, maybe the same should be done also for the omap-alsa-tsc2101. (It is currently relying on to drivers/ssi/omap-tsc2101.c) I could volunteer in that if I manage to get my h6300 kernel updated from 2.6.16 to latest... Mika Kyungmin Park wrote: > Hi, > > A few days ago, Nokia released the their N800 source code. and I found the TSC2301 source code. it's similar with TSC2101 except keypad, and some registers. > > Now the TSC2101 chip is used in H4 and apollon. So I modified the TSC2301 source code to run TSC2101 chip for quick enabling test. > However the TSC2101 use 32-bit spi protocol instead of 16-bit in TSC2301. So we have to modify spi part for 32-bit. but it's main strutures are same. > > In my opinion, the TSC2301 is not merged in omap tree. So if you don't mind we commit TSC2101 first, and after TSC2301 is merged. we make one driver to support both. > > I want to listen your opinions what is better approache > > Thank you, > Kyungmin Park > > P.S., Sorry. It's not full patch. It's just for reference :) > > diff --git a/drivers/spi/omap2_mcspi.c b/drivers/spi/omap2_mcspi.c > index b8d0ec0..e3bdbfa 100644 > --- a/drivers/spi/omap2_mcspi.c > +++ b/drivers/spi/omap2_mcspi.c > @@ -471,7 +473,7 @@ static int omap2_mcspi_setup_transfer(struct spi_device *spi, > > conf = (struct omap2_mcspi_device_config *) spi->controller_data; > > - if (conf->single_channel == 1) > + if (conf && conf->single_channel == 1) > omap2_mcspi_set_master_mode(spi, 1); > else > omap2_mcspi_set_master_mode(spi, 0); > diff --git a/drivers/spi/tsc2101-ts.c b/drivers/spi/tsc2101-ts.c > new file mode 100644 > index 0000000..83b1959 > --- /dev/null > +++ b/drivers/spi/tsc2101-ts.c > @@ -0,0 +1,712 @@ > +/* > + * TSC2101 touchscreen driver > + * > + * Copyright (C) 2005-2006 Nokia Corporation > + * > + * Modified for 32-bit SPI bus by Kyungmin Park > + * > + * Derived from TSC2301 written by Jarkko Oikarinen, Imre Deak and Juha Yrjola > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + * > + */ > + > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/input.h> > +#include <linux/interrupt.h> > +#include <linux/delay.h> > +#include <linux/spi/spi.h> > + > +#ifdef CONFIG_ARCH_OMAP > +#include <asm/arch/gpio.h> > +#endif > + > +#include <linux/spi/tsc2101.h> > + > +/** > + * The touchscreen interface operates as follows: > + * > + * Initialize: > + * Request access to GPIO (DAV) > + * tsc2101_dav_irq_handler will trigger when DAV line goes down > + * > + * 1) Pen is pressed against touchscreeen > + * 2) TSC2101 performs AD conversion > + * 3) After the conversion is done TSC2101 drives DAV line down > + * 4) GPIO IRQ is received and tsc2101_dav_irq_handler is called > + * 5) tsc2101_dav_irq_handler sets up tsc2101_ts_timer in TSC2101_TS_SCAN_TIME > + * 6) tsc2101_ts_timer disables the irq and requests spi driver > + * to read X, Y, Z1 and Z2 > + * 7) SPI framework calls tsc2101_ts_rx after the coordinates are read > + * 8) tsc2101_ts_rx reports coordinates to input layer and > + * sets up tsc2101_ts_timer to be called after TSC2101_TS_SCAN_TIME > + * 9) if tsc2101_tx_timer notices that the pen has been lifted, the lift event > + * is sent, and irq is again enabled. > + */ > + > + > +#define TSC2101_TS_SCAN_TIME 1 > + > +#define TSC2101_ADCREG_CONVERSION_CTRL_BY_TSC2101 0x8000 > +#define TSC2101_ADCREG_CONVERSION_CTRL_BY_HOST 0x0000 > + > +#define TSC2101_ADCREG_FUNCTION_NONE 0x0000 > +#define TSC2101_ADCREG_FUNCTION_XY 0x0400 > +#define TSC2101_ADCREG_FUNCTION_XYZ 0x0800 > +#define TSC2101_ADCREG_FUNCTION_X 0x0C00 > +#define TSC2101_ADCREG_FUNCTION_Y 0x1000 > +#define TSC2101_ADCREG_FUNCTION_Z 0x1400 > +#define TSC2101_ADCREG_FUNCTION_DAT1 0x1800 > +#define TSC2101_ADCREG_FUNCTION_DAT2 0x1C00 > +#define TSC2101_ADCREG_FUNCTION_AUX1 0x2000 > +#define TSC2101_ADCREG_FUNCTION_AUX2 0x2400 > +#define TSC2101_ADCREG_FUNCTION_TEMP 0x2800 > + > +#define TSC2101_ADCREG_RESOLUTION_8BIT 0x0100 > +#define TSC2101_ADCREG_RESOLUTION_10BIT 0x0200 > +#define TSC2101_ADCREG_RESOLUTION_12BIT 0x0300 > + > +#define TSC2101_ADCREG_AVERAGING_NONE 0x0000 > +#define TSC2101_ADCREG_AVERAGING_4AVG 0x0040 > +#define TSC2101_ADCREG_AVERAGING_8AVG 0x0080 > +#define TSC2101_ADCREG_AVERAGING_16AVG 0x00C0 > + > +#define TSC2101_ADCREG_CLOCK_8MHZ 0x0000 > +#define TSC2101_ADCREG_CLOCK_4MHZ 0x0010 > +#define TSC2101_ADCREG_CLOCK_2MHZ 0x0020 > +#define TSC2101_ADCREG_CLOCK_1MHZ 0x0030 > + > +#define TSC2101_ADCREG_VOLTAGE_STAB_0US 0x0000 > +#define TSC2101_ADCREG_VOLTAGE_STAB_100US 0x0002 > +#define TSC2101_ADCREG_VOLTAGE_STAB_500US 0x0004 > +#define TSC2101_ADCREG_VOLTAGE_STAB_1MS 0x0006 > +#define TSC2101_ADCREG_VOLTAGE_STAB_5MS 0x0008 > +#define TSC2101_ADCREG_VOLTAGE_STAB_10MS 0x000A > +#define TSC2101_ADCREG_VOLTAGE_STAB_50MS 0x000C > +#define TSC2101_ADCREG_VOLTAGE_STAB_100MS 0x000E > + > +#define TSC2101_ADCREG_STOP_CONVERSION 0x4000 > + > +#define TSC2101_STATUSREG_DAV 0x4000 > +#define TSC2101_PROGREG_DELAY 0x0900 > + > +#define MAX_12BIT ((1 << 12) - 1) > + > +struct tsc2101_ts { > + struct input_dev *idev; > + char phys[32]; > + struct timer_list timer; > + spinlock_t lock; > + > + struct spi_transfer read_xfer[2]; > + struct spi_message read_msg; > + u32 address[4]; > + u32 data[4]; > + > + int hw_avg_max; > + u16 x; > + u16 y; > + u16 p; > + int sample_cnt; > + > + int ignore_last : 1; > + u16 x_plate_ohm; > + int stab_time; > + int max_pressure; > + int touch_pressure; > + int pressure_limit; > + > + u16 irq_enabled:1; > + u16 pen_down:1; > + u16 disabled:1; > + u16 pending:1; > + > + int hw_flags; > + > + s16 dav_gpio; > + int irq; > +}; > + > + > +static const u32 tsc2101_ts_read_data = ((0x8000 | TSC2101_REG_X) << 16) ; > + > +static int tsc2101_ts_check_config(struct tsc2101_ts *ts, int *hw_flags) > +{ > + int flags; > + > + flags = 0; > + switch (ts->hw_avg_max) { > + case 0: > + flags |= TSC2101_ADCREG_AVERAGING_NONE; > + break; > + case 4: > + flags |= TSC2101_ADCREG_AVERAGING_4AVG; > + break; > + case 8: > + flags |= TSC2101_ADCREG_AVERAGING_8AVG; > + break; > + case 16: > + flags |= TSC2101_ADCREG_AVERAGING_16AVG; > + break; > + default: > + return -EINVAL; > + } > + > + switch (ts->stab_time) { > + case 0: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_0US; > + break; > + case 100: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_100US; > + break; > + case 500: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_500US; > + break; > + case 1000: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_1MS; > + break; > + case 5000: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_5MS; > + break; > + case 10000: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_10MS; > + break; > + case 50000: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_50MS; > + break; > + case 100000: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_100MS; > + break; > + default: > + return -EINVAL; > + } > + > + *hw_flags = flags; > + return 0; > +} > + > +static int tsc2101_ts_configure(struct tsc2101 *tsc, int flags) > +{ > + struct spi_transfer xfer[6]; > + struct spi_transfer *x; > + struct spi_message m; > + int reg; > + u32 val, adc_val; > + u32 data[6]; > + > + /* > + * TSC2101-controlled conversions > + * 12-bit samples > + * continuous X,Y,Z1,Z2 scan mode > + * average (mean) 16 samples per coordinate > + * 1 MHz internal conversion clock > + * 500 usec panel voltage stabilization delay > + * => 0x8bf4 > + */ > + /* Averaging and voltage stabilization settings in flags */ > + adc_val = TSC2101_ADCREG_CONVERSION_CTRL_BY_TSC2101 | > + TSC2101_ADCREG_FUNCTION_XYZ | > + TSC2101_ADCREG_RESOLUTION_12BIT | > + TSC2101_ADCREG_CLOCK_1MHZ | > + flags; > + > + /* Now we prepare the command for transferring */ > + /* Use internal reference clock */ > + reg = TSC2101_REG_REF; > + val = 0x0016; > + data[0] = (reg << 16) | val; > + > + reg = TSC2101_REG_CONFIG; > + val = 0x0008; > + data[1] = (reg << 16) | val; > + > + reg = TSC2101_REG_BUFFER; > + val = 0x0; > + data[2] = (reg << 16) | val; > + > + reg = TSC2101_REG_PROG_DELAY; > + val = 0x0900; > + data[3] = (reg << 16) | val; > + > + reg = TSC2101_REG_STATUS; > + val = TSC2101_STATUSREG_DAV; > + data[4] = (reg << 16) | val; > + > + reg = TSC2101_REG_ADC; > + data[5] = (reg << 16) | adc_val; > + > + spi_message_init(&m); > + m.spi = tsc->spi; > + > + memset(xfer, 0, sizeof(xfer)); > + x = &xfer[0]; > + > + x->tx_buf = &data[0]; > + x->len = 4; > + x->cs_change = 1; > + spi_message_add_tail(x, &m); > + > + x++; > + x->tx_buf = &data[1]; > + x->len = 4; > + x->cs_change = 1; > + spi_message_add_tail(x, &m); > + > + x++; > + x->tx_buf = &data[2]; > + x->len = 4; > + spi_message_add_tail(x, &m); > + > + x++; > + x->tx_buf = &data[3]; > + x->len = 4; > + spi_message_add_tail(x, &m); > + > + x++; > + x->tx_buf = &data[4]; > + x->len = 4; > + spi_message_add_tail(x, &m); > + > + x++; > + x->tx_buf = &data[5]; > + x->len = 4; > + spi_message_add_tail(x, &m); > + > + spi_sync(m.spi, &m); > + > + return 0; > +} > + > +static void tsc2101_ts_start_scan(struct tsc2101 *tsc) > +{ > + tsc2101_ts_configure(tsc, tsc->ts->hw_flags); > +} > + > +static void tsc2101_ts_stop_scan(struct tsc2101 *tsc) > +{ > + tsc2101_ts_configure(tsc, TSC2101_ADCREG_STOP_CONVERSION); > +} > + > +static int device_suspended(struct device *dev) > +{ > + struct tsc2101 *tsc = dev_get_drvdata(dev); > + return dev->power.power_state.event != PM_EVENT_ON || tsc->ts->disabled; > +} > + > +static void update_pen_state(struct tsc2101_ts *ts, int x, int y, int pressure) > +{ > + 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 > +} > + > +/* > + * This procedure is called by the SPI framework after the coordinates > + * have been read from TSC2101 > + */ > +static void tsc2101_ts_rx(void *arg) > +{ > + struct tsc2101 *tsc = arg; > + struct tsc2101_ts *ts = tsc->ts; > + unsigned int x, y, z1, z2, pressure; > + > + x = ts->data[0] & 0xffff; > + y = ts->data[1] & 0xffff; > + z1 = ts->data[2] & 0xffff; > + z2 = ts->data[3] & 0xffff; > + > + 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(ts, ts->x, ts->y, ts->p); > + ts->x = x; > + ts->y = y; > + ts->p = pressure; > + } else > + update_pen_state(ts, x, y, pressure); > + ts->sample_cnt++; > + } > + > + mod_timer(&ts->timer, > + jiffies + msecs_to_jiffies(TSC2101_TS_SCAN_TIME)); > +} > + > +static int is_pen_down(struct tsc2101_ts *ts) > +{ > + return ts->pen_down; > +} > + > +/* > + * Timer is called every TSC2101_TS_SCAN_TIME when the pen is down > + */ > +static void tsc2101_ts_timer(unsigned long arg) > +{ > + struct tsc2101 *tsc = (void *) arg; > + struct tsc2101_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 = 1; > + enable_irq(ts->irq); > + } > + update_pen_state(ts, 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 tsc2101_ts_irq_handler(int irq, void *dev_id) > +{ > + struct tsc2101 *tsc = dev_id; > + struct tsc2101_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(TSC2101_TS_SCAN_TIME)); > + } > + spin_unlock_irqrestore(&ts->lock, flags); > + > + return IRQ_HANDLED; > +} > + > +/* Must be called with ts->lock held */ > +static void tsc2101_ts_disable(struct tsc2101 *tsc) > +{ > + struct tsc2101_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); > + } > + } > + > + spin_unlock_irq(&ts->lock); > + tsc2101_ts_stop_scan(tsc); > + spin_lock_irq(&ts->lock); > +} > + > +static void tsc2101_ts_enable(struct tsc2101 *tsc) > +{ > + struct tsc2101_ts *ts = tsc->ts; > + > + if (!ts->disabled) > + return; > + > + ts->disabled = 0; > + ts->irq_enabled = 1; > + enable_irq(ts->irq); > + > + spin_unlock_irq(&ts->lock); > + tsc2101_ts_start_scan(tsc); > + spin_lock_irq(&ts->lock); > +} > + > +#ifdef CONFIG_PM > +int tsc2101_ts_suspend(struct tsc2101 *tsc) > +{ > + struct tsc2101_ts *ts = tsc->ts; > + > + spin_lock_irq(&ts->lock); > + tsc2101_ts_disable(tsc); > + spin_unlock_irq(&ts->lock); > + > + return 0; > +} > + > +void tsc2101_ts_resume(struct tsc2101 *tsc) > +{ > + struct tsc2101_ts *ts = tsc->ts; > + > + spin_lock_irq(&ts->lock); > + tsc2101_ts_enable(tsc); > + spin_unlock_irq(&ts->lock); > +} > +#endif > + > +void tsc2101_ts_prep_for_clk_stop(struct tsc2101 *tsc) > +{ > +} > + > +void tsc2101_ts_cont_after_clk_stop(struct tsc2101 *tsc) > +{ > +} > + > +static void tsc2101_ts_setup_spi_xfer(struct tsc2101 *tsc) > +{ > + struct tsc2101_ts *ts = tsc->ts; > + struct spi_message *m = &ts->read_msg; > + struct spi_transfer *x = &ts->read_xfer[0]; > + > + ts->address[0] = (0x8000 | TSC2101_REG_X) << 16; > + ts->address[1] = (0x8000 | TSC2101_REG_Y) << 16; > + ts->address[2] = (0x8000 | TSC2101_REG_Z1) << 16; > + ts->address[3] = (0x8000 | TSC2101_REG_Z2) << 16; > + > + spi_message_init(m); > + > + x->tx_buf = &ts->address; > + x->rx_buf = &ts->data; > + x->len = 16; > + spi_message_add_tail(x, m); > + > + m->complete = tsc2101_ts_rx; > + m->context = tsc; > +} > + > +static ssize_t tsc2101_ts_pen_down_show(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct tsc2101 *tsc = dev_get_drvdata(dev); > + > + return sprintf(buf, "%u\n", is_pen_down(tsc->ts)); > +} > + > +static DEVICE_ATTR(pen_down, S_IRUGO, tsc2101_ts_pen_down_show, NULL); > + > +static ssize_t tsc2101_ts_disable_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct tsc2101 *tsc = dev_get_drvdata(dev); > + struct tsc2101_ts *ts = tsc->ts; > + > + return sprintf(buf, "%u\n", ts->disabled); > +} > + > +static ssize_t tsc2101_ts_disable_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct tsc2101 *tsc = dev_get_drvdata(dev); > + struct tsc2101_ts *ts = tsc->ts; > + char *endp; > + int i; > + > + i = simple_strtoul(buf, &endp, 10); > + spin_lock_irq(&ts->lock); > + > + if (i) > + tsc2101_ts_disable(tsc); > + else > + tsc2101_ts_enable(tsc); > + > + spin_unlock_irq(&ts->lock); > + > + return count; > +} > + > +static DEVICE_ATTR(disable_ts, 0664, tsc2101_ts_disable_show, > + tsc2101_ts_disable_store); > + > +int __devinit tsc2101_ts_init(struct tsc2101 *tsc, > + struct tsc2101_platform_data *pdata) > +{ > + struct tsc2101_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); > + ts->irq = OMAP_GPIO_IRQ(dav_gpio); > +#endif > + init_timer(&ts->timer); > + ts->timer.data = (unsigned long) tsc; > + ts->timer.function = tsc2101_ts_timer; > + > + spin_lock_init(&ts->lock); > + > + ts->x_plate_ohm = pdata->ts_x_plate_ohm ? : 280; > + ts->hw_avg_max = pdata->ts_hw_avg; > + 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; > + ts->stab_time = pdata->ts_stab_time; > + > + if ((r = tsc2101_ts_check_config(ts, &ts->hw_flags))) { > + dev_err(&tsc->spi->dev, "invalid configuration\n"); > + goto err2; > + } > + > + idev = input_allocate_device(); > + if (idev == NULL) { > + r = -ENOMEM; > + goto err2; > + } > + idev->cdev.dev = &tsc->spi->dev; > + idev->name = "TSC2101 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; > + > + tsc2101_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); > + > + tsc2101_ts_start_scan(tsc); > + > + ts->irq_enabled = 1; > + r = request_irq(ts->irq, tsc2101_ts_irq_handler, > + IRQF_SAMPLE_RANDOM | IRQF_TRIGGER_FALLING, > + "tsc2101-ts", tsc); > + if (r < 0) { > + dev_err(&tsc->spi->dev, "unable to get DAV IRQ"); > + goto err3; > + } > + set_irq_wake(ts->irq, 1); > + > + r = device_create_file(&tsc->spi->dev, &dev_attr_pen_down); > + r |= device_create_file(&tsc->spi->dev, &dev_attr_disable_ts); > + if (r) > + goto err4; > + > + r = input_register_device(idev); > + if (r < 0) { > + dev_err(&tsc->spi->dev, "can't register touchscreen device\n"); > + goto err5; > + } > + > + return 0; > +err5: > + device_remove_file(&tsc->spi->dev, &dev_attr_disable_ts); > + device_remove_file(&tsc->spi->dev, &dev_attr_pen_down); > +err4: > + free_irq(ts->irq, tsc); > +err3: > + tsc2101_ts_stop_scan(tsc); > + input_free_device(idev); > +err2: > +#ifdef CONFIG_ARCH_OMAP > + omap_free_gpio(dav_gpio); > +#endif > +err1: > + kfree(ts); > + return r; > +} > + > +void __devexit tsc2101_ts_exit(struct tsc2101 *tsc) > +{ > + struct tsc2101_ts *ts = tsc->ts; > + unsigned long flags; > + > + spin_lock_irqsave(&ts->lock, flags); > + tsc2101_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); > +} > +MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>"); > +MODULE_DESCRIPTION("TSC2101 driver"); > +MODULE_LICENSE("GPL"); > diff --git a/include/linux/spi/tsc2101.h b/include/linux/spi/tsc2101.h > new file mode 100644 > index 0000000..70da08a > --- /dev/null > +++ b/include/linux/spi/tsc2101.h > @@ -0,0 +1,62 @@ > +#ifndef _LINUX_SPI_TSC2101_H > +#define _LINUX_SPI_TSC2101_H > + > +struct tsc2101_platform_data { > + /* > + * Touchscreen > + */ > + s16 dav_gpio; > + s16 pen_int_gpio; > + u16 ts_x_plate_ohm; > + u32 ts_stab_time; /* voltage settling time */ > + u8 ts_hw_avg; /* HW assiseted averaging. Can be > + 0, 4, 8, 16 samples per reading */ > + 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 ts2101_ts; > + > +struct tsc2101 { > + struct spi_device *spi; > + > + struct tsc2101_ts *ts; > +}; > + > +#define TSC2101_REG(page, addr) (((page) << 11) | ((addr) << 5)) > + > +/* Page 0, Touchscreen data registers */ > +#define TSC2101_REG_X TSC2101_REG(0, 0) > +#define TSC2101_REG_Y TSC2101_REG(0, 1) > +#define TSC2101_REG_Z1 TSC2101_REG(0, 2) > +#define TSC2101_REG_Z2 TSC2101_REG(0, 3) > +#define TSC2101_REG_TEMP1 TSC2101_REG(0, 9) > +#define TSC2101_REG_TEMP2 TSC2101_REG(0, 10) > + > +/* Page 1, Tochscreen control registers */ > +#define TSC2101_REG_ADC TSC2101_REG(1, 0) > +#define TSC2101_REG_STATUS TSC2101_REG(1, 1) > +#define TSC2101_REG_BUFFER TSC2101_REG(1, 2) > +#define TSC2101_REG_REF TSC2101_REG(1, 3) > +#define TSC2101_REG_CONFIG TSC2101_REG(1, 5) > +#define TSC2101_REG_PROG_DELAY TSC2101_REG(1, 13) > + > +/* Page 2, Audio control registers */ > +#define TSC2101_REG_AUDIO1 TSC2101_REG(2, 0) > +#define TSC2101_REG_DAC_GAIN TSC2101_REG(2, 2) > +#define TSC2101_REG_AUDIO2 TSC2101_REG(2, 4) > +#define TSC2101_REG_DAC_POWER TSC2101_REG(2, 5) > +#define TSC2101_REG_AUDIO3 TSC2101_REG(2, 6) > +#define TSC2101_REG_PLL1 TSC2101_REG(2, 27) > +#define TSC2101_REG_PLL2 TSC2101_REG(2, 28) > +#define TSC2101_REG_AUDIO4 TSC2101_REG(2, 29) > + > +int tsc2101_ts_init(struct tsc2101 *, struct tsc2101_platform_data *); > +void tsc2101_ts_exit(struct tsc2101 *); > + > +#endif > ------------------------------------------------------------------------ > > _______________________________________________ > Linux-omap-open-source mailing list > Linux-omap-open-source@linux.omap.com > http://linux.omap.com/mailman/listinfo/linux-omap-open-source > _______________________________________________ Linux-omap-open-source mailing list Linux-omap-open-source@linux.omap.com http://linux.omap.com/mailman/listinfo/linux-omap-open-source ^ permalink raw reply [flat|nested] 7+ messages in thread
* RE: [RFC] TSC2101 support 2007-01-25 6:08 ` Syed Mohammed, Khasim @ 2007-01-25 6:26 ` Kondaiah G, Manjunath 2007-01-25 12:36 ` Kai Svahn 0 siblings, 1 reply; 7+ messages in thread From: Kondaiah G, Manjunath @ 2007-01-25 6:26 UTC (permalink / raw) To: Syed Mohammed, Khasim, lamikr, kyungmin.park; +Cc: Linux-omap-open-source Khasim, It will be better if single driver supports API's for both TSC2101 and TSC2301 controllers since TSC2301 is superset of TSC2101 which has additional features such as keypad, gpio pins etc. Regards, Manjunath -----Original Message----- From: linux-omap-open-source-bounces+manjugk=ti.com@linux.omap.com [mailto:linux-omap-open-source-bounces+manjugk=ti.com@linux.omap.com] On Behalf Of Syed Mohammed, Khasim Sent: Thursday, January 25, 2007 11:38 AM To: lamikr@cc.jyu.fi; kyungmin.park@samsung.com Cc: Linux-omap-open-source@linux.omap.com Subject: RE: [RFC] TSC2101 support Kyungmin, thanks for the code. I was thinking on similar lines as Mika. We should have tsc2101 driver independent of Touchscreen / Audio / battery etc. Basically it should provide (export) read and write APIs using SPI. The TS part (GPIO registration / passing data to input subsystem etc) should be part of touch screen driver. The TS / Audio / Battery drivers should use these exported APIs to talk to TSC2101. There by all TSC2101 dependent drivers can be isolated from using direct SPI calls. If you agree with this design I can work on this part. But as Mika pointed it will change omapts.c, this is the only concern. Regards, Khasim ________________________________ From: linux-omap-open-source-bounces@linux.omap.com on behalf of lamikr Sent: Wed 1/24/2007 9:13 PM To: kyungmin.park@samsung.com Cc: Linux-omap-open-source@linux.omap.com Subject: Re: [RFC] TSC2101 support It would replace the h2, h3, h6300 touchscreen driver in drivers/input/touchscreen/omap/ directory. (omapts.c and ts_hx.c). But if that can be made to work with h2 and h3, I think it would be fine. Byteway, what about the support for the temperature, battery and aux registers that are supported at least by some of the devices? My understanding is that the reading of temperature, battery and aux data values that the tsc2101 page0 must be handled in the same driver than the reading of the touchscreen related info. (one must ask the tsc2101 to give those values and once ready, tsc2101 sends interrupt and we must check from the status register (01) from page1 informs whether those values are readable or whether the interrupt was touchscreen press related.) I did that once by hacking the omapts.c ts_hx.c framework but the resulting code was not pretty due to big changes in omapts.c which broke all others ts drivers relying to omapts.c Do you have any code for doing the read of those values with the new driver? And if the ts is transfered to use SPI framework, maybe the same should be done also for the omap-alsa-tsc2101. (It is currently relying on to drivers/ssi/omap-tsc2101.c) I could volunteer in that if I manage to get my h6300 kernel updated from 2.6.16 to latest... Mika Kyungmin Park wrote: > Hi, > > A few days ago, Nokia released the their N800 source code. and I found the TSC2301 source code. it's similar with TSC2101 except keypad, and some registers. > > Now the TSC2101 chip is used in H4 and apollon. So I modified the TSC2301 source code to run TSC2101 chip for quick enabling test. > However the TSC2101 use 32-bit spi protocol instead of 16-bit in TSC2301. So we have to modify spi part for 32-bit. but it's main strutures are same. > > In my opinion, the TSC2301 is not merged in omap tree. So if you don't mind we commit TSC2101 first, and after TSC2301 is merged. we make one driver to support both. > > I want to listen your opinions what is better approache > > Thank you, > Kyungmin Park > > P.S., Sorry. It's not full patch. It's just for reference :) > > diff --git a/drivers/spi/omap2_mcspi.c b/drivers/spi/omap2_mcspi.c > index b8d0ec0..e3bdbfa 100644 > --- a/drivers/spi/omap2_mcspi.c > +++ b/drivers/spi/omap2_mcspi.c > @@ -471,7 +473,7 @@ static int omap2_mcspi_setup_transfer(struct spi_device *spi, > > conf = (struct omap2_mcspi_device_config *) spi->controller_data; > > - if (conf->single_channel == 1) > + if (conf && conf->single_channel == 1) > omap2_mcspi_set_master_mode(spi, 1); > else > omap2_mcspi_set_master_mode(spi, 0); > diff --git a/drivers/spi/tsc2101-ts.c b/drivers/spi/tsc2101-ts.c > new file mode 100644 > index 0000000..83b1959 > --- /dev/null > +++ b/drivers/spi/tsc2101-ts.c > @@ -0,0 +1,712 @@ > +/* > + * TSC2101 touchscreen driver > + * > + * Copyright (C) 2005-2006 Nokia Corporation > + * > + * Modified for 32-bit SPI bus by Kyungmin Park > + * > + * Derived from TSC2301 written by Jarkko Oikarinen, Imre Deak and Juha Yrjola > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + * > + */ > + > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/input.h> > +#include <linux/interrupt.h> > +#include <linux/delay.h> > +#include <linux/spi/spi.h> > + > +#ifdef CONFIG_ARCH_OMAP > +#include <asm/arch/gpio.h> > +#endif > + > +#include <linux/spi/tsc2101.h> > + > +/** > + * The touchscreen interface operates as follows: > + * > + * Initialize: > + * Request access to GPIO (DAV) > + * tsc2101_dav_irq_handler will trigger when DAV line goes down > + * > + * 1) Pen is pressed against touchscreeen > + * 2) TSC2101 performs AD conversion > + * 3) After the conversion is done TSC2101 drives DAV line down > + * 4) GPIO IRQ is received and tsc2101_dav_irq_handler is called > + * 5) tsc2101_dav_irq_handler sets up tsc2101_ts_timer in TSC2101_TS_SCAN_TIME > + * 6) tsc2101_ts_timer disables the irq and requests spi driver > + * to read X, Y, Z1 and Z2 > + * 7) SPI framework calls tsc2101_ts_rx after the coordinates are read > + * 8) tsc2101_ts_rx reports coordinates to input layer and > + * sets up tsc2101_ts_timer to be called after TSC2101_TS_SCAN_TIME > + * 9) if tsc2101_tx_timer notices that the pen has been lifted, the lift event > + * is sent, and irq is again enabled. > + */ > + > + > +#define TSC2101_TS_SCAN_TIME 1 > + > +#define TSC2101_ADCREG_CONVERSION_CTRL_BY_TSC2101 0x8000 > +#define TSC2101_ADCREG_CONVERSION_CTRL_BY_HOST 0x0000 > + > +#define TSC2101_ADCREG_FUNCTION_NONE 0x0000 > +#define TSC2101_ADCREG_FUNCTION_XY 0x0400 > +#define TSC2101_ADCREG_FUNCTION_XYZ 0x0800 > +#define TSC2101_ADCREG_FUNCTION_X 0x0C00 > +#define TSC2101_ADCREG_FUNCTION_Y 0x1000 > +#define TSC2101_ADCREG_FUNCTION_Z 0x1400 > +#define TSC2101_ADCREG_FUNCTION_DAT1 0x1800 > +#define TSC2101_ADCREG_FUNCTION_DAT2 0x1C00 > +#define TSC2101_ADCREG_FUNCTION_AUX1 0x2000 > +#define TSC2101_ADCREG_FUNCTION_AUX2 0x2400 > +#define TSC2101_ADCREG_FUNCTION_TEMP 0x2800 > + > +#define TSC2101_ADCREG_RESOLUTION_8BIT 0x0100 > +#define TSC2101_ADCREG_RESOLUTION_10BIT 0x0200 > +#define TSC2101_ADCREG_RESOLUTION_12BIT 0x0300 > + > +#define TSC2101_ADCREG_AVERAGING_NONE 0x0000 > +#define TSC2101_ADCREG_AVERAGING_4AVG 0x0040 > +#define TSC2101_ADCREG_AVERAGING_8AVG 0x0080 > +#define TSC2101_ADCREG_AVERAGING_16AVG 0x00C0 > + > +#define TSC2101_ADCREG_CLOCK_8MHZ 0x0000 > +#define TSC2101_ADCREG_CLOCK_4MHZ 0x0010 > +#define TSC2101_ADCREG_CLOCK_2MHZ 0x0020 > +#define TSC2101_ADCREG_CLOCK_1MHZ 0x0030 > + > +#define TSC2101_ADCREG_VOLTAGE_STAB_0US 0x0000 > +#define TSC2101_ADCREG_VOLTAGE_STAB_100US 0x0002 > +#define TSC2101_ADCREG_VOLTAGE_STAB_500US 0x0004 > +#define TSC2101_ADCREG_VOLTAGE_STAB_1MS 0x0006 > +#define TSC2101_ADCREG_VOLTAGE_STAB_5MS 0x0008 > +#define TSC2101_ADCREG_VOLTAGE_STAB_10MS 0x000A > +#define TSC2101_ADCREG_VOLTAGE_STAB_50MS 0x000C > +#define TSC2101_ADCREG_VOLTAGE_STAB_100MS 0x000E > + > +#define TSC2101_ADCREG_STOP_CONVERSION 0x4000 > + > +#define TSC2101_STATUSREG_DAV 0x4000 > +#define TSC2101_PROGREG_DELAY 0x0900 > + > +#define MAX_12BIT ((1 << 12) - 1) > + > +struct tsc2101_ts { > + struct input_dev *idev; > + char phys[32]; > + struct timer_list timer; > + spinlock_t lock; > + > + struct spi_transfer read_xfer[2]; > + struct spi_message read_msg; > + u32 address[4]; > + u32 data[4]; > + > + int hw_avg_max; > + u16 x; > + u16 y; > + u16 p; > + int sample_cnt; > + > + int ignore_last : 1; > + u16 x_plate_ohm; > + int stab_time; > + int max_pressure; > + int touch_pressure; > + int pressure_limit; > + > + u16 irq_enabled:1; > + u16 pen_down:1; > + u16 disabled:1; > + u16 pending:1; > + > + int hw_flags; > + > + s16 dav_gpio; > + int irq; > +}; > + > + > +static const u32 tsc2101_ts_read_data = ((0x8000 | TSC2101_REG_X) << 16) ; > + > +static int tsc2101_ts_check_config(struct tsc2101_ts *ts, int *hw_flags) > +{ > + int flags; > + > + flags = 0; > + switch (ts->hw_avg_max) { > + case 0: > + flags |= TSC2101_ADCREG_AVERAGING_NONE; > + break; > + case 4: > + flags |= TSC2101_ADCREG_AVERAGING_4AVG; > + break; > + case 8: > + flags |= TSC2101_ADCREG_AVERAGING_8AVG; > + break; > + case 16: > + flags |= TSC2101_ADCREG_AVERAGING_16AVG; > + break; > + default: > + return -EINVAL; > + } > + > + switch (ts->stab_time) { > + case 0: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_0US; > + break; > + case 100: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_100US; > + break; > + case 500: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_500US; > + break; > + case 1000: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_1MS; > + break; > + case 5000: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_5MS; > + break; > + case 10000: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_10MS; > + break; > + case 50000: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_50MS; > + break; > + case 100000: > + flags |= TSC2101_ADCREG_VOLTAGE_STAB_100MS; > + break; > + default: > + return -EINVAL; > + } > + > + *hw_flags = flags; > + return 0; > +} > + > +static int tsc2101_ts_configure(struct tsc2101 *tsc, int flags) > +{ > + struct spi_transfer xfer[6]; > + struct spi_transfer *x; > + struct spi_message m; > + int reg; > + u32 val, adc_val; > + u32 data[6]; > + > + /* > + * TSC2101-controlled conversions > + * 12-bit samples > + * continuous X,Y,Z1,Z2 scan mode > + * average (mean) 16 samples per coordinate > + * 1 MHz internal conversion clock > + * 500 usec panel voltage stabilization delay > + * => 0x8bf4 > + */ > + /* Averaging and voltage stabilization settings in flags */ > + adc_val = TSC2101_ADCREG_CONVERSION_CTRL_BY_TSC2101 | > + TSC2101_ADCREG_FUNCTION_XYZ | > + TSC2101_ADCREG_RESOLUTION_12BIT | > + TSC2101_ADCREG_CLOCK_1MHZ | > + flags; > + > + /* Now we prepare the command for transferring */ > + /* Use internal reference clock */ > + reg = TSC2101_REG_REF; > + val = 0x0016; > + data[0] = (reg << 16) | val; > + > + reg = TSC2101_REG_CONFIG; > + val = 0x0008; > + data[1] = (reg << 16) | val; > + > + reg = TSC2101_REG_BUFFER; > + val = 0x0; > + data[2] = (reg << 16) | val; > + > + reg = TSC2101_REG_PROG_DELAY; > + val = 0x0900; > + data[3] = (reg << 16) | val; > + > + reg = TSC2101_REG_STATUS; > + val = TSC2101_STATUSREG_DAV; > + data[4] = (reg << 16) | val; > + > + reg = TSC2101_REG_ADC; > + data[5] = (reg << 16) | adc_val; > + > + spi_message_init(&m); > + m.spi = tsc->spi; > + > + memset(xfer, 0, sizeof(xfer)); > + x = &xfer[0]; > + > + x->tx_buf = &data[0]; > + x->len = 4; > + x->cs_change = 1; > + spi_message_add_tail(x, &m); > + > + x++; > + x->tx_buf = &data[1]; > + x->len = 4; > + x->cs_change = 1; > + spi_message_add_tail(x, &m); > + > + x++; > + x->tx_buf = &data[2]; > + x->len = 4; > + spi_message_add_tail(x, &m); > + > + x++; > + x->tx_buf = &data[3]; > + x->len = 4; > + spi_message_add_tail(x, &m); > + > + x++; > + x->tx_buf = &data[4]; > + x->len = 4; > + spi_message_add_tail(x, &m); > + > + x++; > + x->tx_buf = &data[5]; > + x->len = 4; > + spi_message_add_tail(x, &m); > + > + spi_sync(m.spi, &m); > + > + return 0; > +} > + > +static void tsc2101_ts_start_scan(struct tsc2101 *tsc) > +{ > + tsc2101_ts_configure(tsc, tsc->ts->hw_flags); > +} > + > +static void tsc2101_ts_stop_scan(struct tsc2101 *tsc) > +{ > + tsc2101_ts_configure(tsc, TSC2101_ADCREG_STOP_CONVERSION); > +} > + > +static int device_suspended(struct device *dev) > +{ > + struct tsc2101 *tsc = dev_get_drvdata(dev); > + return dev->power.power_state.event != PM_EVENT_ON || tsc->ts->disabled; > +} > + > +static void update_pen_state(struct tsc2101_ts *ts, int x, int y, int pressure) > +{ > + 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 > +} > + > +/* > + * This procedure is called by the SPI framework after the coordinates > + * have been read from TSC2101 > + */ > +static void tsc2101_ts_rx(void *arg) > +{ > + struct tsc2101 *tsc = arg; > + struct tsc2101_ts *ts = tsc->ts; > + unsigned int x, y, z1, z2, pressure; > + > + x = ts->data[0] & 0xffff; > + y = ts->data[1] & 0xffff; > + z1 = ts->data[2] & 0xffff; > + z2 = ts->data[3] & 0xffff; > + > + 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(ts, ts->x, ts->y, ts->p); > + ts->x = x; > + ts->y = y; > + ts->p = pressure; > + } else > + update_pen_state(ts, x, y, pressure); > + ts->sample_cnt++; > + } > + > + mod_timer(&ts->timer, > + jiffies + msecs_to_jiffies(TSC2101_TS_SCAN_TIME)); > +} > + > +static int is_pen_down(struct tsc2101_ts *ts) > +{ > + return ts->pen_down; > +} > + > +/* > + * Timer is called every TSC2101_TS_SCAN_TIME when the pen is down > + */ > +static void tsc2101_ts_timer(unsigned long arg) > +{ > + struct tsc2101 *tsc = (void *) arg; > + struct tsc2101_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 = 1; > + enable_irq(ts->irq); > + } > + update_pen_state(ts, 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 tsc2101_ts_irq_handler(int irq, void *dev_id) > +{ > + struct tsc2101 *tsc = dev_id; > + struct tsc2101_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(TSC2101_TS_SCAN_TIME)); > + } > + spin_unlock_irqrestore(&ts->lock, flags); > + > + return IRQ_HANDLED; > +} > + > +/* Must be called with ts->lock held */ > +static void tsc2101_ts_disable(struct tsc2101 *tsc) > +{ > + struct tsc2101_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); > + } > + } > + > + spin_unlock_irq(&ts->lock); > + tsc2101_ts_stop_scan(tsc); > + spin_lock_irq(&ts->lock); > +} > + > +static void tsc2101_ts_enable(struct tsc2101 *tsc) > +{ > + struct tsc2101_ts *ts = tsc->ts; > + > + if (!ts->disabled) > + return; > + > + ts->disabled = 0; > + ts->irq_enabled = 1; > + enable_irq(ts->irq); > + > + spin_unlock_irq(&ts->lock); > + tsc2101_ts_start_scan(tsc); > + spin_lock_irq(&ts->lock); > +} > + > +#ifdef CONFIG_PM > +int tsc2101_ts_suspend(struct tsc2101 *tsc) > +{ > + struct tsc2101_ts *ts = tsc->ts; > + > + spin_lock_irq(&ts->lock); > + tsc2101_ts_disable(tsc); > + spin_unlock_irq(&ts->lock); > + > + return 0; > +} > + > +void tsc2101_ts_resume(struct tsc2101 *tsc) > +{ > + struct tsc2101_ts *ts = tsc->ts; > + > + spin_lock_irq(&ts->lock); > + tsc2101_ts_enable(tsc); > + spin_unlock_irq(&ts->lock); > +} > +#endif > + > +void tsc2101_ts_prep_for_clk_stop(struct tsc2101 *tsc) > +{ > +} > + > +void tsc2101_ts_cont_after_clk_stop(struct tsc2101 *tsc) > +{ > +} > + > +static void tsc2101_ts_setup_spi_xfer(struct tsc2101 *tsc) > +{ > + struct tsc2101_ts *ts = tsc->ts; > + struct spi_message *m = &ts->read_msg; > + struct spi_transfer *x = &ts->read_xfer[0]; > + > + ts->address[0] = (0x8000 | TSC2101_REG_X) << 16; > + ts->address[1] = (0x8000 | TSC2101_REG_Y) << 16; > + ts->address[2] = (0x8000 | TSC2101_REG_Z1) << 16; > + ts->address[3] = (0x8000 | TSC2101_REG_Z2) << 16; > + > + spi_message_init(m); > + > + x->tx_buf = &ts->address; > + x->rx_buf = &ts->data; > + x->len = 16; > + spi_message_add_tail(x, m); > + > + m->complete = tsc2101_ts_rx; > + m->context = tsc; > +} > + > +static ssize_t tsc2101_ts_pen_down_show(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct tsc2101 *tsc = dev_get_drvdata(dev); > + > + return sprintf(buf, "%u\n", is_pen_down(tsc->ts)); > +} > + > +static DEVICE_ATTR(pen_down, S_IRUGO, tsc2101_ts_pen_down_show, NULL); > + > +static ssize_t tsc2101_ts_disable_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct tsc2101 *tsc = dev_get_drvdata(dev); > + struct tsc2101_ts *ts = tsc->ts; > + > + return sprintf(buf, "%u\n", ts->disabled); > +} > + > +static ssize_t tsc2101_ts_disable_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct tsc2101 *tsc = dev_get_drvdata(dev); > + struct tsc2101_ts *ts = tsc->ts; > + char *endp; > + int i; > + > + i = simple_strtoul(buf, &endp, 10); > + spin_lock_irq(&ts->lock); > + > + if (i) > + tsc2101_ts_disable(tsc); > + else > + tsc2101_ts_enable(tsc); > + > + spin_unlock_irq(&ts->lock); > + > + return count; > +} > + > +static DEVICE_ATTR(disable_ts, 0664, tsc2101_ts_disable_show, > + tsc2101_ts_disable_store); > + > +int __devinit tsc2101_ts_init(struct tsc2101 *tsc, > + struct tsc2101_platform_data *pdata) > +{ > + struct tsc2101_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); > + ts->irq = OMAP_GPIO_IRQ(dav_gpio); > +#endif > + init_timer(&ts->timer); > + ts->timer.data = (unsigned long) tsc; > + ts->timer.function = tsc2101_ts_timer; > + > + spin_lock_init(&ts->lock); > + > + ts->x_plate_ohm = pdata->ts_x_plate_ohm ? : 280; > + ts->hw_avg_max = pdata->ts_hw_avg; > + 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; > + ts->stab_time = pdata->ts_stab_time; > + > + if ((r = tsc2101_ts_check_config(ts, &ts->hw_flags))) { > + dev_err(&tsc->spi->dev, "invalid configuration\n"); > + goto err2; > + } > + > + idev = input_allocate_device(); > + if (idev == NULL) { > + r = -ENOMEM; > + goto err2; > + } > + idev->cdev.dev = &tsc->spi->dev; > + idev->name = "TSC2101 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; > + > + tsc2101_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); > + > + tsc2101_ts_start_scan(tsc); > + > + ts->irq_enabled = 1; > + r = request_irq(ts->irq, tsc2101_ts_irq_handler, > + IRQF_SAMPLE_RANDOM | IRQF_TRIGGER_FALLING, > + "tsc2101-ts", tsc); > + if (r < 0) { > + dev_err(&tsc->spi->dev, "unable to get DAV IRQ"); > + goto err3; > + } > + set_irq_wake(ts->irq, 1); > + > + r = device_create_file(&tsc->spi->dev, &dev_attr_pen_down); > + r |= device_create_file(&tsc->spi->dev, &dev_attr_disable_ts); > + if (r) > + goto err4; > + > + r = input_register_device(idev); > + if (r < 0) { > + dev_err(&tsc->spi->dev, "can't register touchscreen device\n"); > + goto err5; > + } > + > + return 0; > +err5: > + device_remove_file(&tsc->spi->dev, &dev_attr_disable_ts); > + device_remove_file(&tsc->spi->dev, &dev_attr_pen_down); > +err4: > + free_irq(ts->irq, tsc); > +err3: > + tsc2101_ts_stop_scan(tsc); > + input_free_device(idev); > +err2: > +#ifdef CONFIG_ARCH_OMAP > + omap_free_gpio(dav_gpio); > +#endif > +err1: > + kfree(ts); > + return r; > +} > + > +void __devexit tsc2101_ts_exit(struct tsc2101 *tsc) > +{ > + struct tsc2101_ts *ts = tsc->ts; > + unsigned long flags; > + > + spin_lock_irqsave(&ts->lock, flags); > + tsc2101_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); > +} > +MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>"); > +MODULE_DESCRIPTION("TSC2101 driver"); > +MODULE_LICENSE("GPL"); > diff --git a/include/linux/spi/tsc2101.h b/include/linux/spi/tsc2101.h > new file mode 100644 > index 0000000..70da08a > --- /dev/null > +++ b/include/linux/spi/tsc2101.h > @@ -0,0 +1,62 @@ > +#ifndef _LINUX_SPI_TSC2101_H > +#define _LINUX_SPI_TSC2101_H > + > +struct tsc2101_platform_data { > + /* > + * Touchscreen > + */ > + s16 dav_gpio; > + s16 pen_int_gpio; > + u16 ts_x_plate_ohm; > + u32 ts_stab_time; /* voltage settling time */ > + u8 ts_hw_avg; /* HW assiseted averaging. Can be > + 0, 4, 8, 16 samples per reading */ > + 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 ts2101_ts; > + > +struct tsc2101 { > + struct spi_device *spi; > + > + struct tsc2101_ts *ts; > +}; > + > +#define TSC2101_REG(page, addr) (((page) << 11) | ((addr) << 5)) > + > +/* Page 0, Touchscreen data registers */ > +#define TSC2101_REG_X TSC2101_REG(0, 0) > +#define TSC2101_REG_Y TSC2101_REG(0, 1) > +#define TSC2101_REG_Z1 TSC2101_REG(0, 2) > +#define TSC2101_REG_Z2 TSC2101_REG(0, 3) > +#define TSC2101_REG_TEMP1 TSC2101_REG(0, 9) > +#define TSC2101_REG_TEMP2 TSC2101_REG(0, 10) > + > +/* Page 1, Tochscreen control registers */ > +#define TSC2101_REG_ADC TSC2101_REG(1, 0) > +#define TSC2101_REG_STATUS TSC2101_REG(1, 1) > +#define TSC2101_REG_BUFFER TSC2101_REG(1, 2) > +#define TSC2101_REG_REF TSC2101_REG(1, 3) > +#define TSC2101_REG_CONFIG TSC2101_REG(1, 5) > +#define TSC2101_REG_PROG_DELAY TSC2101_REG(1, 13) > + > +/* Page 2, Audio control registers */ > +#define TSC2101_REG_AUDIO1 TSC2101_REG(2, 0) > +#define TSC2101_REG_DAC_GAIN TSC2101_REG(2, 2) > +#define TSC2101_REG_AUDIO2 TSC2101_REG(2, 4) > +#define TSC2101_REG_DAC_POWER TSC2101_REG(2, 5) > +#define TSC2101_REG_AUDIO3 TSC2101_REG(2, 6) > +#define TSC2101_REG_PLL1 TSC2101_REG(2, 27) > +#define TSC2101_REG_PLL2 TSC2101_REG(2, 28) > +#define TSC2101_REG_AUDIO4 TSC2101_REG(2, 29) > + > +int tsc2101_ts_init(struct tsc2101 *, struct tsc2101_platform_data *); > +void tsc2101_ts_exit(struct tsc2101 *); > + > +#endif > ------------------------------------------------------------------------ > > _______________________________________________ > Linux-omap-open-source mailing list > Linux-omap-open-source@linux.omap.com > http://linux.omap.com/mailman/listinfo/linux-omap-open-source > _______________________________________________ Linux-omap-open-source mailing list Linux-omap-open-source@linux.omap.com http://linux.omap.com/mailman/listinfo/linux-omap-open-source _______________________________________________ Linux-omap-open-source mailing list Linux-omap-open-source@linux.omap.com http://linux.omap.com/mailman/listinfo/linux-omap-open-source ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC] TSC2101 support 2007-01-25 6:26 ` Kondaiah G, Manjunath @ 2007-01-25 12:36 ` Kai Svahn 2007-01-30 7:20 ` Kyungmin Park 0 siblings, 1 reply; 7+ messages in thread From: Kai Svahn @ 2007-01-25 12:36 UTC (permalink / raw) To: ext Kondaiah G, Manjunath; +Cc: Linux-omap-open-source Hello all, I hope we get also some comments from Nokia driver authors about this issue but they have limited network access until 2nd of February. Cheers, Kai ^ permalink raw reply [flat|nested] 7+ messages in thread
* RE: [RFC] TSC2101 support 2007-01-25 12:36 ` Kai Svahn @ 2007-01-30 7:20 ` Kyungmin Park 0 siblings, 0 replies; 7+ messages in thread From: Kyungmin Park @ 2007-01-30 7:20 UTC (permalink / raw) To: 'Kai Svahn', 'ext Kondaiah G, Manjunath' Cc: Linux-omap-open-source Hi, > > I hope we get also some comments from Nokia driver authors > about this issue but they have limited network access until > 2nd of February. > No problem. Actually I want to co-work with you, Here's TSC patch for both tsc2101 and tsc2301 (only for core and ts, others are you can obtain from n800 sources) Now I don't modify anythings from n800 sources. In touchscreen source, only 3 parts are different. I marked with 'XXX tsc2101 specific' I think we first make common header(include/linux/spi/tsc-common.h) and common code file (drivers/spi/tsc-common.c with CONFIG_SPI_TSC_COMMON(?)) It means we have to modify tsc2301 releated code. please give some comments Any comments are welcome Thank you, Kyungmin Park -- diff --git a/arch/arm/mach-omap2/board-apollon.c b/arch/arm/mach- omap2/board-apollon.c index 5e1cada..d91d919 100644 --- a/arch/arm/mach-omap2/board-apollon.c +++ b/arch/arm/mach-omap2/board-apollon.c @@ -24,9 +24,10 @@ #include <linux/mtd/onenand.h> #include <linux/irq.h> #include <linux/interrupt.h> +#include <linux/spi/spi.h> +#include <linux/spi/tsc2101.h> #include <linux/delay.h> #include <linux/leds.h> -#include <linux/irq.h> #include <linux/err.h> #include <linux/clk.h> @@ -42,6 +43,7 @@ #include <asm/arch/board.h> #include <asm/arch/common.h> #include <asm/arch/gpmc.h> +#include <asm/arch/mcspi.h> #include "prcm-regs.h" /* LED & Switch macros */ @@ -51,6 +53,7 @@ #define SW_ENTER_GPIO16 16 #define SW_UP_GPIO17 17 #define SW_DOWN_GPIO58 58 +#define PINTDAV_GPIO85 85 #define APOLLON_FLASH_CS 0 #define APOLLON_ETH_CS 1 @@ -358,6 +361,30 @@ static void __init apollon_usb_init(void) omap_set_gpio_dataout(12, 0); } +static struct omap2_mcspi_device_config tsc2101_mcspi_config = { + .turbo_mode = 0, + .single_channel = 0, +}; + +static struct tsc2101_platform_data tsc2101_platform_config = { + /* Touchscreen */ + .dav_gpio = PINTDAV_GPIO85, + .ts_x_plate_ohm = 200, + .ts_stab_time = 500, /* 500 usec */ + .ts_hw_avg = 16, +}; + +static struct spi_board_info apollon_spi_board_info[] __initdata = { + [0] = { + .modalias = "tsc2101", + .bus_num = 1, + .chip_select = 0, + .max_speed_hz = 6000000, + .controller_data = &tsc2101_mcspi_config, + .platform_data = &tsc2101_platform_config, + }, +}; + static void __init omap_apollon_init(void) { apollon_led_init(); @@ -373,6 +400,8 @@ static void __init omap_apollon_init(void) * You have to mux them off in device drivers later on * if not needed. */ + spi_register_board_info(apollon_spi_board_info, + ARRAY_SIZE(apollon_spi_board_info)); platform_add_devices(apollon_devices, ARRAY_SIZE(apollon_devices)); omap_board_config = apollon_config; omap_board_config_size = ARRAY_SIZE(apollon_config); diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 948a9dc..4a5dd1b 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -135,6 +135,85 @@ config SPI_S3C24XX comment "SPI Protocol Masters" depends on SPI_MASTER +config SPI_TSC_COMMON + tristate "TSC common driver" + depends on SPI_MASTER + help + Say Y here if you have a TSC series chip connected to a SPI + bus on your board. + + The TSC chip is a highly integrated PDA analog interface circuit. + It contains a complete 12-bit A/D resistive touch screen + converter (ADC) including drivers, touch pressure measurement + capability. + + To compile this driver as a module, choose M here: the + module will be called tsc-common. + +config SPI_TSC2101 + tristate "TSC2101 driver" + depends on SPI_MASTER && SPI_TSC_COMMON + help + Say Y here if you have a TSC2101 chip connected to a SPI + bus on your board. + + The TSC2101 is a highly integrated PDA analog interface circuit. + It contains a complete 12-bit A/D resistive touch screen + converter (ADC) including drivers, touch pressure measurement + capability. + + To compile this driver as a module, choose M here: the + module will be called tsc2101. + +config SPI_TSC2101_TOUCHSCREEN + boolean "TSC2101 touchscreen support" + depends on SPI_TSC2101 + select INPUT_TOUCHSCREEN + help + Say Y here for if you are using the touchscreen features of TSC2101. + +config SPI_TSC2101_AUDIO + boolean "TSC2101 audio support" + depends on SPI_TSC2101 && SND + help + Say Y here for if you are using the audio features of TSC2101. + +config SPI_TSC2301 + tristate "TSC2301 driver" + depends on SPI_MASTER && SPI_TSC_COMMON + help + Say Y here if you have a TSC2301 chip connected to an SPI + bus on your board. + + The TSC2301 is a highly integrated PDA analog interface circuit. + It contains a complete 12-bit A/D resistive touch screen + converter (ADC) including drivers, touch pressure measurement + capability, keypad controller, and 8-bit D/A converter (DAC) output + for LCD contrast control. + + To compile this driver as a module, choose M here: the + module will be called tsc2301. + +config SPI_TSC2301_KEYPAD + boolean "TSC2301 keypad support" + depends on SPI_TSC2301 + select INPUT_KEYBOARD + help + Say Y here for if you are using the keypad features of TSC2301. + +config SPI_TSC2301_TOUCHSCREEN + boolean "TSC2301 touchscreen support" + depends on SPI_TSC2301 + select INPUT_TOUCHSCREEN + help + Say Y here for if you are using the touchscreen features of TSC2301. + +config SPI_TSC2301_AUDIO + boolean "TSC2301 audio support" + depends on SPI_TSC2301 && SND + help + Say Y here for if you are using the audio features of TSC2301. + config TSC2102 depends on SPI_MASTER tristate "TSC2102 codec support" diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 83b3e70..d812927 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -22,6 +22,17 @@ obj-$(CONFIG_SPI_OMAP_UWIRE) += omap_uwire.o # ... add above this line ... # SPI protocol drivers (device/link on bus) +tsc2101-objs += tsc2101-core.o +tsc2101-$(CONFIG_SPI_TSC2101_TOUCHSCREEN) += tsc2101-ts.o +tsc2101-$(CONFIG_SPI_TSC2101_AUDIO) += tsc2101-mixer.o +obj-$(CONFIG_SPI_TSC2101) += tsc2101.o + +tsc2301-objs += tsc2301-core.o +tsc2301-$(CONFIG_SPI_TSC2301_TOUCHSCREEN) += tsc2301-ts.o +tsc2301-$(CONFIG_SPI_TSC2301_KEYPAD) += tsc2301-kp.o +tsc2301-$(CONFIG_SPI_TSC2301_AUDIO) += tsc2301-mixer.o +obj-$(CONFIG_SPI_TSC2301) += tsc2301.o + obj-$(CONFIG_TSC2102) += tsc2102.o # ... add above this line ... diff --git a/drivers/spi/omap2_mcspi.c b/drivers/spi/omap2_mcspi.c index b8d0ec0..e72ef60 100644 --- a/drivers/spi/omap2_mcspi.c +++ b/drivers/spi/omap2_mcspi.c @@ -430,6 +430,7 @@ static void omap2_mcspi_txrx_pio(struct spi_device *spi, struct spi_transfer *xf dev_dbg(&spi->dev, "write-%d %04x\n", word_len, *tx); #endif + printk("write-%d %08x\n", word_len, *tx); __raw_writel(*tx++, tx_reg); } if (rx != NULL) { @@ -441,6 +442,7 @@ static void omap2_mcspi_txrx_pio(struct spi_device *spi, struct spi_transfer *xf dev_dbg(&spi->dev, "read-%d %04x\n", word_len, *(rx - 1)); #endif + printk("read-%d %08x\n", word_len, *(rx - 1)); } } } diff --git a/drivers/spi/tsc2101-core.c b/drivers/spi/tsc2101-core.c new file mode 100644 index 0000000..5b109f5 --- /dev/null +++ b/drivers/spi/tsc2101-core.c @@ -0,0 +1,246 @@ +/* + * TSC2101 driver + * + * Copyright (C) 2005, 2006 Nokia Corporation + * + * Modified for 32-bit SPI bus by Kyungmin Park + * + * Derived from TSC2301 written by Juha Yrjola + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> +#include <linux/spi/tsc2101.h> + +#ifdef CONFIG_ARCH_OMAP +#include <asm/arch/gpio.h> +#endif + +u16 tsc2101_read_reg(struct tsc2101 *tsc, int reg) +{ + struct spi_transfer t; + struct spi_message m; + u32 data = 0, cmd; + + cmd = reg; + cmd |= 0x8000; + cmd <<= 16; + + memset(&t, 0, sizeof(t)); + spi_message_init(&m); + m.spi = tsc->spi; + + t.tx_buf = &cmd; + t.rx_buf = &data; + t.len = 4; + spi_message_add_tail(&t, &m); + + spi_sync(m.spi, &m); + + return (data & 0xffff); +} + +void tsc2101_write_reg(struct tsc2101 *tsc, int reg, u16 val) +{ + struct spi_transfer t; + struct spi_message m; + u32 data; + + /* Now we prepare the command for transferring */ + data = (reg << 16) | val; + + spi_message_init(&m); + m.spi = tsc->spi; + + memset(&t, 0, sizeof(t)); + t.tx_buf = &data; + t.rx_buf = NULL; + t.len = 4; + spi_message_add_tail(&t, &m); + + spi_sync(m.spi, &m); +} + +void tsc2101_read_buf(struct tsc2101 *tsc, int reg, u16 *rx_buf, int len) +{ + /* Not yet implemented... */ +} + +void tsc2101_enable_mclk(struct device *dev) +{ + struct tsc2101 *tsc = dev_get_drvdata(dev); + + if (tsc->enable_clock == NULL) + return; + + tsc->enable_clock(dev); +} + +void tsc2101_disable_mclk(struct device *dev) +{ + struct tsc2101 *tsc = dev_get_drvdata(dev); + + might_sleep(); + if (tsc->disable_clock == NULL) + return; + + /* Submodules might need to take action before stopping the + * clock. Keypad scanning needs to be stopped / restarted for one. + */ + if (tsc->ts != NULL) + tsc2101_ts_prep_for_clk_stop(tsc); + if (tsc->mixer != NULL) + tsc2101_mixer_prep_for_clk_stop(tsc); + + tsc->disable_clock(dev); + + if (tsc->mixer != NULL) + tsc2101_mixer_cont_after_clk_stop(tsc); + if (tsc->ts != NULL) + tsc2101_ts_cont_after_clk_stop(tsc); +} + +static int __devinit tsc2101_probe(struct spi_device *spi) +{ + struct tsc2101 *tsc; + struct tsc2101_platform_data *pdata = spi->dev.platform_data; + int r; + + if (!pdata) { + dev_dbg(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + printk("TSC2101 driver initializing\n"); + + 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; + + tsc->enable_clock = pdata->enable_clock; + tsc->disable_clock = pdata->disable_clock; + + if (pdata->reset_gpio >= 0) { + tsc->reset_gpio = pdata->reset_gpio; +#ifdef CONFIG_ARCH_OMAP + r = omap_request_gpio(tsc->reset_gpio); + if (r < 0) + goto err1; + omap_set_gpio_dataout(tsc->reset_gpio, 1); + omap_set_gpio_direction(tsc->reset_gpio, 0); + mdelay(1); + omap_set_gpio_dataout(tsc->reset_gpio, 0); +#endif + } else + tsc->reset_gpio = -1; + + spi->mode = SPI_MODE_0; + spi->bits_per_word = 32; + /* The max speed might've been defined by the board-specific + * struct */ + if (!spi->max_speed_hz) + spi->max_speed_hz = TSC2101_HZ; + spi_setup(spi); + + r = tsc2101_ts_init(tsc, pdata); + if (r) + goto err1; + r = tsc2101_mixer_init(tsc, pdata); + if (r) + goto err2; + return 0; + +err2: + tsc2101_ts_exit(tsc); +err1: + kfree(tsc); + return r; +} + +static int __devexit tsc2101_remove(struct spi_device *spi) +{ + struct tsc2101 *tsc = dev_get_drvdata(&spi->dev); + + tsc2101_mixer_exit(tsc); + tsc2101_ts_exit(tsc); + kfree(tsc); + + return 0; +} + +#ifdef CONFIG_PM +static int tsc2101_suspend(struct spi_device *spi, pm_message_t mesg) +{ + struct tsc2101 *tsc = dev_get_drvdata(&spi->dev); + int r; + + if ((r = tsc2101_mixer_suspend(tsc)) < 0) + return r; + if ((r = tsc2101_ts_suspend(tsc)) < 0) + goto err1; + + return 0; +err1: + tsc2101_mixer_resume(tsc); + return r; +} + +static int tsc2101_resume(struct spi_device *spi) +{ + struct tsc2101 *tsc = dev_get_drvdata(&spi->dev); + + tsc2101_ts_resume(tsc); + tsc2101_mixer_resume(tsc); + return 0; +} +#endif + +static struct spi_driver tsc2101_driver = { + .driver = { + .name = "tsc2101", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, +#ifdef CONFIG_PM + .suspend = tsc2101_suspend, + .resume = tsc2101_resume, +#endif + .probe = tsc2101_probe, + .remove = __devexit_p(tsc2101_remove), +}; + +static int __init tsc2101_init(void) +{ + return spi_register_driver(&tsc2101_driver); +} +module_init(tsc2101_init); + +static void __exit tsc2101_exit(void) +{ + spi_unregister_driver(&tsc2101_driver); +} +module_exit(tsc2101_exit); + +MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/tsc2101-ts.c b/drivers/spi/tsc2101-ts.c new file mode 100644 index 0000000..bb798bc --- /dev/null +++ b/drivers/spi/tsc2101-ts.c @@ -0,0 +1,718 @@ +/* + * TSC2101 touchscreen driver + * + * Copyright (C) 2005-2006 Nokia Corporation + * + * Modified for 32-bit SPI by Kyungmin Park + * + * Derived from TSC2301 written by Jarkko Oikarinen, Imre Deak and Juha Yrjola + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> + +#ifdef CONFIG_ARCH_OMAP +#include <asm/arch/gpio.h> +#endif + +#include <linux/spi/tsc2101.h> + +/** + * The touchscreen interface operates as follows: + * + * Initialize: + * Request access to GPIO (DAV) + * tsc2101_dav_irq_handler will trigger when DAV line goes down + * + * 1) Pen is pressed against touchscreeen + * 2) TSC2101 performs AD conversion + * 3) After the conversion is done TSC2101 drives DAV line down + * 4) GPIO IRQ is received and tsc2101_dav_irq_handler is called + * 5) tsc2101_dav_irq_handler sets up tsc2101_ts_timer in TSC2101_TS_SCAN_TIME + * 6) tsc2101_ts_timer disables the irq and requests spi driver + * to read X, Y, Z1 and Z2 + * 7) SPI framework calls tsc2101_ts_rx after the coordinates are read + * 8) tsc2101_ts_rx reports coordinates to input layer and + * sets up tsc2101_ts_timer to be called after TSC2101_TS_SCAN_TIME + * 9) if tsc2101_tx_timer notices that the pen has been lifted, the lift event + * is sent, and irq is again enabled. + */ + + +#define TSC2101_TOUCHSCREEN_PRODUCT_ID 0x0052 +#define TSC2101_TOUCHSCREEN_PRODUCT_VERSION 0x0001 + +#define TSC2101_TS_SCAN_TIME 1 + +#define TSC2101_ADCREG_CONVERSION_CTRL_BY_TSC2101 0x8000 +#define TSC2101_ADCREG_CONVERSION_CTRL_BY_HOST 0x0000 + +#define TSC2101_ADCREG_FUNCTION_NONE 0x0000 +#define TSC2101_ADCREG_FUNCTION_XY 0x0400 +#define TSC2101_ADCREG_FUNCTION_XYZ 0x0800 +#define TSC2101_ADCREG_FUNCTION_X 0x0C00 +#define TSC2101_ADCREG_FUNCTION_Y 0x1000 +#define TSC2101_ADCREG_FUNCTION_Z 0x1400 +#define TSC2101_ADCREG_FUNCTION_DAT1 0x1800 +#define TSC2101_ADCREG_FUNCTION_DAT2 0x1C00 +#define TSC2101_ADCREG_FUNCTION_AUX1 0x2000 +#define TSC2101_ADCREG_FUNCTION_AUX2 0x2400 +#define TSC2101_ADCREG_FUNCTION_TEMP 0x2800 + +#define TSC2101_ADCREG_RESOLUTION_8BIT 0x0100 +#define TSC2101_ADCREG_RESOLUTION_10BIT 0x0200 +#define TSC2101_ADCREG_RESOLUTION_12BIT 0x0300 + +#define TSC2101_ADCREG_AVERAGING_NONE 0x0000 +#define TSC2101_ADCREG_AVERAGING_4AVG 0x0040 +#define TSC2101_ADCREG_AVERAGING_8AVG 0x0080 +#define TSC2101_ADCREG_AVERAGING_16AVG 0x00C0 + +#define TSC2101_ADCREG_CLOCK_8MHZ 0x0000 +#define TSC2101_ADCREG_CLOCK_4MHZ 0x0010 +#define TSC2101_ADCREG_CLOCK_2MHZ 0x0020 +#define TSC2101_ADCREG_CLOCK_1MHZ 0x0030 + +#define TSC2101_ADCREG_VOLTAGE_STAB_0US 0x0000 +#define TSC2101_ADCREG_VOLTAGE_STAB_100US 0x0002 +#define TSC2101_ADCREG_VOLTAGE_STAB_500US 0x0004 +#define TSC2101_ADCREG_VOLTAGE_STAB_1MS 0x0006 +#define TSC2101_ADCREG_VOLTAGE_STAB_5MS 0x0008 +#define TSC2101_ADCREG_VOLTAGE_STAB_10MS 0x000A +#define TSC2101_ADCREG_VOLTAGE_STAB_50MS 0x000C +#define TSC2101_ADCREG_VOLTAGE_STAB_100MS 0x000E + +#define TSC2101_ADCREG_STOP_CONVERSION 0x4000 + +#define TSC2101_STATUSREG_DAV 0x4000 +#define TSC2101_PROGREG_DELAY 0x0900 + +#define MAX_12BIT ((1 << 12) - 1) + +struct tsc2101_ts { + struct input_dev *idev; + char phys[32]; + struct timer_list timer; + spinlock_t lock; + + struct spi_transfer read_xfer[2]; + struct spi_message read_msg; +#ifdef CONFIG_SPI_TSC2101 + u32 data[4]; +#else + u16 data[4]; +#endif + + int hw_avg_max; + u16 x; + u16 y; + u16 p; + int sample_cnt; + + int ignore_last : 1; + u16 x_plate_ohm; + int stab_time; + int max_pressure; + int touch_pressure; + int pressure_limit; + + u16 irq_enabled:1; + u16 pen_down:1; + u16 disabled:1; + u16 pending:1; + + int hw_flags; + + s16 dav_gpio; + int irq; +}; + +static int tsc2101_ts_check_config(struct tsc2101_ts *ts, int *hw_flags) +{ + int flags; + + flags = 0; + switch (ts->hw_avg_max) { + case 0: + flags |= TSC2101_ADCREG_AVERAGING_NONE; + break; + case 4: + flags |= TSC2101_ADCREG_AVERAGING_4AVG; + break; + case 8: + flags |= TSC2101_ADCREG_AVERAGING_8AVG; + break; + case 16: + flags |= TSC2101_ADCREG_AVERAGING_16AVG; + break; + default: + return -EINVAL; + } + + switch (ts->stab_time) { + case 0: + flags |= TSC2101_ADCREG_VOLTAGE_STAB_0US; + break; + case 100: + flags |= TSC2101_ADCREG_VOLTAGE_STAB_100US; + break; + case 500: + flags |= TSC2101_ADCREG_VOLTAGE_STAB_500US; + break; + case 1000: + flags |= TSC2101_ADCREG_VOLTAGE_STAB_1MS; + break; + case 5000: + flags |= TSC2101_ADCREG_VOLTAGE_STAB_5MS; + break; + case 10000: + flags |= TSC2101_ADCREG_VOLTAGE_STAB_10MS; + break; + case 50000: + flags |= TSC2101_ADCREG_VOLTAGE_STAB_50MS; + break; + case 100000: + flags |= TSC2101_ADCREG_VOLTAGE_STAB_100MS; + break; + default: + return -EINVAL; + } + + *hw_flags = flags; + return 0; +} + +/* XXX: tsc2101 specific */ +static int tsc2101_ts_configure(struct tsc2101 *tsc, int flags) +{ + struct spi_transfer xfer[6]; + struct spi_transfer *x; + struct spi_message m; + int reg; + u32 val, adc_val; + u32 data[6]; + + /* + * TSC2101-controlled conversions + * 12-bit samples + * continuous X,Y,Z1,Z2 scan mode + * average (mean) 16 samples per coordinate + * 1 MHz internal conversion clock + * 500 usec panel voltage stabilization delay + * => 0x8bf4 + */ + /* Averaging and voltage stabilization settings in flags */ + adc_val = TSC2101_ADCREG_CONVERSION_CTRL_BY_TSC2101 | + TSC2101_ADCREG_FUNCTION_XYZ | + TSC2101_ADCREG_RESOLUTION_12BIT | + TSC2101_ADCREG_CLOCK_1MHZ | + flags; + printk("adc val = 0x%08x\n", adc_val); + + /* Now we prepare the command for transferring */ + /* Use internal reference clock */ + reg = TSC2101_REG_REF; + val = 0x0016; + data[0] = (reg << 16) | val; + + reg = TSC2101_REG_CONFIG; + val = 0x0008; + data[1] = (reg << 16) | val; + + reg = TSC2101_REG_BUFFER; + val = 0x0; + data[2] = (reg << 16) | val; + + reg = TSC2101_REG_PROG_DELAY; + val = 0x0900; + data[3] = (reg << 16) | val; + + reg = TSC2101_REG_STATUS; + val = TSC2101_STATUSREG_DAV; + data[4] = (reg << 16) | val; + + reg = TSC2101_REG_ADC; + data[5] = (reg << 16) | adc_val; + + spi_message_init(&m); + m.spi = tsc->spi; + + memset(xfer, 0, sizeof(xfer)); + x = &xfer[0]; + + x->tx_buf = &data[0]; + x->len = 4; + x->cs_change = 1; + spi_message_add_tail(x, &m); + + x++; + x->tx_buf = &data[1]; + x->len = 4; + x->cs_change = 1; + spi_message_add_tail(x, &m); + + x++; + x->tx_buf = &data[2]; + x->len = 4; + spi_message_add_tail(x, &m); + + x++; + x->tx_buf = &data[3]; + x->len = 4; + spi_message_add_tail(x, &m); + + x++; + x->tx_buf = &data[4]; + x->len = 4; + spi_message_add_tail(x, &m); + + x++; + x->tx_buf = &data[5]; + x->len = 4; + spi_message_add_tail(x, &m); + + spi_sync(m.spi, &m); + + return 0; +} + +static void tsc2101_ts_start_scan(struct tsc2101 *tsc) +{ + tsc2101_ts_configure(tsc, tsc->ts->hw_flags); +} + +static void tsc2101_ts_stop_scan(struct tsc2101 *tsc) +{ + tsc2101_ts_configure(tsc, TSC2101_ADCREG_STOP_CONVERSION); +} + +static int device_suspended(struct device *dev) +{ + struct tsc2101 *tsc = dev_get_drvdata(dev); + return dev->power.power_state.event != PM_EVENT_ON || tsc->ts- >disabled; +} + +static void update_pen_state(struct tsc2101_ts *ts, int x, int y, int pressure) +{ + 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 +} + +/* XXX: TSC2101 specific */ +/* + * This procedure is called by the SPI framework after the coordinates + * have been read from TSC2101 + */ +static void tsc2101_ts_rx(void *arg) +{ + struct tsc2101 *tsc = arg; + struct tsc2101_ts *ts = tsc->ts; + unsigned int x, y, z1, z2, pressure; + + x = ts->data[0] & 0xffff; + y = ts->data[1] & 0xffff; + z1 = ts->data[2] & 0xffff; + z2 = ts->data[3] & 0xffff; + + 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(ts, ts->x, ts->y, ts->p); + ts->x = x; + ts->y = y; + ts->p = pressure; + } else + update_pen_state(ts, x, y, pressure); + ts->sample_cnt++; + } + + mod_timer(&ts->timer, + jiffies + msecs_to_jiffies(TSC2101_TS_SCAN_TIME)); +} + +static int is_pen_down(struct tsc2101_ts *ts) +{ + return ts->pen_down; +} + +/* + * Timer is called every TSC2101_TS_SCAN_TIME when the pen is down + */ +static void tsc2101_ts_timer(unsigned long arg) +{ + struct tsc2101 *tsc = (void *) arg; + struct tsc2101_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 = 1; + enable_irq(ts->irq); + } + update_pen_state(ts, 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 tsc2101_ts_irq_handler(int irq, void *dev_id) +{ + struct tsc2101 *tsc = dev_id; + struct tsc2101_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(TSC2101_TS_SCAN_TIME)); + } + spin_unlock_irqrestore(&ts->lock, flags); + + return IRQ_HANDLED; +} + +/* Must be called with ts->lock held */ +static void tsc2101_ts_disable(struct tsc2101 *tsc) +{ + struct tsc2101_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); + } + } + + spin_unlock_irq(&ts->lock); + tsc2101_ts_stop_scan(tsc); + spin_lock_irq(&ts->lock); +} + +static void tsc2101_ts_enable(struct tsc2101 *tsc) +{ + struct tsc2101_ts *ts = tsc->ts; + + if (!ts->disabled) + return; + + ts->disabled = 0; + ts->irq_enabled = 1; + enable_irq(ts->irq); + + spin_unlock_irq(&ts->lock); + tsc2101_ts_start_scan(tsc); + spin_lock_irq(&ts->lock); +} + +#ifdef CONFIG_PM +int tsc2101_ts_suspend(struct tsc2101 *tsc) +{ + struct tsc2101_ts *ts = tsc->ts; + + spin_lock_irq(&ts->lock); + tsc2101_ts_disable(tsc); + spin_unlock_irq(&ts->lock); + + return 0; +} + +void tsc2101_ts_resume(struct tsc2101 *tsc) +{ + struct tsc2101_ts *ts = tsc->ts; + + spin_lock_irq(&ts->lock); + tsc2101_ts_enable(tsc); + spin_unlock_irq(&ts->lock); +} +#endif + +void tsc2101_ts_prep_for_clk_stop(struct tsc2101 *tsc) +{ +} + +void tsc2101_ts_cont_after_clk_stop(struct tsc2101 *tsc) +{ +} + +/* XXX: tsc2101 specific */ +static const u32 tsc2101_ts_read_data[4] = { + (0x8000 | TSC2101_REG_X) << 16, + (0x8000 | TSC2101_REG_Y) << 16, + (0x8000 | TSC2101_REG_Z1) << 16, + (0x8000 | TSC2101_REG_Z2) << 16, +}; + +static void tsc2101_ts_setup_spi_xfer(struct tsc2101 *tsc) +{ + struct tsc2101_ts *ts = tsc->ts; + struct spi_message *m = &ts->read_msg; + struct spi_transfer *x = &ts->read_xfer[0]; + + spi_message_init(m); + + x->tx_buf = &tsc2101_ts_read_data; + x->rx_buf = &ts->data; + x->len = 16; + spi_message_add_tail(x, m); + + m->complete = tsc2101_ts_rx; + m->context = tsc; +} + +static ssize_t tsc2101_ts_pen_down_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tsc2101 *tsc = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", is_pen_down(tsc->ts)); +} + +static DEVICE_ATTR(pen_down, S_IRUGO, tsc2101_ts_pen_down_show, NULL); + +static ssize_t tsc2101_ts_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsc2101 *tsc = dev_get_drvdata(dev); + struct tsc2101_ts *ts = tsc->ts; + + return sprintf(buf, "%u\n", ts->disabled); +} + +static ssize_t tsc2101_ts_disable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tsc2101 *tsc = dev_get_drvdata(dev); + struct tsc2101_ts *ts = tsc->ts; + char *endp; + int i; + + i = simple_strtoul(buf, &endp, 10); + spin_lock_irq(&ts->lock); + + if (i) + tsc2101_ts_disable(tsc); + else + tsc2101_ts_enable(tsc); + + spin_unlock_irq(&ts->lock); + + return count; +} + +static DEVICE_ATTR(disable_ts, 0664, tsc2101_ts_disable_show, + tsc2101_ts_disable_store); + +int __devinit tsc2101_ts_init(struct tsc2101 *tsc, + struct tsc2101_platform_data *pdata) +{ + struct tsc2101_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); + ts->irq = OMAP_GPIO_IRQ(dav_gpio); +#endif + init_timer(&ts->timer); + ts->timer.data = (unsigned long) tsc; + ts->timer.function = tsc2101_ts_timer; + + spin_lock_init(&ts->lock); + + ts->x_plate_ohm = pdata->ts_x_plate_ohm ? : 280; + ts->hw_avg_max = pdata->ts_hw_avg; + 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; + ts->stab_time = pdata->ts_stab_time; + + if ((r = tsc2101_ts_check_config(ts, &ts->hw_flags))) { + dev_err(&tsc->spi->dev, "invalid configuration\n"); + goto err2; + } + + idev = input_allocate_device(); + if (idev == NULL) { + r = -ENOMEM; + goto err2; + } + idev->cdev.dev = &tsc->spi->dev; + idev->name = "TSC2101 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; + + tsc2101_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); + + tsc2101_ts_start_scan(tsc); + + ts->irq_enabled = 1; + r = request_irq(ts->irq, tsc2101_ts_irq_handler, + SA_SAMPLE_RANDOM | SA_TRIGGER_FALLING, + "tsc2101-ts", tsc); + if (r < 0) { + dev_err(&tsc->spi->dev, "unable to get DAV IRQ"); + goto err3; + } + set_irq_wake(ts->irq, 1); + + r = device_create_file(&tsc->spi->dev, &dev_attr_pen_down); + r = device_create_file(&tsc->spi->dev, &dev_attr_disable_ts); + + r = input_register_device(idev); + if (r < 0) { + dev_err(&tsc->spi->dev, "can't register touchscreen device\n"); + goto err4; + } + + return 0; +err4: + 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); +err3: + tsc2101_ts_stop_scan(tsc); + input_free_device(idev); +err2: +#ifdef CONFIG_ARCH_OMAP + omap_free_gpio(dav_gpio); +#endif +err1: + kfree(ts); + return r; +} + +void __devexit tsc2101_ts_exit(struct tsc2101 *tsc) +{ + struct tsc2101_ts *ts = tsc->ts; + unsigned long flags; + + spin_lock_irqsave(&ts->lock, flags); + tsc2101_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); +} + +MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/tsc2301-core.c b/drivers/spi/tsc2301-core.c new file mode 100644 index 0000000..cbad286 --- /dev/null +++ b/drivers/spi/tsc2301-core.c @@ -0,0 +1,336 @@ +/* + * TSC2301 driver + * + * Copyright (C) 2005, 2006 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> +#include <linux/spi/tsc2301.h> + +#ifdef CONFIG_ARCH_OMAP +#include <asm/arch/gpio.h> +#endif + +u16 tsc2301_read_reg(struct tsc2301 *tsc, int reg) +{ + struct spi_transfer t[2]; + struct spi_message m; + u16 data = 0, cmd; + + cmd = reg; + cmd |= 0x8000; + + memset(t, 0, sizeof(t)); + spi_message_init(&m); + m.spi = tsc->spi; + + t[0].tx_buf = &cmd; + t[0].rx_buf = NULL; + t[0].len = 2; + spi_message_add_tail(&t[0], &m); + + t[1].tx_buf = NULL; + t[1].rx_buf = &data; + t[1].len = 2; + spi_message_add_tail(&t[1], &m); + + spi_sync(m.spi, &m); + + return data; +} + +void tsc2301_write_reg(struct tsc2301 *tsc, int reg, u16 val) +{ + struct spi_transfer t; + struct spi_message m; + u16 data[2]; + + /* Now we prepare the command for transferring */ + data[0] = reg; + data[1] = val; + + spi_message_init(&m); + m.spi = tsc->spi; + + memset(&t, 0, sizeof(t)); + t.tx_buf = data; + t.rx_buf = NULL; + t.len = 4; + spi_message_add_tail(&t, &m); + + spi_sync(m.spi, &m); +} + +void tsc2301_write_kbc(struct tsc2301 *tsc, int val) +{ + u16 w; + + w = tsc->config2_shadow; + w &= ~(0x03 << 14); + w |= (val & 0x03) << 14; + tsc2301_write_reg(tsc, TSC2301_REG_CONFIG2, w); + tsc->config2_shadow = w; +} + +void tsc2301_write_pll(struct tsc2301 *tsc, + int pll_n, int pll_a, int pll_pdc, int pct_e, int pll_o) +{ + u16 w; + + w = tsc->config2_shadow; + w &= ~0x3fff; + w |= (pll_n & 0x0f) | ((pll_a & 0x0f) << 4) | ((pll_pdc & 0x0f) << 8); + w |= pct_e ? (1 << 12) : 0; + w |= pll_o ? (1 << 13) : 0; + tsc2301_write_reg(tsc, TSC2301_REG_CONFIG2, w); + tsc->config2_shadow = w; +} + +void tsc2301_read_buf(struct tsc2301 *tsc, int reg, u16 *rx_buf, int len) +{ + struct spi_transfer t[2]; + struct spi_message m; + u16 cmd, i; + + cmd = reg; + cmd |= 0x8000; + + spi_message_init(&m); + m.spi = tsc->spi; + + memset(t, 0, sizeof(t)); + t[0].tx_buf = &cmd; + t[0].rx_buf = NULL; + t[0].len = 2; + spi_message_add_tail(&t[0], &m); + + t[1].tx_buf = NULL; + t[1].rx_buf = rx_buf; + t[1].len = 2 * len; + spi_message_add_tail(&t[1], &m); + + spi_sync(m.spi, &m); + + for (i = 0; i < len; i++) + printk(KERN_DEBUG "rx_buf[%d]: %04x\n", i, rx_buf[i]); +} + +void tsc2301_enable_mclk(struct device *dev) +{ + struct tsc2301 *tsc = dev_get_drvdata(dev); + + if (tsc->enable_clock == NULL) + return; + + tsc->enable_clock(dev); +} + +void tsc2301_disable_mclk(struct device *dev) +{ + struct tsc2301 *tsc = dev_get_drvdata(dev); + + might_sleep(); + if (tsc->disable_clock == NULL) + return; + + /* Submodules might need to take action before stopping the + * clock. Keypad scanning needs to be stopped / restarted for one. + */ + if (tsc->kp != NULL) + tsc2301_kp_prep_for_clk_stop(tsc); + if (tsc->ts != NULL) + tsc2301_ts_prep_for_clk_stop(tsc); + if (tsc->mixer != NULL) + tsc2301_mixer_prep_for_clk_stop(tsc); + + tsc->disable_clock(dev); + + if (tsc->mixer != NULL) + tsc2301_mixer_cont_after_clk_stop(tsc); + if (tsc->ts != NULL) + tsc2301_ts_cont_after_clk_stop(tsc); + if (tsc->kp != NULL) + tsc2301_kp_cont_after_clk_stop(tsc); +} + +static int __devinit tsc2301_probe(struct spi_device *spi) +{ + struct tsc2301 *tsc; + struct tsc2301_platform_data *pdata = spi->dev.platform_data; + int r; + u16 w; + + if (!pdata) { + dev_dbg(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + printk("TSC2301 driver initializing\n"); + + 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; + + tsc->enable_clock = pdata->enable_clock; + tsc->disable_clock = pdata->disable_clock; + + if (pdata->reset_gpio >= 0) { + tsc->reset_gpio = pdata->reset_gpio; +#ifdef CONFIG_ARCH_OMAP + r = omap_request_gpio(tsc->reset_gpio); + if (r < 0) + goto err1; + omap_set_gpio_dataout(tsc->reset_gpio, 1); + omap_set_gpio_direction(tsc->reset_gpio, 0); + mdelay(1); + omap_set_gpio_dataout(tsc->reset_gpio, 0); +#endif + } else + tsc->reset_gpio = -1; + + spi->mode = SPI_MODE_0; + 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 = TSC2301_HZ; + spi_setup(spi); + + w = tsc2301_read_reg(tsc, TSC2301_REG_ADC); + if (!(w & (1 << 14))) { + dev_err(&spi->dev, "invalid ADC reg value: %04x\n", w); + r = -ENODEV; + goto err1; + } + + w = tsc2301_read_reg(tsc, TSC2301_REG_DAC); + if (!(w & (1 << 15))) { + dev_err(&spi->dev, "invalid DAC reg value: %04x\n", w); + r = -ENODEV; + goto err1; + } + + /* Stop keypad scanning */ + tsc2301_write_reg(tsc, TSC2301_REG_KEY, 0x4000); + + /* We have to cache this for read-modify-write, since we can't + * read back BIT15 */ + w = tsc2301_read_reg(tsc, TSC2301_REG_CONFIG2); + /* By default BIT15 is set */ + w |= 1 << 15; + tsc->config2_shadow = w; + + r = tsc2301_kp_init(tsc, pdata); + if (r) + goto err1; + r = tsc2301_ts_init(tsc, pdata); + if (r) + goto err2; + r = tsc2301_mixer_init(tsc, pdata); + if (r) + goto err3; + return 0; + +err3: + tsc2301_ts_exit(tsc); +err2: + tsc2301_kp_exit(tsc); +err1: + kfree(tsc); + return r; +} + +static int __devexit tsc2301_remove(struct spi_device *spi) +{ + struct tsc2301 *tsc = dev_get_drvdata(&spi->dev); + + tsc2301_mixer_exit(tsc); + tsc2301_ts_exit(tsc); + tsc2301_kp_exit(tsc); + kfree(tsc); + + return 0; +} + +#ifdef CONFIG_PM +static int tsc2301_suspend(struct spi_device *spi, pm_message_t mesg) +{ + struct tsc2301 *tsc = dev_get_drvdata(&spi->dev); + int r; + + if ((r = tsc2301_mixer_suspend(tsc)) < 0) + return r; + if ((r = tsc2301_kp_suspend(tsc)) < 0) + goto err1; + if ((r = tsc2301_ts_suspend(tsc)) < 0) + goto err2; + + return 0; +err2: + tsc2301_kp_resume(tsc); +err1: + tsc2301_mixer_resume(tsc); + return r; +} + +static int tsc2301_resume(struct spi_device *spi) +{ + struct tsc2301 *tsc = dev_get_drvdata(&spi->dev); + + tsc2301_ts_resume(tsc); + tsc2301_kp_resume(tsc); + tsc2301_mixer_resume(tsc); + return 0; +} +#endif + +static struct spi_driver tsc2301_driver = { + .driver = { + .name = "tsc2301", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, +#ifdef CONFIG_PM + .suspend = tsc2301_suspend, + .resume = tsc2301_resume, +#endif + .probe = tsc2301_probe, + .remove = __devexit_p(tsc2301_remove), +}; + +static int __init tsc2301_init(void) +{ + return spi_register_driver(&tsc2301_driver); +} +module_init(tsc2301_init); + +static void __exit tsc2301_exit(void) +{ + spi_unregister_driver(&tsc2301_driver); +} +module_exit(tsc2301_exit); + +MODULE_AUTHOR("Juha Yrj l <juha.yrjola@nokia.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/tsc2301-ts.c b/drivers/spi/tsc2301-ts.c new file mode 100644 index 0000000..f1a74d7 --- /dev/null +++ b/drivers/spi/tsc2301-ts.c @@ -0,0 +1,682 @@ +/* + * TSC2301 touchscreen driver + * + * Copyright (C) 2005-2006 Nokia Corporation + * + * Written by Jarkko Oikarinen, Imre Deak and Juha Yrjola + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> + +#ifdef CONFIG_ARCH_OMAP +#include <asm/arch/gpio.h> +#endif + +#include <linux/spi/tsc2301.h> + +/** + * The touchscreen interface operates as follows: + * + * Initialize: + * Request access to GPIO103 (DAV) + * tsc2301_dav_irq_handler will trigger when DAV line goes down + * + * 1) Pen is pressed against touchscreeen + * 2) TSC2301 performs AD conversion + * 3) After the conversion is done TSC2301 drives DAV line down + * 4) GPIO IRQ is received and tsc2301_dav_irq_handler is called + * 5) tsc2301_dav_irq_handler sets up tsc2301_ts_timer in TSC2301_TS_SCAN_TIME + * 6) tsc2301_ts_timer disables the irq and requests spi driver + * to read X, Y, Z1 and Z2 + * 7) SPI framework calls tsc2301_ts_rx after the coordinates are read + * 8) tsc2301_ts_rx reports coordinates to input layer and + * sets up tsc2301_ts_timer to be called after TSC2301_TS_SCAN_TIME + * 9) if tsc2301_tx_timer notices that the pen has been lifted, the lift event + * is sent, and irq is again enabled. + */ + + +#define TSC2301_TOUCHSCREEN_PRODUCT_ID 0x0052 +#define TSC2301_TOUCHSCREEN_PRODUCT_VERSION 0x0001 + +#define TSC2301_TS_SCAN_TIME 1 + +#define TSC2301_ADCREG_CONVERSION_CTRL_BY_TSC2301 0x8000 +#define TSC2301_ADCREG_CONVERSION_CTRL_BY_HOST 0x0000 + +#define TSC2301_ADCREG_FUNCTION_NONE 0x0000 +#define TSC2301_ADCREG_FUNCTION_XY 0x0400 +#define TSC2301_ADCREG_FUNCTION_XYZ 0x0800 +#define TSC2301_ADCREG_FUNCTION_X 0x0C00 +#define TSC2301_ADCREG_FUNCTION_Y 0x1000 +#define TSC2301_ADCREG_FUNCTION_Z 0x1400 +#define TSC2301_ADCREG_FUNCTION_DAT1 0x1800 +#define TSC2301_ADCREG_FUNCTION_DAT2 0x1C00 +#define TSC2301_ADCREG_FUNCTION_AUX1 0x2000 +#define TSC2301_ADCREG_FUNCTION_AUX2 0x2400 +#define TSC2301_ADCREG_FUNCTION_TEMP 0x2800 + +#define TSC2301_ADCREG_RESOLUTION_8BIT 0x0100 +#define TSC2301_ADCREG_RESOLUTION_10BIT 0x0200 +#define TSC2301_ADCREG_RESOLUTION_12BIT 0x0300 + +#define TSC2301_ADCREG_AVERAGING_NONE 0x0000 +#define TSC2301_ADCREG_AVERAGING_4AVG 0x0040 +#define TSC2301_ADCREG_AVERAGING_8AVG 0x0080 +#define TSC2301_ADCREG_AVERAGING_16AVG 0x00C0 + +#define TSC2301_ADCREG_CLOCK_8MHZ 0x0000 +#define TSC2301_ADCREG_CLOCK_4MHZ 0x0010 +#define TSC2301_ADCREG_CLOCK_2MHZ 0x0020 +#define TSC2301_ADCREG_CLOCK_1MHZ 0x0030 + +#define TSC2301_ADCREG_VOLTAGE_STAB_0US 0x0000 +#define TSC2301_ADCREG_VOLTAGE_STAB_100US 0x0002 +#define TSC2301_ADCREG_VOLTAGE_STAB_500US 0x0004 +#define TSC2301_ADCREG_VOLTAGE_STAB_1MS 0x0006 +#define TSC2301_ADCREG_VOLTAGE_STAB_5MS 0x0008 +#define TSC2301_ADCREG_VOLTAGE_STAB_10MS 0x000A +#define TSC2301_ADCREG_VOLTAGE_STAB_50MS 0x000C +#define TSC2301_ADCREG_VOLTAGE_STAB_100MS 0x000E + +#define TSC2301_ADCREG_STOP_CONVERSION 0x4000 + +#define MAX_12BIT ((1 << 12) - 1) + +struct tsc2301_ts { + struct input_dev *idev; + char phys[32]; + struct timer_list timer; + spinlock_t lock; + + struct spi_transfer read_xfer[2]; + struct spi_message read_msg; + u16 data[4]; + + int hw_avg_max; + u16 x; + u16 y; + u16 p; + int sample_cnt; + + int ignore_last : 1; + u16 x_plate_ohm; + int stab_time; + int max_pressure; + int touch_pressure; + int pressure_limit; + + u16 irq_enabled:1; + u16 pen_down:1; + u16 disabled:1; + u16 pending:1; + + int hw_flags; + + s16 dav_gpio; + int irq; +}; + + +static const u16 tsc2301_ts_read_data = 0x8000 | TSC2301_REG_X; + +static int tsc2301_ts_check_config(struct tsc2301_ts *ts, int *hw_flags) +{ + int flags; + + flags = 0; + switch (ts->hw_avg_max) { + case 0: + flags |= TSC2301_ADCREG_AVERAGING_NONE; + break; + case 4: + flags |= TSC2301_ADCREG_AVERAGING_4AVG; + break; + case 8: + flags |= TSC2301_ADCREG_AVERAGING_8AVG; + break; + case 16: + flags |= TSC2301_ADCREG_AVERAGING_16AVG; + break; + default: + return -EINVAL; + } + + switch (ts->stab_time) { + case 0: + flags |= TSC2301_ADCREG_VOLTAGE_STAB_0US; + break; + case 100: + flags |= TSC2301_ADCREG_VOLTAGE_STAB_100US; + break; + case 500: + flags |= TSC2301_ADCREG_VOLTAGE_STAB_500US; + break; + case 1000: + flags |= TSC2301_ADCREG_VOLTAGE_STAB_1MS; + break; + case 5000: + flags |= TSC2301_ADCREG_VOLTAGE_STAB_5MS; + break; + case 10000: + flags |= TSC2301_ADCREG_VOLTAGE_STAB_10MS; + break; + case 50000: + flags |= TSC2301_ADCREG_VOLTAGE_STAB_50MS; + break; + case 100000: + flags |= TSC2301_ADCREG_VOLTAGE_STAB_100MS; + break; + default: + return -EINVAL; + } + + *hw_flags = flags; + return 0; +} + +/* + * This odd three-time initialization is to work around a bug in TSC2301. + * See TSC2301 errata for details. + */ +static int tsc2301_ts_configure(struct tsc2301 *tsc, int flags) +{ + struct spi_transfer xfer[3]; + struct spi_transfer *x; + struct spi_message m; + int reg = TSC2301_REG_ADC; + u16 val1, val2, val3; + u16 data[6]; + + val1 = TSC2301_ADCREG_CONVERSION_CTRL_BY_HOST | + TSC2301_ADCREG_STOP_CONVERSION | + TSC2301_ADCREG_FUNCTION_NONE | + TSC2301_ADCREG_RESOLUTION_12BIT | + TSC2301_ADCREG_AVERAGING_NONE | + TSC2301_ADCREG_CLOCK_2MHZ | + TSC2301_ADCREG_VOLTAGE_STAB_100MS; + + val2 = TSC2301_ADCREG_CONVERSION_CTRL_BY_HOST | + TSC2301_ADCREG_FUNCTION_XYZ | + TSC2301_ADCREG_RESOLUTION_12BIT | + TSC2301_ADCREG_AVERAGING_16AVG | + TSC2301_ADCREG_CLOCK_1MHZ | + TSC2301_ADCREG_VOLTAGE_STAB_100MS; + + /* Averaging and voltage stabilization settings in flags */ + val3 = TSC2301_ADCREG_CONVERSION_CTRL_BY_TSC2301 | + TSC2301_ADCREG_FUNCTION_XYZ | + TSC2301_ADCREG_RESOLUTION_12BIT | + TSC2301_ADCREG_CLOCK_1MHZ | + flags; + + /* Now we prepare the command for transferring */ + data[0] = reg; + data[1] = val1; + data[2] = reg; + data[3] = val2; + data[4] = reg; + data[5] = val3; + + spi_message_init(&m); + m.spi = tsc->spi; + + memset(xfer, 0, sizeof(xfer)); + x = &xfer[0]; + + x->tx_buf = &data[0]; + x->len = 4; + x->cs_change = 1; + spi_message_add_tail(x, &m); + + x++; + x->tx_buf = &data[2]; + x->len = 4; + x->cs_change = 1; + spi_message_add_tail(x, &m); + + x++; + x->tx_buf = &data[4]; + x->len = 4; + spi_message_add_tail(x, &m); + + spi_sync(m.spi, &m); + + return 0; +} + +static void tsc2301_ts_start_scan(struct tsc2301 *tsc) +{ + tsc2301_ts_configure(tsc, tsc->ts->hw_flags); +} + +static void tsc2301_ts_stop_scan(struct tsc2301 *tsc) +{ + tsc2301_ts_configure(tsc, TSC2301_ADCREG_STOP_CONVERSION); +} + +static int device_suspended(struct device *dev) +{ + struct tsc2301 *tsc = dev_get_drvdata(dev); + return dev->power.power_state.event != PM_EVENT_ON || tsc->ts- >disabled; +} + +static void update_pen_state(struct tsc2301_ts *ts, int x, int y, int pressure) +{ + 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 +} + +/* + * This procedure is called by the SPI framework after the coordinates + * have been read from TSC2301 + */ +static void tsc2301_ts_rx(void *arg) +{ + struct tsc2301 *tsc = arg; + struct tsc2301_ts *ts = tsc->ts; + unsigned int x, y, z1, z2, pressure; + + x = ts->data[0]; + y = ts->data[1]; + z1 = ts->data[2]; + z2 = ts->data[3]; + + 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(ts, ts->x, ts->y, ts->p); + ts->x = x; + ts->y = y; + ts->p = pressure; + } else + update_pen_state(ts, x, y, pressure); + ts->sample_cnt++; + } + + mod_timer(&ts->timer, + jiffies + msecs_to_jiffies(TSC2301_TS_SCAN_TIME)); +} + +static int is_pen_down(struct tsc2301_ts *ts) +{ + return ts->pen_down; +} + +/* + * Timer is called every TSC2301_TS_SCAN_TIME when the pen is down + */ +static void tsc2301_ts_timer(unsigned long arg) +{ + struct tsc2301 *tsc = (void *) arg; + struct tsc2301_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 = 1; + enable_irq(ts->irq); + } + update_pen_state(ts, 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 tsc2301_ts_irq_handler(int irq, void *dev_id) +{ + struct tsc2301 *tsc = dev_id; + struct tsc2301_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(TSC2301_TS_SCAN_TIME)); + } + spin_unlock_irqrestore(&ts->lock, flags); + + return IRQ_HANDLED; +} + +/* Must be called with ts->lock held */ +static void tsc2301_ts_disable(struct tsc2301 *tsc) +{ + struct tsc2301_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); + } + } + + spin_unlock_irq(&ts->lock); + tsc2301_ts_stop_scan(tsc); + spin_lock_irq(&ts->lock); +} + +static void tsc2301_ts_enable(struct tsc2301 *tsc) +{ + struct tsc2301_ts *ts = tsc->ts; + + if (!ts->disabled) + return; + + ts->disabled = 0; + ts->irq_enabled = 1; + enable_irq(ts->irq); + + spin_unlock_irq(&ts->lock); + tsc2301_ts_start_scan(tsc); + spin_lock_irq(&ts->lock); +} + +#ifdef CONFIG_PM +int tsc2301_ts_suspend(struct tsc2301 *tsc) +{ + struct tsc2301_ts *ts = tsc->ts; + + spin_lock_irq(&ts->lock); + tsc2301_ts_disable(tsc); + spin_unlock_irq(&ts->lock); + + return 0; +} + +void tsc2301_ts_resume(struct tsc2301 *tsc) +{ + struct tsc2301_ts *ts = tsc->ts; + + spin_lock_irq(&ts->lock); + tsc2301_ts_enable(tsc); + spin_unlock_irq(&ts->lock); +} +#endif + +void tsc2301_ts_prep_for_clk_stop(struct tsc2301 *tsc) +{ +} + +void tsc2301_ts_cont_after_clk_stop(struct tsc2301 *tsc) +{ +} + +static void tsc2301_ts_setup_spi_xfer(struct tsc2301 *tsc) +{ + struct tsc2301_ts *ts = tsc->ts; + struct spi_message *m = &ts->read_msg; + struct spi_transfer *x = &ts->read_xfer[0]; + + spi_message_init(m); + + x->tx_buf = &tsc2301_ts_read_data; + x->len = 2; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &ts->data; + x->len = 8; + spi_message_add_tail(x, m); + + m->complete = tsc2301_ts_rx; + m->context = tsc; +} + +static ssize_t tsc2301_ts_pen_down_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tsc2301 *tsc = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", is_pen_down(tsc->ts)); +} + +static DEVICE_ATTR(pen_down, S_IRUGO, tsc2301_ts_pen_down_show, NULL); + +static ssize_t tsc2301_ts_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsc2301 *tsc = dev_get_drvdata(dev); + struct tsc2301_ts *ts = tsc->ts; + + return sprintf(buf, "%u\n", ts->disabled); +} + +static ssize_t tsc2301_ts_disable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tsc2301 *tsc = dev_get_drvdata(dev); + struct tsc2301_ts *ts = tsc->ts; + char *endp; + int i; + + i = simple_strtoul(buf, &endp, 10); + spin_lock_irq(&ts->lock); + + if (i) + tsc2301_ts_disable(tsc); + else + tsc2301_ts_enable(tsc); + + spin_unlock_irq(&ts->lock); + + return count; +} + +static DEVICE_ATTR(disable_ts, 0664, tsc2301_ts_disable_show, + tsc2301_ts_disable_store); + +int __devinit tsc2301_ts_init(struct tsc2301 *tsc, + struct tsc2301_platform_data *pdata) +{ + struct tsc2301_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); + ts->irq = OMAP_GPIO_IRQ(dav_gpio); +#endif + init_timer(&ts->timer); + ts->timer.data = (unsigned long) tsc; + ts->timer.function = tsc2301_ts_timer; + + spin_lock_init(&ts->lock); + + ts->x_plate_ohm = pdata->ts_x_plate_ohm ? : 280; + ts->hw_avg_max = pdata->ts_hw_avg; + 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; + ts->stab_time = pdata->ts_stab_time; + + if ((r = tsc2301_ts_check_config(ts, &ts->hw_flags))) { + dev_err(&tsc->spi->dev, "invalid configuration\n"); + goto err2; + } + + idev = input_allocate_device(); + if (idev == NULL) { + r = -ENOMEM; + goto err2; + } + idev->cdev.dev = &tsc->spi->dev; + idev->name = "TSC2301 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; + + tsc2301_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); + + tsc2301_ts_start_scan(tsc); + + ts->irq_enabled = 1; + r = request_irq(ts->irq, tsc2301_ts_irq_handler, + SA_SAMPLE_RANDOM | SA_TRIGGER_FALLING, + "tsc2301-ts", tsc); + if (r < 0) { + dev_err(&tsc->spi->dev, "unable to get DAV IRQ"); + goto err3; + } + set_irq_wake(ts->irq, 1); + + r = device_create_file(&tsc->spi->dev, &dev_attr_pen_down); + r = device_create_file(&tsc->spi->dev, &dev_attr_disable_ts); + + r = input_register_device(idev); + if (r < 0) { + dev_err(&tsc->spi->dev, "can't register touchscreen device\n"); + goto err4; + } + + return 0; +err4: + 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); +err3: + tsc2301_ts_stop_scan(tsc); + input_free_device(idev); +err2: +#ifdef CONFIG_ARCH_OMAP + omap_free_gpio(dav_gpio); +#endif +err1: + kfree(ts); + return r; +} + +void __devexit tsc2301_ts_exit(struct tsc2301 *tsc) +{ + struct tsc2301_ts *ts = tsc->ts; + unsigned long flags; + + spin_lock_irqsave(&ts->lock, flags); + tsc2301_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); +} +MODULE_AUTHOR("Jarkko Oikarinen <jarkko.oikarinen@nokia.com>"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/spi/tsc2101.h b/include/linux/spi/tsc2101.h new file mode 100644 index 0000000..efb4437 --- /dev/null +++ b/include/linux/spi/tsc2101.h @@ -0,0 +1,199 @@ +#ifndef _LINUX_SPI_TSC2101_H +#define _LINUX_SPI_TSC2101_H + +#include <linux/types.h> +#include <linux/timer.h> + +struct tsc2101_platform_data { + /* + * Keypad + */ + s16 reset_gpio; + s16 keyb_int; + s16 keymap[16]; /* Set a key to a negative value if not used */ + unsigned kp_rep:1; /* Enable keypad repeating */ + + /* + * Touchscreen + */ + s16 dav_gpio; + s16 pen_int_gpio; + u16 ts_x_plate_ohm; + u32 ts_stab_time; /* voltage settling time */ + u8 ts_hw_avg; /* HW assiseted averaging. Can be + 0, 4, 8, 16 samples per reading */ + 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; + + /* + * Audio + */ + unsigned pll_pdc:4; + unsigned pll_a:4; + unsigned pll_n:4; + unsigned pll_output:1; /* Output PLL on GPIO_0 */ + + unsigned mclk_ratio:2; + unsigned i2s_sample_rate:4; + unsigned i2s_format:2; + /* Mask for audio blocks to be powered down */ + u16 power_down_blocks; + + /* Called after codec has been initialized, can be NULL */ + int (* codec_init)(struct device *tsc2101_dev); + /* Called when codec is being removed, can be NULL */ + void (* codec_cleanup)(struct device *tsc2101_dev); + int (*enable_clock)(struct device *dev); + void (*disable_clock)(struct device *dev); + int (*get_keyb_irq_state)(struct device *dev); + + const struct tsc2101_mixer_gpio { + const char *name; + unsigned gpio:4; + unsigned inverted:1; + unsigned def_enable:1; /* enable by default */ + unsigned deactivate_on_pd:1; /* power-down flag */ + } *mixer_gpios; + int n_mixer_gpios; +}; + +struct tsc2101_kp; +struct tsc2101_ts; +struct tsc2101_mixer; + +struct tsc2101 { + struct spi_device *spi; + + s16 reset_gpio; + u16 config2_shadow; + + struct tsc2101_kp *kp; + struct tsc2101_ts *ts; + struct tsc2101_mixer *mixer; + + int (*enable_clock)(struct device *dev); + void (*disable_clock)(struct device *dev); +}; + + +#define TSC2101_HZ 33000000 + +#define TSC2101_REG(page, addr) (((page) << 11) | ((addr) << 5)) +#define TSC2101_REG_TO_PAGE(reg) (((reg) >> 11) & 0x03) +#define TSC2101_REG_TO_ADDR(reg) (((reg) >> 5) & 0x1f) + +#define TSC2101_REG_X TSC2101_REG(0, 0) +#define TSC2101_REG_Y TSC2101_REG(0, 1) +#define TSC2101_REG_Z1 TSC2101_REG(0, 2) +#define TSC2101_REG_Z2 TSC2101_REG(0, 3) + +#define TSC2101_REG_ADC TSC2101_REG(1, 0) +#define TSC2101_REG_STATUS TSC2101_REG(1, 1) +#define TSC2101_REG_BUFFER TSC2101_REG(1, 2) +#define TSC2101_REG_REF TSC2101_REG(1, 3) +#define TSC2101_REG_CONFIG TSC2101_REG(1, 5) +#define TSC2101_REG_PROG_DELAY TSC2101_REG(1, 13) + +#define TSC2101_REG_AUDIO1 TSC2101_REG(2, 0) +#define TSC2101_REG_DAC_GAIN TSC2101_REG(2, 2) +#define TSC2101_REG_AUDIO2 TSC2101_REG(2, 4) +#define TSC2101_REG_DAC_POWER TSC2101_REG(2, 5) +#define TSC2101_REG_AUDIO3 TSC2101_REG(2, 6) +#define TSC2101_REG_ADCLKCFG TSC2101_REG(2, 27) + +#define TSC2101_REG_PD_MISC_APD (1 << 15) +#define TSC2101_REG_PD_MISC_AVPD (1 << 14) +#define TSC2101_REG_PD_MISC_ABPD (1 << 13) +#define TSC2101_REG_PD_MISC_HAPD (1 << 12) +#define TSC2101_REG_PD_MISC_MOPD (1 << 11) +#define TSC2101_REG_PD_MISC_DAPD (1 << 10) +#define TSC2101_REG_PD_MISC_ADPDL (1 << 9) +#define TSC2101_REG_PD_MISC_ADPDR (1 << 8) +#define TSC2101_REG_PD_MISC_PDSTS (1 << 7) +#define TSC2101_REG_PD_MISC_MIBPD (1 << 6) + +/* I2S sample rate */ +#define TSC2101_I2S_SR_48000 0x00 +#define TSC2101_I2S_SR_44100 0x01 +#define TSC2101_I2S_SR_32000 0x02 +#define TSC2101_I2S_SR_24000 0x03 +#define TSC2101_I2S_SR_22050 0x04 +#define TSC2101_I2S_SR_16000 0x05 +#define TSC2101_I2S_SR_12000 0x06 +#define TSC2101_I2S_SR_11050 0x07 +#define TSC2101_I2S_SR_8000 0x08 + +/* 16-bit, MSB-first. DAC Right-Justified, ADC Left-Justified */ +#define TSC2101_I2S_FORMAT0 0x00 +/* 20-bit, MSB-first. DAC Right-Justified, ADC Left-Justified */ +#define TSC2101_I2S_FORMAT1 0x01 +/* 20-bit, MSB-first. DAC Left-Justified, ADC Left-Justified */ +#define TSC2101_I2S_FORMAT2 0x02 +/* 20-bit, MSB-first */ +#define TSC2101_I2S_FORMAT3 0x03 + +/* Master Clock Ratio */ +#define TSC2101_MCLK_256xFS 0x00 /* default */ +#define TSC2101_MCLK_384xFS 0x01 +#define TSC2101_MCLK_512xFS 0x02 + + +extern u16 tsc2101_read_reg(struct tsc2101 *tsc, int reg); +extern void tsc2101_write_reg(struct tsc2101 *tsc, int reg, u16 val); +extern void tsc2101_write_kbc(struct tsc2101 *tsc, int val); +extern void tsc2101_write_pll(struct tsc2101 *tsc, int pll_n, int pll_a, + int pll_pdc, int pct_e, int pll_o); +extern void tsc2101_read_buf(struct tsc2101 *tsc, int reg, u16 *buf, int len); + +#define TSC2101_DECL_MOD(module) \ +extern int tsc2101_##module##_init(struct tsc2101 *tsc, \ + struct tsc2101_platform_data *pdata); \ +extern void tsc2101_##module##_exit(struct tsc2101 *tsc); \ +extern void tsc2101_##module##_prep_for_clk_stop(struct tsc2101 *tsc); \ +extern void tsc2101_##module##_cont_after_clk_stop(struct tsc2101 *tsc);\ +extern int tsc2101_##module##_suspend(struct tsc2101 *tsc); \ +extern void tsc2101_##module##_resume(struct tsc2101 *tsc); + +#define TSC2101_DECL_EMPTY_MOD(module) \ +static inline int tsc2101_##module##_init(struct tsc2101 *tsc, \ + struct tsc2101_platform_data *pdata) \ +{ \ + return 0; \ +} \ +static inline void tsc2101_##module##_exit(struct tsc2101 *tsc) {} \ +static inline void tsc2101_##module##_prep_for_clk_stop \ + (struct tsc2101 *tsc) {} \ +static inline void tsc2101_##module##_cont_after_clk_stop \ + (struct tsc2101 *tsc) {} \ +static inline int tsc2101_##module##_suspend(struct tsc2101 *tsc) \ +{ \ + return 0; \ +} \ +static inline void tsc2101_##module##_resume(struct tsc2101 *tsc) {} + +#ifdef CONFIG_SPI_TSC2101_TOUCHSCREEN +TSC2101_DECL_MOD(ts) +#else +TSC2101_DECL_EMPTY_MOD(ts) +#endif + +#ifdef CONFIG_SPI_TSC2101_AUDIO +TSC2101_DECL_MOD(mixer) +extern void tsc2101_mixer_set_power(struct device *tsc_dev, int dac, int adc); + +struct snd_card; +extern int tsc2101_mixer_register_controls(struct device *tsc_dev, + struct snd_card *card); +#else +TSC2101_DECL_EMPTY_MOD(mixer) +#endif + +extern void tsc2101_enable_mclk(struct device *tsc_dev); +extern void tsc2101_disable_mclk(struct device *tsc_dev); + +#endif ^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2007-01-30 7:20 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2007-01-25 6:51 [RFC] TSC2101 support Kyungmin Park -- strict thread matches above, loose matches on Subject: below -- 2007-01-25 1:05 Kyungmin Park 2007-01-25 3:13 ` lamikr 2007-01-25 6:08 ` Syed Mohammed, Khasim 2007-01-25 6:26 ` Kondaiah G, Manjunath 2007-01-25 12:36 ` Kai Svahn 2007-01-30 7:20 ` Kyungmin Park
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox