Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Input: add driver for Hynitron CST816x series

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@gmail.com>
Link: https://lore.kernel.org/r/20250921125939.249788-2-kuzhylol@gmail.com
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>

authored by

Oleh Kuzhylnyi and committed by
Dmitry Torokhov
c87a819b 67c9b685

+266
+12
drivers/input/touchscreen/Kconfig
··· 475 475 To compile this driver as a module, choose M here: the 476 476 module will be called hynitron-cstxxx. 477 477 478 + config TOUCHSCREEN_HYNITRON_CST816X 479 + tristate "Hynitron CST816x touchscreen" 480 + depends on I2C 481 + help 482 + Say Y here if you have a touchscreen using a Hynitron 483 + CST816x series touchscreen controller. 484 + 485 + If unsure, say N. 486 + 487 + To compile this driver as a module, choose M here: the 488 + module will be called hynitron-cst816x. 489 + 478 490 config TOUCHSCREEN_ILI210X 479 491 tristate "Ilitek ILI210X based touchscreen" 480 492 depends on I2C
+1
drivers/input/touchscreen/Makefile
··· 51 51 obj-$(CONFIG_TOUCHSCREEN_HIDEEP) += hideep.o 52 52 obj-$(CONFIG_TOUCHSCREEN_HIMAX_HX852X) += himax_hx852x.o 53 53 obj-$(CONFIG_TOUCHSCREEN_HYNITRON_CSTXXX) += hynitron_cstxxx.o 54 + obj-$(CONFIG_TOUCHSCREEN_HYNITRON_CST816X) += hynitron-cst816x.o 54 55 obj-$(CONFIG_TOUCHSCREEN_ILI210X) += ili210x.o 55 56 obj-$(CONFIG_TOUCHSCREEN_ILITEK) += ilitek_ts_i2c.o 56 57 obj-$(CONFIG_TOUCHSCREEN_IMAGIS) += imagis.o
+253
drivers/input/touchscreen/hynitron-cst816x.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + /* 3 + * Driver for I2C connected Hynitron CST816x Series Touchscreen 4 + * 5 + * Copyright (C) 2025 Oleh Kuzhylnyi <kuzhylol@gmail.com> 6 + */ 7 + 8 + #include <linux/delay.h> 9 + #include <linux/device.h> 10 + #include <linux/err.h> 11 + #include <linux/gpio/consumer.h> 12 + #include <linux/i2c.h> 13 + #include <linux/input.h> 14 + #include <linux/unaligned.h> 15 + #include <linux/interrupt.h> 16 + #include <linux/module.h> 17 + 18 + #define CST816X_RD_REG 0x01 19 + #define CST816X_NUM_KEYS 5 20 + 21 + struct cst816x_touch { 22 + u8 gest; 23 + u8 active; 24 + u16 abs_x; 25 + u16 abs_y; 26 + } __packed; 27 + 28 + struct cst816x_priv { 29 + struct i2c_client *client; 30 + struct gpio_desc *reset; 31 + struct input_dev *input; 32 + unsigned int keycode[CST816X_NUM_KEYS]; 33 + unsigned int keycodemax; 34 + }; 35 + 36 + static int cst816x_parse_keycodes(struct device *dev, struct cst816x_priv *priv) 37 + { 38 + int count; 39 + int error; 40 + 41 + if (device_property_present(dev, "linux,keycodes")) { 42 + count = device_property_count_u32(dev, "linux,keycodes"); 43 + if (count < 0) { 44 + error = count; 45 + dev_err(dev, "failed to count keys: %d\n", error); 46 + return error; 47 + } else if (count > ARRAY_SIZE(priv->keycode)) { 48 + dev_err(dev, "too many keys defined: %d\n", count); 49 + return -EINVAL; 50 + } 51 + priv->keycodemax = count; 52 + 53 + error = device_property_read_u32_array(dev, "linux,keycodes", 54 + priv->keycode, 55 + priv->keycodemax); 56 + if (error) { 57 + dev_err(dev, "failed to read keycodes: %d\n", error); 58 + return error; 59 + } 60 + } 61 + 62 + return 0; 63 + } 64 + 65 + static int cst816x_i2c_read_register(struct cst816x_priv *priv, u8 reg, 66 + void *buf, size_t len) 67 + { 68 + struct i2c_msg xfer[] = { 69 + { 70 + .addr = priv->client->addr, 71 + .flags = 0, 72 + .buf = &reg, 73 + .len = sizeof(reg), 74 + }, 75 + { 76 + .addr = priv->client->addr, 77 + .flags = I2C_M_RD, 78 + .buf = buf, 79 + .len = len, 80 + }, 81 + }; 82 + int error; 83 + int ret; 84 + 85 + ret = i2c_transfer(priv->client->adapter, xfer, ARRAY_SIZE(xfer)); 86 + if (ret != ARRAY_SIZE(xfer)) { 87 + error = ret < 0 ? ret : -EIO; 88 + dev_err(&priv->client->dev, "i2c rx err: %d\n", error); 89 + return error; 90 + } 91 + 92 + return 0; 93 + } 94 + 95 + static u8 cst816x_gest_idx(u8 gest) 96 + { 97 + u8 index; 98 + 99 + switch (gest) { 100 + case 0x01: /* Slide up gesture */ 101 + case 0x02: /* Slide down gesture */ 102 + case 0x03: /* Slide left gesture */ 103 + case 0x04: /* Slide right gesture */ 104 + index = gest; 105 + break; 106 + case 0x0c: /* Long press gesture */ 107 + default: 108 + index = CST816X_NUM_KEYS; 109 + break; 110 + } 111 + 112 + return index - 1; 113 + } 114 + 115 + static bool cst816x_process_touch(struct cst816x_priv *priv, 116 + struct cst816x_touch *tch) 117 + { 118 + if (cst816x_i2c_read_register(priv, CST816X_RD_REG, tch, sizeof(*tch))) 119 + return false; 120 + 121 + tch->abs_x = get_unaligned_be16(&tch->abs_x) & GENMASK(11, 0); 122 + tch->abs_y = get_unaligned_be16(&tch->abs_y) & GENMASK(11, 0); 123 + 124 + dev_dbg(&priv->client->dev, "x: %u, y: %u, t: %u, g: 0x%x\n", 125 + tch->abs_x, tch->abs_y, tch->active, tch->gest); 126 + 127 + return true; 128 + } 129 + 130 + static int cst816x_register_input(struct cst816x_priv *priv) 131 + { 132 + priv->input = devm_input_allocate_device(&priv->client->dev); 133 + if (!priv->input) 134 + return -ENOMEM; 135 + 136 + priv->input->name = "Hynitron CST816x Series Touchscreen"; 137 + priv->input->phys = "input/ts"; 138 + priv->input->id.bustype = BUS_I2C; 139 + 140 + input_set_drvdata(priv->input, priv); 141 + 142 + input_set_abs_params(priv->input, ABS_X, 0, 240, 0, 0); 143 + input_set_abs_params(priv->input, ABS_Y, 0, 240, 0, 0); 144 + input_set_capability(priv->input, EV_KEY, BTN_TOUCH); 145 + 146 + priv->input->keycode = priv->keycode; 147 + priv->input->keycodesize = sizeof(priv->keycode[0]); 148 + priv->input->keycodemax = priv->keycodemax; 149 + 150 + for (int i = 0; i < priv->keycodemax; i++) { 151 + if (priv->keycode[i] == KEY_RESERVED) 152 + continue; 153 + 154 + input_set_capability(priv->input, EV_KEY, priv->keycode[i]); 155 + } 156 + 157 + return input_register_device(priv->input); 158 + } 159 + 160 + static void cst816x_reset(struct cst816x_priv *priv) 161 + { 162 + gpiod_set_value_cansleep(priv->reset, 1); 163 + msleep(50); 164 + gpiod_set_value_cansleep(priv->reset, 0); 165 + msleep(100); 166 + } 167 + 168 + static irqreturn_t cst816x_irq_cb(int irq, void *cookie) 169 + { 170 + struct cst816x_priv *priv = cookie; 171 + struct cst816x_touch tch; 172 + 173 + if (!cst816x_process_touch(priv, &tch)) 174 + return IRQ_HANDLED; 175 + 176 + input_report_abs(priv->input, ABS_X, tch.abs_x); 177 + input_report_abs(priv->input, ABS_Y, tch.abs_y); 178 + 179 + if (tch.gest) 180 + input_report_key(priv->input, 181 + priv->keycode[cst816x_gest_idx(tch.gest)], 182 + tch.active); 183 + 184 + input_report_key(priv->input, BTN_TOUCH, tch.active); 185 + 186 + input_sync(priv->input); 187 + 188 + return IRQ_HANDLED; 189 + } 190 + 191 + static int cst816x_probe(struct i2c_client *client) 192 + { 193 + struct device *dev = &client->dev; 194 + struct cst816x_priv *priv; 195 + int error; 196 + 197 + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 198 + if (!priv) 199 + return -ENOMEM; 200 + 201 + priv->client = client; 202 + 203 + priv->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); 204 + if (IS_ERR(priv->reset)) 205 + return dev_err_probe(dev, PTR_ERR(priv->reset), 206 + "gpio reset request failed\n"); 207 + 208 + if (priv->reset) 209 + cst816x_reset(priv); 210 + 211 + error = cst816x_parse_keycodes(dev, priv); 212 + if (error) 213 + dev_warn(dev, "no gestures found in dt\n"); 214 + 215 + error = cst816x_register_input(priv); 216 + if (error) 217 + return dev_err_probe(dev, error, "input register failed\n"); 218 + 219 + error = devm_request_threaded_irq(dev, client->irq, 220 + NULL, cst816x_irq_cb, IRQF_ONESHOT, 221 + dev_driver_string(dev), priv); 222 + if (error) 223 + return dev_err_probe(dev, error, "irq request failed\n"); 224 + 225 + return 0; 226 + } 227 + 228 + static const struct i2c_device_id cst816x_id[] = { 229 + { .name = "cst816s", 0 }, 230 + { } 231 + }; 232 + MODULE_DEVICE_TABLE(i2c, cst816x_id); 233 + 234 + static const struct of_device_id cst816x_of_match[] = { 235 + { .compatible = "hynitron,cst816s", }, 236 + { } 237 + }; 238 + MODULE_DEVICE_TABLE(of, cst816x_of_match); 239 + 240 + static struct i2c_driver cst816x_driver = { 241 + .driver = { 242 + .name = "cst816x", 243 + .of_match_table = cst816x_of_match, 244 + }, 245 + .id_table = cst816x_id, 246 + .probe = cst816x_probe, 247 + }; 248 + 249 + module_i2c_driver(cst816x_driver); 250 + 251 + MODULE_AUTHOR("Oleh Kuzhylnyi <kuzhylol@gmail.com>"); 252 + MODULE_DESCRIPTION("Hynitron CST816x Series Touchscreen Driver"); 253 + MODULE_LICENSE("GPL");