/* * Copyright (C) 2004 by Jan-Benedict Glaw <jbglaw@lug-owl.de> */ /* * LK keyboard driver for Linux, based on sunkbd.c (C) by Vojtech Pavlik */ /* * DEC LK201 and LK401 keyboard driver for Linux (primary for DECstations * and VAXstations, but can also be used on any standard RS232 with an * adaptor). * * DISCLAIMER: This works for _me_. If you break anything by using the * information given below, I will _not_ be liable! * * RJ10 pinout: To DE9: Or DB25: * 1 - RxD <----> Pin 3 (TxD) <-> Pin 2 (TxD) * 2 - GND <----> Pin 5 (GND) <-> Pin 7 (GND) * 4 - TxD <----> Pin 2 (RxD) <-> Pin 3 (RxD) * 3 - +12V (from HDD drive connector), DON'T connect to DE9 or DB25!!! * * Pin numbers for DE9 and DB25 are noted on the plug (quite small:). For * RJ10, it's like this: * * __=__ Hold the plug in front of you, cable downwards, * /___/| nose is hidden behind the plug. Now, pin 1 is at * |1234|| the left side, pin 4 at the right and 2 and 3 are * |IIII|| in between, of course:) * | || * |____|/ * || So the adaptor consists of three connected cables * || for data transmission (RxD and TxD) and signal ground. * Additionally, you have to get +12V from somewhere. * Most easily, you'll get that from a floppy or HDD power connector. * It's the yellow cable there (black is ground and red is +5V). * * The keyboard and all the commands it understands are documented in * "VCB02 Video Subsystem - Technical Manual", EK-104AA-TM-001. This * document is LK201 specific, but LK401 is mostly compatible. It comes * up in LK201 mode and doesn't report any of the additional keys it * has. These need to be switched on with the LK_CMD_ENABLE_LK401 * command. You'll find this document (scanned .pdf file) on MANX, * a search engine specific to DEC documentation. Try * http://www.vt100.net/manx/details?pn=EK-104AA-TM-001;id=21;cp=1 */ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/delay.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/interrupt.h> #include <linux/init.h> #include <linux/input.h> #include <linux/serio.h> #include <linux/workqueue.h> #define DRIVER_DESC "LK keyboard driver" MODULE_AUTHOR("Jan-Benedict Glaw <jbglaw@lug-owl.de>"); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE("GPL"); /* * Known parameters: * bell_volume * keyclick_volume * ctrlclick_volume * * Please notice that there's not yet an API to set these at runtime. */ static int bell_volume = 100; /* % */ module_param(bell_volume, int, 0); MODULE_PARM_DESC(bell_volume, "Bell volume (in %). default is 100%"); static int keyclick_volume = 100; /* % */ module_param(keyclick_volume, int, 0); MODULE_PARM_DESC(keyclick_volume, "Keyclick volume (in %), default is 100%"); static int ctrlclick_volume = 100; /* % */ module_param(ctrlclick_volume, int, 0); MODULE_PARM_DESC(ctrlclick_volume, "Ctrlclick volume (in %), default is 100%"); static int lk201_compose_is_alt; module_param(lk201_compose_is_alt, int, 0); MODULE_PARM_DESC(lk201_compose_is_alt, "If set non-zero, LK201' Compose key will act as an Alt key"); #undef LKKBD_DEBUG #ifdef LKKBD_DEBUG #define DBG(x...) printk(x) #else #define DBG(x...) do {} while (0) #endif /* LED control */ #define LK_LED_WAIT 0x81 #define LK_LED_COMPOSE 0x82 #define LK_LED_SHIFTLOCK 0x84 #define LK_LED_SCROLLLOCK 0x88 #define LK_CMD_LED_ON 0x13 #define LK_CMD_LED_OFF 0x11 /* Mode control */ #define LK_MODE_DOWN 0x80 #define LK_MODE_AUTODOWN 0x82 #define LK_MODE_UPDOWN 0x86 #define LK_CMD_SET_MODE(mode, div) ((mode) | ((div) << 3)) /* Misc commands */ #define LK_CMD_ENABLE_KEYCLICK 0x1b #define LK_CMD_DISABLE_KEYCLICK 0x99 #define LK_CMD_DISABLE_BELL 0xa1 #define LK_CMD_SOUND_BELL 0xa7 #define LK_CMD_ENABLE_BELL 0x23 #define LK_CMD_DISABLE_CTRCLICK 0xb9 #define LK_CMD_ENABLE_CTRCLICK 0xbb #define LK_CMD_SET_DEFAULTS 0xd3 #define LK_CMD_POWERCYCLE_RESET 0xfd #define LK_CMD_ENABLE_LK401 0xe9 #define LK_CMD_REQUEST_ID 0xab /* Misc responses from keyboard */ #define LK_STUCK_KEY 0x3d #define LK_SELFTEST_FAILED 0x3e #define LK_ALL_KEYS_UP 0xb3 #define LK_METRONOME 0xb4 #define LK_OUTPUT_ERROR 0xb5 #define LK_INPUT_ERROR 0xb6 #define LK_KBD_LOCKED 0xb7 #define LK_KBD_TEST_MODE_ACK 0xb8 #define LK_PREFIX_KEY_DOWN 0xb9 #define LK_MODE_CHANGE_ACK 0xba #define LK_RESPONSE_RESERVED 0xbb #define LK_NUM_KEYCODES 256 #define LK_NUM_IGNORE_BYTES 6 static unsigned short lkkbd_keycode[LK_NUM_KEYCODES] = { [0x56] = KEY_F1, [0x57] = KEY_F2, [0x58] = KEY_F3, [0x59] = KEY_F4, [0x5a] = KEY_F5, [0x64] = KEY_F6, [0x65] = KEY_F7, [0x66] = KEY_F8, [0x67] = KEY_F9, [0x68] = KEY_F10, [0x71] = KEY_F11, [0x72] = KEY_F12, [0x73] = KEY_F13, [0x74] = KEY_F14, [0x7c] = KEY_F15, [0x7d] = KEY_F16, [0x80] = KEY_F17, [0x81] = KEY_F18, [0x82] = KEY_F19, [0x83] = KEY_F20, [0x8a] = KEY_FIND, [0x8b] = KEY_INSERT, [0x8c] = KEY_DELETE, [0x8d] = KEY_SELECT, [0x8e] = KEY_PAGEUP, [0x8f] = KEY_PAGEDOWN, [0x92] = KEY_KP0, [0x94] = KEY_KPDOT, [0x95] = KEY_KPENTER, [0x96] = KEY_KP1, [0x97] = KEY_KP2, [0x98] = KEY_KP3, [0x99] = KEY_KP4, [0x9a] = KEY_KP5, [0x9b] = KEY_KP6, [0x9c] = KEY_KPCOMMA, [0x9d] = KEY_KP7, [0x9e] = KEY_KP8, [0x9f] = KEY_KP9, [0xa0] = KEY_KPMINUS, [0xa1] = KEY_PROG1, [0xa2] = KEY_PROG2, [0xa3] = KEY_PROG3, [0xa4] = KEY_PROG4, [0xa7] = KEY_LEFT, [0xa8] = KEY_RIGHT, [0xa9] = KEY_DOWN, [0xaa] = KEY_UP, [0xab] = KEY_RIGHTSHIFT, [0xac] = KEY_LEFTALT, [0xad] = KEY_COMPOSE, /* Right Compose, that is. */ [0xae] = KEY_LEFTSHIFT, /* Same as KEY_RIGHTSHIFT on LK201 */ [0xaf] = KEY_LEFTCTRL, [0xb0] = KEY_CAPSLOCK, [0xb1] = KEY_COMPOSE, /* Left Compose, that is. */ [0xb2] = KEY_RIGHTALT, [0xbc] = KEY_BACKSPACE, [0xbd] = KEY_ENTER, [0xbe] = KEY_TAB, [0xbf] = KEY_ESC, [0xc0] = KEY_1, [0xc1] = KEY_Q, [0xc2] = KEY_A, [0xc3] = KEY_Z, [0xc5] = KEY_2, [0xc6] = KEY_W, [0xc7] = KEY_S, [0xc8] = KEY_X, [0xc9] = KEY_102ND, [0xcb] = KEY_3, [0xcc] = KEY_E, [0xcd] = KEY_D, [0xce] = KEY_C, [0xd0] = KEY_4, [0xd1] = KEY_R, [0xd2] = KEY_F, [0xd3] = KEY_V, [0xd4] = KEY_SPACE, [0xd6] = KEY_5, [0xd7] = KEY_T, [0xd8] = KEY_G, [0xd9] = KEY_B, [0xdb] = KEY_6, [0xdc] = KEY_Y, [0xdd] = KEY_H, [0xde] = KEY_N, [0xe0] = KEY_7, [0xe1] = KEY_U, [0xe2] = KEY_J, [0xe3] = KEY_M, [0xe5] = KEY_8, [0xe6] = KEY_I, [0xe7] = KEY_K, [0xe8] = KEY_COMMA, [0xea] = KEY_9, [0xeb] = KEY_O, [0xec] = KEY_L, [0xed] = KEY_DOT, [0xef] = KEY_0, [0xf0] = KEY_P, [0xf2] = KEY_SEMICOLON, [0xf3] = KEY_SLASH, [0xf5] = KEY_EQUAL, [0xf6] = KEY_RIGHTBRACE, [0xf7] = KEY_BACKSLASH, [0xf9] = KEY_MINUS, [0xfa] = KEY_LEFTBRACE, [0xfb] = KEY_APOSTROPHE, }; #define CHECK_LED(LK, VAR_ON, VAR_OFF, LED, BITS) do { \ if (test_bit(LED, (LK)->dev->led)) \ VAR_ON |= BITS; \ else \ VAR_OFF |= BITS; \ } while (0) /* * Per-keyboard data */ struct lkkbd { unsigned short keycode[LK_NUM_KEYCODES]; int ignore_bytes; unsigned char id[LK_NUM_IGNORE_BYTES]; struct input_dev *dev; struct serio *serio; struct work_struct tq; char name[64]; char phys[32]; char type; int bell_volume; int keyclick_volume; int ctrlclick_volume; }; #ifdef LKKBD_DEBUG /* * Responses from the keyboard and mapping back to their names. */ static struct { unsigned char value; unsigned char *name; } lk_response[] = { #define RESPONSE(x) { .value = (x), .name = #x, } RESPONSE(LK_STUCK_KEY), RESPONSE(LK_SELFTEST_FAILED), RESPONSE(LK_ALL_KEYS_UP), RESPONSE(LK_METRONOME), RESPONSE(LK_OUTPUT_ERROR), RESPONSE(LK_INPUT_ERROR), RESPONSE(LK_KBD_LOCKED), RESPONSE(LK_KBD_TEST_MODE_ACK), RESPONSE(LK_PREFIX_KEY_DOWN), RESPONSE(LK_MODE_CHANGE_ACK), RESPONSE(LK_RESPONSE_RESERVED), #undef RESPONSE }; static unsigned char *response_name(unsigned char value) { int i; for (i = 0; i < ARRAY_SIZE(lk_response); i++) if (lk_response[i].value == value) return lk_response[i].name; return "<unknown>"; } #endif /* LKKBD_DEBUG */ /* * Calculate volume parameter byte for a given volume. */ static unsigned char volume_to_hw(int volume_percent) { unsigned char ret = 0; if (volume_percent < 0) volume_percent = 0; if (volume_percent > 100) volume_percent = 100; if (volume_percent >= 0) ret = 7; if (volume_percent >= 13) /* 12.5 */ ret = 6; if (volume_percent >= 25) ret = 5; if (volume_percent >= 38) /* 37.5 */ ret = 4; if (volume_percent >= 50) ret = 3; if (volume_percent >= 63) /* 62.5 */ ret = 2; /* This is the default volume */ if (volume_percent >= 75) ret = 1; if (volume_percent >= 88) /* 87.5 */ ret = 0; ret |= 0x80; return ret; } static void lkkbd_detection_done(struct lkkbd *lk) { int i; /* * Reset setting for Compose key. Let Compose be KEY_COMPOSE. */ lk->keycode[0xb1] = KEY_COMPOSE; /* * Print keyboard name and modify Compose=Alt on user's request. */ switch (lk->id[4]) { case 1: strlcpy(lk->name, "DEC LK201 keyboard", sizeof(lk->name)); if (lk201_compose_is_alt) lk->keycode[0xb1] = KEY_LEFTALT; break; case 2: strlcpy(lk->name, "DEC LK401 keyboard", sizeof(lk->name)); break; default: strlcpy(lk->name, "Unknown DEC keyboard", sizeof(lk->name)); printk(KERN_ERR "lkkbd: keyboard on %s is unknown, please report to " "Jan-Benedict Glaw <jbglaw@lug-owl.de>\n", lk->phys); printk(KERN_ERR "lkkbd: keyboard ID'ed as:"); for (i = 0; i < LK_NUM_IGNORE_BYTES; i++) printk(" 0x%02x", lk->id[i]); printk("\n"); break; } printk(KERN_INFO "lkkbd: keyboard on %s identified as: %s\n", lk->phys, lk->name); /* * Report errors during keyboard boot-up. */ switch (lk->id[2]) { case 0x00: /* All okay */ break; case LK_STUCK_KEY: printk(KERN_ERR "lkkbd: Stuck key on keyboard at %s\n", lk->phys); break; case LK_SELFTEST_FAILED: printk(KERN_ERR "lkkbd: Selftest failed on keyboard at %s, " "keyboard may not work properly\n", lk->phys); break; default: printk(KERN_ERR "lkkbd: Unknown error %02x on keyboard at %s\n", lk->id[2], lk->phys); break; } /* * Try to hint user if there's a stuck key. */ if (lk->id[2] == LK_STUCK_KEY && lk->id[3] != 0) printk(KERN_ERR "Scancode of stuck key is 0x%02x, keycode is 0x%04x\n", lk->id[3], lk->keycode[lk->id[3]]); } /* * lkkbd_interrupt() is called by the low level driver when a character * is received. */ static irqreturn_t lkkbd_interrupt(struct serio *serio, unsigned char data, unsigned int flags) { struct lkkbd *lk = serio_get_drvdata(serio); struct input_dev *input_dev = lk->dev; unsigned int keycode; int i; DBG(KERN_INFO "Got byte 0x%02x\n", data); if (lk->ignore_bytes > 0) { DBG(KERN_INFO "Ignoring a byte on %s\n", lk->name); lk->id[LK_NUM_IGNORE_BYTES - lk->ignore_bytes--] = data; if (lk->ignore_bytes == 0) lkkbd_detection_done(lk); return IRQ_HANDLED; } switch (data) { case LK_ALL_KEYS_UP: for (i = 0; i < ARRAY_SIZE(lkkbd_keycode); i++) input_report_key(input_dev, lk->keycode[i], 0); input_sync(input_dev); break; case 0x01: DBG(KERN_INFO "Got 0x01, scheduling re-initialization\n"); lk->ignore_bytes = LK_NUM_IGNORE_BYTES; lk->id[LK_NUM_IGNORE_BYTES - lk->ignore_bytes--] = data; schedule_work(&lk->tq); break; case LK_METRONOME: case LK_OUTPUT_ERROR: case LK_INPUT_ERROR: case LK_KBD_LOCKED: case LK_KBD_TEST_MODE_ACK: case LK_PREFIX_KEY_DOWN: case LK_MODE_CHANGE_ACK: case LK_RESPONSE_RESERVED: DBG(KERN_INFO "Got %s and don't know how to handle...\n", response_name(data)); break; default: keycode = lk->keycode[data]; if (keycode != KEY_RESERVED) { input_report_key(input_dev, keycode, !test_bit(keycode, input_dev->key)); input_sync(input_dev); } else { printk(KERN_WARNING "%s: Unknown key with scancode 0x%02x on %s.\n", __FILE__, data, lk->name); } } return IRQ_HANDLED; } static void lkkbd_toggle_leds(struct lkkbd *lk) { struct serio *serio = lk->serio; unsigned char leds_on = 0; unsigned char leds_off = 0; CHECK_LED(lk, leds_on, leds_off, LED_CAPSL, LK_LED_SHIFTLOCK); CHECK_LED(lk, leds_on, leds_off, LED_COMPOSE, LK_LED_COMPOSE); CHECK_LED(lk, leds_on, leds_off, LED_SCROLLL, LK_LED_SCROLLLOCK); CHECK_LED(lk, leds_on, leds_off, LED_SLEEP, LK_LED_WAIT); if (leds_on != 0) { serio_write(serio, LK_CMD_LED_ON); serio_write(serio, leds_on); } if (leds_off != 0) { serio_write(serio, LK_CMD_LED_OFF); serio_write(serio, leds_off); } } static void lkkbd_toggle_keyclick(struct lkkbd *lk, bool on) { struct serio *serio = lk->serio; if (on) { DBG("%s: Activating key clicks\n", __func__); serio_write(serio, LK_CMD_ENABLE_KEYCLICK); serio_write(serio, volume_to_hw(lk->keyclick_volume)); serio_write(serio, LK_CMD_ENABLE_CTRCLICK); serio_write(serio, volume_to_hw(lk->ctrlclick_volume)); } else { DBG("%s: Deactivating key clicks\n", __func__); serio_write(serio, LK_CMD_DISABLE_KEYCLICK); serio_write(serio, LK_CMD_DISABLE_CTRCLICK); } } /* * lkkbd_event() handles events from the input module. */ static int lkkbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { struct lkkbd *lk = input_get_drvdata(dev); switch (type) { case EV_LED: lkkbd_toggle_leds(lk); return 0; case EV_SND: switch (code) { case SND_CLICK: lkkbd_toggle_keyclick(lk, value); return 0; case SND_BELL: if (value != 0) serio_write(lk->serio, LK_CMD_SOUND_BELL); return 0; } break; default: printk(KERN_ERR "%s(): Got unknown type %d, code %d, value %d\n", __func__, type, code, value); } return -1; } /* * lkkbd_reinit() sets leds and beeps to a state the computer remembers they * were in. */ static void lkkbd_reinit(struct work_struct *work) { struct lkkbd *lk = container_of(work, struct lkkbd, tq); int division; /* Ask for ID */ serio_write(lk->serio, LK_CMD_REQUEST_ID); /* Reset parameters */ serio_write(lk->serio, LK_CMD_SET_DEFAULTS); /* Set LEDs */ lkkbd_toggle_leds(lk); /* * Try to activate extended LK401 mode. This command will * only work with a LK401 keyboard and grants access to * LAlt, RAlt, RCompose and RShift. */ serio_write(lk->serio, LK_CMD_ENABLE_LK401); /* Set all keys to UPDOWN mode */ for (division = 1; division <= 14; division++) serio_write(lk->serio, LK_CMD_SET_MODE(LK_MODE_UPDOWN, division)); /* Enable bell and set volume */ serio_write(lk->serio, LK_CMD_ENABLE_BELL); serio_write(lk->serio, volume_to_hw(lk->bell_volume)); /* Enable/disable keyclick (and possibly set volume) */ lkkbd_toggle_keyclick(lk, test_bit(SND_CLICK, lk->dev->snd)); /* Sound the bell if needed */ if (test_bit(SND_BELL, lk->dev->snd)) serio_write(lk->serio, LK_CMD_SOUND_BELL); } /* * lkkbd_connect() probes for a LK keyboard and fills the necessary structures. */ static int lkkbd_connect(struct serio *serio, struct serio_driver *drv) { struct lkkbd *lk; struct input_dev *input_dev; int i; int err; lk = kzalloc(sizeof(struct lkkbd), GFP_KERNEL); input_dev = input_allocate_device(); if (!lk || !input_dev) { err = -ENOMEM; goto fail1; } lk->serio = serio; lk->dev = input_dev; INIT_WORK(&lk->tq, lkkbd_reinit); lk->bell_volume = bell_volume; lk->keyclick_volume = keyclick_volume; lk->ctrlclick_volume = ctrlclick_volume; memcpy(lk->keycode, lkkbd_keycode, sizeof(lk->keycode)); strlcpy(lk->name, "DEC LK keyboard", sizeof(lk->name)); snprintf(lk->phys, sizeof(lk->phys), "%s/input0", serio->phys); input_dev->name = lk->name; input_dev->phys = lk->phys; input_dev->id.bustype = BUS_RS232; input_dev->id.vendor = SERIO_LKKBD; input_dev->id.product = 0; input_dev->id.version = 0x0100; input_dev->dev.parent = &serio->dev; input_dev->event = lkkbd_event; input_set_drvdata(input_dev, lk); __set_bit(EV_KEY, input_dev->evbit); __set_bit(EV_LED, input_dev->evbit); __set_bit(EV_SND, input_dev->evbit); __set_bit(EV_REP, input_dev->evbit); __set_bit(LED_CAPSL, input_dev->ledbit); __set_bit(LED_SLEEP, input_dev->ledbit); __set_bit(LED_COMPOSE, input_dev->ledbit); __set_bit(LED_SCROLLL, input_dev->ledbit); __set_bit(SND_BELL, input_dev->sndbit); __set_bit(SND_CLICK, input_dev->sndbit); input_dev->keycode = lk->keycode; input_dev->keycodesize = sizeof(lk->keycode[0]); input_dev->keycodemax = ARRAY_SIZE(lk->keycode); for (i = 0; i < LK_NUM_KEYCODES; i++) __set_bit(lk->keycode[i], input_dev->keybit); __clear_bit(KEY_RESERVED, input_dev->keybit); serio_set_drvdata(serio, lk); err = serio_open(serio, drv); if (err) goto fail2; err = input_register_device(lk->dev); if (err) goto fail3; serio_write(lk->serio, LK_CMD_POWERCYCLE_RESET); return 0; fail3: serio_close(serio); fail2: serio_set_drvdata(serio, NULL); fail1: input_free_device(input_dev); kfree(lk); return err; } /* * lkkbd_disconnect() unregisters and closes behind us. */ static void lkkbd_disconnect(struct serio *serio) { struct lkkbd *lk = serio_get_drvdata(serio); input_get_device(lk->dev); input_unregister_device(lk->dev); serio_close(serio); serio_set_drvdata(serio, NULL); input_put_device(lk->dev); kfree(lk); } static struct serio_device_id lkkbd_serio_ids[] = { { .type = SERIO_RS232, .proto = SERIO_LKKBD, .id = SERIO_ANY, .extra = SERIO_ANY, }, { 0 } }; MODULE_DEVICE_TABLE(serio, lkkbd_serio_ids); static struct serio_driver lkkbd_drv = { .driver = { .name = "lkkbd", }, .description = DRIVER_DESC, .id_table = lkkbd_serio_ids, .connect = lkkbd_connect, .disconnect = lkkbd_disconnect, .interrupt = lkkbd_interrupt, }; module_serio_driver(lkkbd_drv);