Loading...
1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * TQC PS/2 Multiplexer driver
4 *
5 * Copyright (C) 2010 Dmitry Eremin-Solenikov
6 */
7
8
9#include <linux/kernel.h>
10#include <linux/slab.h>
11#include <linux/module.h>
12#include <linux/serio.h>
13
14MODULE_AUTHOR("Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>");
15MODULE_DESCRIPTION("TQC PS/2 Multiplexer driver");
16MODULE_LICENSE("GPL");
17
18#define PS2MULT_KB_SELECTOR 0xA0
19#define PS2MULT_MS_SELECTOR 0xA1
20#define PS2MULT_ESCAPE 0x7D
21#define PS2MULT_BSYNC 0x7E
22#define PS2MULT_SESSION_START 0x55
23#define PS2MULT_SESSION_END 0x56
24
25struct ps2mult_port {
26 struct serio *serio;
27 unsigned char sel;
28 bool registered;
29};
30
31#define PS2MULT_NUM_PORTS 2
32#define PS2MULT_KBD_PORT 0
33#define PS2MULT_MOUSE_PORT 1
34
35struct ps2mult {
36 struct serio *mx_serio;
37 struct ps2mult_port ports[PS2MULT_NUM_PORTS];
38
39 spinlock_t lock;
40 struct ps2mult_port *in_port;
41 struct ps2mult_port *out_port;
42 bool escape;
43};
44
45/* First MUST come PS2MULT_NUM_PORTS selectors */
46static const unsigned char ps2mult_controls[] = {
47 PS2MULT_KB_SELECTOR, PS2MULT_MS_SELECTOR,
48 PS2MULT_ESCAPE, PS2MULT_BSYNC,
49 PS2MULT_SESSION_START, PS2MULT_SESSION_END,
50};
51
52static const struct serio_device_id ps2mult_serio_ids[] = {
53 {
54 .type = SERIO_RS232,
55 .proto = SERIO_PS2MULT,
56 .id = SERIO_ANY,
57 .extra = SERIO_ANY,
58 },
59 { 0 }
60};
61
62MODULE_DEVICE_TABLE(serio, ps2mult_serio_ids);
63
64static void ps2mult_select_port(struct ps2mult *psm, struct ps2mult_port *port)
65{
66 struct serio *mx_serio = psm->mx_serio;
67
68 serio_write(mx_serio, port->sel);
69 psm->out_port = port;
70 dev_dbg(&mx_serio->dev, "switched to sel %02x\n", port->sel);
71}
72
73static int ps2mult_serio_write(struct serio *serio, unsigned char data)
74{
75 struct serio *mx_port = serio->parent;
76 struct ps2mult *psm = serio_get_drvdata(mx_port);
77 struct ps2mult_port *port = serio->port_data;
78 bool need_escape;
79 unsigned long flags;
80
81 spin_lock_irqsave(&psm->lock, flags);
82
83 if (psm->out_port != port)
84 ps2mult_select_port(psm, port);
85
86 need_escape = memchr(ps2mult_controls, data, sizeof(ps2mult_controls));
87
88 dev_dbg(&serio->dev,
89 "write: %s%02x\n", need_escape ? "ESC " : "", data);
90
91 if (need_escape)
92 serio_write(mx_port, PS2MULT_ESCAPE);
93
94 serio_write(mx_port, data);
95
96 spin_unlock_irqrestore(&psm->lock, flags);
97
98 return 0;
99}
100
101static int ps2mult_serio_start(struct serio *serio)
102{
103 struct ps2mult *psm = serio_get_drvdata(serio->parent);
104 struct ps2mult_port *port = serio->port_data;
105 unsigned long flags;
106
107 spin_lock_irqsave(&psm->lock, flags);
108 port->registered = true;
109 spin_unlock_irqrestore(&psm->lock, flags);
110
111 return 0;
112}
113
114static void ps2mult_serio_stop(struct serio *serio)
115{
116 struct ps2mult *psm = serio_get_drvdata(serio->parent);
117 struct ps2mult_port *port = serio->port_data;
118 unsigned long flags;
119
120 spin_lock_irqsave(&psm->lock, flags);
121 port->registered = false;
122 spin_unlock_irqrestore(&psm->lock, flags);
123}
124
125static int ps2mult_create_port(struct ps2mult *psm, int i)
126{
127 struct serio *mx_serio = psm->mx_serio;
128 struct serio *serio;
129
130 serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
131 if (!serio)
132 return -ENOMEM;
133
134 strscpy(serio->name, "TQC PS/2 Multiplexer", sizeof(serio->name));
135 snprintf(serio->phys, sizeof(serio->phys),
136 "%s/port%d", mx_serio->phys, i);
137 serio->id.type = SERIO_8042;
138 serio->write = ps2mult_serio_write;
139 serio->start = ps2mult_serio_start;
140 serio->stop = ps2mult_serio_stop;
141 serio->parent = psm->mx_serio;
142 serio->port_data = &psm->ports[i];
143
144 psm->ports[i].serio = serio;
145
146 return 0;
147}
148
149static void ps2mult_reset(struct ps2mult *psm)
150{
151 unsigned long flags;
152
153 spin_lock_irqsave(&psm->lock, flags);
154
155 serio_write(psm->mx_serio, PS2MULT_SESSION_END);
156 serio_write(psm->mx_serio, PS2MULT_SESSION_START);
157
158 ps2mult_select_port(psm, &psm->ports[PS2MULT_KBD_PORT]);
159
160 spin_unlock_irqrestore(&psm->lock, flags);
161}
162
163static int ps2mult_connect(struct serio *serio, struct serio_driver *drv)
164{
165 struct ps2mult *psm;
166 int i;
167 int error;
168
169 if (!serio->write)
170 return -EINVAL;
171
172 psm = kzalloc(sizeof(*psm), GFP_KERNEL);
173 if (!psm)
174 return -ENOMEM;
175
176 spin_lock_init(&psm->lock);
177 psm->mx_serio = serio;
178
179 for (i = 0; i < PS2MULT_NUM_PORTS; i++) {
180 psm->ports[i].sel = ps2mult_controls[i];
181 error = ps2mult_create_port(psm, i);
182 if (error)
183 goto err_out;
184 }
185
186 psm->in_port = psm->out_port = &psm->ports[PS2MULT_KBD_PORT];
187
188 serio_set_drvdata(serio, psm);
189 error = serio_open(serio, drv);
190 if (error)
191 goto err_out;
192
193 ps2mult_reset(psm);
194
195 for (i = 0; i < PS2MULT_NUM_PORTS; i++) {
196 struct serio *s = psm->ports[i].serio;
197
198 dev_info(&serio->dev, "%s port at %s\n", s->name, serio->phys);
199 serio_register_port(s);
200 }
201
202 return 0;
203
204err_out:
205 while (--i >= 0)
206 kfree(psm->ports[i].serio);
207 kfree(psm);
208 return error;
209}
210
211static void ps2mult_disconnect(struct serio *serio)
212{
213 struct ps2mult *psm = serio_get_drvdata(serio);
214
215 /* Note that serio core already take care of children ports */
216 serio_write(serio, PS2MULT_SESSION_END);
217 serio_close(serio);
218 kfree(psm);
219
220 serio_set_drvdata(serio, NULL);
221}
222
223static int ps2mult_reconnect(struct serio *serio)
224{
225 struct ps2mult *psm = serio_get_drvdata(serio);
226
227 ps2mult_reset(psm);
228
229 return 0;
230}
231
232static irqreturn_t ps2mult_interrupt(struct serio *serio,
233 unsigned char data, unsigned int dfl)
234{
235 struct ps2mult *psm = serio_get_drvdata(serio);
236 struct ps2mult_port *in_port;
237 unsigned long flags;
238
239 dev_dbg(&serio->dev, "Received %02x flags %02x\n", data, dfl);
240
241 spin_lock_irqsave(&psm->lock, flags);
242
243 if (psm->escape) {
244 psm->escape = false;
245 in_port = psm->in_port;
246 if (in_port->registered)
247 serio_interrupt(in_port->serio, data, dfl);
248 goto out;
249 }
250
251 switch (data) {
252 case PS2MULT_ESCAPE:
253 dev_dbg(&serio->dev, "ESCAPE\n");
254 psm->escape = true;
255 break;
256
257 case PS2MULT_BSYNC:
258 dev_dbg(&serio->dev, "BSYNC\n");
259 psm->in_port = psm->out_port;
260 break;
261
262 case PS2MULT_SESSION_START:
263 dev_dbg(&serio->dev, "SS\n");
264 break;
265
266 case PS2MULT_SESSION_END:
267 dev_dbg(&serio->dev, "SE\n");
268 break;
269
270 case PS2MULT_KB_SELECTOR:
271 dev_dbg(&serio->dev, "KB\n");
272 psm->in_port = &psm->ports[PS2MULT_KBD_PORT];
273 break;
274
275 case PS2MULT_MS_SELECTOR:
276 dev_dbg(&serio->dev, "MS\n");
277 psm->in_port = &psm->ports[PS2MULT_MOUSE_PORT];
278 break;
279
280 default:
281 in_port = psm->in_port;
282 if (in_port->registered)
283 serio_interrupt(in_port->serio, data, dfl);
284 break;
285 }
286
287 out:
288 spin_unlock_irqrestore(&psm->lock, flags);
289 return IRQ_HANDLED;
290}
291
292static struct serio_driver ps2mult_drv = {
293 .driver = {
294 .name = "ps2mult",
295 },
296 .description = "TQC PS/2 Multiplexer driver",
297 .id_table = ps2mult_serio_ids,
298 .interrupt = ps2mult_interrupt,
299 .connect = ps2mult_connect,
300 .disconnect = ps2mult_disconnect,
301 .reconnect = ps2mult_reconnect,
302};
303
304module_serio_driver(ps2mult_drv);
1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * TQC PS/2 Multiplexer driver
4 *
5 * Copyright (C) 2010 Dmitry Eremin-Solenikov
6 */
7
8
9#include <linux/kernel.h>
10#include <linux/slab.h>
11#include <linux/module.h>
12#include <linux/serio.h>
13
14MODULE_AUTHOR("Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>");
15MODULE_DESCRIPTION("TQC PS/2 Multiplexer driver");
16MODULE_LICENSE("GPL");
17
18#define PS2MULT_KB_SELECTOR 0xA0
19#define PS2MULT_MS_SELECTOR 0xA1
20#define PS2MULT_ESCAPE 0x7D
21#define PS2MULT_BSYNC 0x7E
22#define PS2MULT_SESSION_START 0x55
23#define PS2MULT_SESSION_END 0x56
24
25struct ps2mult_port {
26 struct serio *serio;
27 unsigned char sel;
28 bool registered;
29};
30
31#define PS2MULT_NUM_PORTS 2
32#define PS2MULT_KBD_PORT 0
33#define PS2MULT_MOUSE_PORT 1
34
35struct ps2mult {
36 struct serio *mx_serio;
37 struct ps2mult_port ports[PS2MULT_NUM_PORTS];
38
39 spinlock_t lock;
40 struct ps2mult_port *in_port;
41 struct ps2mult_port *out_port;
42 bool escape;
43};
44
45/* First MUST come PS2MULT_NUM_PORTS selectors */
46static const unsigned char ps2mult_controls[] = {
47 PS2MULT_KB_SELECTOR, PS2MULT_MS_SELECTOR,
48 PS2MULT_ESCAPE, PS2MULT_BSYNC,
49 PS2MULT_SESSION_START, PS2MULT_SESSION_END,
50};
51
52static const struct serio_device_id ps2mult_serio_ids[] = {
53 {
54 .type = SERIO_RS232,
55 .proto = SERIO_PS2MULT,
56 .id = SERIO_ANY,
57 .extra = SERIO_ANY,
58 },
59 { 0 }
60};
61
62MODULE_DEVICE_TABLE(serio, ps2mult_serio_ids);
63
64static void ps2mult_select_port(struct ps2mult *psm, struct ps2mult_port *port)
65{
66 struct serio *mx_serio = psm->mx_serio;
67
68 serio_write(mx_serio, port->sel);
69 psm->out_port = port;
70 dev_dbg(&mx_serio->dev, "switched to sel %02x\n", port->sel);
71}
72
73static int ps2mult_serio_write(struct serio *serio, unsigned char data)
74{
75 struct serio *mx_port = serio->parent;
76 struct ps2mult *psm = serio_get_drvdata(mx_port);
77 struct ps2mult_port *port = serio->port_data;
78 bool need_escape;
79
80 guard(spinlock_irqsave)(&psm->lock);
81
82 if (psm->out_port != port)
83 ps2mult_select_port(psm, port);
84
85 need_escape = memchr(ps2mult_controls, data, sizeof(ps2mult_controls));
86
87 dev_dbg(&serio->dev,
88 "write: %s%02x\n", need_escape ? "ESC " : "", data);
89
90 if (need_escape)
91 serio_write(mx_port, PS2MULT_ESCAPE);
92
93 serio_write(mx_port, data);
94
95 return 0;
96}
97
98static int ps2mult_serio_start(struct serio *serio)
99{
100 struct ps2mult *psm = serio_get_drvdata(serio->parent);
101 struct ps2mult_port *port = serio->port_data;
102
103 guard(spinlock_irqsave)(&psm->lock);
104
105 port->registered = true;
106
107 return 0;
108}
109
110static void ps2mult_serio_stop(struct serio *serio)
111{
112 struct ps2mult *psm = serio_get_drvdata(serio->parent);
113 struct ps2mult_port *port = serio->port_data;
114
115 guard(spinlock_irqsave)(&psm->lock);
116
117 port->registered = false;
118}
119
120static int ps2mult_create_port(struct ps2mult *psm, int i)
121{
122 struct serio *mx_serio = psm->mx_serio;
123 struct serio *serio;
124
125 serio = kzalloc(sizeof(*serio), GFP_KERNEL);
126 if (!serio)
127 return -ENOMEM;
128
129 strscpy(serio->name, "TQC PS/2 Multiplexer", sizeof(serio->name));
130 snprintf(serio->phys, sizeof(serio->phys),
131 "%s/port%d", mx_serio->phys, i);
132 serio->id.type = SERIO_8042;
133 serio->write = ps2mult_serio_write;
134 serio->start = ps2mult_serio_start;
135 serio->stop = ps2mult_serio_stop;
136 serio->parent = psm->mx_serio;
137 serio->port_data = &psm->ports[i];
138
139 psm->ports[i].serio = serio;
140
141 return 0;
142}
143
144static void ps2mult_reset(struct ps2mult *psm)
145{
146 guard(spinlock_irqsave)(&psm->lock);
147
148 serio_write(psm->mx_serio, PS2MULT_SESSION_END);
149 serio_write(psm->mx_serio, PS2MULT_SESSION_START);
150
151 ps2mult_select_port(psm, &psm->ports[PS2MULT_KBD_PORT]);
152}
153
154static int ps2mult_connect(struct serio *serio, struct serio_driver *drv)
155{
156 struct ps2mult *psm;
157 int i;
158 int error;
159
160 if (!serio->write)
161 return -EINVAL;
162
163 psm = kzalloc(sizeof(*psm), GFP_KERNEL);
164 if (!psm)
165 return -ENOMEM;
166
167 spin_lock_init(&psm->lock);
168 psm->mx_serio = serio;
169
170 for (i = 0; i < PS2MULT_NUM_PORTS; i++) {
171 psm->ports[i].sel = ps2mult_controls[i];
172 error = ps2mult_create_port(psm, i);
173 if (error)
174 goto err_out;
175 }
176
177 psm->in_port = psm->out_port = &psm->ports[PS2MULT_KBD_PORT];
178
179 serio_set_drvdata(serio, psm);
180 error = serio_open(serio, drv);
181 if (error)
182 goto err_out;
183
184 ps2mult_reset(psm);
185
186 for (i = 0; i < PS2MULT_NUM_PORTS; i++) {
187 struct serio *s = psm->ports[i].serio;
188
189 dev_info(&serio->dev, "%s port at %s\n", s->name, serio->phys);
190 serio_register_port(s);
191 }
192
193 return 0;
194
195err_out:
196 while (--i >= 0)
197 kfree(psm->ports[i].serio);
198 kfree(psm);
199 return error;
200}
201
202static void ps2mult_disconnect(struct serio *serio)
203{
204 struct ps2mult *psm = serio_get_drvdata(serio);
205
206 /* Note that serio core already take care of children ports */
207 serio_write(serio, PS2MULT_SESSION_END);
208 serio_close(serio);
209 kfree(psm);
210
211 serio_set_drvdata(serio, NULL);
212}
213
214static int ps2mult_reconnect(struct serio *serio)
215{
216 struct ps2mult *psm = serio_get_drvdata(serio);
217
218 ps2mult_reset(psm);
219
220 return 0;
221}
222
223static irqreturn_t ps2mult_interrupt(struct serio *serio,
224 unsigned char data, unsigned int dfl)
225{
226 struct ps2mult *psm = serio_get_drvdata(serio);
227 struct ps2mult_port *in_port;
228
229 dev_dbg(&serio->dev, "Received %02x flags %02x\n", data, dfl);
230
231 guard(spinlock_irqsave)(&psm->lock);
232
233 if (psm->escape) {
234 psm->escape = false;
235 in_port = psm->in_port;
236 if (in_port->registered)
237 serio_interrupt(in_port->serio, data, dfl);
238 goto out;
239 }
240
241 switch (data) {
242 case PS2MULT_ESCAPE:
243 dev_dbg(&serio->dev, "ESCAPE\n");
244 psm->escape = true;
245 break;
246
247 case PS2MULT_BSYNC:
248 dev_dbg(&serio->dev, "BSYNC\n");
249 psm->in_port = psm->out_port;
250 break;
251
252 case PS2MULT_SESSION_START:
253 dev_dbg(&serio->dev, "SS\n");
254 break;
255
256 case PS2MULT_SESSION_END:
257 dev_dbg(&serio->dev, "SE\n");
258 break;
259
260 case PS2MULT_KB_SELECTOR:
261 dev_dbg(&serio->dev, "KB\n");
262 psm->in_port = &psm->ports[PS2MULT_KBD_PORT];
263 break;
264
265 case PS2MULT_MS_SELECTOR:
266 dev_dbg(&serio->dev, "MS\n");
267 psm->in_port = &psm->ports[PS2MULT_MOUSE_PORT];
268 break;
269
270 default:
271 in_port = psm->in_port;
272 if (in_port->registered)
273 serio_interrupt(in_port->serio, data, dfl);
274 break;
275 }
276
277 out:
278 return IRQ_HANDLED;
279}
280
281static struct serio_driver ps2mult_drv = {
282 .driver = {
283 .name = "ps2mult",
284 },
285 .description = "TQC PS/2 Multiplexer driver",
286 .id_table = ps2mult_serio_ids,
287 .interrupt = ps2mult_interrupt,
288 .connect = ps2mult_connect,
289 .disconnect = ps2mult_disconnect,
290 .reconnect = ps2mult_reconnect,
291};
292
293module_serio_driver(ps2mult_drv);