2 * TQC PS/2 Multiplexer driver
4 * Copyright (C) 2010 Dmitry Eremin-Solenikov
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 2 as published by
8 * the Free Software Foundation.
12 #include <linux/kernel.h>
13 #include <linux/slab.h>
14 #include <linux/module.h>
15 #include <linux/serio.h>
17 MODULE_AUTHOR("Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>");
18 MODULE_DESCRIPTION("TQC PS/2 Multiplexer driver");
19 MODULE_LICENSE("GPL");
21 #define PS2MULT_KB_SELECTOR 0xA0
22 #define PS2MULT_MS_SELECTOR 0xA1
23 #define PS2MULT_ESCAPE 0x7D
24 #define PS2MULT_BSYNC 0x7E
25 #define PS2MULT_SESSION_START 0x55
26 #define PS2MULT_SESSION_END 0x56
34 #define PS2MULT_NUM_PORTS 2
35 #define PS2MULT_KBD_PORT 0
36 #define PS2MULT_MOUSE_PORT 1
39 struct serio
*mx_serio
;
40 struct ps2mult_port ports
[PS2MULT_NUM_PORTS
];
43 struct ps2mult_port
*in_port
;
44 struct ps2mult_port
*out_port
;
48 /* First MUST come PS2MULT_NUM_PORTS selectors */
49 static const unsigned char ps2mult_controls
[] = {
50 PS2MULT_KB_SELECTOR
, PS2MULT_MS_SELECTOR
,
51 PS2MULT_ESCAPE
, PS2MULT_BSYNC
,
52 PS2MULT_SESSION_START
, PS2MULT_SESSION_END
,
55 static const struct serio_device_id ps2mult_serio_ids
[] = {
58 .proto
= SERIO_PS2MULT
,
65 MODULE_DEVICE_TABLE(serio
, ps2mult_serio_ids
);
67 static void ps2mult_select_port(struct ps2mult
*psm
, struct ps2mult_port
*port
)
69 struct serio
*mx_serio
= psm
->mx_serio
;
71 serio_write(mx_serio
, port
->sel
);
73 dev_dbg(&mx_serio
->dev
, "switched to sel %02x\n", port
->sel
);
76 static int ps2mult_serio_write(struct serio
*serio
, unsigned char data
)
78 struct serio
*mx_port
= serio
->parent
;
79 struct ps2mult
*psm
= serio_get_drvdata(mx_port
);
80 struct ps2mult_port
*port
= serio
->port_data
;
84 spin_lock_irqsave(&psm
->lock
, flags
);
86 if (psm
->out_port
!= port
)
87 ps2mult_select_port(psm
, port
);
89 need_escape
= memchr(ps2mult_controls
, data
, sizeof(ps2mult_controls
));
92 "write: %s%02x\n", need_escape
? "ESC " : "", data
);
95 serio_write(mx_port
, PS2MULT_ESCAPE
);
97 serio_write(mx_port
, data
);
99 spin_unlock_irqrestore(&psm
->lock
, flags
);
104 static int ps2mult_serio_start(struct serio
*serio
)
106 struct ps2mult
*psm
= serio_get_drvdata(serio
->parent
);
107 struct ps2mult_port
*port
= serio
->port_data
;
110 spin_lock_irqsave(&psm
->lock
, flags
);
111 port
->registered
= true;
112 spin_unlock_irqrestore(&psm
->lock
, flags
);
117 static void ps2mult_serio_stop(struct serio
*serio
)
119 struct ps2mult
*psm
= serio_get_drvdata(serio
->parent
);
120 struct ps2mult_port
*port
= serio
->port_data
;
123 spin_lock_irqsave(&psm
->lock
, flags
);
124 port
->registered
= false;
125 spin_unlock_irqrestore(&psm
->lock
, flags
);
128 static int ps2mult_create_port(struct ps2mult
*psm
, int i
)
130 struct serio
*mx_serio
= psm
->mx_serio
;
133 serio
= kzalloc(sizeof(struct serio
), GFP_KERNEL
);
137 strlcpy(serio
->name
, "TQC PS/2 Multiplexer", sizeof(serio
->name
));
138 snprintf(serio
->phys
, sizeof(serio
->phys
),
139 "%s/port%d", mx_serio
->phys
, i
);
140 serio
->id
.type
= SERIO_8042
;
141 serio
->write
= ps2mult_serio_write
;
142 serio
->start
= ps2mult_serio_start
;
143 serio
->stop
= ps2mult_serio_stop
;
144 serio
->parent
= psm
->mx_serio
;
145 serio
->port_data
= &psm
->ports
[i
];
147 psm
->ports
[i
].serio
= serio
;
152 static void ps2mult_reset(struct ps2mult
*psm
)
156 spin_lock_irqsave(&psm
->lock
, flags
);
158 serio_write(psm
->mx_serio
, PS2MULT_SESSION_END
);
159 serio_write(psm
->mx_serio
, PS2MULT_SESSION_START
);
161 ps2mult_select_port(psm
, &psm
->ports
[PS2MULT_KBD_PORT
]);
163 spin_unlock_irqrestore(&psm
->lock
, flags
);
166 static int ps2mult_connect(struct serio
*serio
, struct serio_driver
*drv
)
175 psm
= kzalloc(sizeof(*psm
), GFP_KERNEL
);
179 spin_lock_init(&psm
->lock
);
180 psm
->mx_serio
= serio
;
182 for (i
= 0; i
< PS2MULT_NUM_PORTS
; i
++) {
183 psm
->ports
[i
].sel
= ps2mult_controls
[i
];
184 error
= ps2mult_create_port(psm
, i
);
189 psm
->in_port
= psm
->out_port
= &psm
->ports
[PS2MULT_KBD_PORT
];
191 serio_set_drvdata(serio
, psm
);
192 error
= serio_open(serio
, drv
);
198 for (i
= 0; i
< PS2MULT_NUM_PORTS
; i
++) {
199 struct serio
*s
= psm
->ports
[i
].serio
;
201 dev_info(&serio
->dev
, "%s port at %s\n", s
->name
, serio
->phys
);
202 serio_register_port(s
);
209 kfree(psm
->ports
[i
].serio
);
214 static void ps2mult_disconnect(struct serio
*serio
)
216 struct ps2mult
*psm
= serio_get_drvdata(serio
);
218 /* Note that serio core already take care of children ports */
219 serio_write(serio
, PS2MULT_SESSION_END
);
223 serio_set_drvdata(serio
, NULL
);
226 static int ps2mult_reconnect(struct serio
*serio
)
228 struct ps2mult
*psm
= serio_get_drvdata(serio
);
235 static irqreturn_t
ps2mult_interrupt(struct serio
*serio
,
236 unsigned char data
, unsigned int dfl
)
238 struct ps2mult
*psm
= serio_get_drvdata(serio
);
239 struct ps2mult_port
*in_port
;
242 dev_dbg(&serio
->dev
, "Received %02x flags %02x\n", data
, dfl
);
244 spin_lock_irqsave(&psm
->lock
, flags
);
248 in_port
= psm
->in_port
;
249 if (in_port
->registered
)
250 serio_interrupt(in_port
->serio
, data
, dfl
);
256 dev_dbg(&serio
->dev
, "ESCAPE\n");
261 dev_dbg(&serio
->dev
, "BSYNC\n");
262 psm
->in_port
= psm
->out_port
;
265 case PS2MULT_SESSION_START
:
266 dev_dbg(&serio
->dev
, "SS\n");
269 case PS2MULT_SESSION_END
:
270 dev_dbg(&serio
->dev
, "SE\n");
273 case PS2MULT_KB_SELECTOR
:
274 dev_dbg(&serio
->dev
, "KB\n");
275 psm
->in_port
= &psm
->ports
[PS2MULT_KBD_PORT
];
278 case PS2MULT_MS_SELECTOR
:
279 dev_dbg(&serio
->dev
, "MS\n");
280 psm
->in_port
= &psm
->ports
[PS2MULT_MOUSE_PORT
];
284 in_port
= psm
->in_port
;
285 if (in_port
->registered
)
286 serio_interrupt(in_port
->serio
, data
, dfl
);
291 spin_unlock_irqrestore(&psm
->lock
, flags
);
295 static struct serio_driver ps2mult_drv
= {
299 .description
= "TQC PS/2 Multiplexer driver",
300 .id_table
= ps2mult_serio_ids
,
301 .interrupt
= ps2mult_interrupt
,
302 .connect
= ps2mult_connect
,
303 .disconnect
= ps2mult_disconnect
,
304 .reconnect
= ps2mult_reconnect
,
307 module_serio_driver(ps2mult_drv
);