18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Parallel port to Keyboard port adapter driver for Linux 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 1999-2004 Vojtech Pavlik 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci/* 108c2ecf20Sopenharmony_ci * To connect an AT or XT keyboard to the parallel port, a fairly simple adapter 118c2ecf20Sopenharmony_ci * can be made: 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * Parallel port Keyboard port 148c2ecf20Sopenharmony_ci * 158c2ecf20Sopenharmony_ci * +5V --------------------- +5V (4) 168c2ecf20Sopenharmony_ci * 178c2ecf20Sopenharmony_ci * ______ 188c2ecf20Sopenharmony_ci * +5V -------|______|--. 198c2ecf20Sopenharmony_ci * | 208c2ecf20Sopenharmony_ci * ACK (10) ------------| 218c2ecf20Sopenharmony_ci * |--- KBD CLOCK (5) 228c2ecf20Sopenharmony_ci * STROBE (1) ---|<|----' 238c2ecf20Sopenharmony_ci * 248c2ecf20Sopenharmony_ci * ______ 258c2ecf20Sopenharmony_ci * +5V -------|______|--. 268c2ecf20Sopenharmony_ci * | 278c2ecf20Sopenharmony_ci * BUSY (11) -----------| 288c2ecf20Sopenharmony_ci * |--- KBD DATA (1) 298c2ecf20Sopenharmony_ci * AUTOFD (14) --|<|----' 308c2ecf20Sopenharmony_ci * 318c2ecf20Sopenharmony_ci * GND (18-25) ------------- GND (3) 328c2ecf20Sopenharmony_ci * 338c2ecf20Sopenharmony_ci * The diodes can be fairly any type, and the resistors should be somewhere 348c2ecf20Sopenharmony_ci * around 5 kOhm, but the adapter will likely work without the resistors, 358c2ecf20Sopenharmony_ci * too. 368c2ecf20Sopenharmony_ci * 378c2ecf20Sopenharmony_ci * The +5V source can be taken either from USB, from mouse or keyboard ports, 388c2ecf20Sopenharmony_ci * or from a joystick port. Unfortunately, the parallel port of a PC doesn't 398c2ecf20Sopenharmony_ci * have a +5V pin, and feeding the keyboard from signal pins is out of question 408c2ecf20Sopenharmony_ci * with 300 mA power reqirement of a typical AT keyboard. 418c2ecf20Sopenharmony_ci */ 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci#include <linux/module.h> 448c2ecf20Sopenharmony_ci#include <linux/parport.h> 458c2ecf20Sopenharmony_ci#include <linux/slab.h> 468c2ecf20Sopenharmony_ci#include <linux/init.h> 478c2ecf20Sopenharmony_ci#include <linux/serio.h> 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); 508c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Parallel port to Keyboard port adapter driver"); 518c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistatic unsigned int parkbd_pp_no; 548c2ecf20Sopenharmony_cimodule_param_named(port, parkbd_pp_no, int, 0); 558c2ecf20Sopenharmony_ciMODULE_PARM_DESC(port, "Parallel port the adapter is connected to (default is 0)"); 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic unsigned int parkbd_mode = SERIO_8042; 588c2ecf20Sopenharmony_cimodule_param_named(mode, parkbd_mode, uint, 0); 598c2ecf20Sopenharmony_ciMODULE_PARM_DESC(mode, "Mode of operation: XT = 0/AT = 1 (default)"); 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci#define PARKBD_CLOCK 0x01 /* Strobe & Ack */ 628c2ecf20Sopenharmony_ci#define PARKBD_DATA 0x02 /* AutoFd & Busy */ 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic int parkbd_buffer; 658c2ecf20Sopenharmony_cistatic int parkbd_counter; 668c2ecf20Sopenharmony_cistatic unsigned long parkbd_last; 678c2ecf20Sopenharmony_cistatic int parkbd_writing; 688c2ecf20Sopenharmony_cistatic unsigned long parkbd_start; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistatic struct pardevice *parkbd_dev; 718c2ecf20Sopenharmony_cistatic struct serio *parkbd_port; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic int parkbd_readlines(void) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci return (parport_read_status(parkbd_dev->port) >> 6) ^ 2; 768c2ecf20Sopenharmony_ci} 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistatic void parkbd_writelines(int data) 798c2ecf20Sopenharmony_ci{ 808c2ecf20Sopenharmony_ci parport_write_control(parkbd_dev->port, (~data & 3) | 0x10); 818c2ecf20Sopenharmony_ci} 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_cistatic int parkbd_write(struct serio *port, unsigned char c) 848c2ecf20Sopenharmony_ci{ 858c2ecf20Sopenharmony_ci unsigned char p; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci if (!parkbd_mode) return -1; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci p = c ^ (c >> 4); 908c2ecf20Sopenharmony_ci p = p ^ (p >> 2); 918c2ecf20Sopenharmony_ci p = p ^ (p >> 1); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci parkbd_counter = 0; 948c2ecf20Sopenharmony_ci parkbd_writing = 1; 958c2ecf20Sopenharmony_ci parkbd_buffer = c | (((int) (~p & 1)) << 8) | 0x600; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci parkbd_writelines(2); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci return 0; 1008c2ecf20Sopenharmony_ci} 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic void parkbd_interrupt(void *dev_id) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci if (parkbd_writing) { 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci if (parkbd_counter && ((parkbd_counter == 11) || time_after(jiffies, parkbd_last + HZ/100))) { 1088c2ecf20Sopenharmony_ci parkbd_counter = 0; 1098c2ecf20Sopenharmony_ci parkbd_buffer = 0; 1108c2ecf20Sopenharmony_ci parkbd_writing = 0; 1118c2ecf20Sopenharmony_ci parkbd_writelines(3); 1128c2ecf20Sopenharmony_ci return; 1138c2ecf20Sopenharmony_ci } 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci parkbd_writelines(((parkbd_buffer >> parkbd_counter++) & 1) | 2); 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci if (parkbd_counter == 11) { 1188c2ecf20Sopenharmony_ci parkbd_counter = 0; 1198c2ecf20Sopenharmony_ci parkbd_buffer = 0; 1208c2ecf20Sopenharmony_ci parkbd_writing = 0; 1218c2ecf20Sopenharmony_ci parkbd_writelines(3); 1228c2ecf20Sopenharmony_ci } 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci } else { 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci if ((parkbd_counter == parkbd_mode + 10) || time_after(jiffies, parkbd_last + HZ/100)) { 1278c2ecf20Sopenharmony_ci parkbd_counter = 0; 1288c2ecf20Sopenharmony_ci parkbd_buffer = 0; 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci parkbd_buffer |= (parkbd_readlines() >> 1) << parkbd_counter++; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci if (parkbd_counter == parkbd_mode + 10) 1348c2ecf20Sopenharmony_ci serio_interrupt(parkbd_port, (parkbd_buffer >> (2 - parkbd_mode)) & 0xff, 0); 1358c2ecf20Sopenharmony_ci } 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci parkbd_last = jiffies; 1388c2ecf20Sopenharmony_ci} 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_cistatic int parkbd_getport(struct parport *pp) 1418c2ecf20Sopenharmony_ci{ 1428c2ecf20Sopenharmony_ci struct pardev_cb parkbd_parport_cb; 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci memset(&parkbd_parport_cb, 0, sizeof(parkbd_parport_cb)); 1458c2ecf20Sopenharmony_ci parkbd_parport_cb.irq_func = parkbd_interrupt; 1468c2ecf20Sopenharmony_ci parkbd_parport_cb.flags = PARPORT_FLAG_EXCL; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci parkbd_dev = parport_register_dev_model(pp, "parkbd", 1498c2ecf20Sopenharmony_ci &parkbd_parport_cb, 0); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci if (!parkbd_dev) 1528c2ecf20Sopenharmony_ci return -ENODEV; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci if (parport_claim(parkbd_dev)) { 1558c2ecf20Sopenharmony_ci parport_unregister_device(parkbd_dev); 1568c2ecf20Sopenharmony_ci return -EBUSY; 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci parkbd_start = jiffies; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci return 0; 1628c2ecf20Sopenharmony_ci} 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cistatic struct serio *parkbd_allocate_serio(void) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci struct serio *serio; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci serio = kzalloc(sizeof(struct serio), GFP_KERNEL); 1698c2ecf20Sopenharmony_ci if (serio) { 1708c2ecf20Sopenharmony_ci serio->id.type = parkbd_mode; 1718c2ecf20Sopenharmony_ci serio->write = parkbd_write, 1728c2ecf20Sopenharmony_ci strlcpy(serio->name, "PARKBD AT/XT keyboard adapter", sizeof(serio->name)); 1738c2ecf20Sopenharmony_ci snprintf(serio->phys, sizeof(serio->phys), "%s/serio0", parkbd_dev->port->name); 1748c2ecf20Sopenharmony_ci } 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci return serio; 1778c2ecf20Sopenharmony_ci} 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_cistatic void parkbd_attach(struct parport *pp) 1808c2ecf20Sopenharmony_ci{ 1818c2ecf20Sopenharmony_ci if (pp->number != parkbd_pp_no) { 1828c2ecf20Sopenharmony_ci pr_debug("Not using parport%d.\n", pp->number); 1838c2ecf20Sopenharmony_ci return; 1848c2ecf20Sopenharmony_ci } 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci if (parkbd_getport(pp)) 1878c2ecf20Sopenharmony_ci return; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci parkbd_port = parkbd_allocate_serio(); 1908c2ecf20Sopenharmony_ci if (!parkbd_port) { 1918c2ecf20Sopenharmony_ci parport_release(parkbd_dev); 1928c2ecf20Sopenharmony_ci parport_unregister_device(parkbd_dev); 1938c2ecf20Sopenharmony_ci return; 1948c2ecf20Sopenharmony_ci } 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci parkbd_writelines(3); 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci serio_register_port(parkbd_port); 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci printk(KERN_INFO "serio: PARKBD %s adapter on %s\n", 2018c2ecf20Sopenharmony_ci parkbd_mode ? "AT" : "XT", parkbd_dev->port->name); 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci return; 2048c2ecf20Sopenharmony_ci} 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_cistatic void parkbd_detach(struct parport *port) 2078c2ecf20Sopenharmony_ci{ 2088c2ecf20Sopenharmony_ci if (!parkbd_port || port->number != parkbd_pp_no) 2098c2ecf20Sopenharmony_ci return; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci parport_release(parkbd_dev); 2128c2ecf20Sopenharmony_ci serio_unregister_port(parkbd_port); 2138c2ecf20Sopenharmony_ci parport_unregister_device(parkbd_dev); 2148c2ecf20Sopenharmony_ci parkbd_port = NULL; 2158c2ecf20Sopenharmony_ci} 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_cistatic struct parport_driver parkbd_parport_driver = { 2188c2ecf20Sopenharmony_ci .name = "parkbd", 2198c2ecf20Sopenharmony_ci .match_port = parkbd_attach, 2208c2ecf20Sopenharmony_ci .detach = parkbd_detach, 2218c2ecf20Sopenharmony_ci .devmodel = true, 2228c2ecf20Sopenharmony_ci}; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_cistatic int __init parkbd_init(void) 2258c2ecf20Sopenharmony_ci{ 2268c2ecf20Sopenharmony_ci return parport_register_driver(&parkbd_parport_driver); 2278c2ecf20Sopenharmony_ci} 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_cistatic void __exit parkbd_exit(void) 2308c2ecf20Sopenharmony_ci{ 2318c2ecf20Sopenharmony_ci parport_unregister_driver(&parkbd_parport_driver); 2328c2ecf20Sopenharmony_ci} 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_cimodule_init(parkbd_init); 2358c2ecf20Sopenharmony_cimodule_exit(parkbd_exit); 236