lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <20250914134458.2624176-3-ivo.ivanov.ivanov1@gmail.com>
Date: Sun, 14 Sep 2025 16:44:57 +0300
From: Ivaylo Ivanov <ivo.ivanov.ivanov1@...il.com>
To: Krzysztof Kozlowski <krzk@...nel.org>,
	Rob Herring <robh@...nel.org>,
	Dmitry Torokhov <dmitry.torokhov@...il.com>,
	Conor Dooley <conor+dt@...nel.org>,
	Henrik Rydberg <rydberg@...math.org>
Cc: linux-samsung-soc@...r.kernel.org,
	linux-input@...r.kernel.org,
	devicetree@...r.kernel.org,
	linux-arm-kernel@...ts.infradead.org,
	linux-kernel@...r.kernel.org
Subject: [PATCH v1 2/2] Input: s6sa552 - add a driver for the Samsung A552 touchscreen controller

The S6SA552 touchscreen is a capacitive multi-touch controller for
mobile use. It connects via i2c at the address 0x48.

Introduce a basic driver, which can handle initialization, touch events
and power states.

At least the firmware for this IC on Galaxy S7 differs from S6SY761
in register layout and bits, as well as some missing registers/functions,
for example for retrieving the max X/Y coordinates and the amount
of TX/RX channels.

Signed-off-by: Ivaylo Ivanov <ivo.ivanov.ivanov1@...il.com>

---
Samsung almost completely didn't mention the IC name in the vendor
drivers.. I retrieved it from a fw-update function that checks a
firmware bit and matches it with an IC model string. Otherwise
everywhere else it's called sec_ts. I believe the main difference
between samsung touchscreen controllers is the firmware they're
running on, but this one is a bit different from s6sy761, featuring
different registers and bits, as well as some missing features. I
went with calling it s6sa552 to follow the "s6s" convention, because
as far as I know, s6sy661 also exists, and the important part is after
"s6s".
---
 drivers/input/touchscreen/Kconfig   |  11 +
 drivers/input/touchscreen/Makefile  |   1 +
 drivers/input/touchscreen/s6sa552.c | 439 ++++++++++++++++++++++++++++
 3 files changed, 451 insertions(+)
 create mode 100644 drivers/input/touchscreen/s6sa552.c

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 196905162..2d65dd13e 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -505,6 +505,17 @@ config TOUCHSCREEN_IPROC
 	  To compile this driver as a module, choose M here: the
 	  module will be called bcm_iproc_tsc.
 
+config TOUCHSCREEN_S6SA552
+	tristate "Samsung S6SA552 Touchscreen driver"
+	depends on I2C
+	help
+	  Say Y if you have the Samsung S6SA552 driver
+
+	  If unsure, say N
+
+	  To compile this driver as module, choose M here: the
+	  module will be called s6sa552.
+
 config TOUCHSCREEN_S6SY761
 	tristate "Samsung S6SY761 Touchscreen driver"
 	depends on I2C
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 97a025c6a..d3be3e21a 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -77,6 +77,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_RM_TS)		+= raydium_i2c_ts.o
+obj-$(CONFIG_TOUCHSCREEN_S6SA552)	+= s6sa552.o
 obj-$(CONFIG_TOUCHSCREEN_S6SY761)	+= s6sy761.o
 obj-$(CONFIG_TOUCHSCREEN_SILEAD)	+= silead.o
 obj-$(CONFIG_TOUCHSCREEN_SIS_I2C)	+= sis_i2c.o
diff --git a/drivers/input/touchscreen/s6sa552.c b/drivers/input/touchscreen/s6sa552.c
new file mode 100644
index 000000000..32ef338de
--- /dev/null
+++ b/drivers/input/touchscreen/s6sa552.c
@@ -0,0 +1,439 @@
+// SPDX-License-Identifier: GPL-2.0
+// Samsung S6SA552 Touchscreen device driver
+//
+// Copyright (c) 2025 Ivaylo Ivanov <ivo.ivanov.ivanov1@...il.com>
+//
+// Based on the s6sy761 driver:
+//   Copyright (c) 2017 Samsung Electronics Co., Ltd.
+//   Copyright (c) 2017 Andi Shyti <andi@...zian.org>
+
+#include <linux/unaligned.h>
+#include <linux/delay.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/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+
+/* commands */
+#define S6SA552_SENSE_ON		0x40
+#define S6SA552_SENSE_OFF		0x41
+#define S6SA552_TOUCH_FUNCTION		0x63
+#define S6SA552_DEVICE_ID		0x52
+#define S6SA552_BOOT_STATUS		0x55
+#define S6SA552_READ_ONE_EVENT		0x71
+#define S6SA552_CLEAR_EVENT_STACK	0x60
+#define S6SA552_PANEL_INFO		0x23
+
+/* acknowledge events */
+#define S6SA552_EVENT_ACK_BOOT		0x0c
+
+/* status event types */
+#define S6SA552_EVENT_TYPE_ACK		0x01
+#define S6SA552_EVENT_TYPE_ERR		0x02
+#define S6SA552_EVENT_TYPE_INFO		0x03
+#define S6SA552_EVENT_TYPE_GEST		0x06
+#define S6SA552_EVENT_TYPE_SPONGE	0x5a
+
+/* boot status (BS) */
+#define S6SA552_BS_APPLICATION		0x20
+
+/* event id */
+#define S6SA552_EVENT_ID_COORDINATE	0x01
+#define S6SA552_EVENT_ID_STATUS		0x00
+
+/* event register masks */
+#define S6SA552_MASK_TID		0x0f  /* byte 1, lower 4 bits */
+#define S6SA552_MASK_NT			0xf0  /* byte 1, upper 4 bits */
+#define S6SA552_MASK_EID		0xc0  /* byte 0, bits 6-7 */
+#define S6SA552_MASK_TOUCH_STATE	0x07  /* byte 0, bits 0-2 */
+#define S6SA552_MASK_TOUCH_TYPE		0x38  /* byte 0, bits 3-5 */
+
+/* touch states */
+#define S6SA552_TS_NONE			0x00
+#define S6SA552_TS_PRESS		0x01
+#define S6SA552_TS_MOVE			0x02
+#define S6SA552_TS_RELEASE		0x03
+
+#define S6SA552_EVENT_SIZE		8
+#define S6SA552_DEVID_SIZE		3
+#define S6SA552_PANEL_ID_SIZE		11
+#define S6SA552_MAX_FINGERS		10
+
+/*
+ * Hardcoded values, since at least on herolte, the subid register doesn't
+ * read/exist.
+ */
+#define S6SA552_MAX_X			4095
+#define S6SA552_MAX_Y			4095
+#define S6SA552_TX_CHANNELS		16
+
+#define S6SA552_DEV_NAME		"s6sa552"
+
+enum s6sa552_regulators {
+	S6SA552_REGULATOR_VDD,
+	S6SA552_REGULATOR_AVDD,
+};
+
+struct s6sa552_data {
+	struct i2c_client *client;
+	struct regulator_bulk_data regulators[2];
+	struct input_dev *input;
+	struct touchscreen_properties prop;
+
+	u8 data[S6SA552_EVENT_SIZE];
+
+	u16 devid;
+	u8 tx_channel;
+};
+
+static void s6sa552_report_coordinates(struct s6sa552_data *sdata,
+				       u8 *event, u8 tid)
+{
+	u8 major = event[6];
+	u8 minor = event[7];
+	u8 z = event[5];
+	u16 x = (event[2] << 4) | ((event[4] >> 4) & 0x0F);
+	u16 y = (event[3] << 4) | (event[4] & 0x0F);
+
+	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 s6sa552_report_release(struct s6sa552_data *sdata, 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 s6sa552_handle_coordinates(struct s6sa552_data *sdata, u8 *event)
+{
+	u8 tid;
+	u8 touch_state;
+
+	if (unlikely(!(event[1] & S6SA552_MASK_TID)))
+		return;
+
+	tid = (event[1] & S6SA552_MASK_TID) - 1;
+	touch_state = event[0] & S6SA552_MASK_TOUCH_STATE;
+
+	switch (touch_state) {
+	case S6SA552_TS_PRESS:
+	case S6SA552_TS_MOVE:
+		s6sa552_report_coordinates(sdata, event, tid);
+		break;
+	case S6SA552_TS_RELEASE:
+		s6sa552_report_release(sdata, tid);
+		break;
+	case S6SA552_TS_NONE:
+	default:
+		break;
+	}
+}
+
+static irqreturn_t s6sa552_irq_handler(int irq, void *dev)
+{
+	struct s6sa552_data *sdata = dev;
+	int ret;
+	u8 event_id;
+
+	ret = i2c_smbus_read_i2c_block_data(sdata->client,
+					    S6SA552_READ_ONE_EVENT,
+					    S6SA552_EVENT_SIZE,
+					    sdata->data);
+	if (ret < 0) {
+		dev_err(&sdata->client->dev, "failed to read event\n");
+		return IRQ_HANDLED;
+	}
+
+	if (!sdata->data[0])
+		return IRQ_HANDLED;
+
+	event_id = sdata->data[0] >> 6;
+
+	switch (event_id) {
+	case S6SA552_EVENT_ID_COORDINATE:
+		s6sa552_handle_coordinates(sdata, sdata->data);
+		break;
+	case S6SA552_EVENT_ID_STATUS:
+		break;
+	default:
+		break;
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int s6sa552_input_open(struct input_dev *dev)
+{
+	struct s6sa552_data *sdata = input_get_drvdata(dev);
+
+	return i2c_smbus_write_byte(sdata->client, S6SA552_SENSE_ON);
+}
+
+static void s6sa552_input_close(struct input_dev *dev)
+{
+	struct s6sa552_data *sdata = input_get_drvdata(dev);
+	int ret;
+
+	ret = i2c_smbus_write_byte(sdata->client, S6SA552_SENSE_OFF);
+	if (ret)
+		dev_err(&sdata->client->dev, "failed to turn off sensing\n");
+}
+
+static ssize_t s6sa552_sysfs_devid(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct s6sa552_data *sdata = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%#x\n", sdata->devid);
+}
+
+static DEVICE_ATTR(devid, 0444, s6sa552_sysfs_devid, NULL);
+
+static struct attribute *s6sa552_sysfs_attrs[] = {
+	&dev_attr_devid.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(s6sa552_sysfs);
+
+static int s6sa552_power_on(struct s6sa552_data *sdata)
+{
+	u8 buffer[S6SA552_EVENT_SIZE];
+	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,
+					    S6SA552_READ_ONE_EVENT,
+					    S6SA552_EVENT_SIZE,
+					    buffer);
+	if (ret < 0)
+		return ret;
+
+	if (buffer[0] != S6SA552_EVENT_TYPE_ACK ||
+	    buffer[1] != S6SA552_EVENT_ACK_BOOT) {
+		return -ENODEV;
+	}
+
+	ret = i2c_smbus_read_byte_data(sdata->client, S6SA552_BOOT_STATUS);
+	if (ret < 0)
+		return ret;
+
+	/* for some reasons the device might be stuck in the bootloader */
+	if (ret != S6SA552_BS_APPLICATION)
+		return -ENODEV;
+
+	/* enable touch functionality */
+	ret = i2c_smbus_write_byte_data(sdata->client,
+					S6SA552_TOUCH_FUNCTION, 0x01);
+	if (ret)
+		return ret;
+
+	mdelay(20); /* make sure everything is up */
+
+	return 0;
+}
+
+static int s6sa552_hw_init(struct s6sa552_data *sdata)
+{
+	u8 buffer[S6SA552_DEVID_SIZE];
+	int ret;
+
+	ret = s6sa552_power_on(sdata);
+	if (ret)
+		return ret;
+
+	ret = i2c_smbus_read_i2c_block_data(sdata->client,
+					    S6SA552_DEVICE_ID,
+					    S6SA552_DEVID_SIZE,
+					    buffer);
+	if (ret < 0)
+		return ret;
+
+	sdata->devid = get_unaligned_be16(buffer + 1);
+
+	return 0;
+}
+
+static void s6sa552_power_off(void *data)
+{
+	struct s6sa552_data *sdata = data;
+
+	disable_irq(sdata->client->irq);
+	regulator_bulk_disable(ARRAY_SIZE(sdata->regulators),
+			       sdata->regulators);
+}
+
+static int s6sa552_probe(struct i2c_client *client)
+{
+	struct s6sa552_data *sdata;
+	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[S6SA552_REGULATOR_VDD].supply = "vdd";
+	sdata->regulators[S6SA552_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, s6sa552_power_off, sdata);
+	if (err)
+		return err;
+
+	err = s6sa552_hw_init(sdata);
+	if (err)
+		return err;
+
+	sdata->input = devm_input_allocate_device(&client->dev);
+	if (!sdata->input)
+		return -ENOMEM;
+
+	sdata->input->name = S6SA552_DEV_NAME;
+	sdata->input->id.bustype = BUS_I2C;
+	sdata->input->open = s6sa552_input_open;
+	sdata->input->close = s6sa552_input_close;
+
+	input_set_abs_params(sdata->input, ABS_MT_POSITION_X, 0, S6SA552_MAX_X,
+			     0, 0);
+	input_set_abs_params(sdata->input, ABS_MT_POSITION_Y, 0, S6SA552_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_PRESSURE, 0, 255, 0, 0);
+
+	touchscreen_parse_properties(sdata->input, true, &sdata->prop);
+
+	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 = input_mt_init_slots(sdata->input, S6SA552_TX_CHANNELS,
+				  INPUT_MT_DIRECT);
+	if (err)
+		return err;
+
+	input_set_drvdata(sdata->input, sdata);
+
+	err = input_register_device(sdata->input);
+	if (err)
+		return err;
+
+	err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+					s6sa552_irq_handler,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					"s6sa552_irq", sdata);
+	if (err)
+		return err;
+
+	pm_runtime_enable(&client->dev);
+
+	return 0;
+}
+
+static void s6sa552_remove(struct i2c_client *client)
+{
+	pm_runtime_disable(&client->dev);
+}
+
+static int s6sa552_runtime_suspend(struct device *dev)
+{
+	struct s6sa552_data *sdata = dev_get_drvdata(dev);
+
+	return i2c_smbus_write_byte(sdata->client, S6SA552_SENSE_OFF);
+}
+
+static int s6sa552_runtime_resume(struct device *dev)
+{
+	struct s6sa552_data *sdata = dev_get_drvdata(dev);
+
+	return i2c_smbus_write_byte(sdata->client, S6SA552_SENSE_ON);
+}
+
+static int s6sa552_suspend(struct device *dev)
+{
+	struct s6sa552_data *sdata = dev_get_drvdata(dev);
+
+	s6sa552_power_off(sdata);
+
+	return 0;
+}
+
+static int s6sa552_resume(struct device *dev)
+{
+	struct s6sa552_data *sdata = dev_get_drvdata(dev);
+
+	enable_irq(sdata->client->irq);
+
+	return s6sa552_power_on(sdata);
+}
+
+static const struct dev_pm_ops s6sa552_pm_ops = {
+	SYSTEM_SLEEP_PM_OPS(s6sa552_suspend, s6sa552_resume)
+	RUNTIME_PM_OPS(s6sa552_runtime_suspend, s6sa552_runtime_resume, NULL)
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id s6sa552_of_match[] = {
+	{ .compatible = "samsung,s6sa552", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, s6sa552_of_match);
+#endif
+
+static const struct i2c_device_id s6sa552_id[] = {
+	{ "s6sa552" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, s6sa552_id);
+
+static struct i2c_driver s6sa552_driver = {
+	.driver = {
+		.name = S6SA552_DEV_NAME,
+		.dev_groups = s6sa552_sysfs_groups,
+		.of_match_table = of_match_ptr(s6sa552_of_match),
+		.pm = pm_ptr(&s6sa552_pm_ops),
+	},
+	.probe = s6sa552_probe,
+	.remove = s6sa552_remove,
+	.id_table = s6sa552_id,
+};
+
+module_i2c_driver(s6sa552_driver);
+
+MODULE_AUTHOR("Ivaylo Ivanov <ivo.ivanov.ivanov1@...il.com>");
+MODULE_DESCRIPTION("Samsung S6SA552 Touch Screen");
+MODULE_LICENSE("GPL");
-- 
2.43.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ