Hi Mika, On Fri, May 06, 2016 at 08:22:16AM +0300, mika.pentt...@nextfour.com wrote: > From: Mika Penttilä <mika.pentt...@nextfour.com> > > 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ä <mika.pentt...@nextfour.com> > Acked-by: Tammy Tseng <tammy_ts...@sis.com> > Acked-by: Yuger Yu <yuger...@sis.com> > --- > 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 <linux/module.h> > +#include <linux/delay.h> > +#include <linux/i2c.h> > +#include <linux/input.h> > +#include <linux/input/mt.h> > +#include <linux/interrupt.h> > +#include <linux/io.h>
Not needed I think. > +#include <linux/platform_device.h> Not needed. > +#include <linux/linkage.h> Not needed. > +#include <linux/slab.h> > +#include <linux/of_gpio.h> Not needed (but gpio/consumer.h is) > +#include <linux/uaccess.h> > +#include <linux/irq.h> Not needed I think. > +#include <asm/unaligned.h> > +#include <linux/crc-itu-t.h> > + > +#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. -- Dmitry Input: add driver for SiS 9200 family I2C touchscreen controllers From: Mika Penttilä <mika.pentt...@nextfour.com> This is a driver for SiS 9200 family touchscreen controllers using I2C bus. Signed-off-by: Mika Penttilä <mika.pentt...@nextfour.com> Acked-by: Tammy Tseng <tammy_ts...@sis.com> Acked-by: Yuger Yu <yuger...@sis.com> Patchwork-Id: 9029121 Signed-off-by: Dmitry Torokhov <dmitry.torok...@gmail.com> --- .../bindings/input/touchscreen/sis_i2c.txt | 33 ++ .../devicetree/bindings/vendor-prefixes.txt | 1 drivers/input/touchscreen/Kconfig | 12 + drivers/input/touchscreen/Makefile | 1 drivers/input/touchscreen/sis_i2c.c | 409 ++++++++++++++++++++ 5 files changed, 456 insertions(+) create mode 100644 Documentation/devicetree/bindings/input/touchscreen/sis_i2c.txt create mode 100644 drivers/input/touchscreen/sis_i2c.c 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 <linux/crc-itu-t.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/interrupt.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <asm/unaligned.h> + +#define SIS_I2C_NAME "sis_i2c_ts" + +/* + * The I2C packet format: + * le16 byte count + * u8 Report ID + * <contact data - variable length> + * 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");