From patchwork Sun Jan 23 17:37:33 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Caleb Connolly X-Patchwork-Id: 12721005 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id E35CCC433EF for ; Sun, 23 Jan 2022 17:37:44 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S239281AbiAWRho (ORCPT ); Sun, 23 Jan 2022 12:37:44 -0500 Received: from mail-4317.proton.ch ([185.70.43.17]:17922 "EHLO mail-4317.proton.ch" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S239269AbiAWRhn (ORCPT ); Sun, 23 Jan 2022 12:37:43 -0500 Date: Sun, 23 Jan 2022 17:37:33 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=connolly.tech; s=protonmail; t=1642959460; bh=FQv1aKzS/LnuVdHDOCDAUsFd7cnwTX2Jbvn0yKrnTD8=; h=Date:To:From:Reply-To:Subject:Message-ID:In-Reply-To:References: From:To:Cc; b=q6g67JyS8HxQhJ7EHX4YP+CwMNGCyEUO0jYvWSbWXoRGJSzpa0DJzPfBcfWuqRxmv bCF8VPbu0TRMDW0TQppmXn7bX0mrpmmAvJ15CjpLm82h78JJqkdQ+VNV0tN8awsAkl XIAQv7i8+zRj8SaaR/DI68NvsuKnWe0I6JBp+OEw= To: Thierry Reding , Sam Ravnborg , David Airlie , Daniel Vetter , Rob Herring , Dmitry Torokhov , Andy Gross , Bjorn Andersson , Kees Cook , Anton Vorontsov , Colin Cross , Tony Luck , Henrik Rydberg , Harigovindan P , Caleb Connolly , dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, linux-arm-msm@vger.kernel.org, Alexander Martinz From: Caleb Connolly Reply-To: Caleb Connolly Subject: [PATCH 2/6] input: touchscreen: add focaltech FTS driver Message-ID: <20220123173650.290349-3-caleb@connolly.tech> In-Reply-To: <20220123173650.290349-1-caleb@connolly.tech> References: <20220123173650.290349-1-caleb@connolly.tech> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org The focaltech FTS driver supports several variants of focaltech touch screens found in ~2018 era smartphones including a variant of the PocoPhone F1 and the SHIFT6mq. This driver is loosely based on the original driver from Focaltech but has been simplified and largely reworked. Signed-off-by: Caleb Connolly --- drivers/input/touchscreen/Kconfig | 9 + drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/focaltech_fts.c | 870 ++++++++++++++++++++++ 3 files changed, 880 insertions(+) create mode 100644 drivers/input/touchscreen/focaltech_fts.c -- 2.34.1 diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 2f6adfb7b938..10874f2d17ac 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -372,6 +372,15 @@ config TOUCHSCREEN_EXC3000 To compile this driver as a module, choose M here: the module will be called exc3000. +config TOUCHSCREEN_FOCALTECH_FTS + tristate "Focaltech FTS Touchscreen" + depends on I2C + help + Say Y here to enable support for Focaltech FTS based + touch panels, including the 5452 and 8917 panels. + + If unsure, say N. + config TOUCHSCREEN_FUJITSU tristate "Fujitsu serial touchscreen" select SERIO diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 39a8127cf6a5..79f877d4ba02 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -44,6 +44,7 @@ obj-$(CONFIG_TOUCHSCREEN_ELO) += elo.o obj-$(CONFIG_TOUCHSCREEN_EGALAX) += egalax_ts.o obj-$(CONFIG_TOUCHSCREEN_EGALAX_SERIAL) += egalax_ts_serial.o obj-$(CONFIG_TOUCHSCREEN_EXC3000) += exc3000.o +obj-$(CONFIG_TOUCHSCREEN_FOCALTECH_FTS) += focaltech_fts.o obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o obj-$(CONFIG_TOUCHSCREEN_GOODIX) += goodix_ts.o obj-$(CONFIG_TOUCHSCREEN_HIDEEP) += hideep.o diff --git a/drivers/input/touchscreen/focaltech_fts.c b/drivers/input/touchscreen/focaltech_fts.c new file mode 100644 index 000000000000..c62dc40a5bd0 --- /dev/null +++ b/drivers/input/touchscreen/focaltech_fts.c @@ -0,0 +1,870 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * FocalTech touchscreen driver. + * + * Copyright (c) 2010-2017, FocalTech Systems, Ltd., all rights reserved. + * Copyright (C) 2018 XiaoMi, Inc. + * Copyright (c) 2021 Caleb Connolly + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FTS_CMD_START1 0x55 +#define FTS_CMD_START2 0xAA +#define FTS_CMD_START_DELAY 10 +#define FTS_CMD_READ_ID 0x90 +#define FTS_CMD_READ_ID_LEN 4 + +#define FTS_REG_INT_CNT 0x8F +#define FTS_REG_FLOW_WORK_CNT 0x91 +#define FTS_REG_WORKMODE 0x00 +#define FTS_REG_WORKMODE_FACTORY_VALUE 0x40 +#define FTS_REG_WORKMODE_WORK_VALUE 0x00 +#define FTS_REG_ESDCHECK_DISABLE 0x8D +#define FTS_REG_CHIP_ID 0xA3 +#define FTS_REG_CHIP_ID2 0x9F +#define FTS_REG_POWER_MODE 0xA5 +#define FTS_REG_POWER_MODE_SLEEP_VALUE 0x03 +#define FTS_REG_FW_VER 0xA6 +#define FTS_REG_VENDOR_ID 0xA8 +#define FTS_REG_LCD_BUSY_NUM 0xAB +#define FTS_REG_FACE_DEC_MODE_EN 0xB0 +#define FTS_REG_FACE_DEC_MODE_STATUS 0x01 +#define FTS_REG_IDE_PARA_VER_ID 0xB5 +#define FTS_REG_IDE_PARA_STATUS 0xB6 +#define FTS_REG_GLOVE_MODE_EN 0xC0 +#define FTS_REG_COVER_MODE_EN 0xC1 +#define FTS_REG_CHARGER_MODE_EN 0x8B +#define FTS_REG_GESTURE_EN 0xD0 +#define FTS_REG_GESTURE_OUTPUT_ADDRESS 0xD3 +#define FTS_REG_MODULE_ID 0xE3 +#define FTS_REG_LIC_VER 0xE4 +#define FTS_REG_ESD_SATURATE 0xED + +#define FTS_MAX_POINTS_SUPPORT 10 +#define FTS_ONE_TCH_LEN 6 + +#define FTS_MAX_ID 0x0A +#define FTS_TOUCH_X_H_POS 3 +#define FTS_TOUCH_X_L_POS 4 +#define FTS_TOUCH_Y_H_POS 5 +#define FTS_TOUCH_Y_L_POS 6 +#define FTS_TOUCH_PRE_POS 7 +#define FTS_TOUCH_AREA_POS 8 +#define FTS_TOUCH_POINT_NUM 2 +#define FTS_TOUCH_EVENT_POS 3 +#define FTS_TOUCH_ID_POS 5 +#define FTS_COORDS_ARR_SIZE 2 + +#define FTS_TOUCH_DOWN 0 +#define FTS_TOUCH_UP 1 +#define FTS_TOUCH_CONTACT 2 + +#define EVENT_DOWN(flag) ((flag == FTS_TOUCH_DOWN) || (flag == FTS_TOUCH_CONTACT)) + +#define FTS_LOCKDOWN_INFO_SIZE 8 +#define LOCKDOWN_INFO_ADDR 0x1FA0 + +#define FTS_DRIVER_NAME "fts-i2c" +#define INTERVAL_READ_REG 100 /* unit:ms */ +#define TIMEOUT_READ_REG 2000 /* unit:ms */ +#define FTS_VDD_MIN_UV 2600000 +#define FTS_VDD_MAX_UV 3300000 +#define FTS_I2C_VCC_MIN_UV 1800000 +#define FTS_I2C_VCC_MAX_UV 1800000 + +#define I2C_RETRY_NUMBER 3 + +#define CHIP_TYPE_5452 0x5452 +#define CHIP_TYPE_8719 0x8719 + +static DEFINE_MUTEX(i2c_rw_access); + +struct ts_event { + int x; /*x coordinate */ + int y; /*y coordinate */ + int p; /* pressure */ + int flag; /* touch event flag: 0 -- down; 1-- up; 2 -- contact */ + int id; /*touch ID */ + int area; +}; + +struct fts_ts_data { + struct i2c_client *client; + struct input_dev *input_dev; + struct regulator *vdd; + struct regulator *vcc_i2c; + spinlock_t irq_lock; + struct mutex report_mutex; + int irq; + bool irq_disabled; + bool power_disabled; + + /* multi-touch */ + struct ts_event *events; + u32 max_touch_number; + u8 *point_buf; + int pnt_buf_size; + int touches; + int touch_point; + int point_num; + bool dev_pm_suspend; + bool low_power_mode; + struct completion dev_pm_suspend_completion; + struct pinctrl *pinctrl; + + // DT data + struct gpio_desc *irq_gpio; + struct gpio_desc *reset_gpio; + u32 width; + u32 height; +}; + +int fts_i2c_read(struct i2c_client *client, char *writebuf, int writelen, + char *readbuf, int readlen) +{ + int ret = 0; + int msg_count = !!writelen + 1; + struct i2c_msg msgs[2]; + + if (readlen < 0 || writelen < 0) + return -EINVAL; + + // If writelen is zero then only populate msgs[0]. + // otherwise we read into msgs[1] + msgs[msg_count-1].len = readlen; + msgs[msg_count-1].buf = readbuf; + msgs[msg_count-1].addr = client->addr; + msgs[msg_count-1].flags = I2C_M_RD; + + if (writelen > 0) { + msgs[0].len = writelen; + msgs[0].buf = writebuf; + msgs[0].addr = client->addr; + msgs[0].flags = 0; + } + + mutex_lock(&i2c_rw_access); + + ret = i2c_transfer(client->adapter, msgs, msg_count); + + mutex_unlock(&i2c_rw_access); + return ret; +} + +int fts_i2c_read_reg(struct i2c_client *client, u8 regaddr, u8 *regvalue) +{ + return fts_i2c_read(client, ®addr, 1, regvalue, 1); +} + +static bool fts_chip_is_valid(struct fts_ts_data *data, u16 id) +{ + if (id != CHIP_TYPE_5452 && id != CHIP_TYPE_8719) + return false; + + return true; +} + +int fts_wait_ready(struct fts_ts_data *data) +{ + int ret = 0; + int cnt = 0; + u8 reg_value[2]; + struct i2c_client *client = data->client; + + do { + ret = fts_i2c_read_reg(client, FTS_REG_CHIP_ID, ®_value[0]); + ret = fts_i2c_read_reg(client, FTS_REG_CHIP_ID2, ®_value[1]); + if (fts_chip_is_valid(data, reg_value[0] << 8 | reg_value[1])) { + dev_dbg(&data->client->dev, "TP Ready, Device ID = 0x%x%x, count = %d", + reg_value[0], reg_value[1], cnt); + return 0; + } + cnt++; + msleep(INTERVAL_READ_REG); + } while ((cnt * INTERVAL_READ_REG) < TIMEOUT_READ_REG); + + return -EIO; +} + +static int fts_power_source_init(struct fts_ts_data *data) +{ + int ret = 0; + + data->vdd = devm_regulator_get(&data->client->dev, "vdd"); + if (IS_ERR_OR_NULL(data->vdd)) { + ret = PTR_ERR(data->vdd); + dev_err(&data->client->dev, "get vdd regulator failed,ret=%d", ret); + return ret; + } + + if (regulator_count_voltages(data->vdd) > 0) { + ret = regulator_set_voltage(data->vdd, FTS_VDD_MIN_UV, + FTS_VDD_MAX_UV); + if (ret < 0) { + dev_err(&data->client->dev, "failed to set vdd regulator ret=%d", ret); + goto exit; + } + } + + data->vcc_i2c = devm_regulator_get(&data->client->dev, "vcc-i2c"); + if (IS_ERR(data->vcc_i2c)) { + ret = PTR_ERR(data->vcc_i2c); + dev_err(&data->client->dev, "get vcc_i2c regulator failed,ret=%d", ret); + return ret; + } + + if (regulator_count_voltages(data->vcc_i2c) > 0) { + ret = regulator_set_voltage(data->vcc_i2c, FTS_I2C_VCC_MIN_UV, + FTS_I2C_VCC_MAX_UV); + if (ret < 0) { + dev_err(&data->client->dev, "failed to set vcc_i2c regulator ret=%d", + ret); + goto exit; + } + } + +exit: + return ret; +} + +static int fts_power_source_release(struct fts_ts_data *data) +{ + if (!data->power_disabled) { + regulator_disable(data->vdd); + regulator_disable(data->vcc_i2c); + } + + devm_regulator_put(data->vdd); + devm_regulator_put(data->vcc_i2c); + + return 0; +} + +static int fts_power_source_ctrl(struct fts_ts_data *data, bool enable) +{ + int ret = 0; + + if (enable) { + if (data->power_disabled) { + ret = regulator_enable(data->vdd); + if (ret < 0) { + dev_err(&data->client->dev, + "enable vdd regulator failed,ret=%d", + ret); + } + + ret = regulator_enable(data->vcc_i2c); + if (ret < 0) { + dev_err(&data->client->dev, "enable vcc_i2c regulator failed,ret=%d", + ret); + } + data->power_disabled = false; + } + } else { + if (!data->power_disabled) { + ret = regulator_disable(data->vdd); + if (ret < 0) { + dev_err(&data->client->dev, + "disable vdd regulator failed,ret=%d", + ret); + } + + ret = regulator_disable(data->vcc_i2c); + if (ret < 0) { + dev_err(&data->client->dev, "disable vcc_i2c regulator failed,ret=%d", + ret); + } + + data->power_disabled = true; + } + } + + return ret; +} + +static int fts_pinctrl_set_active(struct fts_ts_data *data, bool enable) +{ + int ret = 0; + struct pinctrl_state *state = pinctrl_lookup_state(data->pinctrl, + enable ? "default" : "suspend"); + + if (IS_ERR_OR_NULL(state)) { + dev_err(&data->client->dev, "pinctrl lookup %s failed\n", + enable ? "default" : "suspend"); + return -EINVAL; + } + + ret = pinctrl_select_state(data->pinctrl, state); + if (ret < 0) { + dev_err(&data->client->dev, + "Failed to set pinctrl state: enable = %d, ret = %d", + enable, ret); + } + + return ret; +} + +static void fts_release_all_finger(struct fts_ts_data *data) +{ + struct input_dev *input_dev = data->input_dev; + u32 finger_count = 0; + + mutex_lock(&data->report_mutex); + + for (finger_count = 0; finger_count < data->max_touch_number; + finger_count++) { + input_mt_slot(input_dev, finger_count); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, false); + } + + input_report_key(input_dev, BTN_TOUCH, 0); + input_sync(input_dev); + + mutex_unlock(&data->report_mutex); +} + +static int fts_input_report_b(struct fts_ts_data *data) +{ + int i = 0; + int uppoint = 0; + int touches = 0; + bool va_reported = false; + struct ts_event *events = data->events; + + for (i = 0; i < data->touch_point; i++) { + if (events[i].id >= data->max_touch_number) + break; + + va_reported = true; + input_mt_slot(data->input_dev, events[i].id); + + if (events[i].flag == FTS_TOUCH_DOWN || events[i].flag == FTS_TOUCH_CONTACT) { + input_mt_report_slot_state(data->input_dev, + MT_TOOL_FINGER, true); + + if (events[i].p <= 0) + events[i].p = 0x3f; + + input_report_abs(data->input_dev, ABS_MT_PRESSURE, + events[i].p); + + if (events[i].area <= 0) + events[i].area = 0x09; + + input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, + events[i].area); + input_report_abs(data->input_dev, ABS_MT_POSITION_X, + events[i].x); + input_report_abs(data->input_dev, ABS_MT_POSITION_Y, + events[i].y); + + touches |= BIT(events[i].id); + data->touches |= BIT(events[i].id); + } else { + uppoint++; + + input_report_abs(data->input_dev, ABS_MT_PRESSURE, 0); + + input_mt_report_slot_state(data->input_dev, + MT_TOOL_FINGER, false); + data->touches &= ~BIT(events[i].id); + } + } + + if (data->touches ^ touches) { + for (i = 0; i < data->max_touch_number; i++) { + if (BIT(i) & (data->touches ^ touches)) { + va_reported = true; + input_mt_slot(data->input_dev, i); + input_mt_report_slot_state( + data->input_dev, MT_TOOL_FINGER, false); + } + } + } + data->touches = touches; + + if (va_reported) { + if (!data->point_num || !touches) + input_report_key(data->input_dev, BTN_TOUCH, 0); + else + input_report_key(data->input_dev, BTN_TOUCH, 1); + } else { + dev_err(&data->client->dev, "va not reported, but touches=%d", touches); + } + + input_sync(data->input_dev); + return 0; +} + +static int fts_read_touchdata(struct fts_ts_data *data) +{ + int ret = 0; + int i = 0; + u8 pointid; + int base; + struct ts_event *events = data->events; + int max_touch_num = data->max_touch_number; + u8 *buf = data->point_buf; + + data->point_num = 0; + data->touch_point = 0; + + memset(buf, 0xFF, data->pnt_buf_size); + buf[0] = 0x00; + + ret = fts_i2c_read(data->client, buf, 1, buf, data->pnt_buf_size); + if (ret < 0) { + dev_err(&data->client->dev, "read touchdata failed, ret:%d", ret); + return ret; + } + data->point_num = buf[FTS_TOUCH_POINT_NUM] & 0x0F; + + if (data->point_num > max_touch_num) + return -EINVAL; + + for (i = 0; i < max_touch_num; i++) { + base = FTS_ONE_TCH_LEN * i; + + pointid = (buf[FTS_TOUCH_ID_POS + base]) >> 4; + if (pointid >= FTS_MAX_ID) + break; + + else if (pointid >= max_touch_num) + return -EINVAL; + + data->touch_point++; + + events[i].x = ((buf[FTS_TOUCH_X_H_POS + base] & 0x0F) << 8) + + (buf[FTS_TOUCH_X_L_POS + base] & 0xFF); + events[i].y = ((buf[FTS_TOUCH_Y_H_POS + base] & 0x0F) << 8) + + (buf[FTS_TOUCH_Y_L_POS + base] & 0xFF); + events[i].flag = buf[FTS_TOUCH_EVENT_POS + base] >> 6; + events[i].id = buf[FTS_TOUCH_ID_POS + base] >> 4; + events[i].area = buf[FTS_TOUCH_AREA_POS + base] >> 4; + events[i].p = buf[FTS_TOUCH_PRE_POS + base]; + + if (((events[i].flag == FTS_TOUCH_DOWN) || (events[i].flag == FTS_TOUCH_CONTACT)) + && (data->point_num == 0)) { + dev_err(&data->client->dev, "abnormal touch data from fw"); + return -EIO; + } + } + if (data->touch_point == 0) { + dev_err(&data->client->dev, "no touch point information"); + return -EIO; + } + + return 0; +} + +static void fts_report_event(struct fts_ts_data *data) +{ + fts_input_report_b(data); +} + +static irqreturn_t fts_ts_interrupt(int irq, void *d) +{ + int ret = 0; + struct fts_ts_data *data = (struct fts_ts_data *)d; + + if (!data) { + dev_err(&data->client->dev, "%s() Invalid fts_ts_data", __func__); + return IRQ_HANDLED; + } + + if (data->dev_pm_suspend) { + ret = wait_for_completion_timeout( + &data->dev_pm_suspend_completion, + msecs_to_jiffies(700)); + if (!ret) { + dev_err(&data->client->dev, + "Didn't resume in time, skipping wakeup event handling\n"); + return IRQ_HANDLED; + } + } + + ret = fts_read_touchdata(data); + if (ret == 0) { + mutex_lock(&data->report_mutex); + fts_report_event(data); + mutex_unlock(&data->report_mutex); + } + + return IRQ_HANDLED; +} + +static int fts_input_init(struct fts_ts_data *data) +{ + int ret = 0; + struct input_dev *input_dev; + + input_dev = input_allocate_device(); + if (!input_dev) { + dev_err(&data->client->dev, "Failed to allocate memory for input device"); + return -ENOMEM; + } + + /* Init and register Input device */ + input_dev->name = FTS_DRIVER_NAME; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &data->client->dev; + + input_set_drvdata(input_dev, data); + + __set_bit(EV_SYN, input_dev->evbit); + __set_bit(EV_ABS, input_dev->evbit); + __set_bit(BTN_TOUCH, input_dev->keybit); + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + + input_mt_init_slots(input_dev, data->max_touch_number, + INPUT_MT_DIRECT); + + input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, + data->width - 1, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, + data->height - 1, 0, 0); + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 0xFF, 0, 0); + + input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 0xFF, 0, 0); + + data->pnt_buf_size = data->max_touch_number * FTS_ONE_TCH_LEN + 3; + data->point_buf = devm_kzalloc(&data->client->dev, data->pnt_buf_size, GFP_KERNEL); + if (!data->point_buf) { + dev_err(&data->client->dev, "Failed to alloc memory for point buf!"); + ret = -ENOMEM; + goto err_out; + } + + data->events = devm_kzalloc(&data->client->dev, + data->max_touch_number * sizeof(struct ts_event), GFP_KERNEL); + if (!data->events) { + ret = -ENOMEM; + goto err_out; + } + ret = input_register_device(input_dev); + if (ret < 0) { + dev_err(&data->client->dev, "Input device registration failed"); + goto err_out; + } + + data->input_dev = input_dev; + + return 0; + +err_out: + input_set_drvdata(input_dev, NULL); + input_free_device(input_dev); + input_dev = NULL; + + return ret; +} + +static int fts_reset(struct fts_ts_data *data) +{ + gpiod_set_value_cansleep(data->reset_gpio, 0); + msleep(20); + gpiod_set_value_cansleep(data->reset_gpio, 1); + + return 0; +} + +static int fts_parse_dt(struct fts_ts_data *data) +{ + int ret = 0; + struct device *dev = &data->client->dev; + struct device_node *np = dev->of_node; + u32 val; + + ret = of_property_read_u32(np, "touchscreen-size-x", &data->width); + if (ret < 0) { + dev_err(&data->client->dev, "Unable to read property 'touchscreen-size-x'"); + return -EINVAL; + } + + ret = of_property_read_u32(np, "touchscreen-size-y", &data->height); + if (ret < 0) { + dev_err(&data->client->dev, "Unable to read property 'touchscreen-size-y'"); + return -EINVAL; + } + + ret = of_property_read_u32(np, "focaltech,max-touch-number", &val); + if (ret < 0) { + dev_err(&data->client->dev, "Unable to read property 'focaltech,max-touch-number'"); + return -EINVAL; + } + if (val < 2 || val > FTS_MAX_POINTS_SUPPORT) { + dev_err(&data->client->dev, "'focaltech,max-touch-number' out of range [2, %d]", + FTS_MAX_POINTS_SUPPORT); + return -EINVAL; + } + data->max_touch_number = val; + + data->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (data->reset_gpio < 0) { + dev_err(&data->client->dev, "Unable to get reset gpio"); + return -EINVAL; + } + + data->irq_gpio = devm_gpiod_get_optional(dev, "irq", GPIOD_OUT_LOW); + + return 0; +} + +static int fts_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct fts_ts_data *data; + struct pinctrl_state *pinctrl_state_temp; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&data->client->dev, "I2C not supported"); + return -ENODEV; + } + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + ret = fts_parse_dt(data); + if (ret < 0) + return ret; + + i2c_set_clientdata(client, data); + + spin_lock_init(&data->irq_lock); + mutex_init(&data->report_mutex); + + ret = fts_input_init(data); + if (ret < 0) { + dev_err(&data->client->dev, "Input initialization fail"); + goto err_input_init; + } + + ret = fts_power_source_init(data); + if (ret < 0) { + dev_err(&data->client->dev, "fail to get vdd/vcc_i2c regulator"); + goto err_power_init; + } + + data->power_disabled = true; + ret = fts_power_source_ctrl(data, true); + if (ret < 0) { + dev_err(&data->client->dev, "fail to enable vdd/vcc_i2c regulator"); + goto err_power_ctrl; + } + + data->pinctrl = devm_pinctrl_get(&client->dev); + if (IS_ERR_OR_NULL(data->pinctrl)) { + dev_err(&data->client->dev, "Failed to get pinctrl, please check dts"); + ret = PTR_ERR(data->pinctrl); + goto err_power_ctrl; + } + + pinctrl_state_temp = pinctrl_lookup_state(data->pinctrl, "default"); + if (IS_ERR_OR_NULL(pinctrl_state_temp) || + IS_ERR_OR_NULL(pinctrl_lookup_state(data->pinctrl, "suspend"))) { + dev_err(&data->client->dev, "Failed to get default or suspend pinctrl state, please check dts"); + goto err_power_ctrl; + } + + fts_pinctrl_set_active(data, true); + + ret = fts_reset(data); + if (ret < 0) { + dev_err(&data->client->dev, "Failed to reset chip"); + goto err_gpio_config; + } + + ret = fts_wait_ready(data); + if (ret < 0) { + dev_err(&data->client->dev, "Touch IC didn't turn on or is unsupported"); + goto err_gpio_config; + } + + if (data->irq_gpio) { + data->irq = gpiod_to_irq(data->irq_gpio); + + ret = request_threaded_irq(data->irq, NULL, fts_ts_interrupt, + IRQF_ONESHOT, + data->client->name, data); + if (ret < 0) { + dev_err(&data->client->dev, "request irq failed"); + goto err_gpio_config; + } + device_init_wakeup(&client->dev, true); + } + + data->dev_pm_suspend = false; + init_completion(&data->dev_pm_suspend_completion); + + return 0; + +err_gpio_config: + fts_power_source_ctrl(data, false); +err_power_ctrl: + fts_power_source_release(data); +err_power_init: + input_unregister_device(data->input_dev); +err_input_init: + devm_kfree(&client->dev, data); + + return ret; +} + +static int fts_ts_remove(struct i2c_client *client) +{ + struct fts_ts_data *data = i2c_get_clientdata(client); + + free_irq(client->irq, data); + input_unregister_device(data->input_dev); + + fts_power_source_ctrl(data, false); + fts_power_source_release(data); + + kfree(data->point_buf); + kfree(data->events); + + devm_kfree(&client->dev, data); + + return 0; +} + +static int fts_ts_suspend(struct device *dev) +{ + struct fts_ts_data *data = dev_get_drvdata(dev); + int ret = 0; + + disable_irq(data->irq); + + ret = fts_power_source_ctrl(data, false); + if (ret < 0) + dev_err(dev, "power off fail, ret=%d", ret); + fts_pinctrl_set_active(data, false); + + return 0; +} + +static int fts_ts_resume(struct device *dev) +{ + struct fts_ts_data *data = dev_get_drvdata(dev); + + fts_release_all_finger(data); + + fts_power_source_ctrl(data, true); + fts_pinctrl_set_active(data, true); + + fts_wait_ready(data); + + enable_irq(data->irq); + + return 0; +} + +static int fts_pm_suspend(struct device *dev) +{ + struct fts_ts_data *data = dev_get_drvdata(dev); + int ret = 0; + + data->dev_pm_suspend = true; + + if (data->low_power_mode) { + ret = enable_irq_wake(data->irq); + if (ret < 0) { + dev_err(&data->client->dev, "enable_irq_wake(irq:%d) failed", + data->irq); + } + } else { + ret = fts_ts_suspend(dev); + } + + reinit_completion(&data->dev_pm_suspend_completion); + + return ret; +} + +static int fts_pm_resume(struct device *dev) +{ + struct fts_ts_data *data = dev_get_drvdata(dev); + int ret = 0; + + data->dev_pm_suspend = false; + + if (data->low_power_mode) { + ret = disable_irq_wake(data->irq); + if (ret < 0) { + dev_err(&data->client->dev, "disable_irq_wake(irq:%d) failed", + data->irq); + } + } else { + ret = fts_ts_resume(dev); + } + + complete(&data->dev_pm_suspend_completion); + + return 0; +} + +static const struct dev_pm_ops fts_dev_pm_ops = { + .suspend = fts_pm_suspend, + .resume = fts_pm_resume, +}; + +static const struct of_device_id fts_match_table[] = { + { .compatible = "focaltech,fts5452", }, + { .compatible = "focaltech,fts8719", }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, fts_match_table); + +static struct i2c_driver fts_ts_driver = { + .probe = fts_ts_probe, + .remove = fts_ts_remove, + .driver = { + .name = FTS_DRIVER_NAME, + .pm = &fts_dev_pm_ops, + .of_match_table = fts_match_table, + }, +}; +module_i2c_driver(fts_ts_driver); + +MODULE_AUTHOR("Caleb Connolly "); +MODULE_DESCRIPTION("FocalTech touchscreen Driver"); +MODULE_LICENSE("GPL v2");