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