From patchwork Sat Jul 2 00:17:19 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Dmitry Torokhov X-Patchwork-Id: 9210639 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id B6694607D8 for ; Sat, 2 Jul 2016 00:17:36 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id A42BF286D2 for ; Sat, 2 Jul 2016 00:17:36 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 98920286D4; Sat, 2 Jul 2016 00:17:36 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_HI, T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id AF2F3286D2 for ; Sat, 2 Jul 2016 00:17:34 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752457AbcGBARd (ORCPT ); Fri, 1 Jul 2016 20:17:33 -0400 Received: from mail-pf0-f195.google.com ([209.85.192.195]:36470 "EHLO mail-pf0-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752302AbcGBARb (ORCPT ); Fri, 1 Jul 2016 20:17:31 -0400 Received: by mail-pf0-f195.google.com with SMTP id i123so11086764pfg.3; Fri, 01 Jul 2016 17:17:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=date:from:to:cc:subject:message-id:references:mime-version :content-disposition:content-transfer-encoding:in-reply-to :user-agent; bh=g64YkPVklFTy5uu3vD8y4pttw0CS8DkQ9EqJnau+mj4=; b=yBaCLKEDrdzYOenQ+3P5Th/qyK2KEf57kwALCLHLp2zLZjjkhlcJmJf+Q3HjacHJtI whPy8QUYrahQspW18lbbDhe4FZ+zyOVTjXw5xpwrcjOveuk0ScmViEKwv+s8QEJTp4fF DbfvJQgf4TX+aWPP33UYvZjnpFONXxI7jTvVZABVIejMv+bxe7REUwSg/K/ndsPmcI16 kom2MQFegPkztQaJ1yN20aYf/SMw++gZqhzFapHXahds0YDW1ZdqQgi+pibk+48H82jm cCSLZmxwZAleXBMaAmI462CRqPyGpnKDwzk5ssYZ81O0U1RuB2PtXA+Ln8yMXtbgRNGw V54g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:content-transfer-encoding :in-reply-to:user-agent; bh=g64YkPVklFTy5uu3vD8y4pttw0CS8DkQ9EqJnau+mj4=; b=M4cJrShv8hNqet8mOYdTf0d4hm176RVsp/3sp3lBmd9PzVrz1dTlOnQfzAIuJGNApv K9veAtHUJnqDDi6fMdVNFwOHOePt/W8q6JsjsLTxx77Vx8l3xg/dU4utMshQWVE9awiZ PVSUDExOlzHgCdoZ5ETonfTSi0saI9SuUBC2Kyv/7v5gL2mCn0Ijh4SN1z2yQN7WWG6W m2axkz6cY/WKAu6p2kul6L+0RyX5QWSs/6TqcXkLHf6Zd3h4dTmtQSUh8ZGAF9qk+m7i EcoLsV4cm8frehmpVoLILwLNXRf6yZK3Y8Oj7w+jIUg/13kf5nuCUNhDyAvAd8/GICmP Ei6w== X-Gm-Message-State: ALyK8tLNNb8XaJhanVeZkEI5ickCBBaRaN8bhGI0TwMxUD4XIKmqobZ4PYdY8hfgpHAwLg== X-Received: by 10.98.64.193 with SMTP id f62mr1647396pfd.108.1467418642233; Fri, 01 Jul 2016 17:17:22 -0700 (PDT) Received: from dtor-ws ([2620:0:1000:1311:849b:2bb2:69e3:cb77]) by smtp.gmail.com with ESMTPSA id y10sm583030pas.24.2016.07.01.17.17.20 (version=TLS1_2 cipher=AES128-SHA bits=128/128); Fri, 01 Jul 2016 17:17:21 -0700 (PDT) Date: Fri, 1 Jul 2016 17:17:19 -0700 From: Dmitry Torokhov To: mika.penttila@nextfour.com Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, tammy_tseng@sis.com, yuger_yu@sis.com Subject: Re: [PATCH v5 1/2] Input: Driver for SiS 9200 family I2C touchscreen controller. Message-ID: <20160702001719.GA36469@dtor-ws> References: <1462512137-28807-1-git-send-email-mika.penttila@nextfour.com> <1462512137-28807-2-git-send-email-mika.penttila@nextfour.com> MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: <1462512137-28807-2-git-send-email-mika.penttila@nextfour.com> User-Agent: Mutt/1.5.21 (2010-09-15) Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Hi Mika, On Fri, May 06, 2016 at 08:22:16AM +0300, mika.penttila@nextfour.com wrote: > From: Mika Penttilä > > Multitouch protocol B support. > > v5: > - rebased to 4.6.0-rc6 > > v4: > - cleanups and fixes according to review feedback > - irq and reset gpios are now optional, > if not spesified it is expected the firmware / hw > takes care of reset states and irq flow > - irq and reset gpio polarities come from dts/firmware > - report parsing and reporting are done at once > - error handling improvements > - misc cleanups > - added comments > > v3: > - cleanup unused defines > - added acked-bys from SiS > > v2: > - use gpio descriptor api > - probe cleanups > - error handling cleanups > > Signed-off-by: Mika Penttilä > Acked-by: Tammy Tseng > Acked-by: Yuger Yu > --- > drivers/input/touchscreen/Kconfig | 12 + > drivers/input/touchscreen/Makefile | 1 + > drivers/input/touchscreen/sis_i2c.c | 461 ++++++++++++++++++++++++++++++++++++ > 3 files changed, 474 insertions(+) > create mode 100644 drivers/input/touchscreen/sis_i2c.c > > diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig > index 8ecdc38..8f06ae7 100644 > --- a/drivers/input/touchscreen/Kconfig > +++ b/drivers/input/touchscreen/Kconfig > @@ -1155,4 +1155,16 @@ config TOUCHSCREEN_ROHM_BU21023 > To compile this driver as a module, choose M here: the > module will be called bu21023_ts. > > +config TOUCHSCREEN_SIS_I2C > + tristate "SiS 9200 family I2C touchscreen driver" > + depends on I2C > + depends on GPIOLIB > + help > + This enables support for SiS 9200 family over I2C based touchscreens. > + > + If unsure, say N. > + > + To compile this driver as a module, choose M here: the > + module will be called sis_i2c. > + > endif > diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile > index f42975e..0839e99 100644 > --- a/drivers/input/touchscreen/Makefile > +++ b/drivers/input/touchscreen/Makefile > @@ -63,6 +63,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_SIS_I2C) += sis_i2c.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/sis_i2c.c b/drivers/input/touchscreen/sis_i2c.c > new file mode 100644 > index 0000000..f1b66fa > --- /dev/null > +++ b/drivers/input/touchscreen/sis_i2c.c > @@ -0,0 +1,461 @@ > +/* drivers/input/touchscreen/sis_i2c.c > + * - I2C Touch panel driver for SiS 9200 family > + * > + * Copyright (C) 2011 SiS, Inc. > + * Copyright (C) 2015 Nextfour Group > + * > + * This software is licensed under the terms of the GNU General Public > + * License version 2, as published by the Free Software Foundation, and > + * may be copied, distributed, and modified under those terms. > + * > + * 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. > + * > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include Not needed I think. > +#include Not needed. > +#include Not needed. > +#include > +#include Not needed (but gpio/consumer.h is) > +#include > +#include Not needed I think. > +#include > +#include > + > +#define SIS_I2C_NAME "sis_i2c_ts" > +#define MAX_FINGERS 10 > + > +#define SIS_MAX_X 4095 > +#define SIS_MAX_Y 4095 > + > +#define PACKET_BUFFER_SIZE 128 > + > +#define SIS_CMD_NORMAL 0x0 > + > +#define TOUCHDOWN 0x3 > +#define TOUCHUP 0x0 > +#define MAX_BYTE 64 > +#define PRESSURE_MAX 255 > + > +/* Resolution diagonal */ > +#define AREA_LENGTH_LONGER 5792 > +/*((SIS_MAX_X^2) + (SIS_MAX_Y^2))^0.5*/ > +#define AREA_LENGTH_SHORT 5792 > +#define AREA_UNIT (5792/32) > + > +#define P_BYTECOUNT 0 > +#define ALL_IN_ONE_PACKAGE 0x10 > +#define IS_TOUCH(x) ((x) & 0x1) > +#define IS_HIDI2C(x) (((x) & 0xF) == 0x06) > +#define IS_AREA(x) (((x) >> 4) & 0x1) > +#define IS_PRESSURE(x) (((x) >> 5) & 0x1) > +#define IS_SCANTIME(x) (((x) >> 6) & 0x1) > + > +#define NORMAL_LEN_PER_POINT 6 > +#define AREA_LEN_PER_POINT 2 > +#define PRESSURE_LEN_PER_POINT 1 > + > +#define TOUCH_FORMAT 0x1 > +#define HIDI2C_FORMAT 0x6 > +#define P_REPORT_ID 2 > +#define BYTE_BYTECOUNT 2 > +#define BYTE_REPORTID 1 > +#define BYTE_CRC_HIDI2C 0 > +#define BYTE_CRC_I2C 2 > +#define BYTE_SCANTIME 2 > + > +struct _touchpoint { > + u8 id; > + u16 x, y; > + u16 pressure; > + u16 width; > + u16 height; > +}; > + > +struct sistp_driver_data { > + int fingers; > + struct _touchpoint pt[MAX_FINGERS]; > +}; > + > +struct sis_ts_data { > + struct gpio_desc *irq_gpiod; > + struct gpio_desc *reset_gpiod; > + struct i2c_client *client; > + struct input_dev *input_dev; > +struct sistp_driver_data tpinfo; There is actually no need to have storage for contact info in the driver object, you can report them as you parse them. > +}; > + > +static int sis_cul_unit(u8 report_id) > +{ > + int ret = NORMAL_LEN_PER_POINT; > + > + if (report_id != ALL_IN_ONE_PACKAGE) { > + > + if (IS_AREA(report_id) /*&& IS_TOUCH(report_id)*/) > + ret += AREA_LEN_PER_POINT; > + > + if (IS_PRESSURE(report_id)) > + ret += PRESSURE_LEN_PER_POINT; > + } > + > + return ret; > +} > + > +static int sis_readpacket(struct i2c_client *client, u8 cmd, u8 *buf) > +{ > + u8 tmpbuf[MAX_BYTE] = {0}; > + int ret = -1; > + int touchnum = 0; > + int p_count = 0; > + int touch_format_id = 0; > + int location = 0; > + bool read_first = true; > + > +/* > + * I2C touch report format > + * > + * The controller sends one or two > + * 64 byte reports (depending on how many > + * contacts down etc). We read first 64 bytes > + * and then the second chunk if needed. > + * The packets are individually CRC > + * checksummed. > + * > + * buf[0] = Low 8 bits of byte count value > + * buf[1] = High 8 bits of byte counte value > + * buf[2] = Report ID > + * buf[touch num * 6 + 2 ] = Touch information > + * 1 touch point has 6 bytes, it could be none if no touch > + * buf[touch num * 6 + 3] = Touch numbers > + * > + * One touch point information include 6 bytes, the order is > + * > + * 1. status = touch down or touch up > + * 2. id = finger id > + * 3. x axis low 8 bits > + * 4. x axis high 8 bits > + * 5. y axis low 8 bits > + * 6. y axis high 8 bits > + */ > + do { > + if (location >= PACKET_BUFFER_SIZE) { > + dev_err(&client->dev, "sis_readpacket: buffer overflow\n"); > + return -1; > + } > + > + ret = i2c_master_recv(client, tmpbuf, MAX_BYTE); > + > + if (ret <= 0) { > + return touchnum; > + } else if (tmpbuf[P_BYTECOUNT] > MAX_BYTE) { > + dev_err(&client->dev, "sis_readpacket: invalid bytecout\n"); > + return -1; > + } > + > + if (tmpbuf[P_BYTECOUNT] < 10) > + return touchnum; > + > + if (read_first) > + if (tmpbuf[P_BYTECOUNT] == 0) > + return 0; /* touchnum is 0 */ > + > + touch_format_id = tmpbuf[P_REPORT_ID] & 0xf; > + > + if ((touch_format_id != TOUCH_FORMAT) > + && (touch_format_id != HIDI2C_FORMAT) > + && (tmpbuf[P_REPORT_ID] != ALL_IN_ONE_PACKAGE)) { > + dev_err(&client->dev, "sis_readpacket: invalid reportid\n"); > + return -1; > + } > + > + p_count = (int) tmpbuf[P_BYTECOUNT] - 1; /* start from 0 */ > + if (tmpbuf[P_REPORT_ID] != ALL_IN_ONE_PACKAGE) { > + if (IS_TOUCH(tmpbuf[P_REPORT_ID])) { > + p_count -= BYTE_CRC_I2C; /* delete 2 byte crc */ > + } else if (IS_HIDI2C(tmpbuf[P_REPORT_ID])) { > + p_count -= BYTE_CRC_HIDI2C; > + } else { > + dev_err(&client->dev, "sis_readpacket: delete crc error\n"); > + return -1; > + } > + if (IS_SCANTIME(tmpbuf[P_REPORT_ID])) > + p_count -= BYTE_SCANTIME; > + } > + > + if (read_first) > + touchnum = tmpbuf[p_count]; > + else { > + if (tmpbuf[p_count] != 0) { > + dev_err(&client->dev, "sis_readpacket: nonzero point count in tail packet\n"); > + return -1; > + } > + } > + > + if ((touch_format_id != HIDI2C_FORMAT) && > + (tmpbuf[P_BYTECOUNT] > 3)) { > + int crc_end = p_count + > + (IS_SCANTIME(tmpbuf[P_REPORT_ID]) * 2); > + u16 buf_crc = > + crc_itu_t(0, tmpbuf + 2, crc_end - 1); > + int l_package_crc = > + (IS_SCANTIME(tmpbuf[P_REPORT_ID]) * 2) + > + p_count + 1; > + u16 package_crc = > + get_unaligned_le16(&tmpbuf[l_package_crc]); > + if (buf_crc != package_crc) { > + dev_err(&client->dev, "sis_readpacket: CRC Error\n"); > + return -1; > + } > + } > + > + memcpy(&buf[location], &tmpbuf[0], 64); > + /* Buf_Data [0~63] [64~128] */ > + location += MAX_BYTE; > + read_first = false; > + } while (tmpbuf[P_REPORT_ID] != ALL_IN_ONE_PACKAGE && > + tmpbuf[p_count] > 5); > + > + return touchnum; I must say that I find this logic of combining 2 packets into one larger confusing. I tried to rework it so that we fetch, parse and report contacts as we go. Please see below. > +} > + > +static irqreturn_t sis_ts_irq_handler(int irq, void *dev_id) > +{ > + struct sis_ts_data *ts = dev_id; > + struct sistp_driver_data *tpinfo = &ts->tpinfo; > + > + int ret = -1; > + int point_unit; > + u8 buf[PACKET_BUFFER_SIZE] = {0}; > + u8 i = 0, fingers = 0; > + u8 px = 0, py = 0, pstatus = 0; > + u8 p_area = 0; > + u8 p_preasure = 0; > + int slot; > + > +redo: > + /* I2C or SMBUS block data read */ > + ret = sis_readpacket(ts->client, SIS_CMD_NORMAL, buf); > + > + if (ret < 0) > + goto recheck_irq; > + > + else if (ret == 0) { > + fingers = 0; > + goto label_sync_input; > + } > + > + point_unit = sis_cul_unit(buf[P_REPORT_ID]); > + fingers = ret; > + > + tpinfo->fingers = fingers = (fingers > MAX_FINGERS ? 0 : fingers); > + > + for (i = 0; i < fingers; i++) { > + if ((buf[P_REPORT_ID] != ALL_IN_ONE_PACKAGE) && (i >= 5)) { > + pstatus = BYTE_BYTECOUNT + BYTE_REPORTID > + + ((i - 5) * point_unit); > + pstatus += 64; > + } else { > + pstatus = BYTE_BYTECOUNT + BYTE_REPORTID > + + (i * point_unit); > + } > + /* X and Y coordinate locations */ > + px = pstatus + 2; > + py = px + 2; > + > + if ((buf[pstatus]) == TOUCHUP) { > + tpinfo->pt[i].width = 0; > + tpinfo->pt[i].height = 0; > + tpinfo->pt[i].pressure = 0; > + } else if (buf[P_REPORT_ID] == ALL_IN_ONE_PACKAGE > + && (buf[pstatus]) == TOUCHDOWN) { > + tpinfo->pt[i].width = 1; > + tpinfo->pt[i].height = 1; > + tpinfo->pt[i].pressure = 1; > + } else if ((buf[pstatus]) == TOUCHDOWN) { > + p_area = py + 2; > + p_preasure = py + 2 + (IS_AREA(buf[P_REPORT_ID]) * 2); > + > + if (IS_AREA(buf[P_REPORT_ID])) { > + tpinfo->pt[i].width = buf[p_area]; > + tpinfo->pt[i].height = buf[p_area + 1]; > + } else { > + tpinfo->pt[i].width = 1; > + tpinfo->pt[i].height = 1; > + } > + > + if (IS_PRESSURE(buf[P_REPORT_ID])) > + tpinfo->pt[i].pressure = (buf[p_preasure]); > + else > + tpinfo->pt[i].pressure = 1; > + } else { > + dev_err(&ts->client->dev, "Touch status error\n"); > + goto recheck_irq; > + } > + tpinfo->pt[i].id = (buf[pstatus + 1]); > + tpinfo->pt[i].x = le16_to_cpu(get_unaligned_le16(&buf[px])); > + tpinfo->pt[i].y = le16_to_cpu(get_unaligned_le16(&buf[py])); get_unaligned_le16() already converts to native CPU endianness, calling le16_to_cpu on it is a mistake. > + > + slot = input_mt_get_slot_by_key( > + ts->input_dev, tpinfo->pt[i].id); > + > + if (slot < 0) > + continue; > + > + input_mt_slot(ts->input_dev, slot); > + input_mt_report_slot_state(ts->input_dev, > + MT_TOOL_FINGER, tpinfo->pt[i].pressure); > + > + if (tpinfo->pt[i].pressure) { > + > + tpinfo->pt[i].width *= AREA_UNIT; > + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, > + tpinfo->pt[i].width); > + tpinfo->pt[i].height *= AREA_UNIT; > + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MINOR, > + tpinfo->pt[i].height); > + input_report_abs(ts->input_dev, ABS_MT_PRESSURE, > + tpinfo->pt[i].pressure); > + input_report_abs(ts->input_dev, ABS_MT_POSITION_X, > + tpinfo->pt[i].x); > + input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, > + tpinfo->pt[i].y); > + } > + } > + > +label_sync_input: > + input_mt_sync_frame(ts->input_dev); > + input_sync(ts->input_dev); > + > +recheck_irq: > + if (ts->irq_gpiod) { > + /* > + * If provided and interrupt gpio and > + * irq is still asserted, > + * read data until interrupt is deasserted. > + */ > + ret = gpiod_get_value_cansleep(ts->irq_gpiod); > + if (ret == 1) > + goto redo; > + } > + > + return IRQ_HANDLED; > +} > + > +static void sis_ts_reset(struct i2c_client *client, struct sis_ts_data *ts) > +{ > + > + ts->irq_gpiod = devm_gpiod_get_optional(&client->dev, > + "irq", GPIOD_IN); > + ts->reset_gpiod = devm_gpiod_get_optional(&client->dev, > + "reset", GPIOD_OUT_LOW); > + > + if (ts->reset_gpiod) { > + /* Get out of reset */ > + msleep(1); msleep(1) will not work well with low HZ values, you want to use usleep_range(). > + gpiod_set_value(ts->reset_gpiod, 1); > + msleep(1); > + gpiod_set_value(ts->reset_gpiod, 0); > + msleep(100); > + } > +} > + > +static int sis_ts_probe( > + struct i2c_client *client, const struct i2c_device_id *id) > +{ > + int error = 0; > + struct sis_ts_data *ts = NULL; No need to initialize to NULL. > + > + ts = devm_kzalloc(&client->dev, sizeof(struct sis_ts_data), GFP_KERNEL); > + if (!ts) > + return -ENOMEM; > + > + sis_ts_reset(client, ts); > + > + ts->client = client; > + i2c_set_clientdata(client, ts); > + > + ts->input_dev = devm_input_allocate_device(&client->dev); > + if (!ts->input_dev) { > + dev_err(&client->dev, "sis_ts_probe: Failed to allocate input device\n"); > + return -ENOMEM; > + } > + > + ts->input_dev->name = "sis_touch"; > + ts->input_dev->id.bustype = BUS_I2C; > + > + input_set_abs_params(ts->input_dev, ABS_MT_PRESSURE, > + 0, PRESSURE_MAX, 0, 0); > + input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, > + 0, AREA_LENGTH_LONGER, 0, 0); > + input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MINOR, > + 0, AREA_LENGTH_SHORT, 0, 0); > + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, > + 0, SIS_MAX_X, 0, 0); > + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, > + 0, SIS_MAX_Y, 0, 0); > + > + error = input_mt_init_slots(ts->input_dev, MAX_FINGERS, > + INPUT_MT_DROP_UNUSED | INPUT_MT_DIRECT); It seems that the hardware keeps track of the contact identity and reports the release events, so I do not think we need INPUT_MT_DROP_UNUSED. > + > + if (error) { > + dev_err(&client->dev, > + "failed to initialize MT slots: %d\n", error); > + return error; > + } > + > + error = input_register_device(ts->input_dev); > + if (error) { > + dev_err(&client->dev, > + "unable to register input device: %d\n", error); > + return error; > + } > + > + error = devm_request_threaded_irq(&client->dev, client->irq, NULL, > + sis_ts_irq_handler, > + IRQF_ONESHOT, > + client->name, ts); > + > + if (error) { > + dev_err(&client->dev, "request irq failed\n"); > + return error; > + } > + > + return 0; > +} > + > +static const struct i2c_device_id sis_ts_id[] = { > + { SIS_I2C_NAME, 0 }, > + { } > +}; > + > +MODULE_DEVICE_TABLE(i2c, sis_ts_id); > + > +#ifdef CONFIG_OF > +static const struct of_device_id sis_ts_dt_ids[] = { > + { .compatible = "sis,9200_ts" }, DT folks object to using underscores in properties, this should be "sis,9200-ts". > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, sis_ts_dt_ids); > +#endif > + > +static struct i2c_driver sis_ts_driver = { > + .probe = sis_ts_probe, > + .id_table = sis_ts_id, > + .driver = { > + .name = SIS_I2C_NAME, > + .of_match_table = of_match_ptr(sis_ts_dt_ids), > + }, > +}; > + > +module_i2c_driver(sis_ts_driver); > +MODULE_DESCRIPTION("SiS 9200 Family Touchscreen Driver"); > +MODULE_LICENSE("GPL v2"); > -- > 1.9.1 > Below is the version of the patch with reworked parsing and reporting code. Please let me know if it is messed up or if it works for you. Thanks. diff --git a/Documentation/devicetree/bindings/input/touchscreen/sis_i2c.txt b/Documentation/devicetree/bindings/input/touchscreen/sis_i2c.txt new file mode 100644 index 0000000..6b06b20 --- /dev/null +++ b/Documentation/devicetree/bindings/input/touchscreen/sis_i2c.txt @@ -0,0 +1,33 @@ +* SiS I2C Multiple Touch Controller + +Required properties: +- compatible: must be "sis,9200-ts" +- reg: i2c slave address +- interrupt-parent: the phandle for the interrupt controller + (see interrupt binding [0]) +- interrupts: touch controller interrupt (see interrupt + binding [0]) + +Optional properties: +- pinctrl-names: should be "default" (see pinctrl binding [1]). +- pinctrl-0: a phandle pointing to the pin settings for the + device (see pinctrl binding [1]). +- attn-gpio: the gpio pin used as attention line +- reset-gpio: the gpio pin used to reset the controller +- wakeup-source: touchscreen can be used as a wakeup source + +[0]: Documentation/devicetree/bindings/interrupt-controller/interrupts.txt +[1]: Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt + +Example: + + sis9255@5c { + compatible = "sis,9200-ts"; + reg = <0x5c>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_sis>; + interrupt-parent = <&gpio3>; + interrupts = <19 IRQ_TYPE_EDGE_FALLING>; + attn-gpio = <&gpio3 19 GPIO_ACTIVE_LOW>; + reset-gpio = <&gpio2 30 GPIO_ACTIVE_LOW>; + }; diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index 80fdbe2..99029cf 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -221,6 +221,7 @@ simtek sii Seiko Instruments, Inc. silergy Silergy Corp. sirf SiRF Technology, Inc. +sis Silicon Integrated Systems Corp. sitronix Sitronix Technology Corporation skyworks Skyworks Solutions, Inc. smsc Standard Microsystems Corporation diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index ee02dc7..9a29ad1 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -1181,4 +1181,16 @@ config TOUCHSCREEN_ROHM_BU21023 To compile this driver as a module, choose M here: the module will be called bu21023_ts. +config TOUCHSCREEN_SIS_I2C + tristate "SiS 9200 family I2C touchscreen driver" + depends on I2C + depends on GPIOLIB || COMPILE_TEST + help + This enables support for SiS 9200 family over I2C based touchscreens. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called sis_i2c. + endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 3315882..e547399 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_TOUCHSCREEN_PENMOUNT) += penmount.o obj-$(CONFIG_TOUCHSCREEN_PIXCIR) += pixcir_i2c_ts.o obj-$(CONFIG_TOUCHSCREEN_RM_TS) += raydium_i2c_ts.o obj-$(CONFIG_TOUCHSCREEN_S3C2410) += s3c2410_ts.o +obj-$(CONFIG_TOUCHSCREEN_SIS_I2C) += sis_i2c.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/sis_i2c.c b/drivers/input/touchscreen/sis_i2c.c new file mode 100644 index 0000000..bc96c9d --- /dev/null +++ b/drivers/input/touchscreen/sis_i2c.c @@ -0,0 +1,409 @@ +/* + * Touch Screen driver for SiS 9200 family I2C Touch panels + * + * Copyright (C) 2015 SiS, Inc. + * Copyright (C) 2015 Nextfour Group + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SIS_I2C_NAME "sis_i2c_ts" + +/* + * The I2C packet format: + * le16 byte count + * u8 Report ID + * + * u8 Number of contacts + * le16 Scan Time (optional?) + * le16 CRC + * + * One touch point information consists of 6+ bytes, the order is: + * u8 contact state + * u8 finger id + * le16 x axis + * le16 y axis + * u8 contact width (optional) + * u8 contact height (optional) + * u8 pressure (optional) + * + * Maximum amount of data transmitted in one shot is 64 bytes, if controller + * needs to report more contacts than fit in one packet it will send true number + * of contacts in first packet and 0 as number of contacts in second packet. + */ + +#define SIS_MAX_PACKET_SIZE 64 + +#define SIS_PKT_LEN_OFFSET 0 +#define SIS_PKT_REPORT_OFFSET 2 /* Report ID/type */ +#define SIS_PKT_CONTACT_OFFSET 3 /* First contact */ + +#define SIS_SCAN_TIME_LEN 2 + +/* Supported report types */ +#define SIS_ALL_IN_ONE_PACKAGE 0x10 +#define SIS_PKT_IS_TOUCH(x) (((x) & 0x0f) == 0x01) +#define SIS_PKT_IS_HIDI2C(x) (((x) & 0x0f) == 0x06) + +/* Contact properties within report */ +#define SIS_PKT_HAS_AREA(x) ((x) & BIT(4)) +#define SIS_PKT_HAS_PRESSURE(x) ((x) & BIT(5)) +#define SIS_PKT_HAS_SCANTIME(x) ((x) & BIT(6)) + +/* Contact size */ +#define SIS_BASE_LEN_PER_CONTACT 6 +#define SIS_AREA_LEN_PER_CONTACT 2 +#define SIS_PRESSURE_LEN_PER_CONTACT 1 + +/* Offsets within contact data */ +#define SIS_PKT_STATUS_OFFSET 0 +#define SIS_PKT_ID_OFFSET 1 /* Contact ID */ +#define SIS_PKT_X_OFFSET 2 +#define SIS_PKT_Y_OFFSET 4 +#define SIS_PKT_WIDTH_OFFSET 6 +#define SIS_PKT_HEIGHT_OFFSET 7 +#define SIS_PKT_PRESSURE_OFFSET(id) (SIS_PKT_HAS_AREA(id) ? 8 : 6) + +/* Individual contact state */ +#define SIS_STATUS_UP 0x3 +#define SIS_STATUS_DOWN 0x0 + +/* Touchscreen parameters */ +#define SIS_MAX_FINGERS 10 +#define SIS_MAX_X 4095 +#define SIS_MAX_Y 4095 +#define SIS_MAX_PRESSURE 255 + +/* Resolution diagonal */ +#define SIS_AREA_LENGTH_LONGER 5792 +/*((SIS_MAX_X^2) + (SIS_MAX_Y^2))^0.5*/ +#define SIS_AREA_LENGTH_SHORT 5792 +#define SIS_AREA_UNIT (5792/32) + +struct sis_ts_data { + struct i2c_client *client; + struct input_dev *input; + + struct gpio_desc *attn_gpio; + struct gpio_desc *reset_gpio; + + u8 packet[SIS_MAX_PACKET_SIZE]; +}; + +static int sis_read_packet(struct i2c_client *client, u8 *buf, + unsigned int *num_contacts, + unsigned int *contact_size) +{ + int count_idx; + int ret; + u16 len; + u16 crc, pkg_crc; + u8 report_id; + + ret = i2c_master_recv(client, buf, SIS_MAX_PACKET_SIZE); + if (ret <= 0) + return -EIO; + + len = get_unaligned_le16(&buf[SIS_PKT_LEN_OFFSET]); + if (len > SIS_MAX_PACKET_SIZE) { + dev_err(&client->dev, + "%s: invalid packet length (%d vs %d)\n", + __func__, len, SIS_MAX_PACKET_SIZE); + return -E2BIG; + } + + if (len < 10) + return -EINVAL; + + report_id = buf[SIS_PKT_ID_OFFSET]; + count_idx = len - 1; + *contact_size = SIS_BASE_LEN_PER_CONTACT; + + if (report_id != SIS_ALL_IN_ONE_PACKAGE) { + if (SIS_PKT_IS_TOUCH(report_id)) { + /* + * Calculate CRC ignoring packet length + * in the beginning and CRC transmitted + * at the end of the packet. + */ + crc = crc_itu_t(0, buf + SIS_PKT_LEN_OFFSET, + len - SIS_PKT_LEN_OFFSET - 2); + pkg_crc = get_unaligned_le16(&buf[len - 2]); + + if (crc != pkg_crc) { + dev_err(&client->dev, + "%s: CRC Error (%d vs %d)\n", + __func__, crc, pkg_crc); + return -EINVAL; + } + + count_idx -= 2; + + } else if (!SIS_PKT_IS_HIDI2C(report_id)) { + dev_err(&client->dev, + "%s: invalid packet ID %#02x\n", + __func__, report_id); + return -EINVAL; + } + + if (SIS_PKT_HAS_SCANTIME(report_id)) + count_idx -= SIS_SCAN_TIME_LEN; + + if (SIS_PKT_HAS_AREA(report_id)) + *contact_size += SIS_AREA_LEN_PER_CONTACT; + if (SIS_PKT_HAS_PRESSURE(report_id)) + *contact_size += SIS_PRESSURE_LEN_PER_CONTACT; + } + + *num_contacts = buf[count_idx]; + return 0; +} + +static int sis_ts_report_contact(struct sis_ts_data *ts, const u8 *data, u8 id) +{ + struct input_dev *input = ts->input; + int slot; + u8 status = data[SIS_PKT_STATUS_OFFSET]; + u8 pressure; + u8 height, width; + + if (status != SIS_STATUS_DOWN && status != SIS_STATUS_UP) { + dev_err(&ts->client->dev, "Unexpected touch status: %#02x\n", + data[SIS_PKT_STATUS_OFFSET]); + return -EINVAL; + } + + slot = input_mt_get_slot_by_key(input, data[SIS_PKT_ID_OFFSET]); + if (slot < 0) + return -ENOENT; + + input_mt_slot(input, slot); + input_mt_report_slot_state(input, MT_TOOL_FINGER, + status == SIS_STATUS_DOWN); + + if (status == SIS_STATUS_DOWN) { + pressure = height = width = 1; + if (id != SIS_ALL_IN_ONE_PACKAGE) { + if (SIS_PKT_HAS_AREA(id)) { + width = data[SIS_PKT_WIDTH_OFFSET]; + height = data[SIS_PKT_HEIGHT_OFFSET]; + } + + if (SIS_PKT_HAS_PRESSURE(id)) + pressure = data[SIS_PKT_PRESSURE_OFFSET(id)]; + } + + input_report_abs(input, ABS_MT_TOUCH_MAJOR, + width * SIS_AREA_UNIT); + input_report_abs(input, ABS_MT_TOUCH_MINOR, + height * SIS_AREA_UNIT); + input_report_abs(input, ABS_MT_PRESSURE, pressure); + input_report_abs(input, ABS_MT_POSITION_X, + get_unaligned_le16(&data[SIS_PKT_X_OFFSET])); + input_report_abs(input, ABS_MT_POSITION_Y, + get_unaligned_le16(&data[SIS_PKT_Y_OFFSET])); + } + + return 0; +} + +static void sis_ts_handle_packet(struct sis_ts_data *ts) +{ + const u8 *contact; + unsigned int num_to_report = 0; + unsigned int num_contacts; + unsigned int num_reported; + unsigned int contact_size; + int error; + u8 report_id; + + do { + error = sis_read_packet(ts->client, ts->packet, + &num_contacts, &contact_size); + if (error) + break; + + if (num_to_report == 0) { + num_to_report = num_contacts; + } else if (num_contacts != 0) { + dev_err(&ts->client->dev, + "%s: nonzero (%d) point count in tail packet\n", + __func__, num_contacts); + break; + } + + report_id = ts->packet[SIS_PKT_REPORT_OFFSET]; + contact = &ts->packet[SIS_PKT_CONTACT_OFFSET]; + num_reported = 0; + + while (num_to_report > 0) { + error = sis_ts_report_contact(ts, contact, report_id); + if (error) + break; + + contact += contact_size; + num_to_report--; + num_reported++; + + if (report_id != SIS_ALL_IN_ONE_PACKAGE && + num_reported >= 5) { + /* + * The remainder of contacts is sent + * in the 2nd packet. + */ + break; + } + } + } while (num_to_report > 0); + + input_mt_sync_frame(ts->input); + input_sync(ts->input); +} + +static irqreturn_t sis_ts_irq_handler(int irq, void *dev_id) +{ + struct sis_ts_data *ts = dev_id; + + do { + sis_ts_handle_packet(ts); + } while (ts->attn_gpio && gpiod_get_value_cansleep(ts->attn_gpio)); + + return IRQ_HANDLED; +} + +static void sis_ts_reset(struct sis_ts_data *ts) +{ + if (ts->reset_gpio) { + /* Get out of reset */ + usleep_range(1000, 2000); + gpiod_set_value(ts->reset_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value(ts->reset_gpio, 0); + msleep(100); + } +} + +static int sis_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sis_ts_data *ts; + struct input_dev *input; + int error; + + ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + ts->client = client; + i2c_set_clientdata(client, ts); + + ts->attn_gpio = devm_gpiod_get_optional(&client->dev, + "attn", GPIOD_IN); + if (IS_ERR(ts->attn_gpio)) { + error = PTR_ERR(ts->attn_gpio); + if (error != -EPROBE_DEFER) + dev_err(&client->dev, + "Failed to get attention GPIO: %d\n", error); + return error; + } + + ts->reset_gpio = devm_gpiod_get_optional(&client->dev, + "reset", GPIOD_OUT_LOW); + if (IS_ERR(ts->reset_gpio)) { + error = PTR_ERR(ts->reset_gpio); + if (error != -EPROBE_DEFER) + dev_err(&client->dev, + "Failed to get reset GPIO: %d\n", error); + return error; + } + + sis_ts_reset(ts); + + ts->input = input = devm_input_allocate_device(&client->dev); + if (!input) { + dev_err(&client->dev, "Failed to allocate input device\n"); + return -ENOMEM; + } + + input->name = "SiS Touchscreen"; + input->id.bustype = BUS_I2C; + + input_set_abs_params(input, ABS_MT_POSITION_X, 0, SIS_MAX_X, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, SIS_MAX_Y, 0, 0); + input_set_abs_params(input, ABS_MT_PRESSURE, 0, SIS_MAX_PRESSURE, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, + 0, SIS_AREA_LENGTH_LONGER, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MINOR, + 0, SIS_AREA_LENGTH_SHORT, 0, 0); + + error = input_mt_init_slots(input, SIS_MAX_FINGERS, INPUT_MT_DIRECT); + if (error) { + dev_err(&client->dev, + "Failed to initialize MT slots: %d\n", error); + return error; + } + + error = devm_request_threaded_irq(&client->dev, client->irq, + NULL, sis_ts_irq_handler, + IRQF_ONESHOT, + client->name, ts); + if (error) { + dev_err(&client->dev, "Failed to request IRQ: %d\n", error); + return error; + } + + error = input_register_device(ts->input); + if (error) { + dev_err(&client->dev, + "Failed to register input device: %d\n", error); + return error; + } + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id sis_ts_dt_ids[] = { + { .compatible = "sis,9200-ts" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sis_ts_dt_ids); +#endif + +static const struct i2c_device_id sis_ts_id[] = { + { SIS_I2C_NAME, 0 }, + { "9200-ts", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, sis_ts_id); + +static struct i2c_driver sis_ts_driver = { + .driver = { + .name = SIS_I2C_NAME, + .of_match_table = of_match_ptr(sis_ts_dt_ids), + }, + .probe = sis_ts_probe, + .id_table = sis_ts_id, +}; +module_i2c_driver(sis_ts_driver); + +MODULE_DESCRIPTION("SiS 9200 Family Touchscreen Driver"); +MODULE_LICENSE("GPL v2");