|  | /* | 
|  | * 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); |