linux/drivers/input/serio/parkbd.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 *  Parallel port to Keyboard port adapter driver for Linux
   4 *
   5 *  Copyright (c) 1999-2004 Vojtech Pavlik
   6 */
   7
   8
   9/*
  10 * To connect an AT or XT keyboard to the parallel port, a fairly simple adapter
  11 * can be made:
  12 * 
  13 *  Parallel port            Keyboard port
  14 *
  15 *     +5V --------------------- +5V (4)
  16 *  
  17 *                 ______
  18 *     +5V -------|______|--.
  19 *                          |
  20 *     ACK (10) ------------|
  21 *                          |--- KBD CLOCK (5)
  22 *     STROBE (1) ---|<|----'
  23 *     
  24 *                 ______
  25 *     +5V -------|______|--.
  26 *                          |
  27 *     BUSY (11) -----------|
  28 *                          |--- KBD DATA (1)
  29 *     AUTOFD (14) --|<|----'
  30 *
  31 *     GND (18-25) ------------- GND (3)
  32 *     
  33 * The diodes can be fairly any type, and the resistors should be somewhere
  34 * around 5 kOhm, but the adapter will likely work without the resistors,
  35 * too.
  36 *
  37 * The +5V source can be taken either from USB, from mouse or keyboard ports,
  38 * or from a joystick port. Unfortunately, the parallel port of a PC doesn't
  39 * have a +5V pin, and feeding the keyboard from signal pins is out of question
  40 * with 300 mA power reqirement of a typical AT keyboard.
  41 */
  42
  43#include <linux/module.h>
  44#include <linux/parport.h>
  45#include <linux/slab.h>
  46#include <linux/init.h>
  47#include <linux/serio.h>
  48
  49MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
  50MODULE_DESCRIPTION("Parallel port to Keyboard port adapter driver");
  51MODULE_LICENSE("GPL");
  52
  53static unsigned int parkbd_pp_no;
  54module_param_named(port, parkbd_pp_no, int, 0);
  55MODULE_PARM_DESC(port, "Parallel port the adapter is connected to (default is 0)");
  56
  57static unsigned int parkbd_mode = SERIO_8042;
  58module_param_named(mode, parkbd_mode, uint, 0);
  59MODULE_PARM_DESC(mode, "Mode of operation: XT = 0/AT = 1 (default)");
  60
  61#define PARKBD_CLOCK    0x01    /* Strobe & Ack */
  62#define PARKBD_DATA     0x02    /* AutoFd & Busy */
  63
  64static int parkbd_buffer;
  65static int parkbd_counter;
  66static unsigned long parkbd_last;
  67static int parkbd_writing;
  68static unsigned long parkbd_start;
  69
  70static struct pardevice *parkbd_dev;
  71static struct serio *parkbd_port;
  72
  73static int parkbd_readlines(void)
  74{
  75        return (parport_read_status(parkbd_dev->port) >> 6) ^ 2;
  76}
  77
  78static void parkbd_writelines(int data)
  79{
  80        parport_write_control(parkbd_dev->port, (~data & 3) | 0x10);
  81}
  82
  83static int parkbd_write(struct serio *port, unsigned char c)
  84{
  85        unsigned char p;
  86
  87        if (!parkbd_mode) return -1;
  88
  89        p = c ^ (c >> 4);
  90        p = p ^ (p >> 2);
  91        p = p ^ (p >> 1);
  92
  93        parkbd_counter = 0;
  94        parkbd_writing = 1;
  95        parkbd_buffer = c | (((int) (~p & 1)) << 8) | 0x600;
  96
  97        parkbd_writelines(2);
  98
  99        return 0;
 100}
 101
 102static void parkbd_interrupt(void *dev_id)
 103{
 104
 105        if (parkbd_writing) {
 106
 107                if (parkbd_counter && ((parkbd_counter == 11) || time_after(jiffies, parkbd_last + HZ/100))) {
 108                        parkbd_counter = 0;
 109                        parkbd_buffer = 0;
 110                        parkbd_writing = 0;
 111                        parkbd_writelines(3);
 112                        return;
 113                }
 114
 115                parkbd_writelines(((parkbd_buffer >> parkbd_counter++) & 1) | 2);
 116
 117                if (parkbd_counter == 11) {
 118                        parkbd_counter = 0;
 119                        parkbd_buffer = 0;
 120                        parkbd_writing = 0;
 121                        parkbd_writelines(3);
 122                }
 123
 124        } else {
 125
 126                if ((parkbd_counter == parkbd_mode + 10) || time_after(jiffies, parkbd_last + HZ/100)) {
 127                        parkbd_counter = 0;
 128                        parkbd_buffer = 0;
 129                }
 130
 131                parkbd_buffer |= (parkbd_readlines() >> 1) << parkbd_counter++;
 132
 133                if (parkbd_counter == parkbd_mode + 10)
 134                        serio_interrupt(parkbd_port, (parkbd_buffer >> (2 - parkbd_mode)) & 0xff, 0);
 135        }
 136
 137        parkbd_last = jiffies;
 138}
 139
 140static int parkbd_getport(struct parport *pp)
 141{
 142        struct pardev_cb parkbd_parport_cb;
 143
 144        memset(&parkbd_parport_cb, 0, sizeof(parkbd_parport_cb));
 145        parkbd_parport_cb.irq_func = parkbd_interrupt;
 146        parkbd_parport_cb.flags = PARPORT_FLAG_EXCL;
 147
 148        parkbd_dev = parport_register_dev_model(pp, "parkbd",
 149                                                &parkbd_parport_cb, 0);
 150
 151        if (!parkbd_dev)
 152                return -ENODEV;
 153
 154        if (parport_claim(parkbd_dev)) {
 155                parport_unregister_device(parkbd_dev);
 156                return -EBUSY;
 157        }
 158
 159        parkbd_start = jiffies;
 160
 161        return 0;
 162}
 163
 164static struct serio *parkbd_allocate_serio(void)
 165{
 166        struct serio *serio;
 167
 168        serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
 169        if (serio) {
 170                serio->id.type = parkbd_mode;
 171                serio->write = parkbd_write;
 172                strlcpy(serio->name, "PARKBD AT/XT keyboard adapter", sizeof(serio->name));
 173                snprintf(serio->phys, sizeof(serio->phys), "%s/serio0", parkbd_dev->port->name);
 174        }
 175
 176        return serio;
 177}
 178
 179static void parkbd_attach(struct parport *pp)
 180{
 181        if (pp->number != parkbd_pp_no) {
 182                pr_debug("Not using parport%d.\n", pp->number);
 183                return;
 184        }
 185
 186        if (parkbd_getport(pp))
 187                return;
 188
 189        parkbd_port = parkbd_allocate_serio();
 190        if (!parkbd_port) {
 191                parport_release(parkbd_dev);
 192                parport_unregister_device(parkbd_dev);
 193                return;
 194        }
 195
 196        parkbd_writelines(3);
 197
 198        serio_register_port(parkbd_port);
 199
 200        printk(KERN_INFO "serio: PARKBD %s adapter on %s\n",
 201                        parkbd_mode ? "AT" : "XT", parkbd_dev->port->name);
 202
 203        return;
 204}
 205
 206static void parkbd_detach(struct parport *port)
 207{
 208        if (!parkbd_port || port->number != parkbd_pp_no)
 209                return;
 210
 211        parport_release(parkbd_dev);
 212        serio_unregister_port(parkbd_port);
 213        parport_unregister_device(parkbd_dev);
 214        parkbd_port = NULL;
 215}
 216
 217static struct parport_driver parkbd_parport_driver = {
 218        .name = "parkbd",
 219        .match_port = parkbd_attach,
 220        .detach = parkbd_detach,
 221        .devmodel = true,
 222};
 223module_parport_driver(parkbd_parport_driver);
 224