ps2mult.c 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * TQC PS/2 Multiplexer driver
  4. *
  5. * Copyright (C) 2010 Dmitry Eremin-Solenikov
  6. */
  7. #include <linux/kernel.h>
  8. #include <linux/slab.h>
  9. #include <linux/module.h>
  10. #include <linux/serio.h>
  11. MODULE_AUTHOR("Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>");
  12. MODULE_DESCRIPTION("TQC PS/2 Multiplexer driver");
  13. MODULE_LICENSE("GPL");
  14. #define PS2MULT_KB_SELECTOR 0xA0
  15. #define PS2MULT_MS_SELECTOR 0xA1
  16. #define PS2MULT_ESCAPE 0x7D
  17. #define PS2MULT_BSYNC 0x7E
  18. #define PS2MULT_SESSION_START 0x55
  19. #define PS2MULT_SESSION_END 0x56
  20. struct ps2mult_port {
  21. struct serio *serio;
  22. unsigned char sel;
  23. bool registered;
  24. };
  25. #define PS2MULT_NUM_PORTS 2
  26. #define PS2MULT_KBD_PORT 0
  27. #define PS2MULT_MOUSE_PORT 1
  28. struct ps2mult {
  29. struct serio *mx_serio;
  30. struct ps2mult_port ports[PS2MULT_NUM_PORTS];
  31. spinlock_t lock;
  32. struct ps2mult_port *in_port;
  33. struct ps2mult_port *out_port;
  34. bool escape;
  35. };
  36. /* First MUST come PS2MULT_NUM_PORTS selectors */
  37. static const unsigned char ps2mult_controls[] = {
  38. PS2MULT_KB_SELECTOR, PS2MULT_MS_SELECTOR,
  39. PS2MULT_ESCAPE, PS2MULT_BSYNC,
  40. PS2MULT_SESSION_START, PS2MULT_SESSION_END,
  41. };
  42. static const struct serio_device_id ps2mult_serio_ids[] = {
  43. {
  44. .type = SERIO_RS232,
  45. .proto = SERIO_PS2MULT,
  46. .id = SERIO_ANY,
  47. .extra = SERIO_ANY,
  48. },
  49. { 0 }
  50. };
  51. MODULE_DEVICE_TABLE(serio, ps2mult_serio_ids);
  52. static void ps2mult_select_port(struct ps2mult *psm, struct ps2mult_port *port)
  53. {
  54. struct serio *mx_serio = psm->mx_serio;
  55. serio_write(mx_serio, port->sel);
  56. psm->out_port = port;
  57. dev_dbg(&mx_serio->dev, "switched to sel %02x\n", port->sel);
  58. }
  59. static int ps2mult_serio_write(struct serio *serio, unsigned char data)
  60. {
  61. struct serio *mx_port = serio->parent;
  62. struct ps2mult *psm = serio_get_drvdata(mx_port);
  63. struct ps2mult_port *port = serio->port_data;
  64. bool need_escape;
  65. guard(spinlock_irqsave)(&psm->lock);
  66. if (psm->out_port != port)
  67. ps2mult_select_port(psm, port);
  68. need_escape = memchr(ps2mult_controls, data, sizeof(ps2mult_controls));
  69. dev_dbg(&serio->dev,
  70. "write: %s%02x\n", need_escape ? "ESC " : "", data);
  71. if (need_escape)
  72. serio_write(mx_port, PS2MULT_ESCAPE);
  73. serio_write(mx_port, data);
  74. return 0;
  75. }
  76. static int ps2mult_serio_start(struct serio *serio)
  77. {
  78. struct ps2mult *psm = serio_get_drvdata(serio->parent);
  79. struct ps2mult_port *port = serio->port_data;
  80. guard(spinlock_irqsave)(&psm->lock);
  81. port->registered = true;
  82. return 0;
  83. }
  84. static void ps2mult_serio_stop(struct serio *serio)
  85. {
  86. struct ps2mult *psm = serio_get_drvdata(serio->parent);
  87. struct ps2mult_port *port = serio->port_data;
  88. guard(spinlock_irqsave)(&psm->lock);
  89. port->registered = false;
  90. }
  91. static int ps2mult_create_port(struct ps2mult *psm, int i)
  92. {
  93. struct serio *mx_serio = psm->mx_serio;
  94. struct serio *serio;
  95. serio = kzalloc_obj(*serio);
  96. if (!serio)
  97. return -ENOMEM;
  98. strscpy(serio->name, "TQC PS/2 Multiplexer", sizeof(serio->name));
  99. snprintf(serio->phys, sizeof(serio->phys),
  100. "%s/port%d", mx_serio->phys, i);
  101. serio->id.type = SERIO_8042;
  102. serio->write = ps2mult_serio_write;
  103. serio->start = ps2mult_serio_start;
  104. serio->stop = ps2mult_serio_stop;
  105. serio->parent = psm->mx_serio;
  106. serio->port_data = &psm->ports[i];
  107. psm->ports[i].serio = serio;
  108. return 0;
  109. }
  110. static void ps2mult_reset(struct ps2mult *psm)
  111. {
  112. guard(spinlock_irqsave)(&psm->lock);
  113. serio_write(psm->mx_serio, PS2MULT_SESSION_END);
  114. serio_write(psm->mx_serio, PS2MULT_SESSION_START);
  115. ps2mult_select_port(psm, &psm->ports[PS2MULT_KBD_PORT]);
  116. }
  117. static int ps2mult_connect(struct serio *serio, struct serio_driver *drv)
  118. {
  119. struct ps2mult *psm;
  120. int i;
  121. int error;
  122. if (!serio->write)
  123. return -EINVAL;
  124. psm = kzalloc_obj(*psm);
  125. if (!psm)
  126. return -ENOMEM;
  127. spin_lock_init(&psm->lock);
  128. psm->mx_serio = serio;
  129. for (i = 0; i < PS2MULT_NUM_PORTS; i++) {
  130. psm->ports[i].sel = ps2mult_controls[i];
  131. error = ps2mult_create_port(psm, i);
  132. if (error)
  133. goto err_out;
  134. }
  135. psm->in_port = psm->out_port = &psm->ports[PS2MULT_KBD_PORT];
  136. serio_set_drvdata(serio, psm);
  137. error = serio_open(serio, drv);
  138. if (error)
  139. goto err_out;
  140. ps2mult_reset(psm);
  141. for (i = 0; i < PS2MULT_NUM_PORTS; i++) {
  142. struct serio *s = psm->ports[i].serio;
  143. dev_info(&serio->dev, "%s port at %s\n", s->name, serio->phys);
  144. serio_register_port(s);
  145. }
  146. return 0;
  147. err_out:
  148. while (--i >= 0)
  149. kfree(psm->ports[i].serio);
  150. kfree(psm);
  151. return error;
  152. }
  153. static void ps2mult_disconnect(struct serio *serio)
  154. {
  155. struct ps2mult *psm = serio_get_drvdata(serio);
  156. /* Note that serio core already take care of children ports */
  157. serio_write(serio, PS2MULT_SESSION_END);
  158. serio_close(serio);
  159. kfree(psm);
  160. serio_set_drvdata(serio, NULL);
  161. }
  162. static int ps2mult_reconnect(struct serio *serio)
  163. {
  164. struct ps2mult *psm = serio_get_drvdata(serio);
  165. ps2mult_reset(psm);
  166. return 0;
  167. }
  168. static irqreturn_t ps2mult_interrupt(struct serio *serio,
  169. unsigned char data, unsigned int dfl)
  170. {
  171. struct ps2mult *psm = serio_get_drvdata(serio);
  172. struct ps2mult_port *in_port;
  173. dev_dbg(&serio->dev, "Received %02x flags %02x\n", data, dfl);
  174. guard(spinlock_irqsave)(&psm->lock);
  175. if (psm->escape) {
  176. psm->escape = false;
  177. in_port = psm->in_port;
  178. if (in_port->registered)
  179. serio_interrupt(in_port->serio, data, dfl);
  180. goto out;
  181. }
  182. switch (data) {
  183. case PS2MULT_ESCAPE:
  184. dev_dbg(&serio->dev, "ESCAPE\n");
  185. psm->escape = true;
  186. break;
  187. case PS2MULT_BSYNC:
  188. dev_dbg(&serio->dev, "BSYNC\n");
  189. psm->in_port = psm->out_port;
  190. break;
  191. case PS2MULT_SESSION_START:
  192. dev_dbg(&serio->dev, "SS\n");
  193. break;
  194. case PS2MULT_SESSION_END:
  195. dev_dbg(&serio->dev, "SE\n");
  196. break;
  197. case PS2MULT_KB_SELECTOR:
  198. dev_dbg(&serio->dev, "KB\n");
  199. psm->in_port = &psm->ports[PS2MULT_KBD_PORT];
  200. break;
  201. case PS2MULT_MS_SELECTOR:
  202. dev_dbg(&serio->dev, "MS\n");
  203. psm->in_port = &psm->ports[PS2MULT_MOUSE_PORT];
  204. break;
  205. default:
  206. in_port = psm->in_port;
  207. if (in_port->registered)
  208. serio_interrupt(in_port->serio, data, dfl);
  209. break;
  210. }
  211. out:
  212. return IRQ_HANDLED;
  213. }
  214. static struct serio_driver ps2mult_drv = {
  215. .driver = {
  216. .name = "ps2mult",
  217. },
  218. .description = "TQC PS/2 Multiplexer driver",
  219. .id_table = ps2mult_serio_ids,
  220. .interrupt = ps2mult_interrupt,
  221. .connect = ps2mult_connect,
  222. .disconnect = ps2mult_disconnect,
  223. .reconnect = ps2mult_reconnect,
  224. };
  225. module_serio_driver(ps2mult_drv);