diff options
| author | Dmitry Eremin-Solenikov <dbaryshkov@gmail.com> | 2010-10-18 09:18:13 -0700 | 
|---|---|---|
| committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2010-10-18 09:33:31 -0700 | 
| commit | fc58d12be416eb51932eec594667ca3181903b9e (patch) | |
| tree | 71c7b4d4a8f314c48e10dae89060b80b88842899 | |
| parent | 62ecae09a01df507ef52e1bc90fc233a1978c60a (diff) | |
| download | olio-linux-3.10-fc58d12be416eb51932eec594667ca3181903b9e.tar.xz olio-linux-3.10-fc58d12be416eb51932eec594667ca3181903b9e.zip  | |
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>
| -rw-r--r-- | drivers/input/serio/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/input/serio/Makefile | 1 | ||||
| -rw-r--r-- | drivers/input/serio/ps2mult.c | 318 | ||||
| -rw-r--r-- | include/linux/serio.h | 1 | 
4 files changed, 329 insertions, 0 deletions
diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig index 3bfe8fafc6a..6256233d2bf 100644 --- a/drivers/input/serio/Kconfig +++ b/drivers/input/serio/Kconfig @@ -226,4 +226,13 @@ config SERIO_AMS_DELTA  	  To compile this driver as a module, choose M here;  	  the module will be called ams_delta_serio. +config SERIO_PS2MULT +	tristate "TQC PS/2 multiplexer" +	help +	  Say Y here if you have the PS/2 line multiplexer like the one +	  present on TQC boads. + +	  To compile this driver as a module, choose M here: the +	  module will be called ps2mult. +  endif diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile index 84c80bf7185..dbbe37616c9 100644 --- a/drivers/input/serio/Makefile +++ b/drivers/input/serio/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_SERIO_GSCPS2)	+= gscps2.o  obj-$(CONFIG_HP_SDC)		+= hp_sdc.o  obj-$(CONFIG_HIL_MLC)		+= hp_sdc_mlc.o hil_mlc.o  obj-$(CONFIG_SERIO_PCIPS2)	+= pcips2.o +obj-$(CONFIG_SERIO_PS2MULT)	+= ps2mult.o  obj-$(CONFIG_SERIO_MACEPS2)	+= maceps2.o  obj-$(CONFIG_SERIO_LIBPS2)	+= libps2.o  obj-$(CONFIG_SERIO_RAW)		+= serio_raw.o diff --git a/drivers/input/serio/ps2mult.c b/drivers/input/serio/ps2mult.c new file mode 100644 index 00000000000..6bce22e4e49 --- /dev/null +++ b/drivers/input/serio/ps2mult.c @@ -0,0 +1,318 @@ +/* + * 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(serio); +	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, +}; + +static int __init ps2mult_init(void) +{ +	return serio_register_driver(&ps2mult_drv); +} + +static void __exit ps2mult_exit(void) +{ +	serio_unregister_driver(&ps2mult_drv); +} + +module_init(ps2mult_init); +module_exit(ps2mult_exit); diff --git a/include/linux/serio.h b/include/linux/serio.h index 109b237603b..e26f4788845 100644 --- a/include/linux/serio.h +++ b/include/linux/serio.h @@ -198,5 +198,6 @@ static inline void serio_continue_rx(struct serio *serio)  #define SERIO_W8001	0x39  #define SERIO_DYNAPRO	0x3a  #define SERIO_HAMPSHIRE	0x3b +#define SERIO_PS2MULT	0x3c  #endif  |