1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Driver for Hynitron cstxxx Touchscreen |
4 | * |
5 | * Copyright (c) 2022 Chris Morgan <macromorgan@hotmail.com> |
6 | * |
7 | * This code is based on hynitron_core.c authored by Hynitron. |
8 | * Note that no datasheet was available, so much of these registers |
9 | * are undocumented. This is essentially a cleaned-up version of the |
10 | * vendor driver with support removed for hardware I cannot test and |
11 | * device-specific functions replated with generic functions wherever |
12 | * possible. |
13 | */ |
14 | |
15 | #include <linux/delay.h> |
16 | #include <linux/err.h> |
17 | #include <linux/gpio/consumer.h> |
18 | #include <linux/i2c.h> |
19 | #include <linux/input.h> |
20 | #include <linux/input/mt.h> |
21 | #include <linux/input/touchscreen.h> |
22 | #include <linux/mod_devicetable.h> |
23 | #include <linux/module.h> |
24 | #include <linux/property.h> |
25 | #include <asm/unaligned.h> |
26 | |
27 | /* Per chip data */ |
28 | struct hynitron_ts_chip_data { |
29 | unsigned int max_touch_num; |
30 | u32 ic_chkcode; |
31 | int (*firmware_info)(struct i2c_client *client); |
32 | int (*bootloader_enter)(struct i2c_client *client); |
33 | int (*init_input)(struct i2c_client *client); |
34 | void (*report_touch)(struct i2c_client *client); |
35 | }; |
36 | |
37 | /* Data generic to all (supported and non-supported) controllers. */ |
38 | struct hynitron_ts_data { |
39 | const struct hynitron_ts_chip_data *chip; |
40 | struct i2c_client *client; |
41 | struct input_dev *input_dev; |
42 | struct touchscreen_properties prop; |
43 | struct gpio_desc *reset_gpio; |
44 | }; |
45 | |
46 | /* |
47 | * Since I have no datasheet, these values are guessed and/or assumed |
48 | * based on observation and testing. |
49 | */ |
50 | #define CST3XX_FIRMWARE_INFO_START_CMD 0x01d1 |
51 | #define CST3XX_FIRMWARE_INFO_END_CMD 0x09d1 |
52 | #define CST3XX_FIRMWARE_CHK_CODE_REG 0xfcd1 |
53 | #define CST3XX_FIRMWARE_VERSION_REG 0x08d2 |
54 | #define CST3XX_FIRMWARE_VER_INVALID_VAL 0xa5a5a5a5 |
55 | |
56 | #define CST3XX_BOOTLDR_PROG_CMD 0xaa01a0 |
57 | #define CST3XX_BOOTLDR_PROG_CHK_REG 0x02a0 |
58 | #define CST3XX_BOOTLDR_CHK_VAL 0xac |
59 | |
60 | #define CST3XX_TOUCH_DATA_PART_REG 0x00d0 |
61 | #define CST3XX_TOUCH_DATA_FULL_REG 0x07d0 |
62 | #define CST3XX_TOUCH_DATA_CHK_VAL 0xab |
63 | #define CST3XX_TOUCH_DATA_TOUCH_VAL 0x03 |
64 | #define CST3XX_TOUCH_DATA_STOP_CMD 0xab00d0 |
65 | #define CST3XX_TOUCH_COUNT_MASK GENMASK(6, 0) |
66 | |
67 | |
68 | /* |
69 | * Hard coded reset delay value of 20ms not IC dependent in |
70 | * vendor driver. |
71 | */ |
72 | static void hyn_reset_proc(struct i2c_client *client, int delay) |
73 | { |
74 | struct hynitron_ts_data *ts_data = i2c_get_clientdata(client); |
75 | |
76 | gpiod_set_value_cansleep(desc: ts_data->reset_gpio, value: 1); |
77 | msleep(msecs: 20); |
78 | gpiod_set_value_cansleep(desc: ts_data->reset_gpio, value: 0); |
79 | if (delay) |
80 | fsleep(usecs: delay * 1000); |
81 | } |
82 | |
83 | static irqreturn_t hyn_interrupt_handler(int irq, void *dev_id) |
84 | { |
85 | struct i2c_client *client = dev_id; |
86 | struct hynitron_ts_data *ts_data = i2c_get_clientdata(client); |
87 | |
88 | ts_data->chip->report_touch(client); |
89 | |
90 | return IRQ_HANDLED; |
91 | } |
92 | |
93 | /* |
94 | * The vendor driver would retry twice before failing to read or write |
95 | * to the i2c device. |
96 | */ |
97 | |
98 | static int cst3xx_i2c_write(struct i2c_client *client, |
99 | unsigned char *buf, int len) |
100 | { |
101 | int ret; |
102 | int retries = 0; |
103 | |
104 | while (retries < 2) { |
105 | ret = i2c_master_send(client, buf, count: len); |
106 | if (ret == len) |
107 | return 0; |
108 | if (ret <= 0) |
109 | retries++; |
110 | else |
111 | break; |
112 | } |
113 | |
114 | return ret < 0 ? ret : -EIO; |
115 | } |
116 | |
117 | static int cst3xx_i2c_read_register(struct i2c_client *client, u16 reg, |
118 | u8 *val, u16 len) |
119 | { |
120 | __le16 buf = cpu_to_le16(reg); |
121 | struct i2c_msg msgs[] = { |
122 | { |
123 | .addr = client->addr, |
124 | .flags = 0, |
125 | .len = 2, |
126 | .buf = (u8 *)&buf, |
127 | }, |
128 | { |
129 | .addr = client->addr, |
130 | .flags = I2C_M_RD, |
131 | .len = len, |
132 | .buf = val, |
133 | } |
134 | }; |
135 | int err; |
136 | int ret; |
137 | |
138 | ret = i2c_transfer(adap: client->adapter, msgs, ARRAY_SIZE(msgs)); |
139 | if (ret == ARRAY_SIZE(msgs)) |
140 | return 0; |
141 | |
142 | err = ret < 0 ? ret : -EIO; |
143 | dev_err(&client->dev, "Error reading %d bytes from 0x%04x: %d (%d)\n" , |
144 | len, reg, err, ret); |
145 | |
146 | return err; |
147 | } |
148 | |
149 | static int cst3xx_firmware_info(struct i2c_client *client) |
150 | { |
151 | struct hynitron_ts_data *ts_data = i2c_get_clientdata(client); |
152 | int err; |
153 | u32 tmp; |
154 | unsigned char buf[4]; |
155 | |
156 | /* |
157 | * Tests suggest this command needed to read firmware regs. |
158 | */ |
159 | put_unaligned_le16(CST3XX_FIRMWARE_INFO_START_CMD, p: buf); |
160 | err = cst3xx_i2c_write(client, buf, len: 2); |
161 | if (err) |
162 | return err; |
163 | |
164 | usleep_range(min: 10000, max: 11000); |
165 | |
166 | /* |
167 | * Read register for check-code to determine if device detected |
168 | * correctly. |
169 | */ |
170 | err = cst3xx_i2c_read_register(client, CST3XX_FIRMWARE_CHK_CODE_REG, |
171 | val: buf, len: 4); |
172 | if (err) |
173 | return err; |
174 | |
175 | tmp = get_unaligned_le32(p: buf); |
176 | if ((tmp & 0xffff0000) != ts_data->chip->ic_chkcode) { |
177 | dev_err(&client->dev, "%s ic mismatch, chkcode is %u\n" , |
178 | __func__, tmp); |
179 | return -ENODEV; |
180 | } |
181 | |
182 | usleep_range(min: 10000, max: 11000); |
183 | |
184 | /* Read firmware version and test if firmware missing. */ |
185 | err = cst3xx_i2c_read_register(client, CST3XX_FIRMWARE_VERSION_REG, |
186 | val: buf, len: 4); |
187 | if (err) |
188 | return err; |
189 | |
190 | tmp = get_unaligned_le32(p: buf); |
191 | if (tmp == CST3XX_FIRMWARE_VER_INVALID_VAL) { |
192 | dev_err(&client->dev, "Device firmware missing\n" ); |
193 | return -ENODEV; |
194 | } |
195 | |
196 | /* |
197 | * Tests suggest cmd required to exit reading firmware regs. |
198 | */ |
199 | put_unaligned_le16(CST3XX_FIRMWARE_INFO_END_CMD, p: buf); |
200 | err = cst3xx_i2c_write(client, buf, len: 2); |
201 | if (err) |
202 | return err; |
203 | |
204 | usleep_range(min: 5000, max: 6000); |
205 | |
206 | return 0; |
207 | } |
208 | |
209 | static int cst3xx_bootloader_enter(struct i2c_client *client) |
210 | { |
211 | int err; |
212 | u8 retry; |
213 | u32 tmp = 0; |
214 | unsigned char buf[3]; |
215 | |
216 | for (retry = 0; retry < 5; retry++) { |
217 | hyn_reset_proc(client, delay: (7 + retry)); |
218 | /* set cmd to enter program mode */ |
219 | put_unaligned_le24(CST3XX_BOOTLDR_PROG_CMD, p: buf); |
220 | err = cst3xx_i2c_write(client, buf, len: 3); |
221 | if (err) |
222 | continue; |
223 | |
224 | usleep_range(min: 2000, max: 2500); |
225 | |
226 | /* check whether in program mode */ |
227 | err = cst3xx_i2c_read_register(client, |
228 | CST3XX_BOOTLDR_PROG_CHK_REG, |
229 | val: buf, len: 1); |
230 | if (err) |
231 | continue; |
232 | |
233 | tmp = get_unaligned(buf); |
234 | if (tmp == CST3XX_BOOTLDR_CHK_VAL) |
235 | break; |
236 | } |
237 | |
238 | if (tmp != CST3XX_BOOTLDR_CHK_VAL) { |
239 | dev_err(&client->dev, "%s unable to enter bootloader mode\n" , |
240 | __func__); |
241 | return -ENODEV; |
242 | } |
243 | |
244 | hyn_reset_proc(client, delay: 40); |
245 | |
246 | return 0; |
247 | } |
248 | |
249 | static void cst3xx_report_contact(struct hynitron_ts_data *ts_data, |
250 | u8 id, unsigned int x, unsigned int y, u8 w) |
251 | { |
252 | input_mt_slot(dev: ts_data->input_dev, slot: id); |
253 | input_mt_report_slot_state(dev: ts_data->input_dev, MT_TOOL_FINGER, active: 1); |
254 | touchscreen_report_pos(input: ts_data->input_dev, prop: &ts_data->prop, x, y, multitouch: true); |
255 | input_report_abs(dev: ts_data->input_dev, ABS_MT_TOUCH_MAJOR, value: w); |
256 | } |
257 | |
258 | static int cst3xx_finish_touch_read(struct i2c_client *client) |
259 | { |
260 | unsigned char buf[3]; |
261 | int err; |
262 | |
263 | put_unaligned_le24(CST3XX_TOUCH_DATA_STOP_CMD, p: buf); |
264 | err = cst3xx_i2c_write(client, buf, len: 3); |
265 | if (err) { |
266 | dev_err(&client->dev, |
267 | "send read touch info ending failed: %d\n" , err); |
268 | return err; |
269 | } |
270 | |
271 | return 0; |
272 | } |
273 | |
274 | /* |
275 | * Handle events from IRQ. Note that for cst3xx it appears that IRQ |
276 | * fires continuously while touched, otherwise once every 1500ms |
277 | * when not touched (assume touchscreen waking up periodically). |
278 | * Note buffer is sized for 5 fingers, if more needed buffer must |
279 | * be increased. The buffer contains 5 bytes for each touch point, |
280 | * a touch count byte, a check byte, and then a second check byte after |
281 | * all other touch points. |
282 | * |
283 | * For example 1 touch would look like this: |
284 | * touch1[5]:touch_count[1]:chk_byte[1] |
285 | * |
286 | * 3 touches would look like this: |
287 | * touch1[5]:touch_count[1]:chk_byte[1]:touch2[5]:touch3[5]:chk_byte[1] |
288 | */ |
289 | static void cst3xx_touch_report(struct i2c_client *client) |
290 | { |
291 | struct hynitron_ts_data *ts_data = i2c_get_clientdata(client); |
292 | u8 buf[28]; |
293 | u8 finger_id, sw, w; |
294 | unsigned int x, y; |
295 | unsigned int touch_cnt, end_byte; |
296 | unsigned int idx = 0; |
297 | unsigned int i; |
298 | int err; |
299 | |
300 | /* Read and validate the first bits of input data. */ |
301 | err = cst3xx_i2c_read_register(client, CST3XX_TOUCH_DATA_PART_REG, |
302 | val: buf, len: 28); |
303 | if (err || |
304 | buf[6] != CST3XX_TOUCH_DATA_CHK_VAL || |
305 | buf[0] == CST3XX_TOUCH_DATA_CHK_VAL) { |
306 | dev_err(&client->dev, "cst3xx touch read failure\n" ); |
307 | return; |
308 | } |
309 | |
310 | /* Report to the device we're done reading the touch data. */ |
311 | err = cst3xx_finish_touch_read(client); |
312 | if (err) |
313 | return; |
314 | |
315 | touch_cnt = buf[5] & CST3XX_TOUCH_COUNT_MASK; |
316 | /* |
317 | * Check the check bit of the last touch slot. The check bit is |
318 | * always present after touch point 1 for valid data, and then |
319 | * appears as the last byte after all other touch data. |
320 | */ |
321 | if (touch_cnt > 1) { |
322 | end_byte = touch_cnt * 5 + 2; |
323 | if (buf[end_byte] != CST3XX_TOUCH_DATA_CHK_VAL) { |
324 | dev_err(&client->dev, "cst3xx touch read failure\n" ); |
325 | return; |
326 | } |
327 | } |
328 | |
329 | /* Parse through the buffer to capture touch data. */ |
330 | for (i = 0; i < touch_cnt; i++) { |
331 | x = ((buf[idx + 1] << 4) | ((buf[idx + 3] >> 4) & 0x0f)); |
332 | y = ((buf[idx + 2] << 4) | (buf[idx + 3] & 0x0f)); |
333 | w = (buf[idx + 4] >> 3); |
334 | sw = (buf[idx] & 0x0f) >> 1; |
335 | finger_id = (buf[idx] >> 4) & 0x0f; |
336 | |
337 | /* Sanity check we don't have more fingers than we expect */ |
338 | if (ts_data->chip->max_touch_num < finger_id) { |
339 | dev_err(&client->dev, "cst3xx touch read failure\n" ); |
340 | break; |
341 | } |
342 | |
343 | /* sw value of 0 means no touch, 0x03 means touch */ |
344 | if (sw == CST3XX_TOUCH_DATA_TOUCH_VAL) |
345 | cst3xx_report_contact(ts_data, id: finger_id, x, y, w); |
346 | |
347 | idx += 5; |
348 | |
349 | /* Skip the 2 bytes between point 1 and point 2 */ |
350 | if (i == 0) |
351 | idx += 2; |
352 | } |
353 | |
354 | input_mt_sync_frame(dev: ts_data->input_dev); |
355 | input_sync(dev: ts_data->input_dev); |
356 | } |
357 | |
358 | static int cst3xx_input_dev_int(struct i2c_client *client) |
359 | { |
360 | struct hynitron_ts_data *ts_data = i2c_get_clientdata(client); |
361 | int err; |
362 | |
363 | ts_data->input_dev = devm_input_allocate_device(&client->dev); |
364 | if (!ts_data->input_dev) { |
365 | dev_err(&client->dev, "Failed to allocate input device\n" ); |
366 | return -ENOMEM; |
367 | } |
368 | |
369 | ts_data->input_dev->name = "Hynitron cst3xx Touchscreen" ; |
370 | ts_data->input_dev->phys = "input/ts" ; |
371 | ts_data->input_dev->id.bustype = BUS_I2C; |
372 | |
373 | input_set_drvdata(dev: ts_data->input_dev, data: ts_data); |
374 | |
375 | input_set_capability(dev: ts_data->input_dev, EV_ABS, ABS_MT_POSITION_X); |
376 | input_set_capability(dev: ts_data->input_dev, EV_ABS, ABS_MT_POSITION_Y); |
377 | input_set_abs_params(dev: ts_data->input_dev, ABS_MT_TOUCH_MAJOR, |
378 | min: 0, max: 255, fuzz: 0, flat: 0); |
379 | |
380 | touchscreen_parse_properties(input: ts_data->input_dev, multitouch: true, prop: &ts_data->prop); |
381 | |
382 | if (!ts_data->prop.max_x || !ts_data->prop.max_y) { |
383 | dev_err(&client->dev, |
384 | "Invalid x/y (%d, %d), using defaults\n" , |
385 | ts_data->prop.max_x, ts_data->prop.max_y); |
386 | ts_data->prop.max_x = 1152; |
387 | ts_data->prop.max_y = 1920; |
388 | input_abs_set_max(dev: ts_data->input_dev, |
389 | ABS_MT_POSITION_X, val: ts_data->prop.max_x); |
390 | input_abs_set_max(dev: ts_data->input_dev, |
391 | ABS_MT_POSITION_Y, val: ts_data->prop.max_y); |
392 | } |
393 | |
394 | err = input_mt_init_slots(dev: ts_data->input_dev, |
395 | num_slots: ts_data->chip->max_touch_num, |
396 | INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); |
397 | if (err) { |
398 | dev_err(&client->dev, |
399 | "Failed to initialize input slots: %d\n" , err); |
400 | return err; |
401 | } |
402 | |
403 | err = input_register_device(ts_data->input_dev); |
404 | if (err) { |
405 | dev_err(&client->dev, |
406 | "Input device registration failed: %d\n" , err); |
407 | return err; |
408 | } |
409 | |
410 | return 0; |
411 | } |
412 | |
413 | static int hyn_probe(struct i2c_client *client) |
414 | { |
415 | struct hynitron_ts_data *ts_data; |
416 | int err; |
417 | |
418 | ts_data = devm_kzalloc(dev: &client->dev, size: sizeof(*ts_data), GFP_KERNEL); |
419 | if (!ts_data) |
420 | return -ENOMEM; |
421 | |
422 | ts_data->client = client; |
423 | i2c_set_clientdata(client, data: ts_data); |
424 | |
425 | ts_data->chip = device_get_match_data(dev: &client->dev); |
426 | if (!ts_data->chip) |
427 | return -EINVAL; |
428 | |
429 | ts_data->reset_gpio = devm_gpiod_get(dev: &client->dev, |
430 | con_id: "reset" , flags: GPIOD_OUT_LOW); |
431 | err = PTR_ERR_OR_ZERO(ptr: ts_data->reset_gpio); |
432 | if (err) { |
433 | dev_err(&client->dev, "request reset gpio failed: %d\n" , err); |
434 | return err; |
435 | } |
436 | |
437 | hyn_reset_proc(client, delay: 60); |
438 | |
439 | err = ts_data->chip->bootloader_enter(client); |
440 | if (err < 0) |
441 | return err; |
442 | |
443 | err = ts_data->chip->init_input(client); |
444 | if (err < 0) |
445 | return err; |
446 | |
447 | err = ts_data->chip->firmware_info(client); |
448 | if (err < 0) |
449 | return err; |
450 | |
451 | err = devm_request_threaded_irq(dev: &client->dev, irq: client->irq, |
452 | NULL, thread_fn: hyn_interrupt_handler, |
453 | IRQF_ONESHOT, |
454 | devname: "Hynitron Touch Int" , dev_id: client); |
455 | if (err) { |
456 | dev_err(&client->dev, "failed to request IRQ: %d\n" , err); |
457 | return err; |
458 | } |
459 | |
460 | return 0; |
461 | } |
462 | |
463 | static const struct hynitron_ts_chip_data cst3xx_data = { |
464 | .max_touch_num = 5, |
465 | .ic_chkcode = 0xcaca0000, |
466 | .firmware_info = &cst3xx_firmware_info, |
467 | .bootloader_enter = &cst3xx_bootloader_enter, |
468 | .init_input = &cst3xx_input_dev_int, |
469 | .report_touch = &cst3xx_touch_report, |
470 | }; |
471 | |
472 | static const struct i2c_device_id hyn_tpd_id[] = { |
473 | { .name = "hynitron_ts" , 0 }, |
474 | { /* sentinel */ }, |
475 | }; |
476 | MODULE_DEVICE_TABLE(i2c, hyn_tpd_id); |
477 | |
478 | static const struct of_device_id hyn_dt_match[] = { |
479 | { .compatible = "hynitron,cst340" , .data = &cst3xx_data }, |
480 | { /* sentinel */ }, |
481 | }; |
482 | MODULE_DEVICE_TABLE(of, hyn_dt_match); |
483 | |
484 | static struct i2c_driver hynitron_i2c_driver = { |
485 | .driver = { |
486 | .name = "Hynitron-TS" , |
487 | .of_match_table = hyn_dt_match, |
488 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
489 | }, |
490 | .id_table = hyn_tpd_id, |
491 | .probe = hyn_probe, |
492 | }; |
493 | |
494 | module_i2c_driver(hynitron_i2c_driver); |
495 | |
496 | MODULE_AUTHOR("Chris Morgan" ); |
497 | MODULE_DESCRIPTION("Hynitron Touchscreen Driver" ); |
498 | MODULE_LICENSE("GPL" ); |
499 | |