vserver 1.9.5.x5
[linux-2.6.git] / drivers / input / keyboard / atkbd.c
index ec97ae7..8b46866 100644 (file)
@@ -26,6 +26,7 @@
 #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"
 
@@ -170,36 +171,29 @@ static unsigned char atkbd_scroll_keys[5][2] = {
        { ATKBD_SCR_CLICK, 0x60 },
 };
 
-#define ATKBD_FLAG_ACK         0       /* Waiting for ACK/NAK */
-#define ATKBD_FLAG_CMD         1       /* Waiting for command to finish */
-#define ATKBD_FLAG_CMD1                2       /* First byte of command response */
-#define ATKBD_FLAG_ENABLED     3       /* Waining for init to finish */
-
 /*
  * The atkbd control structure
  */
 
 struct atkbd {
 
+       struct ps2dev   ps2dev;
+
        /* Written only during init */
        char name[64];
        char phys[32];
-       struct serio *serio;
        struct input_dev dev;
 
-       unsigned char set;
        unsigned short id;
        unsigned char keycode[512];
+       unsigned char set;
        unsigned char translated;
        unsigned char extra;
        unsigned char write;
-
-       /* Protected by FLAG_ACK */
-       unsigned char nak;
-
-       /* Protected by FLAG_CMD */
-       unsigned char cmdbuf[4];
-       unsigned char cmdcnt;
+       unsigned char softrepeat;
+       unsigned char softraw;
+       unsigned char scroll;
+       unsigned char enabled;
 
        /* Accessed only from interrupt */
        unsigned char emul;
@@ -208,24 +202,31 @@ struct atkbd {
        unsigned char bat_xl;
        unsigned int last;
        unsigned long time;
-
-       /* Ensures that only one command is executing at a time */
-       struct semaphore cmd_sem;
-
-       /* Used to signal completion from interrupt handler */
-       wait_queue_head_t wait;
-
-       /* Flags */
-       unsigned long flags;
 };
 
-/* Work structure to schedule execution of a command */
-struct atkbd_work {
-       struct work_struct work;
-       struct atkbd *atkbd;
-       int command;
-       unsigned char param[0];
-};
+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)
@@ -233,6 +234,7 @@ static void atkbd_report_key(struct input_dev *dev, struct pt_regs *regs, int co
        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);
@@ -268,42 +270,15 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
                atkbd->resend = 0;
 #endif
 
-       if (test_bit(ATKBD_FLAG_ACK, &atkbd->flags)) {
-               switch (code) {
-                       case ATKBD_RET_ACK:
-                               atkbd->nak = 0;
-                               if (atkbd->cmdcnt) {
-                                       set_bit(ATKBD_FLAG_CMD, &atkbd->flags);
-                                       set_bit(ATKBD_FLAG_CMD1, &atkbd->flags);
-                               }
-                               clear_bit(ATKBD_FLAG_ACK, &atkbd->flags);
-                               wake_up_interruptible(&atkbd->wait);
-                               break;
-                       case ATKBD_RET_NAK:
-                               atkbd->nak = 1;
-                               clear_bit(ATKBD_FLAG_ACK, &atkbd->flags);
-                               wake_up_interruptible(&atkbd->wait);
-                               break;
-               }
-               goto out;
-       }
-
-       if (test_bit(ATKBD_FLAG_CMD, &atkbd->flags)) {
-
-               if (atkbd->cmdcnt)
-                       atkbd->cmdbuf[--atkbd->cmdcnt] = code;
-
-               if (test_and_clear_bit(ATKBD_FLAG_CMD1, &atkbd->flags) && atkbd->cmdcnt)
-                       wake_up_interruptible(&atkbd->wait);
+       if (unlikely(atkbd->ps2dev.flags & PS2_FLAG_ACK))
+               if  (ps2_handle_ack(&atkbd->ps2dev, data))
+                       goto out;
 
-               if (!atkbd->cmdcnt) {
-                       clear_bit(ATKBD_FLAG_CMD, &atkbd->flags);
-                       wake_up_interruptible(&atkbd->wait);
-               }
-               goto out;
-       }
+       if (unlikely(atkbd->ps2dev.flags & PS2_FLAG_CMD))
+               if  (ps2_handle_response(&atkbd->ps2dev, data))
+                       goto out;
 
-       if (!test_bit(ATKBD_FLAG_ENABLED, &atkbd->flags))
+       if (!atkbd->enabled)
                goto out;
 
        input_event(&atkbd->dev, EV_MSC, MSC_RAW, code);
@@ -326,8 +301,8 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
 
        switch (code) {
                case ATKBD_RET_BAT:
-                       clear_bit(ATKBD_FLAG_ENABLED, &atkbd->flags);
-                       serio_rescan(atkbd->serio);
+                       atkbd->enabled = 0;
+                       serio_rescan(atkbd->ps2dev.serio);
                        goto out;
                case ATKBD_RET_EMUL0:
                        atkbd->emul = 1;
@@ -378,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;
@@ -396,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:
@@ -427,151 +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.
- *
- * atkbd_sendbyte() can only be called from a process context
- */
-
-static int atkbd_sendbyte(struct atkbd *atkbd, unsigned char byte)
-{
-#ifdef ATKBD_DEBUG
-       printk(KERN_DEBUG "atkbd.c: Sent: %02x\n", byte);
-#endif
-       atkbd->nak = 1;
-       set_bit(ATKBD_FLAG_ACK, &atkbd->flags);
-
-       if (serio_write(atkbd->serio, byte) == 0)
-               wait_event_interruptible_timeout(atkbd->wait,
-                               !test_bit(ATKBD_FLAG_ACK, &atkbd->flags),
-                               msecs_to_jiffies(200));
-
-       clear_bit(ATKBD_FLAG_ACK, &atkbd->flags);
-       return -atkbd->nak;
-}
-
-/*
- * atkbd_command() sends a command, and its parameters to the keyboard,
- * then waits for the response and puts it in the param array.
- *
- * atkbd_command() can only be called from a process context
- */
-
-static int atkbd_command(struct atkbd *atkbd, unsigned char *param, int command)
-{
-       int timeout;
-       int send = (command >> 12) & 0xf;
-       int receive = (command >> 8) & 0xf;
-       int rc = -1;
-       int i;
-
-       timeout = msecs_to_jiffies(command == ATKBD_CMD_RESET_BAT ? 4000 : 500);
-
-       down(&atkbd->cmd_sem);
-       clear_bit(ATKBD_FLAG_CMD, &atkbd->flags);
-
-       if (receive && param)
-               for (i = 0; i < receive; i++)
-                       atkbd->cmdbuf[(receive - 1) - i] = param[i];
-
-       atkbd->cmdcnt = receive;
-
-       if (command & 0xff)
-               if (atkbd_sendbyte(atkbd, command & 0xff))
-                       goto out;
-
-       for (i = 0; i < send; i++)
-               if (atkbd_sendbyte(atkbd, param[i]))
-                       goto out;
-
-       timeout = wait_event_interruptible_timeout(atkbd->wait,
-                               !test_bit(ATKBD_FLAG_CMD1, &atkbd->flags), timeout);
-
-       if (atkbd->cmdcnt && timeout > 0) {
-               if (command == ATKBD_CMD_RESET_BAT && jiffies_to_msecs(timeout) > 100)
-                       timeout = msecs_to_jiffies(100);
-
-               if (command == ATKBD_CMD_GETID &&
-                   atkbd->cmdbuf[receive - 1] != 0xab && atkbd->cmdbuf[receive - 1] != 0xac) {
-                       /*
-                        * Device behind the port is not a keyboard
-                        * so we don't need to wait for the 2nd byte
-                        * of ID response.
-                        */
-                       clear_bit(ATKBD_FLAG_CMD, &atkbd->flags);
-                       atkbd->cmdcnt = 0;
-               }
-
-               wait_event_interruptible_timeout(atkbd->wait,
-                               !test_bit(ATKBD_FLAG_CMD, &atkbd->flags), timeout);
-       }
-
-       if (param)
-               for (i = 0; i < receive; i++)
-                       param[i] = atkbd->cmdbuf[(receive - 1) - i];
-
-       if (atkbd->cmdcnt && (command != ATKBD_CMD_RESET_BAT || atkbd->cmdcnt != 1))
-               goto out;
-
-       rc = 0;
-
-out:
-       clear_bit(ATKBD_FLAG_CMD, &atkbd->flags);
-       clear_bit(ATKBD_FLAG_CMD1, &atkbd->flags);
-       up(&atkbd->cmd_sem);
-
-       return rc;
-}
-
-/*
- * atkbd_execute_scheduled_command() sends a command, previously scheduled by
- * atkbd_schedule_command(), to the keyboard.
- */
-
-static void atkbd_execute_scheduled_command(void *data)
-{
-       struct atkbd_work *atkbd_work = data;
-
-       atkbd_command(atkbd_work->atkbd, atkbd_work->param, atkbd_work->command);
-
-       kfree(atkbd_work);
-}
-
-/*
- * atkbd_schedule_command() allows to schedule delayed execution of a keyboard
- * command and can be used to issue a command from an interrupt or softirq
- * context.
- */
-
-static int atkbd_schedule_command(struct atkbd *atkbd, unsigned char *param, int command)
-{
-       struct atkbd_work *atkbd_work;
-       int send = (command >> 12) & 0xf;
-       int receive = (command >> 8) & 0xf;
-
-       if (!test_bit(ATKBD_FLAG_ENABLED, &atkbd->flags))
-               return -1;
-
-       if (!(atkbd_work = kmalloc(sizeof(struct atkbd_work) + max(send, receive), GFP_ATOMIC)))
-               return -1;
-
-       memset(atkbd_work, 0, sizeof(struct atkbd_work));
-       atkbd_work->atkbd = atkbd;
-       atkbd_work->command = command;
-       memcpy(atkbd_work->param, param, send);
-       INIT_WORK(&atkbd_work->work, atkbd_execute_scheduled_command, atkbd_work);
-
-       if (!schedule_work(&atkbd_work->work)) {
-               kfree(atkbd_work);
-               return -1;
-       }
-
-       return 0;
-}
-
 
 /*
  * Event callback from the input module. Events that change the state of
@@ -599,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_schedule_command(atkbd, param, ATKBD_CMD_SETLEDS);
+                       ps2_schedule_command(&atkbd->ps2dev, param, ATKBD_CMD_SETLEDS);
 
                        if (atkbd->extra) {
                                param[0] = 0;
@@ -608,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_schedule_command(atkbd, param, ATKBD_CMD_EX_SETLEDS);
+                               ps2_schedule_command(&atkbd->ps2dev, param, ATKBD_CMD_EX_SETLEDS);
                        }
 
                        return 0;
@@ -616,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++;
@@ -624,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_schedule_command(atkbd, param, ATKBD_CMD_SETREP);
+                       ps2_schedule_command(&atkbd->ps2dev, param, ATKBD_CMD_SETREP);
 
                        return 0;
        }
@@ -632,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];
 
 /*
@@ -647,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.
@@ -658,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
@@ -666,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) {
@@ -686,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
@@ -706,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];
 
 /*
@@ -754,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;
 
 /*
@@ -762,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;
        }
 
@@ -786,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.
  */
@@ -797,15 +660,113 @@ static void atkbd_disconnect(struct serio *serio)
 {
        struct atkbd *atkbd = serio->private;
 
-       clear_bit(ATKBD_FLAG_ENABLED, &atkbd->flags);
+       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
@@ -816,14 +777,12 @@ static void atkbd_disconnect(struct serio *serio)
 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));
 
-       init_MUTEX(&atkbd->cmd_sem);
-       init_waitqueue_head(&atkbd->wait);
+       ps2_init(&atkbd->ps2dev, serio);
 
        switch (serio->type & SERIO_TYPE) {
 
@@ -841,31 +800,15 @@ static void atkbd_connect(struct serio *serio, struct serio_driver *drv)
                        return;
        }
 
-       if (!atkbd->write)
-               atkbd_softrepeat = 1;
-       if (atkbd_softrepeat)
-               atkbd_softraw = 1;
-
-       if (atkbd->write) {
-               atkbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_LED) | BIT(EV_REP) | BIT(EV_MSC);
-               atkbd->dev.ledbit[0] = BIT(LED_NUML) | BIT(LED_CAPSL) | BIT(LED_SCROLLL);
-       } else  atkbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REP) | BIT(EV_MSC);
-       atkbd->dev.mscbit[0] = atkbd_softraw ? BIT(MSC_SCAN) : BIT(MSC_RAW) | BIT(MSC_SCAN);
-
-       if (!atkbd_softrepeat) {
-               atkbd->dev.rep[REP_DELAY] = 250;
-               atkbd->dev.rep[REP_PERIOD] = 33;
-       } else atkbd_softraw = 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;
 
@@ -883,56 +826,34 @@ static void atkbd_connect(struct serio *serio, struct serio_driver *drv)
                        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;
        }
 
-       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->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;
-
-       for (i = 0; i < 512; i++)
-               if (atkbd->keycode[i] && atkbd->keycode[i] < ATKBD_SPECIAL)
-                       set_bit(atkbd->keycode[i], atkbd->dev.keybit);
+       atkbd_set_keycode_table(atkbd);
+       atkbd_set_device_attrs(atkbd);
 
        input_register_device(&atkbd->dev);
 
-       set_bit(ATKBD_FLAG_ENABLED, &atkbd->flags);
+       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);
+
+       atkbd_enable(atkbd);
 
        printk(KERN_INFO "input: %s on %s\n", atkbd->name, serio->phys);
 }
@@ -948,11 +869,13 @@ static int atkbd_reconnect(struct serio *serio)
        struct serio_driver *drv = serio->drv;
        unsigned char param[1];
 
-       if (!drv) {
+       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)
@@ -960,16 +883,16 @@ 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;
        }
 
-       set_bit(ATKBD_FLAG_ENABLED, &atkbd->flags);
+       atkbd_enable(atkbd);
 
        return 0;
 }
@@ -986,6 +909,192 @@ static struct serio_driver atkbd_drv = {
        .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_driver(&atkbd_drv);