Fedora kernel-2.6.17-1.2142_FC4 patched with stable patch-2.6.17.4-vs2.0.2-rc26.diff
[linux-2.6.git] / drivers / input / keyboard / atkbd.c
index 48fdf1e..5550b6d 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/serio.h>
 #include <linux/workqueue.h>
 #include <linux/libps2.h>
+#include <linux/mutex.h>
 
 #define DRIVER_DESC    "AT and PS/2 keyboard driver"
 
@@ -166,6 +167,9 @@ static unsigned char atkbd_unxlate_table[128] = {
 
 #define ATKBD_SPECIAL          248
 
+#define ATKBD_LED_EVENT_BIT    0
+#define ATKBD_REP_EVENT_BIT    1
+
 static struct {
        unsigned char keycode;
        unsigned char set2;
@@ -185,12 +189,12 @@ static struct {
 
 struct atkbd {
 
-       struct ps2dev   ps2dev;
+       struct ps2dev ps2dev;
+       struct input_dev *dev;
 
        /* Written only during init */
        char name[64];
        char phys[32];
-       struct input_dev dev;
 
        unsigned short id;
        unsigned char keycode[512];
@@ -208,8 +212,13 @@ struct atkbd {
        unsigned char resend;
        unsigned char release;
        unsigned char bat_xl;
+       unsigned char err_xl;
        unsigned int last;
        unsigned long time;
+
+       struct work_struct event_work;
+       struct mutex event_mutex;
+       unsigned long event_mask;
 };
 
 static ssize_t atkbd_attr_show_helper(struct device *dev, char *buf,
@@ -219,15 +228,15 @@ static ssize_t atkbd_attr_set_helper(struct device *dev, const char *buf, 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)                        \
+static ssize_t atkbd_do_show_##_name(struct device *d, struct device_attribute *attr, 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) \
+static ssize_t atkbd_do_set_##_name(struct device *d, struct device_attribute *attr, const char *b, size_t s)  \
 {                                                                              \
        return atkbd_attr_set_helper(d, b, s, atkbd_set_##_name);               \
 }                                                                              \
-static struct device_attribute atkbd_attr_##_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);
@@ -289,28 +298,31 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
        if (!atkbd->enabled)
                goto out;
 
-       input_event(&atkbd->dev, EV_MSC, MSC_RAW, code);
+       input_event(atkbd->dev, EV_MSC, MSC_RAW, code);
 
        if (atkbd->translated) {
 
                if (atkbd->emul ||
-                   !(code == ATKBD_RET_EMUL0 || code == ATKBD_RET_EMUL1 ||
-                     code == ATKBD_RET_HANGUEL || code == ATKBD_RET_HANJA ||
-                     code == ATKBD_RET_ERR ||
-                    (code == ATKBD_RET_BAT && !atkbd->bat_xl))) {
+                   (code != ATKBD_RET_EMUL0 && code != ATKBD_RET_EMUL1 &&
+                    code != ATKBD_RET_HANGUEL && code != ATKBD_RET_HANJA &&
+                    (code != ATKBD_RET_ERR || atkbd->err_xl) &&
+                    (code != ATKBD_RET_BAT || atkbd->bat_xl))) {
                        atkbd->release = code >> 7;
                        code &= 0x7f;
                }
 
-               if (!atkbd->emul &&
-                    (code & 0x7f) == (ATKBD_RET_BAT & 0x7f))
-                       atkbd->bat_xl = !atkbd->release;
+               if (!atkbd->emul) {
+                    if ((code & 0x7f) == (ATKBD_RET_BAT & 0x7f))
+                       atkbd->bat_xl = !(data >> 7);
+                    if ((code & 0x7f) == (ATKBD_RET_ERR & 0x7f))
+                       atkbd->err_xl = !(data >> 7);
+               }
        }
 
        switch (code) {
                case ATKBD_RET_BAT:
                        atkbd->enabled = 0;
-                       serio_rescan(atkbd->ps2dev.serio);
+                       serio_reconnect(atkbd->ps2dev.serio);
                        goto out;
                case ATKBD_RET_EMUL0:
                        atkbd->emul = 1;
@@ -322,13 +334,13 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
                        atkbd->release = 1;
                        goto out;
                case ATKBD_RET_HANGUEL:
-                       atkbd_report_key(&atkbd->dev, regs, KEY_HANGUEL, 3);
+                       atkbd_report_key(atkbd->dev, regs, KEY_HANGUEL, 3);
                        goto out;
                case ATKBD_RET_HANJA:
-                       atkbd_report_key(&atkbd->dev, regs, KEY_HANJA, 3);
+                       atkbd_report_key(atkbd->dev, regs, KEY_HANJA, 3);
                        goto out;
                case ATKBD_RET_ERR:
-                       printk(KERN_DEBUG "atkbd.c: Keyboard on %s reports too many keys pressed.\n", serio->phys);
+//                     printk(KERN_DEBUG "atkbd.c: Keyboard on %s reports too many keys pressed.\n", serio->phys);
                        goto out;
        }
 
@@ -341,16 +353,20 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
        }
 
        if (atkbd->keycode[code] != ATKBD_KEY_NULL)
-               input_event(&atkbd->dev, EV_MSC, MSC_SCAN, code);
+               input_event(atkbd->dev, EV_MSC, MSC_SCAN, code);
 
        switch (atkbd->keycode[code]) {
                case ATKBD_KEY_NULL:
                        break;
                case ATKBD_KEY_UNKNOWN:
                        if (data == ATKBD_RET_ACK || data == ATKBD_RET_NAK) {
+#if 0
+/* Quite a few key switchers and other tools trigger this and it confuses
+   people who can do nothing about it */                       
                                printk(KERN_WARNING "atkbd.c: Spurious %s on %s. Some program, "
                                       "like XFree86, might be trying access hardware directly.\n",
                                       data == ATKBD_RET_ACK ? "ACK" : "NAK", serio->phys);
+#endif                                
                        } else {
                                printk(KERN_WARNING "atkbd.c: Unknown key %s "
                                       "(%s set %d, code %#x on %s).\n",
@@ -361,7 +377,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);
+                       input_sync(atkbd->dev);
                        break;
                case ATKBD_SCR_1:
                        scroll = 1 - atkbd->release * 2;
@@ -386,15 +402,15 @@ 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 */
+                       switch (value) {        /* Workaround Toshiba laptop multiple keypress */
                                case 0:
                                        atkbd->last = 0;
                                        break;
                                case 1:
                                        atkbd->last = code;
-                                       atkbd->time = jiffies + msecs_to_jiffies(atkbd->dev.rep[REP_DELAY]) / 2;
+                                       atkbd->time = jiffies + msecs_to_jiffies(atkbd->dev->rep[REP_DELAY]) / 2;
                                        break;
                                case 2:
                                        if (!time_after(jiffies, atkbd->time) && atkbd->last == code)
@@ -402,16 +418,16 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
                                        break;
                        }
 
-                       atkbd_report_key(&atkbd->dev, regs, atkbd->keycode[code], value);
+                       atkbd_report_key(atkbd->dev, regs, atkbd->keycode[code], value);
        }
 
        if (atkbd->scroll) {
-               input_regs(&atkbd->dev, regs);
+               input_regs(atkbd->dev, regs);
                if (click != -1)
-                       input_report_key(&atkbd->dev, BTN_MIDDLE, click);
-               input_report_rel(&atkbd->dev, REL_WHEEL, scroll);
-               input_report_rel(&atkbd->dev, REL_HWHEEL, hscroll);
-               input_sync(&atkbd->dev);
+                       input_report_key(atkbd->dev, BTN_MIDDLE, click);
+               input_report_rel(atkbd->dev, REL_WHEEL, scroll);
+               input_report_rel(atkbd->dev, REL_HWHEEL, hscroll);
+               input_sync(atkbd->dev);
        }
 
        atkbd->release = 0;
@@ -420,59 +436,86 @@ out:
 }
 
 /*
- * Event callback from the input module. Events that change the state of
- * the hardware are processed here.
+ * atkbd_event_work() is used to complete processing of events that
+ * can not be processed by input_event() which is often called from
+ * interrupt context.
  */
 
-static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
+static void atkbd_event_work(void *data)
 {
-       struct atkbd *atkbd = dev->private;
        const short period[32] =
                { 33,  37,  42,  46,  50,  54,  58,  63,  67,  75,  83,  92, 100, 109, 116, 125,
                 133, 149, 167, 182, 200, 217, 232, 250, 270, 303, 333, 370, 400, 435, 470, 500 };
        const short delay[4] =
                { 250, 500, 750, 1000 };
+
+       struct atkbd *atkbd = data;
+       struct input_dev *dev = atkbd->dev;
        unsigned char param[2];
        int i, j;
 
+       mutex_lock(&atkbd->event_mutex);
+
+       if (test_and_clear_bit(ATKBD_LED_EVENT_BIT, &atkbd->event_mask)) {
+               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);
+               ps2_command(&atkbd->ps2dev, param, ATKBD_CMD_SETLEDS);
+
+               if (atkbd->extra) {
+                       param[0] = 0;
+                       param[1] = (test_bit(LED_COMPOSE, dev->led) ? 0x01 : 0)
+                                | (test_bit(LED_SLEEP,   dev->led) ? 0x02 : 0)
+                                | (test_bit(LED_SUSPEND, dev->led) ? 0x04 : 0)
+                                | (test_bit(LED_MISC,    dev->led) ? 0x10 : 0)
+                                | (test_bit(LED_MUTE,    dev->led) ? 0x20 : 0);
+                       ps2_command(&atkbd->ps2dev, param, ATKBD_CMD_EX_SETLEDS);
+               }
+       }
+
+       if (test_and_clear_bit(ATKBD_REP_EVENT_BIT, &atkbd->event_mask)) {
+               i = j = 0;
+               while (i < 31 && period[i] < dev->rep[REP_PERIOD])
+                       i++;
+               while (j < 3 && delay[j] < dev->rep[REP_DELAY])
+                       j++;
+               dev->rep[REP_PERIOD] = period[i];
+               dev->rep[REP_DELAY] = delay[j];
+               param[0] = i | (j << 5);
+               ps2_command(&atkbd->ps2dev, param, ATKBD_CMD_SETREP);
+       }
+
+       mutex_unlock(&atkbd->event_mutex);
+}
+
+/*
+ * Event callback from the input module. Events that change the state of
+ * the hardware are processed here. If action can not be performed in
+ * interrupt context it is offloaded to atkbd_event_work.
+ */
+
+static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
+{
+       struct atkbd *atkbd = dev->private;
+
        if (!atkbd->write)
                return -1;
 
        switch (type) {
 
                case EV_LED:
-
-                       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);
-                       ps2_schedule_command(&atkbd->ps2dev, param, ATKBD_CMD_SETLEDS);
-
-                       if (atkbd->extra) {
-                               param[0] = 0;
-                               param[1] = (test_bit(LED_COMPOSE, dev->led) ? 0x01 : 0)
-                                        | (test_bit(LED_SLEEP,   dev->led) ? 0x02 : 0)
-                                        | (test_bit(LED_SUSPEND, dev->led) ? 0x04 : 0)
-                                        | (test_bit(LED_MISC,    dev->led) ? 0x10 : 0)
-                                        | (test_bit(LED_MUTE,    dev->led) ? 0x20 : 0);
-                               ps2_schedule_command(&atkbd->ps2dev, param, ATKBD_CMD_EX_SETLEDS);
-                       }
-
+                       set_bit(ATKBD_LED_EVENT_BIT, &atkbd->event_mask);
+                       wmb();
+                       schedule_work(&atkbd->event_work);
                        return 0;
 
-
                case EV_REP:
 
-                       if (atkbd->softrepeat) return 0;
-
-                       i = j = 0;
-                       while (i < 31 && period[i] < dev->rep[REP_PERIOD])
-                               i++;
-                       while (j < 3 && delay[j] < dev->rep[REP_DELAY])
-                               j++;
-                       dev->rep[REP_PERIOD] = period[i];
-                       dev->rep[REP_DELAY] = delay[j];
-                       param[0] = i | (j << 5);
-                       ps2_schedule_command(&atkbd->ps2dev, param, ATKBD_CMD_SETREP);
+                       if (!atkbd->softrepeat) {
+                               set_bit(ATKBD_REP_EVENT_BIT, &atkbd->event_mask);
+                               wmb();
+                               schedule_work(&atkbd->event_work);
+                       }
 
                        return 0;
        }
@@ -689,7 +732,7 @@ static void atkbd_disconnect(struct serio *serio)
        device_remove_file(&serio->dev, &atkbd_attr_softrepeat);
        device_remove_file(&serio->dev, &atkbd_attr_softraw);
 
-       input_unregister_device(&atkbd->dev);
+       input_unregister_device(atkbd->dev);
        serio_close(serio);
        serio_set_drvdata(serio, NULL);
        kfree(atkbd);
@@ -697,7 +740,7 @@ static void atkbd_disconnect(struct serio *serio)
 
 
 /*
- * atkbd_set_device_attrs() initializes keyboard's keycode table
+ * atkbd_set_keycode_table() initializes keyboard's keycode table
  * according to the selected scancode set
  */
 
@@ -733,53 +776,58 @@ static void atkbd_set_keycode_table(struct atkbd *atkbd)
 
 static void atkbd_set_device_attrs(struct atkbd *atkbd)
 {
+       struct input_dev *input_dev = atkbd->dev;
        int i;
 
-       memset(&atkbd->dev, 0, sizeof(struct input_dev));
+       if (atkbd->extra)
+               sprintf(atkbd->name, "AT Set 2 Extra keyboard");
+       else
+               sprintf(atkbd->name, "AT %s Set %d keyboard",
+                       atkbd->translated ? "Translated" : "Raw", atkbd->set);
 
-       init_input_dev(&atkbd->dev);
+       sprintf(atkbd->phys, "%s/input0", atkbd->ps2dev.serio->phys);
 
-       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;
+       input_dev->name = atkbd->name;
+       input_dev->phys = atkbd->phys;
+       input_dev->id.bustype = BUS_I8042;
+       input_dev->id.vendor = 0x0001;
+       input_dev->id.product = atkbd->translated ? 1 : atkbd->set;
+       input_dev->id.version = atkbd->id;
+       input_dev->event = atkbd_event;
+       input_dev->private = atkbd;
+       input_dev->cdev.dev = &atkbd->ps2dev.serio->dev;
 
-       atkbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REP) | BIT(EV_MSC);
+       input_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);
+               input_dev->evbit[0] |= BIT(EV_LED);
+               input_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) |
+               input_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;
+               input_dev->rep[REP_DELAY] = 250;
+               input_dev->rep[REP_PERIOD] = 33;
        }
 
-       atkbd->dev.mscbit[0] = atkbd->softraw ? BIT(MSC_SCAN) : BIT(MSC_RAW) | BIT(MSC_SCAN);
+       input_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) | BIT(REL_HWHEEL);
-               set_bit(BTN_MIDDLE, atkbd->dev.keybit);
+               input_dev->evbit[0] |= BIT(EV_REL);
+               input_dev->relbit[0] = BIT(REL_WHEEL) | BIT(REL_HWHEEL);
+               set_bit(BTN_MIDDLE, input_dev->keybit);
        }
 
-       atkbd->dev.keycode = atkbd->keycode;
-       atkbd->dev.keycodesize = sizeof(unsigned char);
-       atkbd->dev.keycodemax = ARRAY_SIZE(atkbd_set2_keycode);
+       input_dev->keycode = atkbd->keycode;
+       input_dev->keycodesize = sizeof(unsigned char);
+       input_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);
+                       set_bit(atkbd->keycode[i], input_dev->keybit);
 }
 
 /*
@@ -792,14 +840,18 @@ static void atkbd_set_device_attrs(struct atkbd *atkbd)
 static int atkbd_connect(struct serio *serio, struct serio_driver *drv)
 {
        struct atkbd *atkbd;
-       int err;
+       struct input_dev *dev;
+       int err = -ENOMEM;
 
-       if (!(atkbd = kmalloc(sizeof(struct atkbd), GFP_KERNEL)))
-               return - ENOMEM;
-
-       memset(atkbd, 0, sizeof(struct atkbd));
+       atkbd = kzalloc(sizeof(struct atkbd), GFP_KERNEL);
+       dev = input_allocate_device();
+       if (!atkbd || !dev)
+               goto fail;
 
+       atkbd->dev = dev;
        ps2_init(&atkbd->ps2dev, serio);
+       INIT_WORK(&atkbd->event_work, atkbd_event_work, atkbd);
+       mutex_init(&atkbd->event_mutex);
 
        switch (serio->id.type) {
 
@@ -815,28 +867,21 @@ static int atkbd_connect(struct serio *serio, struct serio_driver *drv)
        atkbd->softrepeat = atkbd_softrepeat;
        atkbd->scroll = atkbd_scroll;
 
-       if (!atkbd->write)
-               atkbd->softrepeat = 1;
-
        if (atkbd->softrepeat)
                atkbd->softraw = 1;
 
        serio_set_drvdata(serio, atkbd);
 
        err = serio_open(serio, drv);
-       if (err) {
-               serio_set_drvdata(serio, NULL);
-               kfree(atkbd);
-               return err;
-       }
+       if (err)
+               goto fail;
 
        if (atkbd->write) {
 
                if (atkbd_probe(atkbd)) {
                        serio_close(serio);
-                       serio_set_drvdata(serio, NULL);
-                       kfree(atkbd);
-                       return -ENODEV;
+                       err = -ENODEV;
+                       goto fail;
                }
 
                atkbd->set = atkbd_select_set(atkbd, atkbd_set, atkbd_extra);
@@ -847,19 +892,9 @@ static int atkbd_connect(struct serio *serio, struct serio_driver *drv)
                atkbd->id = 0xab00;
        }
 
-       if (atkbd->extra)
-               sprintf(atkbd->name, "AT Set 2 Extra keyboard");
-       else
-               sprintf(atkbd->name, "AT %s Set %d keyboard",
-                       atkbd->translated ? "Translated" : "Raw", atkbd->set);
-
-       sprintf(atkbd->phys, "%s/input0", serio->phys);
-
        atkbd_set_keycode_table(atkbd);
        atkbd_set_device_attrs(atkbd);
 
-       input_register_device(&atkbd->dev);
-
        device_create_file(&serio->dev, &atkbd_attr_extra);
        device_create_file(&serio->dev, &atkbd_attr_scroll);
        device_create_file(&serio->dev, &atkbd_attr_set);
@@ -868,9 +903,14 @@ static int atkbd_connect(struct serio *serio, struct serio_driver *drv)
 
        atkbd_enable(atkbd);
 
-       printk(KERN_INFO "input: %s on %s\n", atkbd->name, serio->phys);
+       input_register_device(atkbd->dev);
 
        return 0;
+
+ fail: serio_set_drvdata(serio, NULL);
+       input_free_device(dev);
+       kfree(atkbd);
+       return err;
 }
 
 /*
@@ -892,9 +932,9 @@ static int atkbd_reconnect(struct serio *serio)
        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)
-                        | (test_bit(LED_CAPSL,   atkbd->dev.led) ? 4 : 0);
+               param[0] = (test_bit(LED_SCROLLL, atkbd->dev->led) ? 1 : 0)
+                        | (test_bit(LED_NUML,    atkbd->dev->led) ? 2 : 0)
+                        | (test_bit(LED_CAPSL,   atkbd->dev->led) ? 4 : 0);
 
                if (atkbd_probe(atkbd))
                        return -1;
@@ -1004,6 +1044,7 @@ static ssize_t atkbd_show_extra(struct atkbd *atkbd, char *buf)
 
 static ssize_t atkbd_set_extra(struct atkbd *atkbd, const char *buf, size_t count)
 {
+       struct input_dev *new_dev;
        unsigned long value;
        char *rest;
 
@@ -1015,12 +1056,19 @@ static ssize_t atkbd_set_extra(struct atkbd *atkbd, const char *buf, size_t coun
                return -EINVAL;
 
        if (atkbd->extra != value) {
-               /* unregister device as it's properties will change */
-               input_unregister_device(&atkbd->dev);
+               /*
+                * Since device's properties will change we need to
+                * unregister old device. But allocate new one first
+                * to make sure we have it.
+                */
+               if (!(new_dev = input_allocate_device()))
+                       return -ENOMEM;
+               input_unregister_device(atkbd->dev);
+               atkbd->dev = new_dev;
                atkbd->set = atkbd_select_set(atkbd, atkbd->set, value);
                atkbd_activate(atkbd);
                atkbd_set_device_attrs(atkbd);
-               input_register_device(&atkbd->dev);
+               input_register_device(atkbd->dev);
        }
        return count;
 }
@@ -1032,6 +1080,7 @@ static ssize_t atkbd_show_scroll(struct atkbd *atkbd, char *buf)
 
 static ssize_t atkbd_set_scroll(struct atkbd *atkbd, const char *buf, size_t count)
 {
+       struct input_dev *new_dev;
        unsigned long value;
        char *rest;
 
@@ -1040,12 +1089,14 @@ static ssize_t atkbd_set_scroll(struct atkbd *atkbd, const char *buf, size_t cou
                return -EINVAL;
 
        if (atkbd->scroll != value) {
-               /* unregister device as it's properties will change */
-               input_unregister_device(&atkbd->dev);
+               if (!(new_dev = input_allocate_device()))
+                       return -ENOMEM;
+               input_unregister_device(atkbd->dev);
+               atkbd->dev = new_dev;
                atkbd->scroll = value;
                atkbd_set_keycode_table(atkbd);
                atkbd_set_device_attrs(atkbd);
-               input_register_device(&atkbd->dev);
+               input_register_device(atkbd->dev);
        }
        return count;
 }
@@ -1057,6 +1108,7 @@ static ssize_t atkbd_show_set(struct atkbd *atkbd, char *buf)
 
 static ssize_t atkbd_set_set(struct atkbd *atkbd, const char *buf, size_t count)
 {
+       struct input_dev *new_dev;
        unsigned long value;
        char *rest;
 
@@ -1068,13 +1120,15 @@ static ssize_t atkbd_set_set(struct atkbd *atkbd, const char *buf, size_t count)
                return -EINVAL;
 
        if (atkbd->set != value) {
-               /* unregister device as it's properties will change */
-               input_unregister_device(&atkbd->dev);
+               if (!(new_dev = input_allocate_device()))
+                       return -ENOMEM;
+               input_unregister_device(atkbd->dev);
+               atkbd->dev = new_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);
+               input_register_device(atkbd->dev);
        }
        return count;
 }
@@ -1086,6 +1140,7 @@ static ssize_t atkbd_show_softrepeat(struct atkbd *atkbd, char *buf)
 
 static ssize_t atkbd_set_softrepeat(struct atkbd *atkbd, const char *buf, size_t count)
 {
+       struct input_dev *new_dev;
        unsigned long value;
        char *rest;
 
@@ -1097,15 +1152,16 @@ static ssize_t atkbd_set_softrepeat(struct atkbd *atkbd, const char *buf, size_t
                return -EINVAL;
 
        if (atkbd->softrepeat != value) {
-               /* unregister device as it's properties will change */
-               input_unregister_device(&atkbd->dev);
+               if (!(new_dev = input_allocate_device()))
+                       return -ENOMEM;
+               input_unregister_device(atkbd->dev);
+               atkbd->dev = new_dev;
                atkbd->softrepeat = value;
                if (atkbd->softrepeat)
                        atkbd->softraw = 1;
                atkbd_set_device_attrs(atkbd);
-               input_register_device(&atkbd->dev);
+               input_register_device(atkbd->dev);
        }
-
        return count;
 }
 
@@ -1117,6 +1173,7 @@ static ssize_t atkbd_show_softraw(struct atkbd *atkbd, char *buf)
 
 static ssize_t atkbd_set_softraw(struct atkbd *atkbd, const char *buf, size_t count)
 {
+       struct input_dev *new_dev;
        unsigned long value;
        char *rest;
 
@@ -1125,11 +1182,13 @@ static ssize_t atkbd_set_softraw(struct atkbd *atkbd, const char *buf, size_t co
                return -EINVAL;
 
        if (atkbd->softraw != value) {
-               /* unregister device as it's properties will change */
-               input_unregister_device(&atkbd->dev);
+               if (!(new_dev = input_allocate_device()))
+                       return -ENOMEM;
+               input_unregister_device(atkbd->dev);
+               atkbd->dev = new_dev;
                atkbd->softraw = value;
                atkbd_set_device_attrs(atkbd);
-               input_register_device(&atkbd->dev);
+               input_register_device(atkbd->dev);
        }
        return count;
 }