From patchwork Wed Nov 8 08:21:05 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andi Shyti X-Patchwork-Id: 10047983 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 7370D60247 for ; Wed, 8 Nov 2017 08:21:51 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 61CFD2A515 for ; Wed, 8 Nov 2017 08:21:51 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 567002A517; Wed, 8 Nov 2017 08:21:51 +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_SIGNED, 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 424962A515 for ; Wed, 8 Nov 2017 08:21:50 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1750735AbdKHIVs (ORCPT ); Wed, 8 Nov 2017 03:21:48 -0500 Received: from mailout2.samsung.com ([203.254.224.25]:12970 "EHLO mailout2.samsung.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751286AbdKHIVS (ORCPT ); Wed, 8 Nov 2017 03:21:18 -0500 Received: from epcas1p3.samsung.com (unknown [182.195.41.47]) by mailout2.samsung.com (KnoxPortal) with ESMTP id 20171108082116epoutp0255722afac8afc64430adba167c89ad09~1DpN6cgHo2152621526epoutp02L; Wed, 8 Nov 2017 08:21:16 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout2.samsung.com 20171108082116epoutp0255722afac8afc64430adba167c89ad09~1DpN6cgHo2152621526epoutp02L DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1510129276; bh=SJ7qJG4Bhyc0wLsNdIM+Tj7lQ5iG7bKSzejmPDv3N+g=; h=From:To:Cc:Subject:Date:In-reply-to:References:From; b=fHb8CPNFC5+/OI4zjGhoGxPg4QLTyAdEFzMKtw06qlbLgg5wgn/qmQl9RV+0SekZ6 6GRPKsN15AiEYXR6KX1tn1lZwBI4W8xOo0k5bECoB6wNbs8729CeUzQY6IUKOhCF0W uFInLkVYLpj99feF3a2I2M+IOMnXkGw2eB7/QXZ8= Received: from epsmges2p1.samsung.com (unknown [182.195.40.64]) by epcas1p3.samsung.com (KnoxPortal) with ESMTP id 20171108082116epcas1p3233e676c6ae674a9c8de91462a894ff5~1DpNYoYRj0732007320epcas1p3Y; Wed, 8 Nov 2017 08:21:16 +0000 (GMT) Received: from epcas2p1.samsung.com ( [182.195.41.53]) by epsmges2p1.samsung.com (Symantec Messaging Gateway) with SMTP id 6A.59.04393.B7EB20A5; Wed, 8 Nov 2017 17:21:15 +0900 (KST) Received: from epsmgms2p2new.samsung.com (unknown [182.195.42.143]) by epcas2p1.samsung.com (KnoxPortal) with ESMTP id 20171108082115epcas2p1f7ff1c4b2ff1de98ecc5732002502bd3~1DpNKGY4-1016010160epcas2p1K; Wed, 8 Nov 2017 08:21:15 +0000 (GMT) X-AuditID: b6c32a45-333ff70000001129-73-5a02be7b0ea4 Received: from epmmp2 ( [203.254.227.17]) by epsmgms2p2new.samsung.com (Symantec Messaging Gateway) with SMTP id CC.50.03859.B7EB20A5; Wed, 8 Nov 2017 17:21:15 +0900 (KST) Received: from localhost.localdomain ([10.113.62.47]) by mmp2.samsung.com (Oracle Communications Messaging Server 7.0.5.31.0 64bit (built May 5 2014)) with ESMTPA id <0OZ300FQTB7C7I90@mmp2.samsung.com>; Wed, 08 Nov 2017 17:21:15 +0900 (KST) From: Andi Shyti To: Dmitry Torokhov Cc: Rob Herring , linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, Andi Shyti , Andi Shyti Subject: [PATCH v6 2/2] Input: add support for the Samsung S6SY761 touchscreen Date: Wed, 08 Nov 2017 17:21:05 +0900 Message-id: <20171108082105.30739-3-andi.shyti@samsung.com> X-Mailer: git-send-email 2.15.0 In-reply-to: <20171108082105.30739-1-andi.shyti@samsung.com> X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFlrCKsWRmVeSWpSXmKPExsWy7bCmqW71PqYog+e91hbbjzxjtVj84zmT xeFFLxgtbn76xmpxedccNovWvUfYHdg8ri/5xOyxc9Zddo9NqzrZPPq2rGL0+LxJLoA1KtUm IzUxJbVIITUvOT8lMy/dVsk7ON453tTMwFDX0NLCXEkhLzE31VbJxSdA1y0zB+gCJYWyxJxS oFBAYnGxkr6dTVF+aUmqQkZ+cYmtUrShoZGeoYG5npGRkZ6JcayVkSlQSUJqxvJPFxgLdnQw Vry9O5+xgXFpQRcjJ4eEgInE2n9/2EFsIYEdjBIHZgp3MXIB2d8ZJd7fP8nYxcgBVrTiTCpE fAOjxL1vx9jgik5tns4K0s0moCnRdPsHG4gtIqAvsX32L0YQm1lgOaPEzjOeILawQIBE+6e7 YNtYBFQl5i94xgJi8wpYS9yduZQZ4iJ5icXfd7KBLOYUsJHYeFsRZJeEQAebxIqNHawQNS4S vc07oWxhiVfHt7BD2NISz1ZtZIRoaGaU2LDtMhOE08Io8fvlVTaIKmOJU12NTBDX8Ul0HP7L DvEmr0RHmxBEiYfEx2+9jBC2o8SiCStZID7uZ5RoObmfaQKj1AJGhlWMYqkFxbnpqcVGBYZ6 xYm5xaV56XrJ+bmbGMEpRMt1B+OMcz6HGAU4GJV4eC/IMUUJsSaWFVfmHmKU4GBWEuHNiQYK 8aYkVlalFuXHF5XmpBYfYjQFBs5EZinR5HxgessriTc0sTQwMTMzMjezACYHcd76bdcihATS E0tSs1NTC1KLYPqYODilGhgTQ6WnVj9tqmnbOUF8r2eC3tlZK3JerN5cpNYnIaJjsErxbFNG 3vW66xw/7vm+2LHOZdc1te3VcjIdB0vqPmx5nDV3k8jf3c0WrkzFBQHOalu/GZyck/b3IOe3 r4ryWk61O4qZ/V2uPFax1Q5/8rzTYfXGPw2rjslOZL3qrTxv/+Qv/+pnbWFWYinOSDTUYi4q TgQAFx9FfzcDAAA= X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFmplluLIzCtJLcpLzFFi42I5/e+xoG71PqYogwXn9Cy2H3nGarH4x3Mm i8OLXjBa3Pz0jdXi8q45bBate4+wO7B5XF/yidlj56y77B6bVnWyefRtWcXo8XmTXABrFJdN SmpOZllqkb5dAlfG8k8XGAt2dDBWvL07n7GBcWlBFyMHh4SAicSKM6ldjJwcQgLrGCV2bozo YuQCsr8zSpxec5kFJMEmoCnRdPsHG4gtIqAvsX32L0aQImaB5YwSzzomsYMMEhbwk9h8LRCk hkVAVWL+gmdgvbwC1hJ3Zy5lBrElBOQlFn/fyQZSzilgI7HxtiLEXmuJhpdz2CYw8ixgZFjF KJlaUJybnltsVGCUl1quV5yYW1yal66XnJ+7iREYONsOa/XvYHy8JP4QowAHoxIPr4YiU5QQ a2JZcWXuIUYJDmYlEd6caKAQb0piZVVqUX58UWlOavEhRmkOFiVxXv78Y5FCAumJJanZqakF qUUwWSYOTqkGxuTUt9Wm92TSDHgeZ+RY/DX3lEg3W9zqNF+9evP0+Wnit/ILr96w3tz5+OoP l0dvOq/4LV8/idvspm3PwWUMXrMj6n9r7d4tec1vbnrQ/fp71+3MJ037EKLHEHvA9/7P+vvT I6ykpb4GHffhNsxU7GNj+LL6NM+8UwcEf3A+uDqXeY7v9yviXEosxRmJhlrMRcWJAABPgNQY AgAA X-CMS-MailID: 20171108082115epcas2p1f7ff1c4b2ff1de98ecc5732002502bd3 X-Msg-Generator: CA CMS-TYPE: 102P DLP-Filter: Pass X-CFilter-Loop: Reflected X-CMS-RootMailID: 20171108082115epcas2p1f7ff1c4b2ff1de98ecc5732002502bd3 X-RootMTR: 20171108082115epcas2p1f7ff1c4b2ff1de98ecc5732002502bd3 References: <20171108082105.30739-1-andi.shyti@samsung.com> 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 The S6SY761 touchscreen is a capicitive multi-touch controller for mobile use. It's connected with i2c at the address 0x48. This commit provides a basic version of the driver which can handle only initialization, touch events and power states. The controller is controlled by a firmware which, in the version I currently have, doesn't provide all the possible functionalities mentioned in the datasheet. Signed-off-by: Andi Shyti --- drivers/input/touchscreen/Kconfig | 11 + drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/s6sy761.c | 564 ++++++++++++++++++++++++++++++++++++ 3 files changed, 576 insertions(+) create mode 100644 drivers/input/touchscreen/s6sy761.c diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 073956d2551c..74c5fa5fd6a2 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -393,6 +393,17 @@ config TOUCHSCREEN_S3C2410 To compile this driver as a module, choose M here: the module will be called s3c2410_ts. +config TOUCHSCREEN_S6SY761 + tristate "Samsung S6SY761 Touchscreen driver" + depends on I2C + help + Say Y if you have the Samsung S6SY761 driver + + If unsure, say N + + To compile this driver as module, choose M here: the + module will be called s6sy761. + config TOUCHSCREEN_GUNZE tristate "Gunze AHL-51S touchscreen" select SERIO diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 2fa458088e3b..715e001e8926 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -66,6 +66,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_S6SY761) += s6sy761.o obj-$(CONFIG_TOUCHSCREEN_SILEAD) += silead.o obj-$(CONFIG_TOUCHSCREEN_SIS_I2C) += sis_i2c.o obj-$(CONFIG_TOUCHSCREEN_ST1232) += st1232.o diff --git a/drivers/input/touchscreen/s6sy761.c b/drivers/input/touchscreen/s6sy761.c new file mode 100644 index 000000000000..27239f3a7132 --- /dev/null +++ b/drivers/input/touchscreen/s6sy761.c @@ -0,0 +1,564 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. + * Author: Andi Shyti + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Samsung S6SY761 Touchscreen device driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* commands */ +#define S6SY761_SENSE_ON 0x10 +#define S6SY761_SENSE_OFF 0x11 +#define S6SY761_TOUCH_FUNCTION 0x30 /* R/W for get/set */ +#define S6SY761_FIRMWARE_INTEGRITY 0x21 +#define S6SY761_PANEL_INFO 0x23 +#define S6SY761_DEVICE_ID 0x52 +#define S6SY761_BOOT_STATUS 0x55 +#define S6SY761_READ_ONE_EVENT 0x60 +#define S6SY761_READ_ALL_EVENT 0x61 +#define S6SY761_CLEAR_EVENT_STACK 0x62 +#define S6SY761_APPLICATION_MODE 0xe4 + +/* events */ +#define S6SY761_EVENT_INFO 0x02 +#define S6SY761_EVENT_VENDOR_INFO 0x07 + +/* info */ +#define S6SY761_INFO_BOOT_COMPLETE 0x00 + +/* firmware status */ +#define S6SY761_FW_OK 0x80 + +/* + * the functionalities are put as a reference + * as in the device I am using none of them + * works therefore not used in this driver yet. + */ +/* touchscreen functionalities */ +#define S6SY761_MASK_TOUCH BIT(0) +#define S6SY761_MASK_HOVER BIT(1) +#define S6SY761_MASK_COVER BIT(2) +#define S6SY761_MASK_GLOVE BIT(3) +#define S6SY761_MASK_STYLUS BIT(4) +#define S6SY761_MASK_PALM BIT(5) +#define S6SY761_MASK_WET BIT(6) +#define S6SY761_MASK_PROXIMITY BIT(7) + +/* boot status (BS) */ +#define S6SY761_BS_BOOT_LOADER 0x10 +#define S6SY761_BS_APPLICATION 0x20 + +/* event id */ +#define S6SY761_EVENT_ID_COORDINATE 0x00 +#define S6SY761_EVENT_ID_STATUS 0x01 + +/* event register masks */ +#define S6SY761_MASK_TOUCH_STATE 0xc0 /* byte 0 */ +#define S6SY761_MASK_TID 0x3c +#define S6SY761_MASK_EID 0x03 +#define S6SY761_MASK_X 0xf0 /* byte 3 */ +#define S6SY761_MASK_Y 0x0f +#define S6SY761_MASK_Z 0x3f /* byte 6 */ +#define S6SY761_MASK_LEFT_EVENTS 0x3f /* byte 7 */ +#define S6SY761_MASK_TOUCH_TYPE 0xc0 /* MSB in byte 6, LSB in byte 7 */ + +/* event touch state values */ +#define S6SY761_TS_NONE 0x00 +#define S6SY761_TS_PRESS 0x01 +#define S6SY761_TS_MOVE 0x02 +#define S6SY761_TS_RELEASE 0x03 + +/* application modes */ +#define S6SY761_APP_NORMAL 0x0 +#define S6SY761_APP_LOW_POWER 0x1 +#define S6SY761_APP_TEST 0x2 +#define S6SY761_APP_FLASH 0x3 +#define S6SY761_APP_SLEEP 0x4 + +#define S6SY761_EVENT_SIZE 8 +#define S6SY761_EVENT_COUNT 32 +#define S6SY761_ALL_EVENT (S6SY761_EVENT_SIZE * S6SY761_EVENT_COUNT) +#define S6SY761_DEVID_SIZE 3 +#define S6SY761_PANEL_ID_SIZE 11 +#define S6SY761_TS_STATUS_SIZE 5 +#define S6SY761_MAX_FINGERS 10 + +#define S6SY761_DEV_NAME "s6sy761" + +enum s6sy761_regulators { + S6SY761_REGULATOR_VDD, + S6SY761_REGULATOR_AVDD, +}; + +struct s6sy761_data { + struct i2c_client *client; + struct regulator_bulk_data regulators[2]; + struct input_dev *input; + struct touchscreen_properties prop; + + u8 data[S6SY761_ALL_EVENT]; + + u16 devid; + u8 tx_channel; +}; + +/* + * We can't simply use i2c_smbus_read_i2c_block_data because we + * need to read more than 255 bytes + */ +static int s6sy761_read_events(struct s6sy761_data *sdata, u16 n_events) +{ + u8 cmd = S6SY761_READ_ALL_EVENT; + struct i2c_msg msgs[2] = { + { + .addr = sdata->client->addr, + .len = 1, + .buf = &cmd, + }, + { + .addr = sdata->client->addr, + .flags = I2C_M_RD, + .len = (n_events * S6SY761_EVENT_SIZE), + .buf = sdata->data + S6SY761_EVENT_SIZE, + }, + }; + int ret; + + ret = i2c_transfer(sdata->client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + + return ret == ARRAY_SIZE(msgs) ? 0 : -EIO; +} + +static void s6sy761_report_coordinates(struct s6sy761_data *sdata, + u8 *event, u8 tid) +{ + u8 major = event[4]; + u8 minor = event[5]; + u8 z = event[6] & S6SY761_MASK_Z; + u16 x = (event[1] << 3) | ((event[3] & S6SY761_MASK_X) >> 4); + u16 y = (event[2] << 3) | (event[3] & S6SY761_MASK_Y); + + input_mt_slot(sdata->input, tid); + + input_mt_report_slot_state(sdata->input, MT_TOOL_FINGER, true); + input_report_abs(sdata->input, ABS_MT_POSITION_X, x); + input_report_abs(sdata->input, ABS_MT_POSITION_Y, y); + input_report_abs(sdata->input, ABS_MT_TOUCH_MAJOR, major); + input_report_abs(sdata->input, ABS_MT_TOUCH_MINOR, minor); + input_report_abs(sdata->input, ABS_MT_PRESSURE, z); + + input_sync(sdata->input); +} + +static void s6sy761_report_release(struct s6sy761_data *sdata, + u8 *event, u8 tid) +{ + input_mt_slot(sdata->input, tid); + input_mt_report_slot_state(sdata->input, MT_TOOL_FINGER, false); + + input_sync(sdata->input); +} + +static void s6sy761_handle_coordinates(struct s6sy761_data *sdata, u8 *event) +{ + u8 tid; + u8 touch_state; + + if (unlikely(!(event[0] & S6SY761_MASK_TID))) + return; + + tid = ((event[0] & S6SY761_MASK_TID) >> 2) - 1; + touch_state = (event[0] & S6SY761_MASK_TOUCH_STATE) >> 6; + + switch (touch_state) { + + case S6SY761_TS_NONE: + break; + case S6SY761_TS_RELEASE: + s6sy761_report_release(sdata, event, tid); + break; + case S6SY761_TS_PRESS: + case S6SY761_TS_MOVE: + s6sy761_report_coordinates(sdata, event, tid); + break; + } +} + +static void s6sy761_handle_events(struct s6sy761_data *sdata, u8 n_events) +{ + int i; + + for (i = 0; i < n_events; i++) { + u8 *event = &sdata->data[i * S6SY761_EVENT_SIZE]; + u8 event_id = event[0] & S6SY761_MASK_EID; + + if (!event[0]) + return; + + switch (event_id) { + + case S6SY761_EVENT_ID_COORDINATE: + s6sy761_handle_coordinates(sdata, event); + break; + + case S6SY761_EVENT_ID_STATUS: + break; + + default: + break; + } + } +} + +static irqreturn_t s6sy761_irq_handler(int irq, void *dev) +{ + struct s6sy761_data *sdata = dev; + int ret; + u8 n_events; + + ret = i2c_smbus_read_i2c_block_data(sdata->client, + S6SY761_READ_ONE_EVENT, + S6SY761_EVENT_SIZE, + sdata->data); + if (ret < 0) { + dev_err(&sdata->client->dev, "failed to read events\n"); + return IRQ_HANDLED; + } + + if (!sdata->data[0]) + return IRQ_HANDLED; + + n_events = sdata->data[7] & S6SY761_MASK_LEFT_EVENTS; + if (unlikely(n_events > S6SY761_EVENT_COUNT - 1)) + return IRQ_HANDLED; + + if (n_events) { + ret = s6sy761_read_events(sdata, n_events); + if (ret < 0) { + dev_err(&sdata->client->dev, "failed to read events\n"); + return IRQ_HANDLED; + } + } + + s6sy761_handle_events(sdata, n_events + 1); + + return IRQ_HANDLED; +} + +static int s6sy761_input_open(struct input_dev *dev) +{ + struct s6sy761_data *sdata = input_get_drvdata(dev); + + return i2c_smbus_write_byte(sdata->client, S6SY761_SENSE_ON); +} + +static void s6sy761_input_close(struct input_dev *dev) +{ + struct s6sy761_data *sdata = input_get_drvdata(dev); + int ret; + + ret = i2c_smbus_write_byte(sdata->client, S6SY761_SENSE_OFF); + if (ret) + dev_err(&sdata->client->dev, "failed to turn off sensing\n"); +} + +static ssize_t s6sy761_sysfs_devid(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct s6sy761_data *sdata = dev_get_drvdata(dev); + + return sprintf(buf, "%#x\n", sdata->devid); +} + +static DEVICE_ATTR(devid, 0444, s6sy761_sysfs_devid, NULL); + +static struct attribute *s6sy761_sysfs_attrs[] = { + &dev_attr_devid.attr, + NULL +}; + +static struct attribute_group s6sy761_attribute_group = { + .attrs = s6sy761_sysfs_attrs +}; + +static int s6sy761_power_on(struct s6sy761_data *sdata) +{ + u8 buffer[S6SY761_EVENT_SIZE]; + u8 event; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(sdata->regulators), + sdata->regulators); + if (ret) + return ret; + + msleep(140); + + /* double check whether the touch is functional */ + ret = i2c_smbus_read_i2c_block_data(sdata->client, + S6SY761_READ_ONE_EVENT, + S6SY761_EVENT_SIZE, + buffer); + if (ret < 0) + return ret; + + event = (buffer[0] >> 2) & 0xf; + + if ((event != S6SY761_EVENT_INFO && + event != S6SY761_EVENT_VENDOR_INFO) || + buffer[1] != S6SY761_INFO_BOOT_COMPLETE) + return -ENODEV; + + ret = i2c_smbus_read_byte_data(sdata->client, S6SY761_BOOT_STATUS); + if (ret < 0) + return ret; + + /* for some reasons the device might be stuck in the bootloader */ + if (ret != S6SY761_BS_APPLICATION) + return -ENODEV; + + /* enable touch functionality */ + ret = i2c_smbus_write_word_data(sdata->client, + S6SY761_TOUCH_FUNCTION, + S6SY761_MASK_TOUCH); + if (ret) + return ret; + + return 0; +} + +static int s6sy761_hw_init(struct s6sy761_data *sdata, + unsigned int *max_x, unsigned int *max_y) +{ + u8 buffer[S6SY761_PANEL_ID_SIZE]; /* larger read size */ + int ret; + + ret = s6sy761_power_on(sdata); + if (ret) + return ret; + + ret = i2c_smbus_read_i2c_block_data(sdata->client, + S6SY761_DEVICE_ID, + S6SY761_DEVID_SIZE, + buffer); + if (ret < 0) + return ret; + + sdata->devid = get_unaligned_be16(buffer + 1); + + ret = i2c_smbus_read_i2c_block_data(sdata->client, + S6SY761_PANEL_INFO, + S6SY761_PANEL_ID_SIZE, + buffer); + if (ret < 0) + return ret; + + *max_x = get_unaligned_be16(buffer); + *max_y = get_unaligned_be16(buffer + 2); + + /* if no tx channels defined, at least keep one */ + sdata->tx_channel = max_t(u8, buffer[8], 1); + + ret = i2c_smbus_read_byte_data(sdata->client, + S6SY761_FIRMWARE_INTEGRITY); + if (ret < 0) + return ret; + else if (ret != S6SY761_FW_OK) + return -ENODEV; + + return 0; +} + +static void s6sy761_power_off(void *data) +{ + struct s6sy761_data *sdata = data; + + disable_irq(sdata->client->irq); + regulator_bulk_disable(ARRAY_SIZE(sdata->regulators), + sdata->regulators); +} + +static int s6sy761_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct s6sy761_data *sdata; + unsigned int max_x, max_y; + int err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK)) + return -ENODEV; + + sdata = devm_kzalloc(&client->dev, sizeof(*sdata), GFP_KERNEL); + if (!sdata) + return -ENOMEM; + + i2c_set_clientdata(client, sdata); + sdata->client = client; + + sdata->regulators[S6SY761_REGULATOR_VDD].supply = "vdd"; + sdata->regulators[S6SY761_REGULATOR_AVDD].supply = "avdd"; + err = devm_regulator_bulk_get(&client->dev, + ARRAY_SIZE(sdata->regulators), + sdata->regulators); + if (err) + return err; + + err = devm_add_action_or_reset(&client->dev, s6sy761_power_off, sdata); + if (err) + return err; + + err = s6sy761_hw_init(sdata, &max_x, &max_y); + if (err) + return err; + + sdata->input = devm_input_allocate_device(&client->dev); + if (!sdata->input) + return -ENOMEM; + + sdata->input->name = S6SY761_DEV_NAME; + sdata->input->id.bustype = BUS_I2C; + sdata->input->open = s6sy761_input_open; + sdata->input->close = s6sy761_input_close; + + input_set_abs_params(sdata->input, ABS_MT_POSITION_X, 0, max_x, 0, 0); + input_set_abs_params(sdata->input, ABS_MT_POSITION_Y, 0, max_y, 0, 0); + input_set_abs_params(sdata->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + input_set_abs_params(sdata->input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0); + input_set_abs_params(sdata->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + input_set_abs_params(sdata->input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0); + input_set_abs_params(sdata->input, ABS_MT_PRESSURE, 0, 255, 0, 0); + + touchscreen_parse_properties(sdata->input, true, &sdata->prop); + + err = input_mt_init_slots(sdata->input, + sdata->tx_channel, + INPUT_MT_DIRECT); + if (err) + return err; + + input_set_drvdata(sdata->input, sdata); + + err = input_register_device(sdata->input); + if (err) + return err; + + /* check if registered coordinates either read from + * device or from device tree don't make sense, + * perhaps the device might rely on the user's setting + * while the user might rely on the device firmware + */ + if (!input_abs_get_max(sdata->input, ABS_X) || + !input_abs_get_max(sdata->input, ABS_Y)) + dev_warn(&client->dev, "the axis have not been set\n"); + + err = devm_request_threaded_irq(&client->dev, client->irq, NULL, + s6sy761_irq_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "s6sy761_irq", sdata); + if (err) + return err; + + err = devm_device_add_group(&client->dev, &s6sy761_attribute_group); + if (err) + return err; + + pm_runtime_enable(&client->dev); + + return 0; +} + +static int s6sy761_remove(struct i2c_client *client) +{ + pm_runtime_disable(&client->dev); + + return 0; +} + +static int __maybe_unused s6sy761_runtime_suspend(struct device *dev) +{ + struct s6sy761_data *sdata = dev_get_drvdata(dev); + + return i2c_smbus_write_byte_data(sdata->client, + S6SY761_APPLICATION_MODE, S6SY761_APP_SLEEP); +} + +static int __maybe_unused s6sy761_runtime_resume(struct device *dev) +{ + struct s6sy761_data *sdata = dev_get_drvdata(dev); + + return i2c_smbus_write_byte_data(sdata->client, + S6SY761_APPLICATION_MODE, S6SY761_APP_NORMAL); +} + +static int __maybe_unused s6sy761_suspend(struct device *dev) +{ + struct s6sy761_data *sdata = dev_get_drvdata(dev); + + s6sy761_power_off(sdata); + + return 0; +} + +static int __maybe_unused s6sy761_resume(struct device *dev) +{ + struct s6sy761_data *sdata = dev_get_drvdata(dev); + + enable_irq(sdata->client->irq); + + return s6sy761_power_on(sdata); +} + +static const struct dev_pm_ops s6sy761_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(s6sy761_suspend, s6sy761_resume) + SET_RUNTIME_PM_OPS(s6sy761_runtime_suspend, + s6sy761_runtime_resume, NULL) +}; + +#ifdef CONFIG_OF +static const struct of_device_id s6sy761_of_match[] = { + { .compatible = "samsung,s6sy761", }, + { }, +}; +MODULE_DEVICE_TABLE(of, s6sy761_of_match); +#endif + +static const struct i2c_device_id s6sy761_id[] = { + { "s6sy761", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, s6sy761_id); + +static struct i2c_driver s6sy761_driver = { + .driver = { + .name = S6SY761_DEV_NAME, + .of_match_table = of_match_ptr(s6sy761_of_match), + .pm = &s6sy761_pm_ops, + }, + .probe = s6sy761_probe, + .remove = s6sy761_remove, + .id_table = s6sy761_id, +}; + +module_i2c_driver(s6sy761_driver); + +MODULE_AUTHOR("Andi Shyti "); +MODULE_DESCRIPTION("Samsung S6SY761 Touch Screen"); +MODULE_LICENSE("GPL v2");