18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * TQC PS/2 Multiplexer driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2010 Dmitry Eremin-Solenikov
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/kernel.h>
108c2ecf20Sopenharmony_ci#include <linux/slab.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/serio.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ciMODULE_AUTHOR("Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>");
158c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TQC PS/2 Multiplexer driver");
168c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#define PS2MULT_KB_SELECTOR		0xA0
198c2ecf20Sopenharmony_ci#define PS2MULT_MS_SELECTOR		0xA1
208c2ecf20Sopenharmony_ci#define PS2MULT_ESCAPE			0x7D
218c2ecf20Sopenharmony_ci#define PS2MULT_BSYNC			0x7E
228c2ecf20Sopenharmony_ci#define PS2MULT_SESSION_START		0x55
238c2ecf20Sopenharmony_ci#define PS2MULT_SESSION_END		0x56
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cistruct ps2mult_port {
268c2ecf20Sopenharmony_ci	struct serio *serio;
278c2ecf20Sopenharmony_ci	unsigned char sel;
288c2ecf20Sopenharmony_ci	bool registered;
298c2ecf20Sopenharmony_ci};
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci#define PS2MULT_NUM_PORTS	2
328c2ecf20Sopenharmony_ci#define PS2MULT_KBD_PORT	0
338c2ecf20Sopenharmony_ci#define PS2MULT_MOUSE_PORT	1
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistruct ps2mult {
368c2ecf20Sopenharmony_ci	struct serio *mx_serio;
378c2ecf20Sopenharmony_ci	struct ps2mult_port ports[PS2MULT_NUM_PORTS];
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	spinlock_t lock;
408c2ecf20Sopenharmony_ci	struct ps2mult_port *in_port;
418c2ecf20Sopenharmony_ci	struct ps2mult_port *out_port;
428c2ecf20Sopenharmony_ci	bool escape;
438c2ecf20Sopenharmony_ci};
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci/* First MUST come PS2MULT_NUM_PORTS selectors */
468c2ecf20Sopenharmony_cistatic const unsigned char ps2mult_controls[] = {
478c2ecf20Sopenharmony_ci	PS2MULT_KB_SELECTOR, PS2MULT_MS_SELECTOR,
488c2ecf20Sopenharmony_ci	PS2MULT_ESCAPE, PS2MULT_BSYNC,
498c2ecf20Sopenharmony_ci	PS2MULT_SESSION_START, PS2MULT_SESSION_END,
508c2ecf20Sopenharmony_ci};
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_cistatic const struct serio_device_id ps2mult_serio_ids[] = {
538c2ecf20Sopenharmony_ci	{
548c2ecf20Sopenharmony_ci		.type	= SERIO_RS232,
558c2ecf20Sopenharmony_ci		.proto	= SERIO_PS2MULT,
568c2ecf20Sopenharmony_ci		.id	= SERIO_ANY,
578c2ecf20Sopenharmony_ci		.extra	= SERIO_ANY,
588c2ecf20Sopenharmony_ci	},
598c2ecf20Sopenharmony_ci	{ 0 }
608c2ecf20Sopenharmony_ci};
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(serio, ps2mult_serio_ids);
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic void ps2mult_select_port(struct ps2mult *psm, struct ps2mult_port *port)
658c2ecf20Sopenharmony_ci{
668c2ecf20Sopenharmony_ci	struct serio *mx_serio = psm->mx_serio;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	serio_write(mx_serio, port->sel);
698c2ecf20Sopenharmony_ci	psm->out_port = port;
708c2ecf20Sopenharmony_ci	dev_dbg(&mx_serio->dev, "switched to sel %02x\n", port->sel);
718c2ecf20Sopenharmony_ci}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_cistatic int ps2mult_serio_write(struct serio *serio, unsigned char data)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	struct serio *mx_port = serio->parent;
768c2ecf20Sopenharmony_ci	struct ps2mult *psm = serio_get_drvdata(mx_port);
778c2ecf20Sopenharmony_ci	struct ps2mult_port *port = serio->port_data;
788c2ecf20Sopenharmony_ci	bool need_escape;
798c2ecf20Sopenharmony_ci	unsigned long flags;
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	spin_lock_irqsave(&psm->lock, flags);
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	if (psm->out_port != port)
848c2ecf20Sopenharmony_ci		ps2mult_select_port(psm, port);
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	need_escape = memchr(ps2mult_controls, data, sizeof(ps2mult_controls));
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	dev_dbg(&serio->dev,
898c2ecf20Sopenharmony_ci		"write: %s%02x\n", need_escape ? "ESC " : "", data);
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	if (need_escape)
928c2ecf20Sopenharmony_ci		serio_write(mx_port, PS2MULT_ESCAPE);
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	serio_write(mx_port, data);
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&psm->lock, flags);
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	return 0;
998c2ecf20Sopenharmony_ci}
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_cistatic int ps2mult_serio_start(struct serio *serio)
1028c2ecf20Sopenharmony_ci{
1038c2ecf20Sopenharmony_ci	struct ps2mult *psm = serio_get_drvdata(serio->parent);
1048c2ecf20Sopenharmony_ci	struct ps2mult_port *port = serio->port_data;
1058c2ecf20Sopenharmony_ci	unsigned long flags;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	spin_lock_irqsave(&psm->lock, flags);
1088c2ecf20Sopenharmony_ci	port->registered = true;
1098c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&psm->lock, flags);
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	return 0;
1128c2ecf20Sopenharmony_ci}
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_cistatic void ps2mult_serio_stop(struct serio *serio)
1158c2ecf20Sopenharmony_ci{
1168c2ecf20Sopenharmony_ci	struct ps2mult *psm = serio_get_drvdata(serio->parent);
1178c2ecf20Sopenharmony_ci	struct ps2mult_port *port = serio->port_data;
1188c2ecf20Sopenharmony_ci	unsigned long flags;
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	spin_lock_irqsave(&psm->lock, flags);
1218c2ecf20Sopenharmony_ci	port->registered = false;
1228c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&psm->lock, flags);
1238c2ecf20Sopenharmony_ci}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic int ps2mult_create_port(struct ps2mult *psm, int i)
1268c2ecf20Sopenharmony_ci{
1278c2ecf20Sopenharmony_ci	struct serio *mx_serio = psm->mx_serio;
1288c2ecf20Sopenharmony_ci	struct serio *serio;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
1318c2ecf20Sopenharmony_ci	if (!serio)
1328c2ecf20Sopenharmony_ci		return -ENOMEM;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	strlcpy(serio->name, "TQC PS/2 Multiplexer", sizeof(serio->name));
1358c2ecf20Sopenharmony_ci	snprintf(serio->phys, sizeof(serio->phys),
1368c2ecf20Sopenharmony_ci		 "%s/port%d", mx_serio->phys, i);
1378c2ecf20Sopenharmony_ci	serio->id.type = SERIO_8042;
1388c2ecf20Sopenharmony_ci	serio->write = ps2mult_serio_write;
1398c2ecf20Sopenharmony_ci	serio->start = ps2mult_serio_start;
1408c2ecf20Sopenharmony_ci	serio->stop = ps2mult_serio_stop;
1418c2ecf20Sopenharmony_ci	serio->parent = psm->mx_serio;
1428c2ecf20Sopenharmony_ci	serio->port_data = &psm->ports[i];
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	psm->ports[i].serio = serio;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	return 0;
1478c2ecf20Sopenharmony_ci}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_cistatic void ps2mult_reset(struct ps2mult *psm)
1508c2ecf20Sopenharmony_ci{
1518c2ecf20Sopenharmony_ci	unsigned long flags;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	spin_lock_irqsave(&psm->lock, flags);
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	serio_write(psm->mx_serio, PS2MULT_SESSION_END);
1568c2ecf20Sopenharmony_ci	serio_write(psm->mx_serio, PS2MULT_SESSION_START);
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	ps2mult_select_port(psm, &psm->ports[PS2MULT_KBD_PORT]);
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&psm->lock, flags);
1618c2ecf20Sopenharmony_ci}
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_cistatic int ps2mult_connect(struct serio *serio, struct serio_driver *drv)
1648c2ecf20Sopenharmony_ci{
1658c2ecf20Sopenharmony_ci	struct ps2mult *psm;
1668c2ecf20Sopenharmony_ci	int i;
1678c2ecf20Sopenharmony_ci	int error;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	if (!serio->write)
1708c2ecf20Sopenharmony_ci		return -EINVAL;
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	psm = kzalloc(sizeof(*psm), GFP_KERNEL);
1738c2ecf20Sopenharmony_ci	if (!psm)
1748c2ecf20Sopenharmony_ci		return -ENOMEM;
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	spin_lock_init(&psm->lock);
1778c2ecf20Sopenharmony_ci	psm->mx_serio = serio;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	for (i = 0; i < PS2MULT_NUM_PORTS; i++) {
1808c2ecf20Sopenharmony_ci		psm->ports[i].sel = ps2mult_controls[i];
1818c2ecf20Sopenharmony_ci		error = ps2mult_create_port(psm, i);
1828c2ecf20Sopenharmony_ci		if (error)
1838c2ecf20Sopenharmony_ci			goto err_out;
1848c2ecf20Sopenharmony_ci	}
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	psm->in_port = psm->out_port = &psm->ports[PS2MULT_KBD_PORT];
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	serio_set_drvdata(serio, psm);
1898c2ecf20Sopenharmony_ci	error = serio_open(serio, drv);
1908c2ecf20Sopenharmony_ci	if (error)
1918c2ecf20Sopenharmony_ci		goto err_out;
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	ps2mult_reset(psm);
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	for (i = 0; i <  PS2MULT_NUM_PORTS; i++) {
1968c2ecf20Sopenharmony_ci		struct serio *s = psm->ports[i].serio;
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci		dev_info(&serio->dev, "%s port at %s\n", s->name, serio->phys);
1998c2ecf20Sopenharmony_ci		serio_register_port(s);
2008c2ecf20Sopenharmony_ci	}
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	return 0;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_cierr_out:
2058c2ecf20Sopenharmony_ci	while (--i >= 0)
2068c2ecf20Sopenharmony_ci		kfree(psm->ports[i].serio);
2078c2ecf20Sopenharmony_ci	kfree(psm);
2088c2ecf20Sopenharmony_ci	return error;
2098c2ecf20Sopenharmony_ci}
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_cistatic void ps2mult_disconnect(struct serio *serio)
2128c2ecf20Sopenharmony_ci{
2138c2ecf20Sopenharmony_ci	struct ps2mult *psm = serio_get_drvdata(serio);
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	/* Note that serio core already take care of children ports */
2168c2ecf20Sopenharmony_ci	serio_write(serio, PS2MULT_SESSION_END);
2178c2ecf20Sopenharmony_ci	serio_close(serio);
2188c2ecf20Sopenharmony_ci	kfree(psm);
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	serio_set_drvdata(serio, NULL);
2218c2ecf20Sopenharmony_ci}
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_cistatic int ps2mult_reconnect(struct serio *serio)
2248c2ecf20Sopenharmony_ci{
2258c2ecf20Sopenharmony_ci	struct ps2mult *psm = serio_get_drvdata(serio);
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	ps2mult_reset(psm);
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	return 0;
2308c2ecf20Sopenharmony_ci}
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_cistatic irqreturn_t ps2mult_interrupt(struct serio *serio,
2338c2ecf20Sopenharmony_ci				     unsigned char data, unsigned int dfl)
2348c2ecf20Sopenharmony_ci{
2358c2ecf20Sopenharmony_ci	struct ps2mult *psm = serio_get_drvdata(serio);
2368c2ecf20Sopenharmony_ci	struct ps2mult_port *in_port;
2378c2ecf20Sopenharmony_ci	unsigned long flags;
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	dev_dbg(&serio->dev, "Received %02x flags %02x\n", data, dfl);
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	spin_lock_irqsave(&psm->lock, flags);
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	if (psm->escape) {
2448c2ecf20Sopenharmony_ci		psm->escape = false;
2458c2ecf20Sopenharmony_ci		in_port = psm->in_port;
2468c2ecf20Sopenharmony_ci		if (in_port->registered)
2478c2ecf20Sopenharmony_ci			serio_interrupt(in_port->serio, data, dfl);
2488c2ecf20Sopenharmony_ci		goto out;
2498c2ecf20Sopenharmony_ci	}
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	switch (data) {
2528c2ecf20Sopenharmony_ci	case PS2MULT_ESCAPE:
2538c2ecf20Sopenharmony_ci		dev_dbg(&serio->dev, "ESCAPE\n");
2548c2ecf20Sopenharmony_ci		psm->escape = true;
2558c2ecf20Sopenharmony_ci		break;
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	case PS2MULT_BSYNC:
2588c2ecf20Sopenharmony_ci		dev_dbg(&serio->dev, "BSYNC\n");
2598c2ecf20Sopenharmony_ci		psm->in_port = psm->out_port;
2608c2ecf20Sopenharmony_ci		break;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	case PS2MULT_SESSION_START:
2638c2ecf20Sopenharmony_ci		dev_dbg(&serio->dev, "SS\n");
2648c2ecf20Sopenharmony_ci		break;
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	case PS2MULT_SESSION_END:
2678c2ecf20Sopenharmony_ci		dev_dbg(&serio->dev, "SE\n");
2688c2ecf20Sopenharmony_ci		break;
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	case PS2MULT_KB_SELECTOR:
2718c2ecf20Sopenharmony_ci		dev_dbg(&serio->dev, "KB\n");
2728c2ecf20Sopenharmony_ci		psm->in_port = &psm->ports[PS2MULT_KBD_PORT];
2738c2ecf20Sopenharmony_ci		break;
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	case PS2MULT_MS_SELECTOR:
2768c2ecf20Sopenharmony_ci		dev_dbg(&serio->dev, "MS\n");
2778c2ecf20Sopenharmony_ci		psm->in_port = &psm->ports[PS2MULT_MOUSE_PORT];
2788c2ecf20Sopenharmony_ci		break;
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci	default:
2818c2ecf20Sopenharmony_ci		in_port = psm->in_port;
2828c2ecf20Sopenharmony_ci		if (in_port->registered)
2838c2ecf20Sopenharmony_ci			serio_interrupt(in_port->serio, data, dfl);
2848c2ecf20Sopenharmony_ci		break;
2858c2ecf20Sopenharmony_ci	}
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci out:
2888c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&psm->lock, flags);
2898c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
2908c2ecf20Sopenharmony_ci}
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_cistatic struct serio_driver ps2mult_drv = {
2938c2ecf20Sopenharmony_ci	.driver		= {
2948c2ecf20Sopenharmony_ci		.name	= "ps2mult",
2958c2ecf20Sopenharmony_ci	},
2968c2ecf20Sopenharmony_ci	.description	= "TQC PS/2 Multiplexer driver",
2978c2ecf20Sopenharmony_ci	.id_table	= ps2mult_serio_ids,
2988c2ecf20Sopenharmony_ci	.interrupt	= ps2mult_interrupt,
2998c2ecf20Sopenharmony_ci	.connect	= ps2mult_connect,
3008c2ecf20Sopenharmony_ci	.disconnect	= ps2mult_disconnect,
3018c2ecf20Sopenharmony_ci	.reconnect	= ps2mult_reconnect,
3028c2ecf20Sopenharmony_ci};
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_cimodule_serio_driver(ps2mult_drv);
305