vserver 1.9.5.x5
[linux-2.6.git] / drivers / input / keyboard / atkbd.c
index 00ec1f8..8b46866 100644 (file)
 #include <linux/input.h>
 #include <linux/serio.h>
 #include <linux/workqueue.h>
+#include <linux/libps2.h>
+
+#define DRIVER_DESC    "AT and PS/2 keyboard driver"
 
 MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>");
-MODULE_DESCRIPTION("AT and PS/2 keyboard driver");
+MODULE_DESCRIPTION(DRIVER_DESC);
 MODULE_LICENSE("GPL");
 
 static int atkbd_set = 2;
@@ -47,6 +50,10 @@ static int atkbd_softrepeat;
 module_param_named(softrepeat, atkbd_softrepeat, bool, 0);
 MODULE_PARM_DESC(softrepeat, "Use software keyboard repeat");
 
+static int atkbd_softraw = 1;
+module_param_named(softraw, atkbd_softraw, bool, 0);
+MODULE_PARM_DESC(softraw, "Use software generated rawmode");
+
 static int atkbd_scroll;
 module_param_named(scroll, atkbd_scroll, bool, 0);
 MODULE_PARM_DESC(scroll, "Enable scroll-wheel on MS Office and similar keyboards");
@@ -169,36 +176,65 @@ static unsigned char atkbd_scroll_keys[5][2] = {
  */
 
 struct atkbd {
-       unsigned char keycode[512];
-       struct input_dev dev;
-       struct serio *serio;
 
+       struct ps2dev   ps2dev;
+
+       /* Written only during init */
        char name[64];
        char phys[32];
+       struct input_dev dev;
+
        unsigned short id;
+       unsigned char keycode[512];
        unsigned char set;
-       unsigned int translated:1;
-       unsigned int extra:1;
-       unsigned int write:1;
-
-       unsigned char cmdbuf[4];
-       unsigned char cmdcnt;
-       volatile signed char ack;
+       unsigned char translated;
+       unsigned char extra;
+       unsigned char write;
+       unsigned char softrepeat;
+       unsigned char softraw;
+       unsigned char scroll;
+       unsigned char enabled;
+
+       /* Accessed only from interrupt */
        unsigned char emul;
-       unsigned int resend:1;
-       unsigned int release:1;
-       unsigned int bat_xl:1;
-       unsigned int enabled:1;
-
+       unsigned char resend;
+       unsigned char release;
+       unsigned char bat_xl;
        unsigned int last;
        unsigned long time;
 };
 
+static ssize_t atkbd_attr_show_helper(struct device *dev, char *buf,
+                               ssize_t (*handler)(struct atkbd *, char *));
+static ssize_t atkbd_attr_set_helper(struct device *dev, const char *buf, size_t count,
+                               ssize_t (*handler)(struct atkbd *, const char *, size_t));
+#define ATKBD_DEFINE_ATTR(_name)                                               \
+static ssize_t atkbd_show_##_name(struct atkbd *, char *);                     \
+static ssize_t atkbd_set_##_name(struct atkbd *, const char *, size_t);                \
+static ssize_t atkbd_do_show_##_name(struct device *d, char *b)                        \
+{                                                                              \
+       return atkbd_attr_show_helper(d, b, atkbd_show_##_name);                \
+}                                                                              \
+static ssize_t atkbd_do_set_##_name(struct device *d, const char *b, size_t s) \
+{                                                                              \
+       return atkbd_attr_set_helper(d, b, s, atkbd_set_##_name);               \
+}                                                                              \
+static struct device_attribute atkbd_attr_##_name =                            \
+       __ATTR(_name, S_IWUSR | S_IRUGO, atkbd_do_show_##_name, atkbd_do_set_##_name);
+
+ATKBD_DEFINE_ATTR(extra);
+ATKBD_DEFINE_ATTR(scroll);
+ATKBD_DEFINE_ATTR(set);
+ATKBD_DEFINE_ATTR(softrepeat);
+ATKBD_DEFINE_ATTR(softraw);
+
+
 static void atkbd_report_key(struct input_dev *dev, struct pt_regs *regs, int code, int value)
 {
        input_regs(dev, regs);
        if (value == 3) {
                input_report_key(dev, code, 1);
+               input_sync(dev);
                input_report_key(dev, code, 0);
        } else
                input_event(dev, EV_KEY, code, value);
@@ -224,7 +260,7 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
 
 #if !defined(__i386__) && !defined (__x86_64__)
        if ((flags & (SERIO_FRAME | SERIO_PARITY)) && (~flags & SERIO_TIMEOUT) && !atkbd->resend && atkbd->write) {
-               printk("atkbd.c: frame/parity error: %02x\n", flags);
+               printk(KERN_WARNING "atkbd.c: frame/parity error: %02x\n", flags);
                serio_write(serio, ATKBD_CMD_RESEND);
                atkbd->resend = 1;
                goto out;
@@ -234,24 +270,19 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
                atkbd->resend = 0;
 #endif
 
-       if (!atkbd->ack)
-               switch (code) {
-                       case ATKBD_RET_ACK:
-                               atkbd->ack = 1;
-                               goto out;
-                       case ATKBD_RET_NAK:
-                               atkbd->ack = -1;
-                               goto out;
-               }
+       if (unlikely(atkbd->ps2dev.flags & PS2_FLAG_ACK))
+               if  (ps2_handle_ack(&atkbd->ps2dev, data))
+                       goto out;
 
-       if (atkbd->cmdcnt) {
-               atkbd->cmdbuf[--atkbd->cmdcnt] = code;
-               goto out;
-       }
+       if (unlikely(atkbd->ps2dev.flags & PS2_FLAG_CMD))
+               if  (ps2_handle_response(&atkbd->ps2dev, data))
+                       goto out;
 
        if (!atkbd->enabled)
                goto out;
 
+       input_event(&atkbd->dev, EV_MSC, MSC_RAW, code);
+
        if (atkbd->translated) {
 
                if (atkbd->emul ||
@@ -270,7 +301,8 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
 
        switch (code) {
                case ATKBD_RET_BAT:
-                       serio_rescan(atkbd->serio);
+                       atkbd->enabled = 0;
+                       serio_rescan(atkbd->ps2dev.serio);
                        goto out;
                case ATKBD_RET_EMUL0:
                        atkbd->emul = 1;
@@ -300,6 +332,9 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
                code |= (atkbd->set != 3) ? 0x80 : 0x100;
        }
 
+       if (atkbd->keycode[code] != ATKBD_KEY_NULL)
+               input_event(&atkbd->dev, EV_MSC, MSC_SCAN, code);
+
        switch (atkbd->keycode[code]) {
                case ATKBD_KEY_NULL:
                        break;
@@ -318,6 +353,7 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
                                       "to make it known.\n",
                                       code & 0x80 ? "e0" : "", code & 0x7f);
                        }
+                       input_sync(&atkbd->dev);
                        break;
                case ATKBD_SCR_1:
                        scroll = 1 - atkbd->release * 2;
@@ -336,7 +372,7 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
                        break;
                default:
                        value = atkbd->release ? 0 :
-                               (1 + (!atkbd_softrepeat && test_bit(atkbd->keycode[code], atkbd->dev.key)));
+                               (1 + (!atkbd->softrepeat && test_bit(atkbd->keycode[code], atkbd->dev.key)));
 
                        switch (value) {        /* Workaround Toshiba laptop multiple keypress */
                                case 0:
@@ -367,87 +403,6 @@ out:
        return IRQ_HANDLED;
 }
 
-/*
- * atkbd_sendbyte() sends a byte to the keyboard, and waits for
- * acknowledge. It doesn't handle resends according to the keyboard
- * protocol specs, because if these are needed, the keyboard needs
- * replacement anyway, and they only make a mess in the protocol.
- */
-
-static int atkbd_sendbyte(struct atkbd *atkbd, unsigned char byte)
-{
-       int timeout = 20000; /* 200 msec */
-       atkbd->ack = 0;
-
-#ifdef ATKBD_DEBUG
-       printk(KERN_DEBUG "atkbd.c: Sent: %02x\n", byte);
-#endif
-       if (serio_write(atkbd->serio, byte))
-               return -1;
-
-       while (!atkbd->ack && timeout--) udelay(10);
-
-       return -(atkbd->ack <= 0);
-}
-
-/*
- * atkbd_command() sends a command, and its parameters to the keyboard,
- * then waits for the response and puts it in the param array.
- */
-
-static int atkbd_command(struct atkbd *atkbd, unsigned char *param, int command)
-{
-       int timeout = 500000; /* 500 msec */
-       int send = (command >> 12) & 0xf;
-       int receive = (command >> 8) & 0xf;
-       int i;
-
-       atkbd->cmdcnt = receive;
-
-       if (command == ATKBD_CMD_RESET_BAT)
-               timeout = 2000000; /* 2 sec */
-
-       if (receive && param)
-               for (i = 0; i < receive; i++)
-                       atkbd->cmdbuf[(receive - 1) - i] = param[i];
-
-       if (command & 0xff)
-               if (atkbd_sendbyte(atkbd, command & 0xff))
-                       return (atkbd->cmdcnt = 0) - 1;
-
-       for (i = 0; i < send; i++)
-               if (atkbd_sendbyte(atkbd, param[i]))
-                       return (atkbd->cmdcnt = 0) - 1;
-
-       while (atkbd->cmdcnt && timeout--) {
-
-               if (atkbd->cmdcnt == 1 &&
-                   command == ATKBD_CMD_RESET_BAT && timeout > 100000)
-                       timeout = 100000;
-
-               if (atkbd->cmdcnt == 1 && command == ATKBD_CMD_GETID &&
-                   atkbd->cmdbuf[1] != 0xab && atkbd->cmdbuf[1] != 0xac) {
-                       atkbd->cmdcnt = 0;
-                       break;
-               }
-
-               udelay(1);
-       }
-
-       if (param)
-               for (i = 0; i < receive; i++)
-                       param[i] = atkbd->cmdbuf[(receive - 1) - i];
-
-       if (command == ATKBD_CMD_RESET_BAT && atkbd->cmdcnt == 1)
-               atkbd->cmdcnt = 0;
-
-       if (atkbd->cmdcnt) {
-               atkbd->cmdcnt = 0;
-               return -1;
-       }
-
-       return 0;
-}
 
 /*
  * Event callback from the input module. Events that change the state of
@@ -475,7 +430,7 @@ static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int co
                        param[0] = (test_bit(LED_SCROLLL, dev->led) ? 1 : 0)
                                 | (test_bit(LED_NUML,    dev->led) ? 2 : 0)
                                 | (test_bit(LED_CAPSL,   dev->led) ? 4 : 0);
-                       atkbd_command(atkbd, param, ATKBD_CMD_SETLEDS);
+                       ps2_schedule_command(&atkbd->ps2dev, param, ATKBD_CMD_SETLEDS);
 
                        if (atkbd->extra) {
                                param[0] = 0;
@@ -484,7 +439,7 @@ static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int co
                                         | (test_bit(LED_SUSPEND, dev->led) ? 0x04 : 0)
                                         | (test_bit(LED_MISC,    dev->led) ? 0x10 : 0)
                                         | (test_bit(LED_MUTE,    dev->led) ? 0x20 : 0);
-                               atkbd_command(atkbd, param, ATKBD_CMD_EX_SETLEDS);
+                               ps2_schedule_command(&atkbd->ps2dev, param, ATKBD_CMD_EX_SETLEDS);
                        }
 
                        return 0;
@@ -492,7 +447,7 @@ static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int co
 
                case EV_REP:
 
-                       if (atkbd_softrepeat) return 0;
+                       if (atkbd->softrepeat) return 0;
 
                        i = j = 0;
                        while (i < 32 && period[i] < dev->rep[REP_PERIOD]) i++;
@@ -500,7 +455,7 @@ static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int co
                        dev->rep[REP_PERIOD] = period[i];
                        dev->rep[REP_DELAY] = delay[j];
                        param[0] = i | (j << 5);
-                       atkbd_command(atkbd, param, ATKBD_CMD_SETREP);
+                       ps2_schedule_command(&atkbd->ps2dev, param, ATKBD_CMD_SETREP);
 
                        return 0;
        }
@@ -508,12 +463,37 @@ static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int co
        return -1;
 }
 
+/*
+ * atkbd_enable() signals that interrupt handler is allowed to
+ * generate input events.
+ */
+
+static inline void atkbd_enable(struct atkbd *atkbd)
+{
+       serio_pause_rx(atkbd->ps2dev.serio);
+       atkbd->enabled = 1;
+       serio_continue_rx(atkbd->ps2dev.serio);
+}
+
+/*
+ * atkbd_disable() tells input handler that all incoming data except
+ * for ACKs and command response should be dropped.
+ */
+
+static inline void atkbd_disable(struct atkbd *atkbd)
+{
+       serio_pause_rx(atkbd->ps2dev.serio);
+       atkbd->enabled = 0;
+       serio_continue_rx(atkbd->ps2dev.serio);
+}
+
 /*
  * atkbd_probe() probes for an AT keyboard on a serio port.
  */
 
 static int atkbd_probe(struct atkbd *atkbd)
 {
+       struct ps2dev *ps2dev = &atkbd->ps2dev;
        unsigned char param[2];
 
 /*
@@ -523,8 +503,8 @@ static int atkbd_probe(struct atkbd *atkbd)
  */
 
        if (atkbd_reset)
-               if (atkbd_command(atkbd, NULL, ATKBD_CMD_RESET_BAT))
-                       printk(KERN_WARNING "atkbd.c: keyboard reset failed on %s\n", atkbd->serio->phys);
+               if (ps2_command(ps2dev, NULL, ATKBD_CMD_RESET_BAT))
+                       printk(KERN_WARNING "atkbd.c: keyboard reset failed on %s\n", ps2dev->serio->phys);
 
 /*
  * Then we check the keyboard ID. We should get 0xab83 under normal conditions.
@@ -534,7 +514,7 @@ static int atkbd_probe(struct atkbd *atkbd)
  */
 
        param[0] = param[1] = 0xa5;     /* initialize with invalid values */
-       if (atkbd_command(atkbd, param, ATKBD_CMD_GETID)) {
+       if (ps2_command(ps2dev, param, ATKBD_CMD_GETID)) {
 
 /*
  * If the get ID command failed, we check if we can at least set the LEDs on
@@ -542,14 +522,17 @@ static int atkbd_probe(struct atkbd *atkbd)
  * the LEDs off, which we want anyway.
  */
                param[0] = 0;
-               if (atkbd_command(atkbd, param, ATKBD_CMD_SETLEDS))
+               if (ps2_command(ps2dev, param, ATKBD_CMD_SETLEDS))
                        return -1;
                atkbd->id = 0xabba;
                return 0;
        }
 
-       if (param[0] != 0xab && param[0] != 0xac)
+       if (param[0] != 0xab && param[0] != 0xac &&     /* Regular and NCD Sun keyboards */
+           param[0] != 0x2b && param[0] != 0x5d &&     /* Trust keyboard, raw and translated */
+           param[0] != 0x60 && param[0] != 0x47)       /* NMB SGI keyboard, raw and translated */
                return -1;
+
        atkbd->id = (param[0] << 8) | param[1];
 
        if (atkbd->id == 0xaca1 && atkbd->translated) {
@@ -562,15 +545,17 @@ static int atkbd_probe(struct atkbd *atkbd)
 }
 
 /*
- * atkbd_set_3 checks if a keyboard has a working Set 3 support, and
+ * atkbd_select_set checks if a keyboard has a working Set 3 support, and
  * sets it into that. Unfortunately there are keyboards that can be switched
  * to Set 3, but don't work well in that (BTC Multimedia ...)
  */
 
-static int atkbd_set_3(struct atkbd *atkbd)
+static int atkbd_select_set(struct atkbd *atkbd, int target_set, int allow_extra)
 {
+       struct ps2dev *ps2dev = &atkbd->ps2dev;
        unsigned char param[2];
 
+       atkbd->extra = 0;
 /*
  * For known special keyboards we can go ahead and set the correct set.
  * We check for NCD PS/2 Sun, NorthGate OmniKey 101 and
@@ -582,47 +567,48 @@ static int atkbd_set_3(struct atkbd *atkbd)
 
        if (atkbd->id == 0xaca1) {
                param[0] = 3;
-               atkbd_command(atkbd, param, ATKBD_CMD_SSCANSET);
+               ps2_command(ps2dev, param, ATKBD_CMD_SSCANSET);
                return 3;
        }
 
-       if (atkbd_extra) {
+       if (allow_extra) {
                param[0] = 0x71;
-               if (!atkbd_command(atkbd, param, ATKBD_CMD_EX_ENABLE)) {
+               if (!ps2_command(ps2dev, param, ATKBD_CMD_EX_ENABLE)) {
                        atkbd->extra = 1;
                        return 2;
                }
        }
 
-       if (atkbd_set != 3)
+       if (target_set != 3)
                return 2;
 
-       if (!atkbd_command(atkbd, param, ATKBD_CMD_OK_GETID)) {
+       if (!ps2_command(ps2dev, param, ATKBD_CMD_OK_GETID)) {
                atkbd->id = param[0] << 8 | param[1];
                return 2;
        }
 
        param[0] = 3;
-       if (atkbd_command(atkbd, param, ATKBD_CMD_SSCANSET))
+       if (ps2_command(ps2dev, param, ATKBD_CMD_SSCANSET))
                return 2;
 
        param[0] = 0;
-       if (atkbd_command(atkbd, param, ATKBD_CMD_GSCANSET))
+       if (ps2_command(ps2dev, param, ATKBD_CMD_GSCANSET))
                return 2;
 
        if (param[0] != 3) {
                param[0] = 2;
-               if (atkbd_command(atkbd, param, ATKBD_CMD_SSCANSET))
+               if (ps2_command(ps2dev, param, ATKBD_CMD_SSCANSET))
                return 2;
        }
 
-       atkbd_command(atkbd, param, ATKBD_CMD_SETALL_MBR);
+       ps2_command(ps2dev, param, ATKBD_CMD_SETALL_MBR);
 
        return 3;
 }
 
-static int atkbd_enable(struct atkbd *atkbd)
+static int atkbd_activate(struct atkbd *atkbd)
 {
+       struct ps2dev *ps2dev = &atkbd->ps2dev;
        unsigned char param[1];
 
 /*
@@ -630,7 +616,7 @@ static int atkbd_enable(struct atkbd *atkbd)
  */
 
        param[0] = 0;
-       if (atkbd_command(atkbd, param, ATKBD_CMD_SETLEDS))
+       if (ps2_command(ps2dev, param, ATKBD_CMD_SETLEDS))
                return -1;
 
 /*
@@ -638,16 +624,16 @@ static int atkbd_enable(struct atkbd *atkbd)
  */
 
        param[0] = 0;
-       if (atkbd_command(atkbd, param, ATKBD_CMD_SETREP))
+       if (ps2_command(ps2dev, param, ATKBD_CMD_SETREP))
                return -1;
 
 /*
  * Enable the keyboard to receive keystrokes.
  */
 
-       if (atkbd_command(atkbd, NULL, ATKBD_CMD_ENABLE)) {
+       if (ps2_command(ps2dev, NULL, ATKBD_CMD_ENABLE)) {
                printk(KERN_ERR "atkbd.c: Failed to enable keyboard on %s\n",
-                       atkbd->serio->phys);
+                       ps2dev->serio->phys);
                return -1;
        }
 
@@ -662,9 +648,10 @@ static int atkbd_enable(struct atkbd *atkbd)
 static void atkbd_cleanup(struct serio *serio)
 {
        struct atkbd *atkbd = serio->private;
-       atkbd_command(atkbd, NULL, ATKBD_CMD_RESET_BAT);
+       ps2_command(&atkbd->ps2dev, NULL, ATKBD_CMD_RESET_BAT);
 }
 
+
 /*
  * atkbd_disconnect() closes and frees.
  */
@@ -672,11 +659,114 @@ static void atkbd_cleanup(struct serio *serio)
 static void atkbd_disconnect(struct serio *serio)
 {
        struct atkbd *atkbd = serio->private;
+
+       atkbd_disable(atkbd);
+
+       /* make sure we don't have a command in flight */
+       synchronize_kernel();
+       flush_scheduled_work();
+
+       device_remove_file(&serio->dev, &atkbd_attr_extra);
+       device_remove_file(&serio->dev, &atkbd_attr_scroll);
+       device_remove_file(&serio->dev, &atkbd_attr_set);
+       device_remove_file(&serio->dev, &atkbd_attr_softrepeat);
+       device_remove_file(&serio->dev, &atkbd_attr_softraw);
+
        input_unregister_device(&atkbd->dev);
        serio_close(serio);
        kfree(atkbd);
 }
 
+
+/*
+ * atkbd_set_device_attrs() initializes keyboard's keycode table
+ * according to the selected scancode set
+ */
+
+static void atkbd_set_keycode_table(struct atkbd *atkbd)
+{
+       int i, j;
+
+       memset(atkbd->keycode, 0, sizeof(atkbd->keycode));
+
+       if (atkbd->translated) {
+               for (i = 0; i < 128; i++) {
+                       atkbd->keycode[i] = atkbd_set2_keycode[atkbd_unxlate_table[i]];
+                       atkbd->keycode[i | 0x80] = atkbd_set2_keycode[atkbd_unxlate_table[i] | 0x80];
+                       if (atkbd->scroll)
+                               for (j = 0; i < 5; i++) {
+                                       if (atkbd_unxlate_table[i] == atkbd_scroll_keys[j][1])
+                                               atkbd->keycode[i] = atkbd_scroll_keys[j][0];
+                                       if ((atkbd_unxlate_table[i] | 0x80) == atkbd_scroll_keys[j][1])
+                                               atkbd->keycode[i | 0x80] = atkbd_scroll_keys[j][0];
+                               }
+               }
+       } else if (atkbd->set == 3) {
+               memcpy(atkbd->keycode, atkbd_set3_keycode, sizeof(atkbd->keycode));
+       } else {
+               memcpy(atkbd->keycode, atkbd_set2_keycode, sizeof(atkbd->keycode));
+
+               if (atkbd->scroll)
+                       for (i = 0; i < 5; i++)
+                               atkbd->keycode[atkbd_scroll_keys[i][1]] = atkbd_scroll_keys[i][0];
+       }
+}
+
+/*
+ * atkbd_set_device_attrs() sets up keyboard's input device structure
+ */
+
+static void atkbd_set_device_attrs(struct atkbd *atkbd)
+{
+       int i;
+
+       memset(&atkbd->dev, 0, sizeof(struct input_dev));
+
+       init_input_dev(&atkbd->dev);
+
+       atkbd->dev.name = atkbd->name;
+       atkbd->dev.phys = atkbd->phys;
+       atkbd->dev.id.bustype = BUS_I8042;
+       atkbd->dev.id.vendor = 0x0001;
+       atkbd->dev.id.product = atkbd->translated ? 1 : atkbd->set;
+       atkbd->dev.id.version = atkbd->id;
+       atkbd->dev.event = atkbd_event;
+       atkbd->dev.private = atkbd;
+       atkbd->dev.dev = &atkbd->ps2dev.serio->dev;
+
+       atkbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REP) | BIT(EV_MSC);
+
+       if (atkbd->write) {
+               atkbd->dev.evbit[0] |= BIT(EV_LED);
+               atkbd->dev.ledbit[0] = BIT(LED_NUML) | BIT(LED_CAPSL) | BIT(LED_SCROLLL);
+       }
+
+       if (atkbd->extra)
+               atkbd->dev.ledbit[0] |= BIT(LED_COMPOSE) | BIT(LED_SUSPEND) |
+                                       BIT(LED_SLEEP) | BIT(LED_MUTE) | BIT(LED_MISC);
+
+       if (!atkbd->softrepeat) {
+               atkbd->dev.rep[REP_DELAY] = 250;
+               atkbd->dev.rep[REP_PERIOD] = 33;
+       }
+
+       atkbd->dev.mscbit[0] = atkbd->softraw ? BIT(MSC_SCAN) : BIT(MSC_RAW) | BIT(MSC_SCAN);
+
+       if (atkbd->scroll) {
+               atkbd->dev.evbit[0] |= BIT(EV_REL);
+               atkbd->dev.relbit[0] = BIT(REL_WHEEL);
+               set_bit(BTN_MIDDLE, atkbd->dev.keybit);
+       }
+
+       atkbd->dev.keycode = atkbd->keycode;
+       atkbd->dev.keycodesize = sizeof(unsigned char);
+       atkbd->dev.keycodemax = ARRAY_SIZE(atkbd_set2_keycode);
+
+       for (i = 0; i < 512; i++)
+               if (atkbd->keycode[i] && atkbd->keycode[i] < ATKBD_SPECIAL)
+                       set_bit(atkbd->keycode[i], atkbd->dev.keybit);
+}
+
 /*
  * atkbd_connect() is called when the serio module finds and interface
  * that isn't handled yet by an appropriate device driver. We check if
@@ -684,15 +774,16 @@ static void atkbd_disconnect(struct serio *serio)
  * to the input module.
  */
 
-static void atkbd_connect(struct serio *serio, struct serio_dev *dev)
+static void atkbd_connect(struct serio *serio, struct serio_driver *drv)
 {
        struct atkbd *atkbd;
-       int i;
 
        if (!(atkbd = kmalloc(sizeof(struct atkbd), GFP_KERNEL)))
                return;
        memset(atkbd, 0, sizeof(struct atkbd));
 
+       ps2_init(&atkbd->ps2dev, serio);
+
        switch (serio->type & SERIO_TYPE) {
 
                case SERIO_8042_XL:
@@ -709,30 +800,19 @@ static void atkbd_connect(struct serio *serio, struct serio_dev *dev)
                        return;
        }
 
-       if (atkbd->write) {
-               atkbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_LED) | BIT(EV_REP);
-               atkbd->dev.ledbit[0] = BIT(LED_NUML) | BIT(LED_CAPSL) | BIT(LED_SCROLLL);
-       } else  atkbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REP);
-
-       if (!atkbd_softrepeat) {
-               atkbd->dev.rep[REP_DELAY] = 250;
-               atkbd->dev.rep[REP_PERIOD] = 33;
-       }
-
-       atkbd->ack = 1;
-       atkbd->serio = serio;
+       atkbd->softraw = atkbd_softraw;
+       atkbd->softrepeat = atkbd_softrepeat;
+       atkbd->scroll = atkbd_scroll;
 
-       init_input_dev(&atkbd->dev);
+       if (!atkbd->write)
+               atkbd->softrepeat = 1;
 
-       atkbd->dev.keycode = atkbd->keycode;
-       atkbd->dev.keycodesize = sizeof(unsigned char);
-       atkbd->dev.keycodemax = ARRAY_SIZE(atkbd_set2_keycode);
-       atkbd->dev.event = atkbd_event;
-       atkbd->dev.private = atkbd;
+       if (atkbd->softrepeat)
+               atkbd->softraw = 1;
 
        serio->private = atkbd;
 
-       if (serio_open(serio, dev)) {
+       if (serio_open(serio, drv)) {
                kfree(atkbd);
                return;
        }
@@ -746,56 +826,34 @@ static void atkbd_connect(struct serio *serio, struct serio_dev *dev)
                        return;
                }
 
-               atkbd->set = atkbd_set_3(atkbd);
-               atkbd_enable(atkbd);
+               atkbd->set = atkbd_select_set(atkbd, atkbd_set, atkbd_extra);
+               atkbd_activate(atkbd);
 
        } else {
                atkbd->set = 2;
                atkbd->id = 0xab00;
        }
 
-       atkbd->enabled = 1;
-
-       if (atkbd->extra) {
-               atkbd->dev.ledbit[0] |= BIT(LED_COMPOSE) | BIT(LED_SUSPEND) | BIT(LED_SLEEP) | BIT(LED_MUTE) | BIT(LED_MISC);
+       if (atkbd->extra)
                sprintf(atkbd->name, "AT Set 2 Extra keyboard");
-       else
+       else
                sprintf(atkbd->name, "AT %s Set %d keyboard",
                        atkbd->translated ? "Translated" : "Raw", atkbd->set);
 
        sprintf(atkbd->phys, "%s/input0", serio->phys);
 
-       if (atkbd_scroll) {
-               for (i = 0; i < 5; i++)
-                       atkbd_set2_keycode[atkbd_scroll_keys[i][1]] = atkbd_scroll_keys[i][0];
-               atkbd->dev.evbit[0] |= BIT(EV_REL);
-               atkbd->dev.relbit[0] = BIT(REL_WHEEL);
-               set_bit(BTN_MIDDLE, atkbd->dev.keybit);
-       }
-
-       if (atkbd->translated) {
-               for (i = 0; i < 128; i++) {
-                       atkbd->keycode[i] = atkbd_set2_keycode[atkbd_unxlate_table[i]];
-                       atkbd->keycode[i | 0x80] = atkbd_set2_keycode[atkbd_unxlate_table[i] | 0x80];
-               }
-       } else if (atkbd->set == 3) {
-               memcpy(atkbd->keycode, atkbd_set3_keycode, sizeof(atkbd->keycode));
-       } else {
-               memcpy(atkbd->keycode, atkbd_set2_keycode, sizeof(atkbd->keycode));
-       }
+       atkbd_set_keycode_table(atkbd);
+       atkbd_set_device_attrs(atkbd);
 
-       atkbd->dev.name = atkbd->name;
-       atkbd->dev.phys = atkbd->phys;
-       atkbd->dev.id.bustype = BUS_I8042;
-       atkbd->dev.id.vendor = 0x0001;
-       atkbd->dev.id.product = atkbd->translated ? 1 : atkbd->set;
-       atkbd->dev.id.version = atkbd->id;
+       input_register_device(&atkbd->dev);
 
-       for (i = 0; i < 512; i++)
-               if (atkbd->keycode[i] && atkbd->keycode[i] < ATKBD_SPECIAL)
-                       set_bit(atkbd->keycode[i], atkbd->dev.keybit);
+       device_create_file(&serio->dev, &atkbd_attr_extra);
+       device_create_file(&serio->dev, &atkbd_attr_scroll);
+       device_create_file(&serio->dev, &atkbd_attr_set);
+       device_create_file(&serio->dev, &atkbd_attr_softrepeat);
+       device_create_file(&serio->dev, &atkbd_attr_softraw);
 
-       input_register_device(&atkbd->dev);
+       atkbd_enable(atkbd);
 
        printk(KERN_INFO "input: %s on %s\n", atkbd->name, serio->phys);
 }
@@ -808,14 +866,16 @@ static void atkbd_connect(struct serio *serio, struct serio_dev *dev)
 static int atkbd_reconnect(struct serio *serio)
 {
        struct atkbd *atkbd = serio->private;
-       struct serio_dev *dev = serio->dev;
+       struct serio_driver *drv = serio->drv;
        unsigned char param[1];
 
-       if (!dev) {
+       if (!atkbd || !drv) {
                printk(KERN_DEBUG "atkbd: reconnect request, but serio is disconnected, ignoring...\n");
                return -1;
        }
 
+       atkbd_disable(atkbd);
+
        if (atkbd->write) {
                param[0] = (test_bit(LED_SCROLLL, atkbd->dev.led) ? 1 : 0)
                         | (test_bit(LED_NUML,    atkbd->dev.led) ? 2 : 0)
@@ -823,35 +883,227 @@ static int atkbd_reconnect(struct serio *serio)
 
                if (atkbd_probe(atkbd))
                        return -1;
-               if (atkbd->set != atkbd_set_3(atkbd))
+               if (atkbd->set != atkbd_select_set(atkbd, atkbd->set, atkbd->extra))
                        return -1;
 
-               atkbd_enable(atkbd);
+               atkbd_activate(atkbd);
 
-               if (atkbd_command(atkbd, param, ATKBD_CMD_SETLEDS))
+               if (ps2_command(&atkbd->ps2dev, param, ATKBD_CMD_SETLEDS))
                        return -1;
        }
 
+       atkbd_enable(atkbd);
+
        return 0;
 }
 
-static struct serio_dev atkbd_dev = {
-       .interrupt =    atkbd_interrupt,
-       .connect =      atkbd_connect,
-       .reconnect =    atkbd_reconnect,
-       .disconnect =   atkbd_disconnect,
-       .cleanup =      atkbd_cleanup,
+static struct serio_driver atkbd_drv = {
+       .driver         = {
+               .name   = "atkbd",
+       },
+       .description    = DRIVER_DESC,
+       .interrupt      = atkbd_interrupt,
+       .connect        = atkbd_connect,
+       .reconnect      = atkbd_reconnect,
+       .disconnect     = atkbd_disconnect,
+       .cleanup        = atkbd_cleanup,
 };
 
+static ssize_t atkbd_attr_show_helper(struct device *dev, char *buf,
+                               ssize_t (*handler)(struct atkbd *, char *))
+{
+       struct serio *serio = to_serio_port(dev);
+       int retval;
+
+       retval = serio_pin_driver(serio);
+       if (retval)
+               return retval;
+
+       if (serio->drv != &atkbd_drv) {
+               retval = -ENODEV;
+               goto out;
+       }
+
+       retval = handler((struct atkbd *)serio->private, buf);
+
+out:
+       serio_unpin_driver(serio);
+       return retval;
+}
+
+static ssize_t atkbd_attr_set_helper(struct device *dev, const char *buf, size_t count,
+                               ssize_t (*handler)(struct atkbd *, const char *, size_t))
+{
+       struct serio *serio = to_serio_port(dev);
+       struct atkbd *atkbd;
+       int retval;
+
+       retval = serio_pin_driver(serio);
+       if (retval)
+               return retval;
+
+       if (serio->drv != &atkbd_drv) {
+               retval = -ENODEV;
+               goto out;
+       }
+
+       atkbd = serio->private;
+       atkbd_disable(atkbd);
+       retval = handler(atkbd, buf, count);
+       atkbd_enable(atkbd);
+
+out:
+       serio_unpin_driver(serio);
+       return retval;
+}
+
+static ssize_t atkbd_show_extra(struct atkbd *atkbd, char *buf)
+{
+       return sprintf(buf, "%d\n", atkbd->extra ? 1 : 0);
+}
+
+static ssize_t atkbd_set_extra(struct atkbd *atkbd, const char *buf, size_t count)
+{
+       unsigned long value;
+       char *rest;
+
+       if (!atkbd->write)
+               return -EIO;
+
+       value = simple_strtoul(buf, &rest, 10);
+       if (*rest || value > 1)
+               return -EINVAL;
+
+       if (atkbd->extra != value) {
+               /* unregister device as it's properties will change */
+               input_unregister_device(&atkbd->dev);
+               atkbd->set = atkbd_select_set(atkbd, atkbd->set, value);
+               atkbd_activate(atkbd);
+               atkbd_set_device_attrs(atkbd);
+               input_register_device(&atkbd->dev);
+       }
+       return count;
+}
+
+static ssize_t atkbd_show_scroll(struct atkbd *atkbd, char *buf)
+{
+       return sprintf(buf, "%d\n", atkbd->scroll ? 1 : 0);
+}
+
+static ssize_t atkbd_set_scroll(struct atkbd *atkbd, const char *buf, size_t count)
+{
+       unsigned long value;
+       char *rest;
+
+       value = simple_strtoul(buf, &rest, 10);
+       if (*rest || value > 1)
+               return -EINVAL;
+
+       if (atkbd->scroll != value) {
+               /* unregister device as it's properties will change */
+               input_unregister_device(&atkbd->dev);
+               atkbd->scroll = value;
+               atkbd_set_keycode_table(atkbd);
+               atkbd_set_device_attrs(atkbd);
+               input_register_device(&atkbd->dev);
+       }
+       return count;
+}
+
+static ssize_t atkbd_show_set(struct atkbd *atkbd, char *buf)
+{
+       return sprintf(buf, "%d\n", atkbd->set);
+}
+
+static ssize_t atkbd_set_set(struct atkbd *atkbd, const char *buf, size_t count)
+{
+       unsigned long value;
+       char *rest;
+
+       if (!atkbd->write)
+               return -EIO;
+
+       value = simple_strtoul(buf, &rest, 10);
+       if (*rest || (value != 2 && value != 3))
+               return -EINVAL;
+
+       if (atkbd->set != value) {
+               /* unregister device as it's properties will change */
+               input_unregister_device(&atkbd->dev);
+               atkbd->set = atkbd_select_set(atkbd, value, atkbd->extra);
+               atkbd_activate(atkbd);
+               atkbd_set_keycode_table(atkbd);
+               atkbd_set_device_attrs(atkbd);
+               input_register_device(&atkbd->dev);
+       }
+       return count;
+}
+
+static ssize_t atkbd_show_softrepeat(struct atkbd *atkbd, char *buf)
+{
+       return sprintf(buf, "%d\n", atkbd->softrepeat ? 1 : 0);
+}
+
+static ssize_t atkbd_set_softrepeat(struct atkbd *atkbd, const char *buf, size_t count)
+{
+       unsigned long value;
+       char *rest;
+
+       if (!atkbd->write)
+               return -EIO;
+
+       value = simple_strtoul(buf, &rest, 10);
+       if (*rest || value > 1)
+               return -EINVAL;
+
+       if (atkbd->softrepeat != value) {
+               /* unregister device as it's properties will change */
+               input_unregister_device(&atkbd->dev);
+               atkbd->softrepeat = value;
+               if (atkbd->softrepeat)
+                       atkbd->softraw = 1;
+               atkbd_set_device_attrs(atkbd);
+               input_register_device(&atkbd->dev);
+       }
+
+       return count;
+}
+
+
+static ssize_t atkbd_show_softraw(struct atkbd *atkbd, char *buf)
+{
+       return sprintf(buf, "%d\n", atkbd->softraw ? 1 : 0);
+}
+
+static ssize_t atkbd_set_softraw(struct atkbd *atkbd, const char *buf, size_t count)
+{
+       unsigned long value;
+       char *rest;
+
+       value = simple_strtoul(buf, &rest, 10);
+       if (*rest || value > 1)
+               return -EINVAL;
+
+       if (atkbd->softraw != value) {
+               /* unregister device as it's properties will change */
+               input_unregister_device(&atkbd->dev);
+               atkbd->softraw = value;
+               atkbd_set_device_attrs(atkbd);
+               input_register_device(&atkbd->dev);
+       }
+       return count;
+}
+
+
 int __init atkbd_init(void)
 {
-       serio_register_device(&atkbd_dev);
+       serio_register_driver(&atkbd_drv);
        return 0;
 }
 
 void __exit atkbd_exit(void)
 {
-       serio_unregister_device(&atkbd_dev);
+       serio_unregister_driver(&atkbd_drv);
 }
 
 module_init(atkbd_init);