[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20250629162523.291887-2-kuzhylol@gmail.com>
Date: Sun, 29 Jun 2025 18:25:23 +0200
From: Oleh Kuzhylnyi <kuzhylol@...il.com>
To: Dmitry Torokhov <dmitry.torokhov@...il.com>
Cc: Rob Herring <robh@...nel.org>,
Krzysztof Kozlowski <krzk@...nel.org>,
Conor Dooley <conor+dt@...nel.org>,
Sasha Finkelstein <fnkl.kernel@...il.com>,
Javier Carrasco <javier.carrasco.cruz@...il.com>,
Linus Walleij <linus.walleij@...aro.org>,
Javier Martinez Canillas <javierm@...hat.com>,
Janne Grunau <j@...nau.net>,
Igor Opaniuk <igor.opaniuk@...il.com>,
Neal Gompa <neal@...pa.dev>,
Jeff LaBundy <jeff@...undy.com>,
Neil Armstrong <neil.armstrong@...aro.org>,
Oleh Kuzhylnyi <kuzhylol@...il.com>,
linux-input@...r.kernel.org,
devicetree@...r.kernel.org,
linux-kernel@...r.kernel.org
Subject: [PATCH v8 2/2] input: add hynitron cst816x series touchscreen
Introduce support for the Hynitron CST816X series touchscreen controller
used for 240×240 1.28-inch Round LCD Display Module manufactured
by Waveshare Electronics. The driver is designed based on an Arduino
implementation marked as under MIT License. This driver is written
for a particular round display based on the CST816S controller, which
is not compatiable with existing driver for Hynitron controllers.
Signed-off-by: Oleh Kuzhylnyi <kuzhylol@...il.com>
---
Changes in v8:
- Add array of Linux input event codes to map gestures in DT bindings
- Reword function names and error messages
- Wrap coordinates reporting between touch press and release
- Minor reword in Kconfig
Changes in v7:
- Update commit based on Jeffs's feedback:
- Move event_map table to Device Tree
- Implement DT parsing functionality
- Use "int" type for iterators
- Get rid of "*dev" entry from private struct
- Use touch_info directly as a buffer for I2C
- Fix coding style tweaks
Changes in v6:
- No code changes
Changes in v5:
- Update commit based on Dmitry's feedback:
- Make GPIO reset optional
- Combine declaration and initialization for i2c_xfer
- Return 0 explicitly where possible
- Rename rc (return code) to error
- Make Touch processing call return boolean
- Improve error handling for i2c_transfer
- Use get_unaligned_be16 for getting coordinates
- Move touch event completeness upper to irq callback
Changes in v4:
- Update commit based on Dmitry's feedback:
- Move abs_x and abs_y to u16
- Remove __packed qualifier for touch_info struct
- Hide tiny touch irq context to stack
- Extend cst816x_i2c_read_register() with buf and buf_size
- Remove loop from event lookup
Changes in v3:
- Drop timer and delayed work
- Process touch in threaded IRQ context
- Fix chip reset sequence
- Move input_register() before devm_request_threaded_irq()
- Re-arrange and document input reporting
- Set u16 data type for event_code
- Remove double tap event to prevent continuous double side sliding
Changes in v2:
- Apply dev_err_probe() for better error handling
- Remove redundant printing, remove dev_warn() message spamming
- Get rid of PM since the touchscreen goes into sleep mode automatically
- Get rid of IRQ control and IRQF_NO_AUTOEN flag
- Reduce timer timeout up to 10ms to handle touch events faster
- Skip registering of non-gesture CST816X_SWIPE event
- Shift input_register_device() as a final call in probe() callback
- Specify name of i2c_device_id explicitly
- Update module description and fix typo
- Add necessary spaces between lines
drivers/input/touchscreen/Kconfig | 12 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/hynitron-cst816x.c | 227 +++++++++++++++++++
3 files changed, 240 insertions(+)
create mode 100644 drivers/input/touchscreen/hynitron-cst816x.c
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 91a2b584dab1..c2c115804c92 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -466,6 +466,18 @@ config TOUCHSCREEN_HYNITRON_CSTXXX
To compile this driver as a module, choose M here: the
module will be called hynitron-cstxxx.
+config TOUCHSCREEN_HYNITRON_CST816X
+ tristate "Hynitron CST816x touchscreen"
+ depends on I2C
+ help
+ Say Y here if you have a touchscreen using a Hynitron
+ CST816x series touchscreen controller.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called hynitron-cst816x.
+
config TOUCHSCREEN_ILI210X
tristate "Ilitek ILI210X based touchscreen"
depends on I2C
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 97a025c6a377..6805ab60f016 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -50,6 +50,7 @@ obj-$(CONFIG_TOUCHSCREEN_GOODIX_BERLIN_I2C) += goodix_berlin_i2c.o
obj-$(CONFIG_TOUCHSCREEN_GOODIX_BERLIN_SPI) += goodix_berlin_spi.o
obj-$(CONFIG_TOUCHSCREEN_HIDEEP) += hideep.o
obj-$(CONFIG_TOUCHSCREEN_HYNITRON_CSTXXX) += hynitron_cstxxx.o
+obj-$(CONFIG_TOUCHSCREEN_HYNITRON_CST816X) += hynitron-cst816x.o
obj-$(CONFIG_TOUCHSCREEN_ILI210X) += ili210x.o
obj-$(CONFIG_TOUCHSCREEN_ILITEK) += ilitek_ts_i2c.o
obj-$(CONFIG_TOUCHSCREEN_IMAGIS) += imagis.o
diff --git a/drivers/input/touchscreen/hynitron-cst816x.c b/drivers/input/touchscreen/hynitron-cst816x.c
new file mode 100644
index 000000000000..e7a235a2b25d
--- /dev/null
+++ b/drivers/input/touchscreen/hynitron-cst816x.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for I2C connected Hynitron CST816x Series Touchscreen
+ *
+ * Copyright (C) 2025 Oleh Kuzhylnyi <kuzhylol@...il.com>
+ */
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/unaligned.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+
+#define CST816X_NUM_KEYS 16
+#define CST816X_RD_REG 0x01
+#define CST816X_TOUCH 0x00
+
+struct cst816x_touch_desc {
+ u8 gesture;
+ u8 touch;
+ __be16 abs_x;
+ __be16 abs_y;
+} __packed;
+
+struct cst816x_priv {
+ struct i2c_client *client;
+ struct gpio_desc *reset;
+ struct input_dev *input;
+ unsigned int keycode[CST816X_NUM_KEYS];
+ unsigned int keycodemax;
+};
+
+static int cst816x_parse_keycodes(struct device *dev, struct cst816x_priv *priv)
+{
+ int ret;
+
+ if (device_property_present(dev, "linux,keycodes")) {
+ ret = device_property_count_u32(dev, "linux,keycodes");
+ if (ret < 0) {
+ dev_err(dev, "failed to count keys: %d\n", ret);
+ return ret;
+ } else if (ret > ARRAY_SIZE(priv->keycode)) {
+ dev_err(dev, "too many keys defined: %d\n", ret);
+ return -EINVAL;
+ }
+ priv->keycodemax = ret;
+
+ ret = device_property_read_u32_array(dev, "linux,keycodes",
+ priv->keycode,
+ priv->keycodemax);
+ if (ret) {
+ dev_err(dev, "failed to read keycodes: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int cst816x_i2c_read_register(struct cst816x_priv *priv, u8 reg,
+ void *buf, size_t len)
+{
+ int ret;
+ struct i2c_msg xfer[] = {
+ {
+ .addr = priv->client->addr,
+ .flags = 0,
+ .buf = ®,
+ .len = sizeof(reg),
+ },
+ {
+ .addr = priv->client->addr,
+ .flags = I2C_M_RD,
+ .buf = buf,
+ .len = len,
+ },
+ };
+
+ ret = i2c_transfer(priv->client->adapter, xfer, ARRAY_SIZE(xfer));
+ if (ret != ARRAY_SIZE(xfer)) {
+ ret = ret < 0 ? ret : -EIO;
+ dev_err(&priv->client->dev, "i2c rx err: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static bool cst816x_process_touch(struct cst816x_priv *priv,
+ struct cst816x_touch_desc *desc)
+{
+ if (cst816x_i2c_read_register(priv, CST816X_RD_REG, desc, sizeof(*desc)))
+ return false;
+
+ desc->abs_x = get_unaligned_be16(&desc->abs_x) & GENMASK(11, 0);
+ desc->abs_y = get_unaligned_be16(&desc->abs_y) & GENMASK(11, 0);
+
+ dev_dbg(&priv->client->dev, "x: %u, y: %u, t: %u, g: 0x%x\n",
+ desc->abs_x, desc->abs_y, desc->touch, desc->gesture);
+
+ return true;
+}
+
+static int cst816x_register_input(struct cst816x_priv *priv)
+{
+ priv->input = devm_input_allocate_device(&priv->client->dev);
+ if (!priv->input)
+ return -ENOMEM;
+
+ priv->input->name = "Hynitron CST816x Series Touchscreen";
+ priv->input->phys = "input/ts";
+ priv->input->id.bustype = BUS_I2C;
+ input_set_drvdata(priv->input, priv);
+
+ input_set_abs_params(priv->input, ABS_X, 0, 240, 0, 0);
+ input_set_abs_params(priv->input, ABS_Y, 0, 240, 0, 0);
+
+ for (int i = 0; i < priv->keycodemax; i++) {
+ if (priv->keycode[i] == KEY_RESERVED)
+ continue;
+
+ input_set_capability(priv->input, EV_KEY, priv->keycode[i]);
+ }
+
+ return input_register_device(priv->input);
+}
+
+static void cst816x_reset(struct cst816x_priv *priv)
+{
+ gpiod_set_value_cansleep(priv->reset, 1);
+ msleep(50);
+ gpiod_set_value_cansleep(priv->reset, 0);
+ msleep(100);
+}
+
+static irqreturn_t cst816x_irq_cb(int irq, void *cookie)
+{
+ struct cst816x_priv *priv = cookie;
+ struct cst816x_touch_desc desc;
+
+ if (!cst816x_process_touch(priv, &desc))
+ return IRQ_HANDLED;
+
+ if (desc.touch) {
+ input_report_key(priv->input, priv->keycode[CST816X_TOUCH], 1);
+ input_report_abs(priv->input, ABS_X, desc.abs_x);
+ input_report_abs(priv->input, ABS_Y, desc.abs_y);
+ }
+
+ if (desc.gesture) {
+ input_report_key(priv->input, priv->keycode[desc.gesture & 0x0F],
+ desc.touch);
+
+ if (!desc.touch)
+ input_report_key(priv->input,
+ priv->keycode[CST816X_TOUCH], 0);
+ }
+
+ input_sync(priv->input);
+
+ return IRQ_HANDLED;
+}
+
+static int cst816x_probe(struct i2c_client *client)
+{
+ struct cst816x_priv *priv;
+ struct device *dev = &client->dev;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->client = client;
+
+ priv->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(priv->reset))
+ return dev_err_probe(dev, PTR_ERR(priv->reset),
+ "gpio reset request failed\n");
+
+ if (priv->reset)
+ cst816x_reset(priv);
+
+ ret = cst816x_parse_keycodes(dev, priv);
+ if (ret)
+ dev_warn(dev, "no gestures found in dt\n");
+
+ ret = cst816x_register_input(priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "input register failed\n");
+
+ ret = devm_request_threaded_irq(dev, client->irq, NULL, cst816x_irq_cb,
+ IRQF_ONESHOT, dev->driver->name, priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "irq request failed\n");
+
+ return 0;
+}
+
+static const struct i2c_device_id cst816x_id[] = {
+ { .name = "cst816s", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, cst816x_id);
+
+static const struct of_device_id cst816x_of_match[] = {
+ { .compatible = "hynitron,cst816s", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, cst816x_of_match);
+
+static struct i2c_driver cst816x_driver = {
+ .driver = {
+ .name = "cst816x",
+ .of_match_table = cst816x_of_match,
+ },
+ .id_table = cst816x_id,
+ .probe = cst816x_probe,
+};
+
+module_i2c_driver(cst816x_driver);
+
+MODULE_AUTHOR("Oleh Kuzhylnyi <kuzhylol@...il.com>");
+MODULE_DESCRIPTION("Hynitron CST816x Series Touchscreen Driver");
+MODULE_LICENSE("GPL");
--
2.34.1
Powered by blists - more mailing lists