1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * TSC-40 serial touchscreen driver. It should be compatible with
4 * TSC-10 and 25.
5 *
6 * Author: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
7 */
8
9#include <linux/kernel.h>
10#include <linux/module.h>
11#include <linux/slab.h>
12#include <linux/input.h>
13#include <linux/serio.h>
14
15#define PACKET_LENGTH  5
16struct tsc_ser {
17	struct input_dev *dev;
18	struct serio *serio;
19	u32 idx;
20	unsigned char data[PACKET_LENGTH];
21	char phys[32];
22};
23
24static void tsc_process_data(struct tsc_ser *ptsc)
25{
26	struct input_dev *dev = ptsc->dev;
27	u8 *data = ptsc->data;
28	u32 x;
29	u32 y;
30
31	x = ((data[1] & 0x03) << 8) | data[2];
32	y = ((data[3] & 0x03) << 8) | data[4];
33
34	input_report_abs(dev, ABS_X, x);
35	input_report_abs(dev, ABS_Y, y);
36	input_report_key(dev, BTN_TOUCH, 1);
37
38	input_sync(dev);
39}
40
41static irqreturn_t tsc_interrupt(struct serio *serio,
42		unsigned char data, unsigned int flags)
43{
44	struct tsc_ser *ptsc = serio_get_drvdata(serio);
45	struct input_dev *dev = ptsc->dev;
46
47	ptsc->data[ptsc->idx] = data;
48	switch (ptsc->idx++) {
49	case 0:
50		if (unlikely((data & 0x3e) != 0x10)) {
51			dev_dbg(&serio->dev,
52				"unsynchronized packet start (0x%02x)\n", data);
53			ptsc->idx = 0;
54		} else if (!(data & 0x01)) {
55			input_report_key(dev, BTN_TOUCH, 0);
56			input_sync(dev);
57			ptsc->idx = 0;
58		}
59		break;
60
61	case 1:
62	case 3:
63		if (unlikely(data & 0xfc)) {
64			dev_dbg(&serio->dev,
65				"unsynchronized data 0x%02x at offset %d\n",
66				data, ptsc->idx - 1);
67			ptsc->idx = 0;
68		}
69		break;
70
71	case 4:
72		tsc_process_data(ptsc);
73		ptsc->idx = 0;
74		break;
75	}
76
77	return IRQ_HANDLED;
78}
79
80static int tsc_connect(struct serio *serio, struct serio_driver *drv)
81{
82	struct tsc_ser *ptsc;
83	struct input_dev *input_dev;
84	int error;
85
86	ptsc = kzalloc(sizeof(struct tsc_ser), GFP_KERNEL);
87	input_dev = input_allocate_device();
88	if (!ptsc || !input_dev) {
89		error = -ENOMEM;
90		goto fail1;
91	}
92
93	ptsc->serio = serio;
94	ptsc->dev = input_dev;
95	snprintf(ptsc->phys, sizeof(ptsc->phys), "%s/input0", serio->phys);
96
97	input_dev->name = "TSC-10/25/40 Serial TouchScreen";
98	input_dev->phys = ptsc->phys;
99	input_dev->id.bustype = BUS_RS232;
100	input_dev->id.vendor = SERIO_TSC40;
101	input_dev->id.product = 40;
102	input_dev->id.version = 0x0001;
103	input_dev->dev.parent = &serio->dev;
104
105	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
106	__set_bit(BTN_TOUCH, input_dev->keybit);
107	input_set_abs_params(ptsc->dev, ABS_X, 0, 0x3ff, 0, 0);
108	input_set_abs_params(ptsc->dev, ABS_Y, 0, 0x3ff, 0, 0);
109
110	serio_set_drvdata(serio, ptsc);
111
112	error = serio_open(serio, drv);
113	if (error)
114		goto fail2;
115
116	error = input_register_device(ptsc->dev);
117	if (error)
118		goto fail3;
119
120	return 0;
121
122fail3:
123	serio_close(serio);
124fail2:
125	serio_set_drvdata(serio, NULL);
126fail1:
127	input_free_device(input_dev);
128	kfree(ptsc);
129	return error;
130}
131
132static void tsc_disconnect(struct serio *serio)
133{
134	struct tsc_ser *ptsc = serio_get_drvdata(serio);
135
136	serio_close(serio);
137
138	input_unregister_device(ptsc->dev);
139	kfree(ptsc);
140
141	serio_set_drvdata(serio, NULL);
142}
143
144static const struct serio_device_id tsc_serio_ids[] = {
145	{
146		.type   = SERIO_RS232,
147		.proto  = SERIO_TSC40,
148		.id     = SERIO_ANY,
149		.extra  = SERIO_ANY,
150	},
151	{ 0 }
152};
153MODULE_DEVICE_TABLE(serio, tsc_serio_ids);
154
155#define DRIVER_DESC    "TSC-10/25/40 serial touchscreen driver"
156
157static struct serio_driver tsc_drv = {
158	.driver	= {
159		.name   = "tsc40",
160	},
161	.description    = DRIVER_DESC,
162	.id_table	= tsc_serio_ids,
163	.interrupt      = tsc_interrupt,
164	.connect	= tsc_connect,
165	.disconnect     = tsc_disconnect,
166};
167
168module_serio_driver(tsc_drv);
169
170MODULE_AUTHOR("Sebastian Andrzej Siewior <bigeasy@linutronix.de>");
171MODULE_DESCRIPTION(DRIVER_DESC);
172MODULE_LICENSE("GPL v2");
173