Re: [PATCH v5 2/3] Input: stmfts - support FTS5

Dmitry Torokhov <[email protected]>
Newsgroups org.kernel.vger.linux-arm-msm,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-input,org.kernel.vger.linux-kernel,org.kernel.vger.phone-devel
Message-ID <[email protected]>
Hi David,

On Mon, Aug 10, 2026 at 05:21:41PM +0200, David Heidelberg via B4 Relay wrote:
> From: David Heidelberg <[email protected]>
> 
> FTS support SLPI and AP mode, introduce mode-switch GPIO to switch between
> those two. Currently we can handle only full power AP mode, so we just
> keep the AP on.
> 
> Useful for devices like Pixel 3 (blueline), Pixel 4a (sunfish),
> Xiaomi Mi 8 (dipper), and many others.
> 
> Based on work of  Petr Hodina <[email protected]>
> Signed-off-by: David Heidelberg <[email protected]>
> ---
>  drivers/input/touchscreen/stmfts.c | 430 ++++++++++++++++++++++++++++++++++---
>  1 file changed, 396 insertions(+), 34 deletions(-)
> 
> diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
> index 972687797f826..1da113d7197e6 100644
> --- a/drivers/input/touchscreen/stmfts.c
> +++ b/drivers/input/touchscreen/stmfts.c
> @@ -1,23 +1,27 @@
>  // SPDX-License-Identifier: GPL-2.0
> -// STMicroelectronics FTS Touchscreen device driver
> -//
> -// Copyright (c) 2017 Samsung Electronics Co., Ltd.
> -// Copyright (c) 2017 Andi Shyti <[email protected]>
> +/* STMicroelectronics FTS Touchscreen device driver
> + *
> + * Copyright 2017 Samsung Electronics Co., Ltd.
> + * Copyright 2017 Andi Shyti <[email protected]>
> + * Copyright David Heidelberg <[email protected]>
> + * Copyright Petr Hodina <[email protected]>
> + */
>  
>  #include <linux/delay.h>
>  #include <linux/gpio/consumer.h>
>  #include <linux/i2c.h>
>  #include <linux/input/mt.h>
>  #include <linux/input/touchscreen.h>
>  #include <linux/interrupt.h>
>  #include <linux/irq.h>
>  #include <linux/leds.h>
>  #include <linux/module.h>
> +#include <linux/of_device.h>
>  #include <linux/pm_runtime.h>
>  #include <linux/regulator/consumer.h>
>  
>  /* I2C commands */
>  #define STMFTS_READ_INFO			0x80
>  #define STMFTS_READ_STATUS			0x84
>  #define STMFTS_READ_ONE_EVENT			0x85
>  #define STMFTS_READ_ALL_EVENT			0x86
> @@ -30,16 +34,17 @@
>  #define STMFTS_SS_HOVER_SENSE_ON		0x95
>  #define STMFTS_MS_KEY_SENSE_OFF			0x9a
>  #define STMFTS_MS_KEY_SENSE_ON			0x9b
>  #define STMFTS_SYSTEM_RESET			0xa0
>  #define STMFTS_CLEAR_EVENT_STACK		0xa1
>  #define STMFTS_FULL_FORCE_CALIBRATION		0xa2
>  #define STMFTS_MS_CX_TUNING			0xa3
>  #define STMFTS_SS_CX_TUNING			0xa4
> +#define STMFTS5_SET_SCAN_MODE			0xa0
>  
>  /* events */
>  #define STMFTS_EV_NO_EVENT			0x00
>  #define STMFTS_EV_MULTI_TOUCH_DETECTED		0x02
>  #define STMFTS_EV_MULTI_TOUCH_ENTER		0x03
>  #define STMFTS_EV_MULTI_TOUCH_LEAVE		0x04
>  #define STMFTS_EV_MULTI_TOUCH_MOTION		0x05
>  #define STMFTS_EV_HOVER_ENTER			0x07
> @@ -47,22 +52,42 @@
>  #define STMFTS_EV_HOVER_MOTION			0x09
>  #define STMFTS_EV_KEY_STATUS			0x0e
>  #define STMFTS_EV_ERROR				0x0f
>  #define STMFTS_EV_CONTROLLER_READY		0x10
>  #define STMFTS_EV_SLEEP_OUT_CONTROLLER_READY	0x11
>  #define STMFTS_EV_STATUS			0x16
>  #define STMFTS_EV_DEBUG				0xdb
>  
> +/* events FTS5 */
> +#define STMFTS5_EV_CONTROLLER_READY		0x03
> +/* FTM5 event IDs (full byte, not masked) */
> +#define STMFTS5_EV_MULTI_TOUCH_ENTER		0x13
> +#define STMFTS5_EV_MULTI_TOUCH_MOTION		0x23
> +#define STMFTS5_EV_MULTI_TOUCH_LEAVE		0x33
> +#define STMFTS5_EV_STATUS_UPDATE		0x43
> +#define STMFTS5_EV_USER_REPORT			0x53
> +#define STMFTS5_EV_DEBUG			0xe3
> +#define STMFTS5_EV_ERROR			0xf3
> +
>  /* multi touch related event masks */
>  #define STMFTS_MASK_EVENT_ID			0x0f
>  #define STMFTS_MASK_TOUCH_ID			0xf0
>  #define STMFTS_MASK_LEFT_EVENT			0x0f
>  #define STMFTS_MASK_X_MSB			0x0f
>  #define STMFTS_MASK_Y_LSB			0xf0
> +#define STMFTS5_MASK_TOUCH_TYPE			0x0f
> +
> +/* touch type classifications */
> +#define STMFTS_TOUCH_TYPE_INVALID		0x00
> +#define STMFTS_TOUCH_TYPE_FINGER		0x01
> +#define STMFTS_TOUCH_TYPE_GLOVE			0x02
> +#define STMFTS_TOUCH_TYPE_STYLUS		0x03
> +#define STMFTS_TOUCH_TYPE_PALM			0x04
> +#define STMFTS_TOUCH_TYPE_HOVER			0x05
>  
>  /* key related event masks */
>  #define STMFTS_MASK_KEY_NO_TOUCH		0x00
>  #define STMFTS_MASK_KEY_MENU			0x01
>  #define STMFTS_MASK_KEY_BACK			0x02
>  
>  #define STMFTS_EVENT_SIZE	8
>  #define STMFTS_STACK_DEPTH	32
> @@ -71,19 +96,22 @@
>  #define STMFTS_DEV_NAME		"stmfts"
>  
>  static const struct regulator_bulk_data stmfts_supplies[] = {
>  	{ .supply = "vdd" },
>  	{ .supply = "avdd" },
>  };
>  
>  struct stmfts_data {
> +	const struct stmfts_chip_ops *ops;
> +
>  	struct i2c_client *client;
>  	struct input_dev *input;
>  	struct gpio_desc *reset_gpio;
> +	struct gpio_desc *mode_switch_gpio;
>  	struct led_classdev led_cdev;
>  	struct mutex mutex;
>  
>  	struct touchscreen_properties prop;
>  
>  	struct regulator_bulk_data *supplies;
>  
>  	/*
> @@ -100,19 +128,31 @@ struct stmfts_data {
>  
>  	u8 data[STMFTS_DATA_MAX_SIZE];
>  
>  	struct completion cmd_done;
>  
>  	bool use_key;
>  	bool led_status;
>  	bool hover_enabled;
> +	bool stylus_enabled;
>  	bool running;
>  };
>  
> +struct stmfts_chip_ops {
> +	int  (*configure)(struct stmfts_data *sdata);
> +	void (*power_off)(struct stmfts_data *sdata);
> +	int  (*setup_input)(struct stmfts_data *sdata);
> +	int  (*input_open)(struct input_dev *dev);
> +	void (*input_close)(struct input_dev *dev);
> +	void (*parse_events)(struct stmfts_data *sdata);
> +	int  (*set_hover)(struct stmfts_data *sdata, bool enable);
> +	int  (*runtime_resume)(struct stmfts_data *sdata);
> +};
> +
>  static int stmfts_brightness_set(struct led_classdev *led_cdev,
>  				 enum led_brightness value)
>  {
>  	struct stmfts_data *sdata = container_of(led_cdev,
>  					struct stmfts_data, led_cdev);
>  	int err;
>  
>  	if (value != sdata->led_status && sdata->ledvdd) {
> @@ -165,16 +205,17 @@ static int stmfts_read_events(struct stmfts_data *sdata)
>  
>  	ret = i2c_transfer(sdata->client->adapter, msgs, ARRAY_SIZE(msgs));
>  	if (ret < 0)
>  		return ret;
>  
>  	return ret == ARRAY_SIZE(msgs) ? 0 : -EIO;
>  }
>  
> +/* FTS4 event handling functions */
>  static void stmfts_report_contact_event(struct stmfts_data *sdata,
>  					const u8 event[])
>  {
>  	u8 slot_id = (event[0] & STMFTS_MASK_TOUCH_ID) >> 4;
>  	u16 x = event[1] | ((event[2] & STMFTS_MASK_X_MSB) << 8);
>  	u16 y = (event[2] >> 4) | (event[3] << 4);
>  	u8 maj = event[4];
>  	u8 min = event[5];
> @@ -200,16 +241,114 @@ static void stmfts_report_contact_release(struct stmfts_data *sdata,
>  	u8 slot_id = (event[0] & STMFTS_MASK_TOUCH_ID) >> 4;
>  
>  	input_mt_slot(sdata->input, slot_id);
>  	input_mt_report_slot_inactive(sdata->input);
>  
>  	input_sync(sdata->input);
>  }
>  
> +/* FTS5 event handling functions */
> +static void stmfts5_report_contact_event(struct stmfts_data *sdata,
> +					 const u8 event[])
> +{
> +	u8 area;
> +	u8 maj;
> +	u8 min;
> +	/* FTM5 event format:
> +	 * event[0] = event ID (0x13/0x23)
> +	 * event[1] = touch type (low 4 bits) | touch ID (high 4 bits)
> +	 * event[2] = X LSB
> +	 * event[3] = X MSB (low 4 bits) | Y MSB (high 4 bits)
> +	 * event[4] = Y LSB
> +	 * event[5] = pressure
> +	 * event[6] = major (low 4 bits) | minor (high 4 bits)
> +	 * event[7] = minor (high 2 bits)
> +	 */
> +	u8 touch_id = (event[1] & STMFTS_MASK_TOUCH_ID) >> 4;
> +	u8 touch_type = event[1] & STMFTS5_MASK_TOUCH_TYPE;
> +	int x, y, distance;
> +	unsigned int tool = MT_TOOL_FINGER;
> +
> +	/* Parse coordinates with better precision */
> +	x = (((int)event[3] & STMFTS_MASK_X_MSB) << 8) | event[2];
> +	y = ((int)event[4] << 4) | ((event[3] & STMFTS_MASK_Y_LSB) >> 4);

This does not match the comment above. This treats byte[4] as Y MSB, but
comment above says that it is Y LSB...

> +
> +	/* Parse pressure - ensure non-zero for active touch */
> +	area = event[5];
> +	if (area <= 0 && touch_type != STMFTS_TOUCH_TYPE_HOVER) {

Area can't be less than 0, it's a u8.

> +		/* Should not happen for contact events. Set minimum pressure
> +		 * to prevent touch from being dropped
> +		 */
> +		dev_warn_once(&sdata->client->dev,
> +			      "zero pressure on contact event, slot %d\n", touch_id);
> +		area = 1;
> +	}

Should this check be pas the switch()? No need to complain about area
for invalid touch types.

> +
> +	/* Parse touch area with improved bit extraction */
> +	maj = (((event[0] & 0x0C) << 2) | ((event[6] & 0xF0) >> 4));

Why do we need event[0] in major? It contains event ID... Could you add
an comment explaining it.

> +	min = (((event[7] & 0xC0) >> 2) | (event[6] & 0x0F));
> +
> +	/* Distance is 0 for touching, max for hovering */
> +	distance = 0;
> +
> +	/* Classify touch type and set appropriate tool and parameters */
> +	switch (touch_type) {
> +	case STMFTS_TOUCH_TYPE_STYLUS:
> +		if (sdata->stylus_enabled) {
> +			tool = MT_TOOL_PEN;
> +			break;
> +		}
> +		fallthrough; /* Report as finger if stylus not enabled */
> +
> +	case STMFTS_TOUCH_TYPE_FINGER:
> +	case STMFTS_TOUCH_TYPE_GLOVE:
> +		tool = MT_TOOL_FINGER;
> +		break;
> +
> +	case STMFTS_TOUCH_TYPE_PALM:
> +		/* Palm touch - report but can be filtered by userspace */
> +		tool = MT_TOOL_PALM;
> +		break;
> +
> +	case STMFTS_TOUCH_TYPE_HOVER:
> +		tool = MT_TOOL_FINGER;
> +		area = 0;
> +		distance = 255;
> +		break;
> +
> +	case STMFTS_TOUCH_TYPE_INVALID:
> +	default:
> +		dev_warn(&sdata->client->dev,
> +			 "invalid touch type %d for slot %d\n",
> +			 touch_type, touch_id);
> +		return;
> +	}
> +
> +	input_mt_slot(sdata->input, touch_id);
> +	input_mt_report_slot_state(sdata->input, tool, 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, maj);
> +	input_report_abs(sdata->input, ABS_MT_TOUCH_MINOR, min);
> +	input_report_abs(sdata->input, ABS_MT_PRESSURE, area);
> +	input_report_abs(sdata->input, ABS_MT_DISTANCE, distance);
> +}
> +
> +static void stmfts5_report_contact_release(struct stmfts_data *sdata,
> +					   const u8 event[])
> +{
> +	/* FTM5 format: touch ID is in high 4 bits of event[1] */
> +	u8 touch_id = (event[1] & STMFTS_MASK_TOUCH_ID) >> 4;
> +
> +	input_mt_slot(sdata->input, touch_id);
> +	input_mt_report_slot_inactive(sdata->input);
> +}
> +
>  static void stmfts_report_hover_event(struct stmfts_data *sdata,
>  				      const u8 event[])
>  {
>  	u16 x = (event[2] << 4) | (event[4] >> 4);
>  	u16 y = (event[3] << 4) | (event[4] & STMFTS_MASK_Y_LSB);
>  	u8 z = event[5];
>  
>  	input_report_abs(sdata->input, ABS_X, x);
> @@ -292,29 +431,72 @@ static void stmfts_parse_events(struct stmfts_data *sdata)
>  
>  		default:
>  			dev_err(&sdata->client->dev,
>  				"unknown event %#02x\n", event[0]);
>  		}
>  	}
>  }
>  
> +static void stmfts5_parse_events(struct stmfts_data *sdata)
> +{
> +	for (int i = 0; i < STMFTS_STACK_DEPTH; i++) {
> +		u8 *event = &sdata->data[i * STMFTS_EVENT_SIZE];
> +
> +		switch (event[0]) {
> +		case STMFTS5_EV_CONTROLLER_READY:
> +			complete(&sdata->cmd_done);
> +			fallthrough;
> +
> +		case STMFTS_EV_NO_EVENT:
> +		case STMFTS5_EV_STATUS_UPDATE:
> +		case STMFTS5_EV_USER_REPORT:
> +		case STMFTS5_EV_DEBUG:
> +			goto sync;
> +
> +		case STMFTS5_EV_MULTI_TOUCH_ENTER:
> +		case STMFTS5_EV_MULTI_TOUCH_MOTION:
> +			stmfts5_report_contact_event(sdata, event);
> +			break;
> +
> +		case STMFTS5_EV_MULTI_TOUCH_LEAVE:
> +			stmfts5_report_contact_release(sdata, event);
> +			break;
> +
> +		case STMFTS5_EV_ERROR:
> +			dev_warn(&sdata->client->dev,
> +				 "error code: 0x%x%x%x%x%x%x",
> +				 event[6], event[5], event[4],
> +				 event[3], event[2], event[1]);
> +			break;
> +
> +		default:
> +			dev_err(&sdata->client->dev,
> +				"unknown FTS5 event %#02x\n", event[0]);
> +		}
> +	}
> +
> +sync:
> +	input_mt_sync_frame(sdata->input);
> +	input_sync(sdata->input);
> +}
> +
>  static irqreturn_t stmfts_irq_handler(int irq, void *dev)
>  {
>  	struct stmfts_data *sdata = dev;
>  	int err;
>  
>  	guard(mutex)(&sdata->mutex);
>  
>  	err = stmfts_read_events(sdata);
>  	if (unlikely(err))
>  		dev_err(&sdata->client->dev,
>  			"failed to read events: %d\n", err);
>  	else
> -		stmfts_parse_events(sdata);
> +		sdata->ops->parse_events(sdata);
>  
>  	return IRQ_HANDLED;
>  }
>  
>  static int stmfts_command(struct stmfts_data *sdata, const u8 cmd)
>  {
>  	int err;
>  
> @@ -326,16 +508,29 @@ static int stmfts_command(struct stmfts_data *sdata, const u8 cmd)
>  
>  	if (!wait_for_completion_timeout(&sdata->cmd_done,
>  					 msecs_to_jiffies(1000)))
>  		return -ETIMEDOUT;
>  
>  	return 0;
>  }
>  
> +static int stmfts5_set_scan_mode(struct stmfts_data *sdata, const u8 val)
> +{
> +	u8 scan_mode_cmd[3] = { STMFTS5_SET_SCAN_MODE, 0x00, val };
> +	int err;
> +
> +	err = i2c_master_send(sdata->client, scan_mode_cmd,
> +			      sizeof(scan_mode_cmd));
> +	if (err != sizeof(scan_mode_cmd))
> +		return err < 0 ? err : -EIO;
> +
> +	return 0;
> +}
> +
>  static int stmfts_input_open(struct input_dev *dev)
>  {
>  	struct stmfts_data *sdata = input_get_drvdata(dev);
>  	int err;
>  
>  	err = pm_runtime_resume_and_get(&sdata->client->dev);
>  	if (err)
>  		return err;
> @@ -365,16 +560,37 @@ static int stmfts_input_open(struct input_dev *dev)
>  			/* I can still use only the touch screen */
>  			dev_warn(&sdata->client->dev,
>  				 "failed to enable touchkey\n");
>  	}
>  
>  	return 0;
>  }
>  
> +static int stmfts5_input_open(struct input_dev *dev)
> +{
> +	struct stmfts_data *sdata = input_get_drvdata(dev);
> +	int err;
> +
> +	err = pm_runtime_resume_and_get(&sdata->client->dev);
> +	if (err)
> +		return err;
> +
> +	err = stmfts5_set_scan_mode(sdata, 0xff);
> +	if (err) {
> +		pm_runtime_put_sync(&sdata->client->dev);
> +		return err;
> +	}
> +
> +	scoped_guard(mutex, &sdata->mutex)
> +		sdata->running = true;
> +
> +	return 0;

I wonder if input open/close can not be merged into shared
implementations. You already have set_hover() and other helpers...

> +}
> +
>  static void stmfts_input_close(struct input_dev *dev)
>  {
>  	struct stmfts_data *sdata = input_get_drvdata(dev);
>  	int err;
>  
>  	err = i2c_smbus_write_byte(sdata->client, STMFTS_MS_MT_SENSE_OFF);
>  	if (err)
>  		dev_warn(&sdata->client->dev,
> @@ -398,16 +614,32 @@ static void stmfts_input_close(struct input_dev *dev)
>  		if (err)
>  			dev_warn(&sdata->client->dev,
>  				 "failed to disable touchkey: %d\n", err);
>  	}
>  
>  	pm_runtime_put_sync(&sdata->client->dev);
>  }
>  
> +static void stmfts5_input_close(struct input_dev *dev)
> +{
> +	struct stmfts_data *sdata = input_get_drvdata(dev);
> +	int err;
> +
> +	err = stmfts5_set_scan_mode(sdata, 0x00);
> +	if (err)
> +		dev_warn(&sdata->client->dev,
> +			 "failed to disable touchscreen: %d\n", err);
> +
> +	scoped_guard(mutex, &sdata->mutex)
> +		sdata->running = false;
> +
> +	pm_runtime_put_sync(&sdata->client->dev);
> +}
> +
>  static ssize_t stmfts_sysfs_chip_id(struct device *dev,
>  				    struct device_attribute *attr, char *buf)
>  {
>  	struct stmfts_data *sdata = dev_get_drvdata(dev);
>  
>  	return sysfs_emit(buf, "%#x\n", sdata->chip_id);
>  }
>  
> @@ -482,20 +714,18 @@ static ssize_t stmfts_sysfs_hover_enable_write(struct device *dev,
>  	if (kstrtoul(buf, 0, &value))
>  		return -EINVAL;
>  
>  	hover = !!value;
>  
>  	guard(mutex)(&sdata->mutex);
>  
>  	if (hover != sdata->hover_enabled) {
> -		if (sdata->running) {
> -			err = i2c_smbus_write_byte(sdata->client,
> -						   value ? STMFTS_SS_HOVER_SENSE_ON :
> -							   STMFTS_SS_HOVER_SENSE_OFF);
> +		if (sdata->running && sdata->ops->set_hover) {
> +			err = sdata->ops->set_hover(sdata, hover);
>  			if (err)
>  				return err;
>  		}
>  
>  		sdata->hover_enabled = hover;
>  	}
>  
>  	return len;
> @@ -551,16 +781,20 @@ static void stmfts_reset(struct stmfts_data *sdata)
>  	gpiod_set_value_cansleep(sdata->reset_gpio, 0);
>  	msleep(50);
>  }
>  
>  static int stmfts_configure(struct stmfts_data *sdata)
>  {
>  	int err;
>  
> +	err = stmfts_read_system_info(sdata);
> +	if (err)
> +		return err;
> +
>  	err = stmfts_command(sdata, STMFTS_SYSTEM_RESET);
>  	if (err)
>  		return err;
>  
>  	err = stmfts_command(sdata, STMFTS_SLEEP_OUT);
>  	if (err)
>  		return err;
>  
> @@ -596,55 +830,123 @@ static int stmfts_power_on(struct stmfts_data *sdata)
>  	 * The datasheet does not specify the power on time, but considering
>  	 * that the reset time is < 10ms, I sleep 20ms to be sure
>  	 */
>  	msleep(20);
>  
>  	if (sdata->reset_gpio)
>  		stmfts_reset(sdata);
>  
> -	err = stmfts_read_system_info(sdata);
> -	if (err)
> -		goto err_disable_regulators;
> -
>  	enable_irq(sdata->client->irq);
>  
>  	msleep(50);
>  
> -	err = stmfts_configure(sdata);
> +	err = sdata->ops->configure(sdata);
>  	if (err)
>  		goto err_disable_irq;
>  
>  	/*
>  	 * At this point no one is using the touchscreen
>  	 * and I don't really care about the return value
>  	 */
>  	(void)i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
>  
>  	return 0;
>  
>  err_disable_irq:
>  	disable_irq(sdata->client->irq);
> -err_disable_regulators:
>  	regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies), sdata->supplies);
>  	return err;
>  }
>  
> +static int stmfts5_configure(struct stmfts_data *sdata)
> +{
> +	u8 event[STMFTS_EVENT_SIZE];
> +	int err;
> +
> +	/* Verify I2C communication */
> +	err = i2c_smbus_read_i2c_block_data(sdata->client,
> +					    STMFTS_READ_ALL_EVENT,
> +					    sizeof(event), event);
> +	if (err < 0)
> +		return err;
> +
> +	return 0;
> +}
> +
> +static void stmfts5_chip_power_off(struct stmfts_data *sdata)
> +{
> +	i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
> +	msleep(20);
> +}
> +
>  static void stmfts_power_off(void *data)
>  {
>  	struct stmfts_data *sdata = data;
>  
>  	disable_irq(sdata->client->irq);
>  
>  	if (sdata->reset_gpio)
>  		gpiod_set_value_cansleep(sdata->reset_gpio, 1);
>  
> +	if (sdata->ops->power_off)
> +		sdata->ops->power_off(sdata);

As Sashiko mentioned, you want to do this before asserting reset.

> +
>  	regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies), sdata->supplies);
>  }
>  
> +static int stmfts_setup_input(struct stmfts_data *sdata)
> +{
> +	struct device *dev = &sdata->client->dev;
> +
> +	input_set_abs_params(sdata->input, ABS_MT_ORIENTATION, 0, 255, 0, 0);
> +	input_set_abs_params(sdata->input, ABS_DISTANCE, 0, 255, 0, 0);
> +
> +	sdata->use_key = device_property_read_bool(dev, "touch-key-connected");
> +	if (sdata->use_key) {
> +		input_set_capability(sdata->input, EV_KEY, KEY_MENU);
> +		input_set_capability(sdata->input, EV_KEY, KEY_BACK);
> +	}
> +
> +	return input_mt_init_slots(sdata->input, STMFTS_MAX_FINGERS,
> +				   INPUT_MT_DIRECT);
> +}
> +
> +static int stmfts5_setup_input(struct stmfts_data *sdata)
> +{
> +	struct device *dev = &sdata->client->dev;
> +
> +	sdata->mode_switch_gpio = devm_gpiod_get_optional(dev, "mode-switch",
> +							  GPIOD_OUT_HIGH);
> +	if (IS_ERR(sdata->mode_switch_gpio))
> +		return dev_err_probe(dev, PTR_ERR(sdata->mode_switch_gpio),
> +				     "Failed to get GPIO 'switch'\n");
> +
> +	/* Set resolution for accurate calibration */
> +	if (!input_abs_get_res(sdata->input, ABS_MT_POSITION_X)) {
> +		input_abs_set_res(sdata->input, ABS_MT_POSITION_X, 10);
> +		input_abs_set_res(sdata->input, ABS_MT_POSITION_Y, 10);
> +	}
> +
> +	input_set_abs_params(sdata->input, ABS_MT_DISTANCE, 0, 255, 0, 0);
> +
> +	/* Enable stylus support if requested */
> +	sdata->stylus_enabled = device_property_read_bool(dev, "stylus-enabled");
> +
> +	return input_mt_init_slots(sdata->input, STMFTS_MAX_FINGERS,
> +				   INPUT_MT_DIRECT);
> +}
> +
> +static int stmfts_set_hover(struct stmfts_data *sdata, bool enable)
> +{
> +	return i2c_smbus_write_byte(sdata->client,
> +				    enable ? STMFTS_SS_HOVER_SENSE_ON :
> +					     STMFTS_SS_HOVER_SENSE_OFF);
> +}
> +
>  static int stmfts_enable_led(struct stmfts_data *sdata)
>  {
>  	int err;
>  
>  	/* get the regulator for powering the leds on */
>  	sdata->ledvdd = devm_regulator_get(&sdata->client->dev, "ledvdd");
>  	if (IS_ERR(sdata->ledvdd))
>  		return PTR_ERR(sdata->ledvdd);
> @@ -680,16 +982,18 @@ static int stmfts_probe(struct i2c_client *client)
>  		return -ENOMEM;
>  
>  	i2c_set_clientdata(client, sdata);
>  
>  	sdata->client = client;
>  	mutex_init(&sdata->mutex);
>  	init_completion(&sdata->cmd_done);
>  
> +	sdata->ops = of_device_get_match_data(dev);
> +
>  	err = devm_regulator_bulk_get_const(dev,
>  					    ARRAY_SIZE(stmfts_supplies),
>  					    stmfts_supplies,
>  					    &sdata->supplies);
>  	if (err)
>  		return err;
>  
>  	sdata->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> @@ -698,37 +1002,28 @@ static int stmfts_probe(struct i2c_client *client)
>  				     "Failed to get GPIO 'reset'\n");
>  
>  	sdata->input = devm_input_allocate_device(dev);
>  	if (!sdata->input)
>  		return -ENOMEM;
>  
>  	sdata->input->name = STMFTS_DEV_NAME;
>  	sdata->input->id.bustype = BUS_I2C;
> -	sdata->input->open = stmfts_input_open;
> -	sdata->input->close = stmfts_input_close;
> +	sdata->input->open = sdata->ops->input_open;
> +	sdata->input->close = sdata->ops->input_close;
>  
>  	input_set_capability(sdata->input, EV_ABS, ABS_MT_POSITION_X);
>  	input_set_capability(sdata->input, EV_ABS, ABS_MT_POSITION_Y);
>  	touchscreen_parse_properties(sdata->input, true, &sdata->prop);
>  
>  	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_ORIENTATION, 0, 255, 0, 0);
>  	input_set_abs_params(sdata->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
> -	input_set_abs_params(sdata->input, ABS_DISTANCE, 0, 255, 0, 0);
> -
> -	sdata->use_key = device_property_read_bool(dev, "touch-key-connected");
> -	if (sdata->use_key) {
> -		input_set_capability(sdata->input, EV_KEY, KEY_MENU);
> -		input_set_capability(sdata->input, EV_KEY, KEY_BACK);
> -	}
>  
> -	err = input_mt_init_slots(sdata->input,
> -				  STMFTS_MAX_FINGERS, INPUT_MT_DIRECT);
> +	err = sdata->ops->setup_input(sdata);
>  	if (err)
>  		return err;
>  
>  	input_set_drvdata(sdata->input, sdata);
>  
>  	/*
>  	 * stmfts_power_on expects interrupt to be disabled, but
>  	 * at this point the device is still off and I do not trust
> @@ -789,27 +1084,71 @@ static int stmfts_runtime_suspend(struct device *dev)
>  
>  	ret = i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
>  	if (ret)
>  		dev_warn(dev, "failed to suspend device: %d\n", ret);
>  
>  	return ret;
>  }
>  
> +static int stmfts_chip_runtime_resume(struct stmfts_data *sdata)
> +{
> +	return i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_OUT);
> +}
> +
> +static int stmfts5_chip_runtime_resume(struct stmfts_data *sdata)
> +{
> +	struct i2c_client *client = sdata->client;
> +	struct device *dev = &client->dev;
> +	u8 int_enable_cmd[4] = { 0xB6, 0x00, 0x2C, 0x01 };
> +	int err;
> +
> +	err = i2c_smbus_write_byte(client, STMFTS_SLEEP_OUT);
> +	if (err)
> +		return err;
> +
> +	msleep(20);
> +
> +	/* Perform capacitance tuning after wakeup */
> +	err = i2c_smbus_write_byte(client, STMFTS_MS_CX_TUNING);
> +	if (err)
> +		dev_warn(dev, "MS_CX_TUNING failed: %d\n", err);
> +	msleep(20);
> +
> +	err = i2c_smbus_write_byte(client, STMFTS_SS_CX_TUNING);
> +	if (err)
> +		dev_warn(dev, "SS_CX_TUNING failed: %d\n", err);
> +	msleep(20);
> +
> +	/* Force calibration */
> +	err = i2c_smbus_write_byte(client, STMFTS_FULL_FORCE_CALIBRATION);
> +	if (err)
> +		dev_warn(dev, "FORCE_CALIBRATION failed: %d\n", err);
> +	msleep(50);
> +
> +	/* Enable controller interrupts */
> +	err = i2c_master_send(client, int_enable_cmd, sizeof(int_enable_cmd));
> +	if (err != sizeof(int_enable_cmd))
> +		return err < 0 ? err : -EIO;
> +
> +	msleep(20);
> +
> +	return 0;
> +}
> +
>  static int stmfts_runtime_resume(struct device *dev)
>  {
>  	struct stmfts_data *sdata = dev_get_drvdata(dev);
> -	struct i2c_client *client = sdata->client;
> -	int ret;
> +	int err;
>  
> -	ret = i2c_smbus_write_byte(client, STMFTS_SLEEP_OUT);
> -	if (ret)
> -		dev_err(dev, "failed to resume device: %d\n", ret);
> +	err = sdata->ops->runtime_resume(sdata);
> +	if (err)
> +		dev_err(dev, "failed to resume device: %d\n", err);
>  
> -	return ret;
> +	return err;
>  }
>  
>  static int stmfts_suspend(struct device *dev)
>  {
>  	struct stmfts_data *sdata = dev_get_drvdata(dev);
>  
>  	stmfts_power_off(sdata);
>  
> @@ -824,18 +1163,39 @@ static int stmfts_resume(struct device *dev)
>  }
>  
>  static const struct dev_pm_ops stmfts_pm_ops = {
>  	SYSTEM_SLEEP_PM_OPS(stmfts_suspend, stmfts_resume)
>  	RUNTIME_PM_OPS(stmfts_runtime_suspend, stmfts_runtime_resume, NULL)
>  };
>  
>  #ifdef CONFIG_OF
> +static const struct stmfts_chip_ops stmfts4_ops = {
> +	.configure	= stmfts_configure,
> +	.setup_input	= stmfts_setup_input,
> +	.input_open	= stmfts_input_open,
> +	.input_close	= stmfts_input_close,
> +	.parse_events	= stmfts_parse_events,
> +	.set_hover	= stmfts_set_hover,
> +	.runtime_resume	= stmfts_chip_runtime_resume,
> +};
> +
> +static const struct stmfts_chip_ops stmfts5_ops = {
> +	.configure	= stmfts5_configure,
> +	.power_off	= stmfts5_chip_power_off,
> +	.setup_input	= stmfts5_setup_input,
> +	.input_open	= stmfts5_input_open,
> +	.input_close	= stmfts5_input_close,
> +	.parse_events	= stmfts5_parse_events,
> +	.runtime_resume	= stmfts5_chip_runtime_resume,
> +};
> +
>  static const struct of_device_id stmfts_of_match[] = {
> -	{ .compatible = "st,stmfts", },
> +	{ .compatible = "st,stmfts",	.data = &stmfts4_ops },
> +	{ .compatible = "st,stmfts5",	.data = &stmfts5_ops },
>  	{ },
>  };
>  MODULE_DEVICE_TABLE(of, stmfts_of_match);
>  #endif
>  
>  static const struct i2c_device_id stmfts_id[] = {
>  	{ .name = "stmfts" },
>  	{ }
> @@ -853,10 +1213,12 @@ static struct i2c_driver stmfts_driver = {
>  	.probe = stmfts_probe,
>  	.remove = stmfts_remove,
>  	.id_table = stmfts_id,
>  };
>  
>  module_i2c_driver(stmfts_driver);
>  
>  MODULE_AUTHOR("Andi Shyti <[email protected]>");
> +MODULE_AUTHOR("David Heidelberg <[email protected]>");
> +MODULE_AUTHOR("Petr Hodina <[email protected]>");
>  MODULE_DESCRIPTION("STMicroelectronics FTS Touch Screen");
>  MODULE_LICENSE("GPL");
> 


Thanks.

-- 
Dmitry
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.