From: <mika.penttila@nextfour.com> To: <linux-input@vger.kernel.org>, <linux-kernel@vger.kernel.org> Cc: tammy_tseng@sis.com, yuger_yu@sis.com, dmitry.torokhov@gmail.com, "Mika Penttilä" <mika.penttila@nextfour.com> Subject: [PATCH v5 1/2] Input: Driver for SiS 9200 family I2C touchscreen controller. Date: Fri, 6 May 2016 08:22:16 +0300 [thread overview] Message-ID: <1462512137-28807-2-git-send-email-mika.penttila@nextfour.com> (raw) In-Reply-To: <1462512137-28807-1-git-send-email-mika.penttila@nextfour.com> From: Mika Penttilä <mika.penttila@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.penttila@nextfour.com> Acked-by: Tammy Tseng <tammy_tseng@sis.com> Acked-by: Yuger Yu <yuger_yu@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> +#include <linux/platform_device.h> +#include <linux/linkage.h> +#include <linux/slab.h> +#include <linux/of_gpio.h> +#include <linux/uaccess.h> +#include <linux/irq.h> +#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; +}; + +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; +} + +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])); + + 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); + 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; + + 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); + + 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" }, + { /* 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
WARNING: multiple messages have this Message-ID (diff)
From: <mika.penttila@nextfour.com> To: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org Cc: tammy_tseng@sis.com, yuger_yu@sis.com, dmitry.torokhov@gmail.com, "Mika Penttilä" <mika.penttila@nextfour.com> Subject: [PATCH v5 1/2] Input: Driver for SiS 9200 family I2C touchscreen controller. Date: Fri, 6 May 2016 08:22:16 +0300 [thread overview] Message-ID: <1462512137-28807-2-git-send-email-mika.penttila@nextfour.com> (raw) In-Reply-To: <1462512137-28807-1-git-send-email-mika.penttila@nextfour.com> From: Mika Penttilä <mika.penttila@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.penttila@nextfour.com> Acked-by: Tammy Tseng <tammy_tseng@sis.com> Acked-by: Yuger Yu <yuger_yu@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> +#include <linux/platform_device.h> +#include <linux/linkage.h> +#include <linux/slab.h> +#include <linux/of_gpio.h> +#include <linux/uaccess.h> +#include <linux/irq.h> +#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; +}; + +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; +} + +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])); + + 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); + 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; + + 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); + + 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" }, + { /* 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
next prev parent reply other threads:[~2016-05-06 5:23 UTC|newest] Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top 2016-05-06 5:22 [PATCH v5 0/2] Input: SiS 9200 family I2C touchscreen controller driver mika.penttila 2016-05-06 5:22 ` mika.penttila 2016-05-06 5:22 ` mika.penttila [this message] 2016-05-06 5:22 ` [PATCH v5 1/2] Input: Driver for SiS 9200 family I2C touchscreen controller mika.penttila 2016-07-02 0:17 ` Dmitry Torokhov 2016-07-02 0:17 ` Dmitry Torokhov 2016-05-06 5:22 ` [PATCH v5 2/2] Input: SiS 9200 documentation parts mika.penttila 2016-05-06 5:22 ` mika.penttila
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=1462512137-28807-2-git-send-email-mika.penttila@nextfour.com \ --to=mika.penttila@nextfour.com \ --cc=dmitry.torokhov@gmail.com \ --cc=linux-input@vger.kernel.org \ --cc=linux-kernel@vger.kernel.org \ --cc=tammy_tseng@sis.com \ --cc=yuger_yu@sis.com \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: linkBe sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.