Release 4.7 drivers/input/serio/ps2mult.c
  
  
/*
 * 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);
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| dmitry eremin-baryshkov | dmitry eremin-baryshkov | 54 | 100.00% | 1 | 100.00% | 
 | Total | 54 | 100.00% | 1 | 100.00% | 
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;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| dmitry eremin-baryshkov | dmitry eremin-baryshkov | 138 | 100.00% | 1 | 100.00% | 
 | Total | 138 | 100.00% | 1 | 100.00% | 
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;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| dmitry eremin-baryshkov | dmitry eremin-baryshkov | 65 | 100.00% | 1 | 100.00% | 
 | Total | 65 | 100.00% | 1 | 100.00% | 
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);
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| dmitry eremin-baryshkov | dmitry eremin-baryshkov | 62 | 100.00% | 1 | 100.00% | 
 | Total | 62 | 100.00% | 1 | 100.00% | 
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;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| dmitry eremin-baryshkov | dmitry eremin-baryshkov | 148 | 100.00% | 1 | 100.00% | 
 | Total | 148 | 100.00% | 1 | 100.00% | 
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);
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| dmitry eremin-baryshkov | dmitry eremin-baryshkov | 66 | 100.00% | 1 | 100.00% | 
 | Total | 66 | 100.00% | 1 | 100.00% | 
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;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| dmitry eremin-baryshkov | dmitry eremin-baryshkov | 246 | 99.60% | 1 | 50.00% | 
| axel lin | axel lin | 1 | 0.40% | 1 | 50.00% | 
 | Total | 247 | 100.00% | 2 | 100.00% | 
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);
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| dmitry eremin-baryshkov | dmitry eremin-baryshkov | 46 | 100.00% | 1 | 100.00% | 
 | Total | 46 | 100.00% | 1 | 100.00% | 
static int ps2mult_reconnect(struct serio *serio)
{
	struct ps2mult *psm = serio_get_drvdata(serio);
	ps2mult_reset(psm);
	return 0;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| dmitry eremin-baryshkov | dmitry eremin-baryshkov | 29 | 100.00% | 1 | 100.00% | 
 | Total | 29 | 100.00% | 1 | 100.00% | 
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;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| dmitry eremin-baryshkov | dmitry eremin-baryshkov | 270 | 100.00% | 1 | 100.00% | 
 | Total | 270 | 100.00% | 1 | 100.00% | 
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);
Overall Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| dmitry eremin-baryshkov | dmitry eremin-baryshkov | 1352 | 99.78% | 1 | 33.33% | 
| axel lin | axel lin | 3 | 0.22% | 2 | 66.67% | 
 | Total | 1355 | 100.00% | 3 | 100.00% | 
  
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.