162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Parallel port to Keyboard port adapter driver for Linux 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 1999-2004 Vojtech Pavlik 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci/* 1062306a36Sopenharmony_ci * To connect an AT or XT keyboard to the parallel port, a fairly simple adapter 1162306a36Sopenharmony_ci * can be made: 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * Parallel port Keyboard port 1462306a36Sopenharmony_ci * 1562306a36Sopenharmony_ci * +5V --------------------- +5V (4) 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * ______ 1862306a36Sopenharmony_ci * +5V -------|______|--. 1962306a36Sopenharmony_ci * | 2062306a36Sopenharmony_ci * ACK (10) ------------| 2162306a36Sopenharmony_ci * |--- KBD CLOCK (5) 2262306a36Sopenharmony_ci * STROBE (1) ---|<|----' 2362306a36Sopenharmony_ci * 2462306a36Sopenharmony_ci * ______ 2562306a36Sopenharmony_ci * +5V -------|______|--. 2662306a36Sopenharmony_ci * | 2762306a36Sopenharmony_ci * BUSY (11) -----------| 2862306a36Sopenharmony_ci * |--- KBD DATA (1) 2962306a36Sopenharmony_ci * AUTOFD (14) --|<|----' 3062306a36Sopenharmony_ci * 3162306a36Sopenharmony_ci * GND (18-25) ------------- GND (3) 3262306a36Sopenharmony_ci * 3362306a36Sopenharmony_ci * The diodes can be fairly any type, and the resistors should be somewhere 3462306a36Sopenharmony_ci * around 5 kOhm, but the adapter will likely work without the resistors, 3562306a36Sopenharmony_ci * too. 3662306a36Sopenharmony_ci * 3762306a36Sopenharmony_ci * The +5V source can be taken either from USB, from mouse or keyboard ports, 3862306a36Sopenharmony_ci * or from a joystick port. Unfortunately, the parallel port of a PC doesn't 3962306a36Sopenharmony_ci * have a +5V pin, and feeding the keyboard from signal pins is out of question 4062306a36Sopenharmony_ci * with 300 mA power reqirement of a typical AT keyboard. 4162306a36Sopenharmony_ci */ 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci#include <linux/module.h> 4462306a36Sopenharmony_ci#include <linux/parport.h> 4562306a36Sopenharmony_ci#include <linux/slab.h> 4662306a36Sopenharmony_ci#include <linux/init.h> 4762306a36Sopenharmony_ci#include <linux/serio.h> 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ciMODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); 5062306a36Sopenharmony_ciMODULE_DESCRIPTION("Parallel port to Keyboard port adapter driver"); 5162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic unsigned int parkbd_pp_no; 5462306a36Sopenharmony_cimodule_param_named(port, parkbd_pp_no, int, 0); 5562306a36Sopenharmony_ciMODULE_PARM_DESC(port, "Parallel port the adapter is connected to (default is 0)"); 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistatic unsigned int parkbd_mode = SERIO_8042; 5862306a36Sopenharmony_cimodule_param_named(mode, parkbd_mode, uint, 0); 5962306a36Sopenharmony_ciMODULE_PARM_DESC(mode, "Mode of operation: XT = 0/AT = 1 (default)"); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci#define PARKBD_CLOCK 0x01 /* Strobe & Ack */ 6262306a36Sopenharmony_ci#define PARKBD_DATA 0x02 /* AutoFd & Busy */ 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cistatic int parkbd_buffer; 6562306a36Sopenharmony_cistatic int parkbd_counter; 6662306a36Sopenharmony_cistatic unsigned long parkbd_last; 6762306a36Sopenharmony_cistatic int parkbd_writing; 6862306a36Sopenharmony_cistatic unsigned long parkbd_start; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic struct pardevice *parkbd_dev; 7162306a36Sopenharmony_cistatic struct serio *parkbd_port; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic int parkbd_readlines(void) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci return (parport_read_status(parkbd_dev->port) >> 6) ^ 2; 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic void parkbd_writelines(int data) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci parport_write_control(parkbd_dev->port, (~data & 3) | 0x10); 8162306a36Sopenharmony_ci} 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic int parkbd_write(struct serio *port, unsigned char c) 8462306a36Sopenharmony_ci{ 8562306a36Sopenharmony_ci unsigned char p; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci if (!parkbd_mode) return -1; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci p = c ^ (c >> 4); 9062306a36Sopenharmony_ci p = p ^ (p >> 2); 9162306a36Sopenharmony_ci p = p ^ (p >> 1); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci parkbd_counter = 0; 9462306a36Sopenharmony_ci parkbd_writing = 1; 9562306a36Sopenharmony_ci parkbd_buffer = c | (((int) (~p & 1)) << 8) | 0x600; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci parkbd_writelines(2); 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci return 0; 10062306a36Sopenharmony_ci} 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_cistatic void parkbd_interrupt(void *dev_id) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci if (parkbd_writing) { 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci if (parkbd_counter && ((parkbd_counter == 11) || time_after(jiffies, parkbd_last + HZ/100))) { 10862306a36Sopenharmony_ci parkbd_counter = 0; 10962306a36Sopenharmony_ci parkbd_buffer = 0; 11062306a36Sopenharmony_ci parkbd_writing = 0; 11162306a36Sopenharmony_ci parkbd_writelines(3); 11262306a36Sopenharmony_ci return; 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci parkbd_writelines(((parkbd_buffer >> parkbd_counter++) & 1) | 2); 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci if (parkbd_counter == 11) { 11862306a36Sopenharmony_ci parkbd_counter = 0; 11962306a36Sopenharmony_ci parkbd_buffer = 0; 12062306a36Sopenharmony_ci parkbd_writing = 0; 12162306a36Sopenharmony_ci parkbd_writelines(3); 12262306a36Sopenharmony_ci } 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci } else { 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci if ((parkbd_counter == parkbd_mode + 10) || time_after(jiffies, parkbd_last + HZ/100)) { 12762306a36Sopenharmony_ci parkbd_counter = 0; 12862306a36Sopenharmony_ci parkbd_buffer = 0; 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci parkbd_buffer |= (parkbd_readlines() >> 1) << parkbd_counter++; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci if (parkbd_counter == parkbd_mode + 10) 13462306a36Sopenharmony_ci serio_interrupt(parkbd_port, (parkbd_buffer >> (2 - parkbd_mode)) & 0xff, 0); 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci parkbd_last = jiffies; 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic int parkbd_getport(struct parport *pp) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci struct pardev_cb parkbd_parport_cb; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci memset(&parkbd_parport_cb, 0, sizeof(parkbd_parport_cb)); 14562306a36Sopenharmony_ci parkbd_parport_cb.irq_func = parkbd_interrupt; 14662306a36Sopenharmony_ci parkbd_parport_cb.flags = PARPORT_FLAG_EXCL; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci parkbd_dev = parport_register_dev_model(pp, "parkbd", 14962306a36Sopenharmony_ci &parkbd_parport_cb, 0); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if (!parkbd_dev) 15262306a36Sopenharmony_ci return -ENODEV; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci if (parport_claim(parkbd_dev)) { 15562306a36Sopenharmony_ci parport_unregister_device(parkbd_dev); 15662306a36Sopenharmony_ci return -EBUSY; 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci parkbd_start = jiffies; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci return 0; 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic struct serio *parkbd_allocate_serio(void) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci struct serio *serio; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci serio = kzalloc(sizeof(struct serio), GFP_KERNEL); 16962306a36Sopenharmony_ci if (serio) { 17062306a36Sopenharmony_ci serio->id.type = parkbd_mode; 17162306a36Sopenharmony_ci serio->write = parkbd_write; 17262306a36Sopenharmony_ci strscpy(serio->name, "PARKBD AT/XT keyboard adapter", sizeof(serio->name)); 17362306a36Sopenharmony_ci snprintf(serio->phys, sizeof(serio->phys), "%s/serio0", parkbd_dev->port->name); 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci return serio; 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_cistatic void parkbd_attach(struct parport *pp) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci if (pp->number != parkbd_pp_no) { 18262306a36Sopenharmony_ci pr_debug("Not using parport%d.\n", pp->number); 18362306a36Sopenharmony_ci return; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci if (parkbd_getport(pp)) 18762306a36Sopenharmony_ci return; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci parkbd_port = parkbd_allocate_serio(); 19062306a36Sopenharmony_ci if (!parkbd_port) { 19162306a36Sopenharmony_ci parport_release(parkbd_dev); 19262306a36Sopenharmony_ci parport_unregister_device(parkbd_dev); 19362306a36Sopenharmony_ci return; 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci parkbd_writelines(3); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci serio_register_port(parkbd_port); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci printk(KERN_INFO "serio: PARKBD %s adapter on %s\n", 20162306a36Sopenharmony_ci parkbd_mode ? "AT" : "XT", parkbd_dev->port->name); 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci return; 20462306a36Sopenharmony_ci} 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_cistatic void parkbd_detach(struct parport *port) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci if (!parkbd_port || port->number != parkbd_pp_no) 20962306a36Sopenharmony_ci return; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci parport_release(parkbd_dev); 21262306a36Sopenharmony_ci serio_unregister_port(parkbd_port); 21362306a36Sopenharmony_ci parport_unregister_device(parkbd_dev); 21462306a36Sopenharmony_ci parkbd_port = NULL; 21562306a36Sopenharmony_ci} 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_cistatic struct parport_driver parkbd_parport_driver = { 21862306a36Sopenharmony_ci .name = "parkbd", 21962306a36Sopenharmony_ci .match_port = parkbd_attach, 22062306a36Sopenharmony_ci .detach = parkbd_detach, 22162306a36Sopenharmony_ci .devmodel = true, 22262306a36Sopenharmony_ci}; 22362306a36Sopenharmony_cimodule_parport_driver(parkbd_parport_driver); 224