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