307 lines · 6.7 KB · C
RawDownload
/* * 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);