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