From patchwork Mon Jun 23 09:43:01 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Barry Song <21cnbao@gmail.com> X-Patchwork-Id: 4401131 Return-Path: X-Original-To: patchwork-linux-input@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 015E9BEEAA for ; Mon, 23 Jun 2014 09:44:44 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 504A4202C8 for ; Mon, 23 Jun 2014 09:44:42 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 4D0C5202E5 for ; Mon, 23 Jun 2014 09:44:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752445AbaFWJof (ORCPT ); Mon, 23 Jun 2014 05:44:35 -0400 Received: from mail-pd0-f173.google.com ([209.85.192.173]:33180 "EHLO mail-pd0-f173.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752366AbaFWJoe (ORCPT ); Mon, 23 Jun 2014 05:44:34 -0400 Received: by mail-pd0-f173.google.com with SMTP id r10so5458882pdi.18 for ; Mon, 23 Jun 2014 02:44:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=sVasUnMu993JrctDT1N+Md6bxmFsV2LPn+fDOiTVzG0=; b=Tk5wt66OnP5aePl6gwAKQrvres5MQXhTYqQqi4SipVfZCxKTcRkrLs6g/EakDr3GRx 6k/pUK5yLI4jBD6t+USN864pqeF5tJaXxBNB9RoMmEtMKyKXeAVkgwPmViR/7CMXhVEA erq9mF7M/621ybZeJ9I7ubZGt5bxuZLaCdr3qcbwXHoHVyQyEzu4k12wLFdREnGoWWGC Y4JB9ci/+IkMo6XbWni6PVNl99N7dK8yc0KfxKnfnWP21EGkfQBPKyQ/Cdb5d6rQ4bIM HPm1oUuYQ8vYI3ON9irEUMejd9w1EwOjFJLILOlsF2BvxbcpnLzLpbqi7srgIggSy5Vw Ghgw== X-Received: by 10.68.174.33 with SMTP id bp1mr27589561pbc.74.1403516674336; Mon, 23 Jun 2014 02:44:34 -0700 (PDT) Received: from localhost.localdomain ([117.136.8.1]) by mx.google.com with ESMTPSA id no9sm25949264pbc.83.2014.06.23.02.44.26 for (version=TLSv1.1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Mon, 23 Jun 2014 02:44:33 -0700 (PDT) From: Barry Song <21cnbao@gmail.com> To: dmitry.torokhov@gmail.com, dtor@mail.ru, akpm@linux-foundation.org Cc: linux-input@vger.kernel.org, workgroup.linux@csr.com, Guoying Zhang , Jonathan Cameron , Yibo Cai , Zhiwu Song , Barry Song Subject: [PATCH 2/2] input: sirfsoc_rs - add sirfsoc internal ADC-based touchscreen driver Date: Mon, 23 Jun 2014 17:43:01 +0800 Message-Id: <1403516581-12560-3-git-send-email-21cnbao@gmail.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1403516581-12560-1-git-send-email-21cnbao@gmail.com> References: <1403516581-12560-1-git-send-email-21cnbao@gmail.com> Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Spam-Status: No, score=-6.8 required=5.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_HI, T_DKIM_INVALID, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Guoying Zhang The Touch Screen (TS) Controller is used for driving the touch screen to perform coordinates and pressure measurements. It supports a 4-wire resistive touch screen interface. in this driver, we support both single and dual touch. The dual touch is actually based on a software algorithm. The idea is to recover original dual touch coordinates from AD samples. - Equivalent schematic. touch1 touch2 | | v v RX1 A RX2 D RX3 Upper Plane: XP ----/\/\/\----o----/\/\/\----o----/\/\/\---- XN | | / / Rtouch1 \ Rtouch2 \ / / \ \ | | Lower Plane: YP ----/\/\/\----o----/\/\/\----o----/\/\/\---- YN RY1 B RY2 C RY3 Touch points(touch1/2) introduce two resisters(Rtouch1/2) between upper plane and lower plane, as well as cutting each plane into three consecutive resisters(RX1/2/3, RY1/2/3). - Eight AD samples can be got from sirf touchscreen controller, which serve as known conditions to solve the above resister network. --------------------------------- | Power | AD samples | --------------------------------- | Vcc | GND | Sample1 | Sample2 | --------------------------------- | XP | XN | YP | YN | \ --------------------------------- > Recover coordinates | YP | YN | XP | XN | / --------------------------------- | XP | YN | YP | XN | \ --------------------------------- > Calculate Rtouch | YP | XN | XP | YN | / --------------------------------- - Non-trivial calculation is required to recover dual touch coordinates as we need to resolve some second order equations and deal with very big or small float numbers. Some tricks are necessary to make it work. * float numbers are represented by enlarged integers. ex. (float)1.03 -> *1024 -> (u32)1054 * Sequence of multiply and division is important as it may result in overflow or underflow if switched, though in theory it's nonsense. * Single touch dominates dual touch. Single touch detection must be kept fast and accurate. Cc: Andrew Morton Cc: Jonathan Cameron Signed-off-by: Guoying Zhang Signed-off-by: Yibo Cai Signed-off-by: Zhiwu Song Signed-off-by: Barry Song --- drivers/input/touchscreen/Kconfig | 10 + drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/sirfsoc_ts.c | 658 +++++++++++++++++++++++++++++++++ 3 files changed, 669 insertions(+) create mode 100644 drivers/input/touchscreen/sirfsoc_ts.c diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index a23a94b..8312b7c 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -868,6 +868,16 @@ config TOUCHSCREEN_PCAP To compile this driver as a module, choose M here: the module will be called pcap_ts. +config TOUCHSCREEN_SIRFSOC + tristate "SiRFSoC touchscreen" + depends on ARCH_SIRF + select IIO + help + Say Y here if you have a SiRFSoC internal ADC based touchscreen. + + To compile this driver as a module, choose M here: the + module will be called sirfsoc_ts. + config TOUCHSCREEN_ST1232 tristate "Sitronix ST1232 touchscreen controllers" depends on I2C diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 126479d..ef438f7 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -52,6 +52,7 @@ obj-$(CONFIG_TOUCHSCREEN_PCAP) += pcap_ts.o obj-$(CONFIG_TOUCHSCREEN_PENMOUNT) += penmount.o obj-$(CONFIG_TOUCHSCREEN_PIXCIR) += pixcir_i2c_ts.o obj-$(CONFIG_TOUCHSCREEN_S3C2410) += s3c2410_ts.o +obj-$(CONFIG_TOUCHSCREEN_SIRFSOC) += sirfsoc_ts.o obj-$(CONFIG_TOUCHSCREEN_ST1232) += st1232.o obj-$(CONFIG_TOUCHSCREEN_STMPE) += stmpe-ts.o obj-$(CONFIG_TOUCHSCREEN_SUN4I) += sun4i-ts.o diff --git a/drivers/input/touchscreen/sirfsoc_ts.c b/drivers/input/touchscreen/sirfsoc_ts.c new file mode 100644 index 0000000..77cde19 --- /dev/null +++ b/drivers/input/touchscreen/sirfsoc_ts.c @@ -0,0 +1,658 @@ +/* + * sirfsoc touch controller Driver + * + * Copyright (c) 2014 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "sirfsoc_tsc" + +#define DATA_SHIFT_BITS 14 +#define DATA_XMASK (0x3FFF << 0) +#define DATA_YMASK (0x3FFF << DATA_SHIFT_BITS) +#define GETX(val) ((val) & DATA_XMASK) +#define GETY(val) (((val) & DATA_YMASK) >> DATA_SHIFT_BITS) + +/* + * If new coordinates are close enough to last ones, last values + * should be used to suppress glitches. + */ +#define TS_GLITCH_GAP 60 + +/* Dual touch: Configurable parameters */ +#define TS_PREC_BITS 10 /* Precision, must >= 2 */ +#define TS_DUAL_MIN 15 /* Min UAD/UBC of dual touch points */ +/* Dual touch: Fixed parameter */ +#define TS_V_MAX (1 << DATA_SHIFT_BITS) /* Full scale AD */ +/* Dual touch: Screen dependent parameters */ +#define TS_RX 694 /* X-plane resister */ +#define TS_RY 228 /* Y-plane resister */ +#define TS_V 10800 /* Max AD (LeftUpper corner) */ +#define TS_COEF_MIN ((u32)(0.100f * (1 << TS_PREC_BITS))) +#define TS_COEF_MAX ((u32)(100.0f * (1 << TS_PREC_BITS))) +#define TS_COEF_DEFAULT ((u32)(2.000f * (1 << TS_PREC_BITS))) +#define TS_RTOUCH_NORMAL 700 /* Normal touch resister */ +#define TS_RTOUCH_MIN (70 << TS_PREC_BITS) /* 70 Ohm */ +#define TS_RTOUCH_MAX (1700 << TS_PREC_BITS) /* 1700 Ohm */ +#define TS_RTOUCH_DUAL_UP 350 /* Upper bound of dual touch */ +#define TS_RTOUCH_SINGLE_LOW 450 /* Lower bound of single touch */ +#define TS_SINGLE_MAXGAP_X 100 /* Max UAD of single touch point */ +#define TS_SINGLE_MAXGAP_Y 50 /* Max UBC of single touch point */ + +/* Select AD samples to read (SEL bits in ADC_CONTROL1 register) */ +#define SIRFSOC_TS_SEL_X 0x01 /* x sample */ +#define SIRFSOC_TS_SEL_Y 0x02 /* y sample */ +#define SIRFSOC_TS_SEL_DUAL 0x0F /* eight samples for dual touch */ +#define SIRFSOC_TS_CTL1(sel) (ADC_POLL | ADC_SEL(sel) | ADC_DEL_SET(6) \ + | ADC_FREQ_6K | ADC_TP_TIME(0) | ADC_SGAIN(0) \ + | ADC_EXTCM(0) | ADC_RBAT_DISABLE | ADC_MORE_CTL1) + +/* AD sample indexes (single touch) */ +enum { + X, + Y, + AD_SAMPLE_COUNT_SINGLE +}; + +/* AD sample indexes (dual touch) */ +enum { + XPXN_YP, + YPYN_XP, + XPXN_YN, + YPYN_XN, + XPYN_YP, + XPYN_XN, + YPXN_XP, + YPXN_YN, + AD_SAMPLE_COUNT_DUAL +}; + +#define AD_REG_COUNT (AD_SAMPLE_COUNT_DUAL / 2) + +struct sirfsoc_ts { + char phys[32]; + + /* AD samples buffer of current detection */ + u32 samples[AD_SAMPLE_COUNT_DUAL]; + + /* Calculated coordinates of current detection */ + int sampled_x[2], sampled_y[2]; + /* Last successfully calculated and issued coordinates */ + int issued_x[2], issued_y[2]; + bool press_down; + + /* Fingers detected pressing on the screen + * always 1 for single touch driver + * maybe 1 or 2 for dual touch driver, depends on calculation + */ + int fingers; + + /* Last touching event is a dual touch */ + bool last_is_dual; + + struct input_dev *input; + + /* Points to touch specific data */ + const struct sirfsoc_ts_of_data_touch *touch; + + /* ADC data channel for ts*/ + struct iio_channel *chan; +}; + +/* Single/dual touch specific data and routines */ +struct sirfsoc_ts_of_data_touch { + /* Number of continuous stable samples for debouncing */ + int debounce_rep; + /* Max deviation of AD values for stable samples */ + int debounce_dev; + /* Callback routine to read and calculate coordinates */ + int (*get_coord_and_pen)(struct sirfsoc_ts *); + /* Callback routine to read AD samples */ + int (*read_samples)(struct sirfsoc_ts *, u32 *); +}; + +/* + * Debounce routine shared by single and dual touch + * sample_count: how many AD samples needs to take care + */ +static int sirfsoc_ts_debounce(struct sirfsoc_ts *ts, int sample_count) +{ + int i, j; + u32 samples[AD_SAMPLE_COUNT_DUAL]; /* Max of single/dual samples */ + + /* First sample */ + if ((*(ts->touch->read_samples))(ts, ts->samples)) + return -EBUSY; + + /* Debouncing + * - Condition for a stable reading: Three (debounce_rep) continuous + * reading with AD samples deviation in bound (debounce_dev) + * - ts->samples[] stores sum of previous readings + * - samples[] stored current reading + */ + for (i = 1; i < ts->touch->debounce_rep; i++) { + /* Get raw AD samples */ + if ((*(ts->touch->read_samples))(ts, samples)) + return -EBUSY; + /* Verify adjacent samples deviation */ + for (j = 0; j < sample_count; j++) { + if (abs(ts->samples[j] / i - samples[j]) > + ts->touch->debounce_dev) + return -EINVAL; + ts->samples[j] += samples[j]; + } + } + + /* Average samples, store to ts->samples */ + for (i = 0; i < ts->touch->debounce_rep; i++) + ts->samples[i] /= ts->touch->debounce_rep; + + return 0; +} + +static int sirfsoc_ts_read_samples_single(struct sirfsoc_ts *ts, u32 *samples) +{ + int raw_sample; + int ret; + + ret = iio_read_channel_raw(ts->chan, &raw_sample); + if (ret < 0) { + ts->press_down = false; + return ret; + } + + /* x sample */ + samples[X] = GETX(raw_sample); + /* y sample */ + samples[Y] = GETY(raw_sample); + + ts->press_down = true; + return 0; +} + +/* + * Calculate single touch coordinate + * coord: (ts->sampled_x[0], y[0]) + */ +static int sirfsoc_ts_get_coord_and_pen_single(struct sirfsoc_ts *ts) +{ + int ret; + + ret = sirfsoc_ts_debounce(ts, AD_SAMPLE_COUNT_SINGLE); + if (ret < 0) + return ret; + + ts->sampled_x[0] = ts->samples[X]; + ts->sampled_y[0] = ts->samples[Y]; + + return 0; +} + +/* Read eight AD samples from adc */ +static int sirfsoc_ts_read_samples_dual(struct sirfsoc_ts *ts, u32 *samples) +{ + int i, ret; + int raw_samples[AD_REG_COUNT]; + + ret = iio_read_channel_raw(ts->chan, raw_samples); + if (ret < 0) { + ts->press_down = false; + return ret; + } + + for (i = 0; i < AD_REG_COUNT; i++) { + *samples++ = GETX(raw_samples[i]); + *samples++ = GETY(raw_samples[i]); + } + + ts->press_down = true; + return 0; +} + +/* + * Calculate dual touch coordinates + * + * - Schematic + * + * RX1 A RX2 D RX3 + * XP ----/\/\/\----o----/\/\/\----o----/\/\/\---- XN + * | | + * / / + * Rtouch \ Rtouch \ + * / / + * \ \ + * | | + * YP ----/\/\/\----o----/\/\/\----o----/\/\/\---- YN + * RY1 B RY2 C RY3 + * + * coord1: (ts->sampled_x[0], y[0]); coord2: (ts->sampled_x[1], y[1]) + */ +static int sirfsoc_ts_calculate_dual(struct sirfsoc_ts *ts) +{ + u32 tmp; + int ubc, uad; /* |UB-UC|, |UA-UD| */ + u32 x, y, rx2, ry2; /* Intermediate results */ + u64 a64, b64, c64; /* Equation coefficients */ + u32 rtouch; /* Touch resister between X & Y planes */ + bool neg_slope; /* Slope of the line connecting dual points, + negative if LeftUpper and RightDown */ + u32 *samples = ts->samples; + + /* + * Step 1: Calculate Rtouch + * + * Rtouch depends on pressure and single/dual touch, + * Dual touch halves the value approximately. + * Two equivalent approaches: + * 1. TS_RY * ypyn_xp * (xpyn_xn/xpyn_yp - 1) / TS_V + * 2. TS_RX * xpxn_yp * (ypxn_yn/ypxn_xp - 1) / TS_V + * + * CAUTION: + * rtouch is multiplied by 1024 to keep enough precision bits + */ + a64 = TS_RY * samples[YPYN_XP]; + a64 <<= TS_PREC_BITS; /* *1024 */ + do_div(a64, TS_V); + b64 = a64 * samples[XPYN_XN]; + do_div(b64, samples[XPYN_YP]); + rtouch = (u32)(b64 - a64); + if (rtouch < TS_RTOUCH_MIN || rtouch > TS_RTOUCH_MAX) + return -EINVAL; /* Unstable reading */ + + /* + * Step2: Check single touch + * + * As single touch is the dominant case, it should be handled first. + * Chance is prominent that we may skip all dual touch related messes. + */ + ubc = samples[XPXN_YP] - samples[XPXN_YN]; + uad = samples[YPYN_XP] - samples[YPYN_XN]; + neg_slope = (ubc < 0); + ubc = abs(ubc); + + /* UA-UD should follow the same sign as UB-UC */ + if ((uad < 0) != neg_slope) + goto single_touch; + uad = abs(uad); + /* Very closed samples mean single touch or vertical/horizontal */ + if (ubc <= TS_DUAL_MIN || uad <= TS_DUAL_MIN) + goto single_touch; + /* Fast scratching on the screen may cause misdetection */ + if (rtouch > (TS_RTOUCH_SINGLE_LOW<>= TS_PREC_BITS; + a64 += uad; + b64 = uad; + b64 *= TS_RY; + b64 *= ((1<<(TS_PREC_BITS-1)) + (x>>1)); /* *512 */ + c64 = 2 * uad; + c64 *= TS_RY; + c64 *= TS_RTOUCH_NORMAL; /* Fixed RTouch */ + + /* a: normal; b: *512; c: normal */ + do_div(b64, a64); /* (b/2a)*1024 */ + do_div(c64, a64); /* (c/a) */ + + c64 <<= (2*TS_PREC_BITS); + a64 = int64_sqrt(b64*b64 + c64); + a64 += b64; + a64 >>= 2; /* *256 */ + y = (u32)a64; + + ry2 = y; + rx2 = (x * y) >> TS_PREC_BITS; + /* rx2, ry2: Left shifted 8 bits */ + + /* Step5: Calculate coordinates + * + * center: x0 = (UB+UC)/2, y0 = (UA+UD)/2 + * delta : dx = 0.5 * TS_V * RX2 / TS_RX + * dy = 0.5 * TS_V * RY2 / TS_RY + * ------------------------- + * |points |x | y | + * ------------------------- + * |(x1,y1) |x0-dx | y0-dy | + * |(x2,y2) |x0+dx | y0+dy | + * ------------------------- + */ + + /* x1, x2 */ + x = (samples[XPXN_YP] + samples[XPXN_YN]) << (TS_PREC_BITS-2); + tmp = (TS_V * rx2) / TS_RX; + /* x = x0 * 512, tmp = dx * 512 */ + if (unlikely(x <= tmp)) + ts->sampled_x[0] = 1; + else + ts->sampled_x[0] = (x - tmp) >> (TS_PREC_BITS-1); + ts->sampled_x[1] = (x + tmp) >> (TS_PREC_BITS-1); + + /* y1, y2 */ + y = (samples[YPYN_XP] + samples[YPYN_XN]) << (TS_PREC_BITS-2); + tmp = (TS_V * ry2) / TS_RY; + /* y = y0 * 512, tmp = dy * 512 */ + if (unlikely(y <= tmp)) + ts->sampled_y[0] = 1; + else + ts->sampled_y[0] = (y - tmp) >> (TS_PREC_BITS-1); + ts->sampled_y[1] = (y + tmp) >> (TS_PREC_BITS-1); + + /* Verify data */ + if (unlikely(ts->sampled_x[0] > ts->sampled_x[1] || + ts->sampled_y[0] > ts->sampled_y[1] || + ts->sampled_x[1] >= TS_V_MAX || + ts->sampled_y[1] >= TS_V_MAX)) + return -EINVAL; /* Illegal data */ + if (ts->sampled_x[1] >= TS_V) + ts->sampled_x[1] = TS_V - 1; + if (ts->sampled_y[1] >= TS_V) + ts->sampled_y[1] = TS_V - 1; + + /* Swap x coordinates if negative slope */ + if (neg_slope) { + tmp = ts->sampled_x[0]; + ts->sampled_x[0] = ts->sampled_x[1]; + ts->sampled_x[1] = tmp; + } + + /* Dual touch detected */ + ts->fingers = 2; + return 0; + + /* Fall back to single touch */ +single_touch: + /* + * Make sure it's not a misdetected dual touch: + * - Touch resister must be above upper bound of dual touch + * - |UB-UC| and |UA-UD| are below upper bound of single touch + */ + if (rtouch >= (TS_RTOUCH_DUAL_UP<sampled_x[0] = (samples[XPXN_YP] + samples[XPXN_YN]) / 2; + ts->sampled_y[0] = (samples[YPYN_XP] + samples[YPYN_XN]) / 2; + /* Single touch detected */ + ts->fingers = 1; + return 0; + } else { + return -EINVAL; /* Drop uncertainty */ + } +} + +static int sirfsoc_ts_get_coord_and_pen_dual(struct sirfsoc_ts *ts) +{ + int ret; + bool is_dual; + + /* Read samples and do debouncing */ + ret = sirfsoc_ts_debounce(ts, AD_SAMPLE_COUNT_DUAL); + if (ret < 0) + return ret; + + /* Calculate coordinates */ + ret = sirfsoc_ts_calculate_dual(ts); + if (ret < 0) + return ret; + + /* + * First switching from single to dual or dual to single are + * sometimes unstable, drop it. + */ + is_dual = (ts->fingers == 2); /* Current is dual touch? */ + if (is_dual != ts->last_is_dual) { + ts->last_is_dual = is_dual; + return -EINVAL; + } + + return 0; +} + +/* Report touch events to event driver */ +static void sirfsoc_ts_report_coord(struct sirfsoc_ts *ts) +{ + int i; + + input_report_abs(ts->input, ABS_PRESSURE, 1); + input_report_key(ts->input, BTN_TOUCH, 1); + + for (i = 0; i < ts->fingers; i++) { + /* Filter small glitches */ + if (abs(ts->sampled_x[i] - ts->issued_x[i]) < TS_GLITCH_GAP && + abs(ts->sampled_y[i] - ts->issued_y[i]) < TS_GLITCH_GAP) { + /* + * New coord is very close to last checking, + * adopt old coord instead + */ + ts->sampled_x[i] = ts->issued_x[i]; + ts->sampled_y[i] = ts->issued_y[i]; + } else { + /* Save new coord for next time comparison */ + ts->issued_x[i] = ts->sampled_x[i]; + ts->issued_y[i] = ts->sampled_y[i]; + } + + input_report_abs(ts->input, ABS_MT_POSITION_X, + ts->sampled_x[i]); + input_report_abs(ts->input, ABS_MT_POSITION_Y, + ts->sampled_y[i]); + input_mt_sync(ts->input); + } + + input_sync(ts->input); +} + +static irqreturn_t sirfsoc_ts_thread_irq(int irq, void *handle) +{ + struct sirfsoc_ts *ts = (struct sirfsoc_ts *)handle; + struct input_dev *input = ts->input; + int ret; + + /* + * we have pen down interrupt, but before pen up, no more will come + */ + do { + ret = (*(ts->touch->get_coord_and_pen))(ts); + if (!ret) + sirfsoc_ts_report_coord(ts); + + usleep_range(5000, 8000); + } while (ts->press_down); + + input_report_key(input, BTN_TOUCH, 0); + input_report_abs(input, ABS_PRESSURE, 0); + input_sync(input); + + return IRQ_HANDLED; +} + +static const struct sirfsoc_ts_of_data_touch sirfsoc_ts_of_data_single = { + .debounce_rep = 3, + .debounce_dev = 200, + .get_coord_and_pen = sirfsoc_ts_get_coord_and_pen_single, + .read_samples = sirfsoc_ts_read_samples_single, +}; + +static const struct sirfsoc_ts_of_data_touch sirfsoc_ts_of_data_dual = { + .debounce_rep = 4, + .debounce_dev = 500, + .get_coord_and_pen = sirfsoc_ts_get_coord_and_pen_dual, + .read_samples = sirfsoc_ts_read_samples_dual, +}; + +static const struct of_device_id sirfsoc_ts_of_match[] = { + { .compatible = "sirf,prima2-tsc", + .data = &sirfsoc_ts_of_data_single }, + { .compatible = "sirf,dualtouch-tsc", + .data = &sirfsoc_ts_of_data_dual }, + {} +}; + +static int sirfsoc_ts_probe(struct platform_device *pdev) +{ + struct input_dev *input_dev; + struct sirfsoc_ts *ts; + int ret; + int i; + int irq; + const struct of_device_id *match; + + const unsigned int codes[] = { + KEY_HOME, KEY_MENU, KEY_BACK, KEY_SEARCH, + }; + + ts = devm_kzalloc(&pdev->dev, sizeof(struct sirfsoc_ts), GFP_KERNEL); + if (!ts) { + dev_err(&pdev->dev, + "sirfsoc ts: Cant allocate driver private data\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, ts); + + ts->chan = iio_channel_get(&pdev->dev, NULL); + if (IS_ERR(ts->chan)) { + dev_err(&pdev->dev, "sirfsoc ts: Unable to get the adc channel\n"); + ret = PTR_ERR(ts->chan); + goto out0; + } + + input_dev = input_allocate_device(); + if (!input_dev) { + dev_err(&pdev->dev, + "sirfsoc ts: Unable to allocate input device\n"); + ret = -ENOMEM; + goto out1; + } + + snprintf(ts->phys, sizeof("SIRFSOC-TS"), "SIRFSOC-TS"); + input_dev->name = "sirfsoc_touchscreen"; + input_dev->phys = ts->phys; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) + | BIT_MASK(EV_SYN); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + + input_set_abs_params(input_dev, ABS_PRESSURE, 0, 1, 0, 0); + input_set_abs_params(input_dev, ABS_TOOL_WIDTH, 0, 15, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 1, 0, 0); + input_set_abs_params(input_dev, ABS_MT_WIDTH_MAJOR, 0, 15, 0, 0); + + for (i = 0; i < ARRAY_SIZE(codes); i++) + input_set_capability(input_dev, EV_KEY, codes[i]); + + ret = input_register_device(input_dev); + if (ret) { + dev_err(&pdev->dev, + "sirfsoc ts: Unable to register input device\n"); + goto out2; + } + ts->input = input_dev; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "sirfsoc tsc: get irq failed!\n"); + ret = -ENOMEM; + goto out3; + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + sirfsoc_ts_thread_irq, IRQF_ONESHOT, DRIVER_NAME, ts); + if (ret < 0) { + dev_err(&pdev->dev, "sirfsoc ts: regist irq handler failed!\n"); + ret = -ENODEV; + goto out3; + } + + input_set_abs_params(input_dev, ABS_X, 0, 0x3FFF, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, 0x3FFF, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, 0x3FFF, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, 0x3FFF, 0, 0); + + /* Default to single touch */ + ts->fingers = 1; + + /* Touch specific data */ + match = of_match_device(of_match_ptr(sirfsoc_ts_of_match), &pdev->dev); + ts->touch = match->data; + + return 0; +out3: + input_unregister_device(input_dev); +out2: + input_free_device(input_dev); +out1: + iio_channel_release(ts->chan); +out0: + return ret; +} + +static int sirfsoc_ts_remove(struct platform_device *pdev) +{ + struct sirfsoc_ts *ts = platform_get_drvdata(pdev); + + input_unregister_device(ts->input); + iio_channel_release(ts->chan); + + return 0; +} + +static void sirfsoc_ts_shutdown(struct platform_device *dev) +{ + sirfsoc_ts_remove(dev); +} + +static struct platform_driver tsc_sirfsoc_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = sirfsoc_ts_of_match, + }, + .probe = sirfsoc_ts_probe, + .remove = sirfsoc_ts_remove, + .shutdown = sirfsoc_ts_shutdown, +}; + +module_platform_driver(tsc_sirfsoc_driver); + +MODULE_AUTHOR("Guoying Zhang "); +MODULE_AUTHOR("Yibo Cai "); +MODULE_AUTHOR("Sober Song "); +MODULE_DESCRIPTION("SiRF SoC On-chip Touch screen driver"); +MODULE_LICENSE("GPL v2");