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

Input: serio - add support for PS2Mult multiplexer protocol

PS2Mult is a simple serial protocol used for multiplexing several PS/2
streams into one serial data stream. It's used e.g. on TQM85xx series
of boards.

Signed-off-by: Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>

authored by

Dmitry Eremin-Solenikov and committed by
Dmitry Torokhov
fc58d12b 62ecae09

+329
+9
drivers/input/serio/Kconfig
··· 226 226 To compile this driver as a module, choose M here; 227 227 the module will be called ams_delta_serio. 228 228 229 + config SERIO_PS2MULT 230 + tristate "TQC PS/2 multiplexer" 231 + help 232 + Say Y here if you have the PS/2 line multiplexer like the one 233 + present on TQC boads. 234 + 235 + To compile this driver as a module, choose M here: the 236 + module will be called ps2mult. 237 + 229 238 endif
+1
drivers/input/serio/Makefile
··· 18 18 obj-$(CONFIG_HP_SDC) += hp_sdc.o 19 19 obj-$(CONFIG_HIL_MLC) += hp_sdc_mlc.o hil_mlc.o 20 20 obj-$(CONFIG_SERIO_PCIPS2) += pcips2.o 21 + obj-$(CONFIG_SERIO_PS2MULT) += ps2mult.o 21 22 obj-$(CONFIG_SERIO_MACEPS2) += maceps2.o 22 23 obj-$(CONFIG_SERIO_LIBPS2) += libps2.o 23 24 obj-$(CONFIG_SERIO_RAW) += serio_raw.o
+318
drivers/input/serio/ps2mult.c
··· 1 + /* 2 + * TQC PS/2 Multiplexer driver 3 + * 4 + * Copyright (C) 2010 Dmitry Eremin-Solenikov 5 + * 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. 9 + */ 10 + 11 + 12 + #include <linux/kernel.h> 13 + #include <linux/slab.h> 14 + #include <linux/module.h> 15 + #include <linux/serio.h> 16 + 17 + MODULE_AUTHOR("Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>"); 18 + MODULE_DESCRIPTION("TQC PS/2 Multiplexer driver"); 19 + MODULE_LICENSE("GPL"); 20 + 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 27 + 28 + struct ps2mult_port { 29 + struct serio *serio; 30 + unsigned char sel; 31 + bool registered; 32 + }; 33 + 34 + #define PS2MULT_NUM_PORTS 2 35 + #define PS2MULT_KBD_PORT 0 36 + #define PS2MULT_MOUSE_PORT 1 37 + 38 + struct ps2mult { 39 + struct serio *mx_serio; 40 + struct ps2mult_port ports[PS2MULT_NUM_PORTS]; 41 + 42 + spinlock_t lock; 43 + struct ps2mult_port *in_port; 44 + struct ps2mult_port *out_port; 45 + bool escape; 46 + }; 47 + 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, 53 + }; 54 + 55 + static const struct serio_device_id ps2mult_serio_ids[] = { 56 + { 57 + .type = SERIO_RS232, 58 + .proto = SERIO_PS2MULT, 59 + .id = SERIO_ANY, 60 + .extra = SERIO_ANY, 61 + }, 62 + { 0 } 63 + }; 64 + 65 + MODULE_DEVICE_TABLE(serio, ps2mult_serio_ids); 66 + 67 + static void ps2mult_select_port(struct ps2mult *psm, struct ps2mult_port *port) 68 + { 69 + struct serio *mx_serio = psm->mx_serio; 70 + 71 + serio_write(mx_serio, port->sel); 72 + psm->out_port = port; 73 + dev_dbg(&mx_serio->dev, "switched to sel %02x\n", port->sel); 74 + } 75 + 76 + static int ps2mult_serio_write(struct serio *serio, unsigned char data) 77 + { 78 + struct serio *mx_port = serio->parent; 79 + struct ps2mult *psm = serio_get_drvdata(mx_port); 80 + struct ps2mult_port *port = serio->port_data; 81 + bool need_escape; 82 + unsigned long flags; 83 + 84 + spin_lock_irqsave(&psm->lock, flags); 85 + 86 + if (psm->out_port != port) 87 + ps2mult_select_port(psm, port); 88 + 89 + need_escape = memchr(ps2mult_controls, data, sizeof(ps2mult_controls)); 90 + 91 + dev_dbg(&serio->dev, 92 + "write: %s%02x\n", need_escape ? "ESC " : "", data); 93 + 94 + if (need_escape) 95 + serio_write(mx_port, PS2MULT_ESCAPE); 96 + 97 + serio_write(mx_port, data); 98 + 99 + spin_unlock_irqrestore(&psm->lock, flags); 100 + 101 + return 0; 102 + } 103 + 104 + static int ps2mult_serio_start(struct serio *serio) 105 + { 106 + struct ps2mult *psm = serio_get_drvdata(serio->parent); 107 + struct ps2mult_port *port = serio->port_data; 108 + unsigned long flags; 109 + 110 + spin_lock_irqsave(&psm->lock, flags); 111 + port->registered = true; 112 + spin_unlock_irqrestore(&psm->lock, flags); 113 + 114 + return 0; 115 + } 116 + 117 + static void ps2mult_serio_stop(struct serio *serio) 118 + { 119 + struct ps2mult *psm = serio_get_drvdata(serio->parent); 120 + struct ps2mult_port *port = serio->port_data; 121 + unsigned long flags; 122 + 123 + spin_lock_irqsave(&psm->lock, flags); 124 + port->registered = false; 125 + spin_unlock_irqrestore(&psm->lock, flags); 126 + } 127 + 128 + static int ps2mult_create_port(struct ps2mult *psm, int i) 129 + { 130 + struct serio *mx_serio = psm->mx_serio; 131 + struct serio *serio; 132 + 133 + serio = kzalloc(sizeof(struct serio), GFP_KERNEL); 134 + if (!serio) 135 + return -ENOMEM; 136 + 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]; 146 + 147 + psm->ports[i].serio = serio; 148 + 149 + return 0; 150 + } 151 + 152 + static void ps2mult_reset(struct ps2mult *psm) 153 + { 154 + unsigned long flags; 155 + 156 + spin_lock_irqsave(&psm->lock, flags); 157 + 158 + serio_write(psm->mx_serio, PS2MULT_SESSION_END); 159 + serio_write(psm->mx_serio, PS2MULT_SESSION_START); 160 + 161 + ps2mult_select_port(psm, &psm->ports[PS2MULT_KBD_PORT]); 162 + 163 + spin_unlock_irqrestore(&psm->lock, flags); 164 + } 165 + 166 + static int ps2mult_connect(struct serio *serio, struct serio_driver *drv) 167 + { 168 + struct ps2mult *psm; 169 + int i; 170 + int error; 171 + 172 + if (!serio->write) 173 + return -EINVAL; 174 + 175 + psm = kzalloc(sizeof(*psm), GFP_KERNEL); 176 + if (!psm) 177 + return -ENOMEM; 178 + 179 + spin_lock_init(&psm->lock); 180 + psm->mx_serio = serio; 181 + 182 + for (i = 0; i < PS2MULT_NUM_PORTS; i++) { 183 + psm->ports[i].sel = ps2mult_controls[i]; 184 + error = ps2mult_create_port(psm, i); 185 + if (error) 186 + goto err_out; 187 + } 188 + 189 + psm->in_port = psm->out_port = &psm->ports[PS2MULT_KBD_PORT]; 190 + 191 + serio_set_drvdata(serio, psm); 192 + error = serio_open(serio, drv); 193 + if (error) 194 + goto err_out; 195 + 196 + ps2mult_reset(psm); 197 + 198 + for (i = 0; i < PS2MULT_NUM_PORTS; i++) { 199 + struct serio *s = psm->ports[i].serio; 200 + 201 + dev_info(&serio->dev, "%s port at %s\n", s->name, serio->phys); 202 + serio_register_port(s); 203 + } 204 + 205 + return 0; 206 + 207 + err_out: 208 + while (--i >= 0) 209 + kfree(psm->ports[i].serio); 210 + kfree(serio); 211 + return error; 212 + } 213 + 214 + static void ps2mult_disconnect(struct serio *serio) 215 + { 216 + struct ps2mult *psm = serio_get_drvdata(serio); 217 + 218 + /* Note that serio core already take care of children ports */ 219 + serio_write(serio, PS2MULT_SESSION_END); 220 + serio_close(serio); 221 + kfree(psm); 222 + 223 + serio_set_drvdata(serio, NULL); 224 + } 225 + 226 + static int ps2mult_reconnect(struct serio *serio) 227 + { 228 + struct ps2mult *psm = serio_get_drvdata(serio); 229 + 230 + ps2mult_reset(psm); 231 + 232 + return 0; 233 + } 234 + 235 + static irqreturn_t ps2mult_interrupt(struct serio *serio, 236 + unsigned char data, unsigned int dfl) 237 + { 238 + struct ps2mult *psm = serio_get_drvdata(serio); 239 + struct ps2mult_port *in_port; 240 + unsigned long flags; 241 + 242 + dev_dbg(&serio->dev, "Received %02x flags %02x\n", data, dfl); 243 + 244 + spin_lock_irqsave(&psm->lock, flags); 245 + 246 + if (psm->escape) { 247 + psm->escape = false; 248 + in_port = psm->in_port; 249 + if (in_port->registered) 250 + serio_interrupt(in_port->serio, data, dfl); 251 + goto out; 252 + } 253 + 254 + switch (data) { 255 + case PS2MULT_ESCAPE: 256 + dev_dbg(&serio->dev, "ESCAPE\n"); 257 + psm->escape = true; 258 + break; 259 + 260 + case PS2MULT_BSYNC: 261 + dev_dbg(&serio->dev, "BSYNC\n"); 262 + psm->in_port = psm->out_port; 263 + break; 264 + 265 + case PS2MULT_SESSION_START: 266 + dev_dbg(&serio->dev, "SS\n"); 267 + break; 268 + 269 + case PS2MULT_SESSION_END: 270 + dev_dbg(&serio->dev, "SE\n"); 271 + break; 272 + 273 + case PS2MULT_KB_SELECTOR: 274 + dev_dbg(&serio->dev, "KB\n"); 275 + psm->in_port = &psm->ports[PS2MULT_KBD_PORT]; 276 + break; 277 + 278 + case PS2MULT_MS_SELECTOR: 279 + dev_dbg(&serio->dev, "MS\n"); 280 + psm->in_port = &psm->ports[PS2MULT_MOUSE_PORT]; 281 + break; 282 + 283 + default: 284 + in_port = psm->in_port; 285 + if (in_port->registered) 286 + serio_interrupt(in_port->serio, data, dfl); 287 + break; 288 + } 289 + 290 + out: 291 + spin_unlock_irqrestore(&psm->lock, flags); 292 + return IRQ_HANDLED; 293 + } 294 + 295 + static struct serio_driver ps2mult_drv = { 296 + .driver = { 297 + .name = "ps2mult", 298 + }, 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, 305 + }; 306 + 307 + static int __init ps2mult_init(void) 308 + { 309 + return serio_register_driver(&ps2mult_drv); 310 + } 311 + 312 + static void __exit ps2mult_exit(void) 313 + { 314 + serio_unregister_driver(&ps2mult_drv); 315 + } 316 + 317 + module_init(ps2mult_init); 318 + module_exit(ps2mult_exit);
+1
include/linux/serio.h
··· 198 198 #define SERIO_W8001 0x39 199 199 #define SERIO_DYNAPRO 0x3a 200 200 #define SERIO_HAMPSHIRE 0x3b 201 + #define SERIO_PS2MULT 0x3c 201 202 202 203 #endif