123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- /*
- * TQC PS/2 Multiplexer driver
- *
- * Copyright (C) 2010 Dmitry Eremin-Solenikov
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 as published by
- * the Free Software Foundation.
- */
- #include <linux/kernel.h>
- #include <linux/slab.h>
- #include <linux/module.h>
- #include <linux/serio.h>
- MODULE_AUTHOR("Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>");
- MODULE_DESCRIPTION("TQC PS/2 Multiplexer driver");
- MODULE_LICENSE("GPL");
- #define PS2MULT_KB_SELECTOR 0xA0
- #define PS2MULT_MS_SELECTOR 0xA1
- #define PS2MULT_ESCAPE 0x7D
- #define PS2MULT_BSYNC 0x7E
- #define PS2MULT_SESSION_START 0x55
- #define PS2MULT_SESSION_END 0x56
- struct ps2mult_port {
- struct serio *serio;
- unsigned char sel;
- bool registered;
- };
- #define PS2MULT_NUM_PORTS 2
- #define PS2MULT_KBD_PORT 0
- #define PS2MULT_MOUSE_PORT 1
- struct ps2mult {
- struct serio *mx_serio;
- struct ps2mult_port ports[PS2MULT_NUM_PORTS];
- spinlock_t lock;
- struct ps2mult_port *in_port;
- struct ps2mult_port *out_port;
- bool escape;
- };
- /* First MUST come PS2MULT_NUM_PORTS selectors */
- static const unsigned char ps2mult_controls[] = {
- PS2MULT_KB_SELECTOR, PS2MULT_MS_SELECTOR,
- PS2MULT_ESCAPE, PS2MULT_BSYNC,
- PS2MULT_SESSION_START, PS2MULT_SESSION_END,
- };
- static const struct serio_device_id ps2mult_serio_ids[] = {
- {
- .type = SERIO_RS232,
- .proto = SERIO_PS2MULT,
- .id = SERIO_ANY,
- .extra = SERIO_ANY,
- },
- { 0 }
- };
- MODULE_DEVICE_TABLE(serio, ps2mult_serio_ids);
- static void ps2mult_select_port(struct ps2mult *psm, struct ps2mult_port *port)
- {
- struct serio *mx_serio = psm->mx_serio;
- serio_write(mx_serio, port->sel);
- psm->out_port = port;
- dev_dbg(&mx_serio->dev, "switched to sel %02x\n", port->sel);
- }
- static int ps2mult_serio_write(struct serio *serio, unsigned char data)
- {
- struct serio *mx_port = serio->parent;
- struct ps2mult *psm = serio_get_drvdata(mx_port);
- struct ps2mult_port *port = serio->port_data;
- bool need_escape;
- unsigned long flags;
- spin_lock_irqsave(&psm->lock, flags);
- if (psm->out_port != port)
- ps2mult_select_port(psm, port);
- need_escape = memchr(ps2mult_controls, data, sizeof(ps2mult_controls));
- dev_dbg(&serio->dev,
- "write: %s%02x\n", need_escape ? "ESC " : "", data);
- if (need_escape)
- serio_write(mx_port, PS2MULT_ESCAPE);
- serio_write(mx_port, data);
- spin_unlock_irqrestore(&psm->lock, flags);
- return 0;
- }
- static int ps2mult_serio_start(struct serio *serio)
- {
- struct ps2mult *psm = serio_get_drvdata(serio->parent);
- struct ps2mult_port *port = serio->port_data;
- unsigned long flags;
- spin_lock_irqsave(&psm->lock, flags);
- port->registered = true;
- spin_unlock_irqrestore(&psm->lock, flags);
- return 0;
- }
- static void ps2mult_serio_stop(struct serio *serio)
- {
- struct ps2mult *psm = serio_get_drvdata(serio->parent);
- struct ps2mult_port *port = serio->port_data;
- unsigned long flags;
- spin_lock_irqsave(&psm->lock, flags);
- port->registered = false;
- spin_unlock_irqrestore(&psm->lock, flags);
- }
- static int ps2mult_create_port(struct ps2mult *psm, int i)
- {
- struct serio *mx_serio = psm->mx_serio;
- struct serio *serio;
- serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
- if (!serio)
- return -ENOMEM;
- strlcpy(serio->name, "TQC PS/2 Multiplexer", sizeof(serio->name));
- snprintf(serio->phys, sizeof(serio->phys),
- "%s/port%d", mx_serio->phys, i);
- serio->id.type = SERIO_8042;
- serio->write = ps2mult_serio_write;
- serio->start = ps2mult_serio_start;
- serio->stop = ps2mult_serio_stop;
- serio->parent = psm->mx_serio;
- serio->port_data = &psm->ports[i];
- psm->ports[i].serio = serio;
- return 0;
- }
- static void ps2mult_reset(struct ps2mult *psm)
- {
- unsigned long flags;
- spin_lock_irqsave(&psm->lock, flags);
- serio_write(psm->mx_serio, PS2MULT_SESSION_END);
- serio_write(psm->mx_serio, PS2MULT_SESSION_START);
- ps2mult_select_port(psm, &psm->ports[PS2MULT_KBD_PORT]);
- spin_unlock_irqrestore(&psm->lock, flags);
- }
- static int ps2mult_connect(struct serio *serio, struct serio_driver *drv)
- {
- struct ps2mult *psm;
- int i;
- int error;
- if (!serio->write)
- return -EINVAL;
- psm = kzalloc(sizeof(*psm), GFP_KERNEL);
- if (!psm)
- return -ENOMEM;
- spin_lock_init(&psm->lock);
- psm->mx_serio = serio;
- for (i = 0; i < PS2MULT_NUM_PORTS; i++) {
- psm->ports[i].sel = ps2mult_controls[i];
- error = ps2mult_create_port(psm, i);
- if (error)
- goto err_out;
- }
- psm->in_port = psm->out_port = &psm->ports[PS2MULT_KBD_PORT];
- serio_set_drvdata(serio, psm);
- error = serio_open(serio, drv);
- if (error)
- goto err_out;
- ps2mult_reset(psm);
- for (i = 0; i < PS2MULT_NUM_PORTS; i++) {
- struct serio *s = psm->ports[i].serio;
- dev_info(&serio->dev, "%s port at %s\n", s->name, serio->phys);
- serio_register_port(s);
- }
- return 0;
- err_out:
- while (--i >= 0)
- kfree(psm->ports[i].serio);
- kfree(psm);
- return error;
- }
- static void ps2mult_disconnect(struct serio *serio)
- {
- struct ps2mult *psm = serio_get_drvdata(serio);
- /* Note that serio core already take care of children ports */
- serio_write(serio, PS2MULT_SESSION_END);
- serio_close(serio);
- kfree(psm);
- serio_set_drvdata(serio, NULL);
- }
- static int ps2mult_reconnect(struct serio *serio)
- {
- struct ps2mult *psm = serio_get_drvdata(serio);
- ps2mult_reset(psm);
- return 0;
- }
- static irqreturn_t ps2mult_interrupt(struct serio *serio,
- unsigned char data, unsigned int dfl)
- {
- struct ps2mult *psm = serio_get_drvdata(serio);
- struct ps2mult_port *in_port;
- unsigned long flags;
- dev_dbg(&serio->dev, "Received %02x flags %02x\n", data, dfl);
- spin_lock_irqsave(&psm->lock, flags);
- if (psm->escape) {
- psm->escape = false;
- in_port = psm->in_port;
- if (in_port->registered)
- serio_interrupt(in_port->serio, data, dfl);
- goto out;
- }
- switch (data) {
- case PS2MULT_ESCAPE:
- dev_dbg(&serio->dev, "ESCAPE\n");
- psm->escape = true;
- break;
- case PS2MULT_BSYNC:
- dev_dbg(&serio->dev, "BSYNC\n");
- psm->in_port = psm->out_port;
- break;
- case PS2MULT_SESSION_START:
- dev_dbg(&serio->dev, "SS\n");
- break;
- case PS2MULT_SESSION_END:
- dev_dbg(&serio->dev, "SE\n");
- break;
- case PS2MULT_KB_SELECTOR:
- dev_dbg(&serio->dev, "KB\n");
- psm->in_port = &psm->ports[PS2MULT_KBD_PORT];
- break;
- case PS2MULT_MS_SELECTOR:
- dev_dbg(&serio->dev, "MS\n");
- psm->in_port = &psm->ports[PS2MULT_MOUSE_PORT];
- break;
- default:
- in_port = psm->in_port;
- if (in_port->registered)
- serio_interrupt(in_port->serio, data, dfl);
- break;
- }
- out:
- spin_unlock_irqrestore(&psm->lock, flags);
- return IRQ_HANDLED;
- }
- static struct serio_driver ps2mult_drv = {
- .driver = {
- .name = "ps2mult",
- },
- .description = "TQC PS/2 Multiplexer driver",
- .id_table = ps2mult_serio_ids,
- .interrupt = ps2mult_interrupt,
- .connect = ps2mult_connect,
- .disconnect = ps2mult_disconnect,
- .reconnect = ps2mult_reconnect,
- };
- module_serio_driver(ps2mult_drv);
|