Merge branch 'next' into for-linus
[safe/jmp/linux-2.6] / drivers / input / keyboard / atkbd.c
index 7d26ab4..c9523e4 100644 (file)
@@ -19,7 +19,6 @@
 
 #include <linux/delay.h>
 #include <linux/module.h>
-#include <linux/moduleparam.h>
 #include <linux/slab.h>
 #include <linux/interrupt.h>
 #include <linux/init.h>
@@ -28,6 +27,7 @@
 #include <linux/workqueue.h>
 #include <linux/libps2.h>
 #include <linux/mutex.h>
+#include <linux/dmi.h>
 
 #define DRIVER_DESC    "AT and PS/2 keyboard driver"
 
@@ -55,7 +55,7 @@ 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 = 0;
+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");
 
@@ -63,16 +63,14 @@ static int atkbd_extra;
 module_param_named(extra, atkbd_extra, bool, 0);
 MODULE_PARM_DESC(extra, "Enable extra LEDs and keys on IBM RapidAcces, EzKey and similar keyboards");
 
-__obsolete_setup("atkbd_set=");
-__obsolete_setup("atkbd_reset");
-__obsolete_setup("atkbd_softrepeat=");
-
 /*
  * Scancode to keycode tables. These are just the default setting, and
- * are loadable via an userland utility.
+ * are loadable via a userland utility.
  */
 
-static unsigned char atkbd_set2_keycode[512] = {
+#define ATKBD_KEYMAP_SIZE      512
+
+static const unsigned short atkbd_set2_keycode[ATKBD_KEYMAP_SIZE] = {
 
 #ifdef CONFIG_KEYBOARD_ATKBD_HP_KEYCODES
 
@@ -93,7 +91,7 @@ static unsigned char atkbd_set2_keycode[512] = {
          0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
        217,100,255,  0, 97,165,  0,  0,156,  0,  0,  0,  0,  0,  0,125,
        173,114,  0,113,  0,  0,  0,126,128,  0,  0,140,  0,  0,  0,127,
-       159,  0,115,  0,164,  0,  0,116,158,  0,150,166,  0,  0,  0,142,
+       159,  0,115,  0,164,  0,  0,116,158,  0,172,166,  0,  0,  0,142,
        157,  0,  0,  0,  0,  0,  0,  0,155,  0, 98,  0,  0,163,  0,  0,
        226,  0,  0,  0,  0,  0,  0,  0,  0,255, 96,  0,  0,  0,143,  0,
          0,  0,  0,  0,  0,  0,  0,  0,  0,107,  0,105,102,  0,  0,112,
@@ -103,7 +101,7 @@ static unsigned char atkbd_set2_keycode[512] = {
 #endif
 };
 
-static unsigned char atkbd_set3_keycode[512] = {
+static const unsigned short atkbd_set3_keycode[ATKBD_KEYMAP_SIZE] = {
 
          0,  0,  0,  0,  0,  0,  0, 59,  1,138,128,129,130, 15, 41, 60,
        131, 29, 42, 86, 58, 16,  2, 61,133, 56, 44, 31, 30, 17,  3, 62,
@@ -115,11 +113,11 @@ static unsigned char atkbd_set3_keycode[512] = {
         82, 83, 80, 76, 77, 72, 69, 98,  0, 96, 81,  0, 78, 73, 55,183,
 
        184,185,186,187, 74, 94, 92, 93,  0,  0,  0,125,126,127,112,  0,
-         0,139,150,163,165,115,152,150,166,140,160,154,113,114,167,168,
+         0,139,172,163,165,115,152,172,166,140,160,154,113,114,167,168,
        148,149,147,140
 };
 
-static unsigned char atkbd_unxlate_table[128] = {
+static const unsigned short atkbd_unxlate_table[128] = {
           0,118, 22, 30, 38, 37, 46, 54, 61, 62, 70, 69, 78, 85,102, 13,
          21, 29, 36, 45, 44, 53, 60, 67, 68, 77, 84, 91, 90, 20, 28, 27,
          35, 43, 52, 51, 59, 66, 75, 76, 82, 14, 18, 93, 26, 34, 33, 42,
@@ -150,8 +148,8 @@ static unsigned char atkbd_unxlate_table[128] = {
 #define ATKBD_RET_EMUL0                0xe0
 #define ATKBD_RET_EMUL1                0xe1
 #define ATKBD_RET_RELEASE      0xf0
-#define ATKBD_RET_HANGUEL      0xf1
-#define ATKBD_RET_HANJA                0xf2
+#define ATKBD_RET_HANJA                0xf1
+#define ATKBD_RET_HANGEUL      0xf2
 #define ATKBD_RET_ERR          0xff
 
 #define ATKBD_KEY_UNKNOWN        0
@@ -165,12 +163,19 @@ static unsigned char atkbd_unxlate_table[128] = {
 #define ATKBD_SCR_LEFT         249
 #define ATKBD_SCR_RIGHT                248
 
-#define ATKBD_SPECIAL          248
+#define ATKBD_SPECIAL          ATKBD_SCR_RIGHT
 
 #define ATKBD_LED_EVENT_BIT    0
 #define ATKBD_REP_EVENT_BIT    1
 
-static struct {
+#define ATKBD_XL_ERR           0x01
+#define ATKBD_XL_BAT           0x02
+#define ATKBD_XL_ACK           0x04
+#define ATKBD_XL_NAK           0x08
+#define ATKBD_XL_HANGEUL       0x10
+#define ATKBD_XL_HANJA         0x20
+
+static const struct {
        unsigned char keycode;
        unsigned char set2;
 } atkbd_scroll_keys[] = {
@@ -197,7 +202,8 @@ struct atkbd {
        char phys[32];
 
        unsigned short id;
-       unsigned char keycode[512];
+       unsigned short keycode[ATKBD_KEYMAP_SIZE];
+       DECLARE_BITMAP(force_release_mask, ATKBD_KEYMAP_SIZE);
        unsigned char set;
        unsigned char translated;
        unsigned char extra;
@@ -211,16 +217,23 @@ struct atkbd {
        unsigned char emul;
        unsigned char resend;
        unsigned char release;
-       unsigned char bat_xl;
-       unsigned char err_xl;
+       unsigned long xl_bit;
        unsigned int last;
        unsigned long time;
+       unsigned long err_count;
 
-       struct work_struct event_work;
+       struct delayed_work event_work;
+       unsigned long event_jiffies;
        struct mutex event_mutex;
        unsigned long event_mask;
 };
 
+/*
+ * System-specific ketymap fixup routine
+ */
+static void (*atkbd_platform_fixup)(struct atkbd *, const void *data);
+static void *atkbd_platform_fixup_data;
+
 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,
@@ -228,11 +241,13 @@ 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, struct device_attribute *attr, 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, struct device_attribute *attr, 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);               \
 }                                                                              \
@@ -240,22 +255,98 @@ 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(force_release);
 ATKBD_DEFINE_ATTR(scroll);
 ATKBD_DEFINE_ATTR(set);
 ATKBD_DEFINE_ATTR(softrepeat);
 ATKBD_DEFINE_ATTR(softraw);
 
+#define ATKBD_DEFINE_RO_ATTR(_name)                                            \
+static ssize_t atkbd_show_##_name(struct atkbd *, char *);                     \
+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 struct device_attribute atkbd_attr_##_name =                            \
+       __ATTR(_name, S_IRUGO, atkbd_do_show_##_name, NULL);
+
+ATKBD_DEFINE_RO_ATTR(err_count);
+
+static struct attribute *atkbd_attributes[] = {
+       &atkbd_attr_extra.attr,
+       &atkbd_attr_force_release.attr,
+       &atkbd_attr_scroll.attr,
+       &atkbd_attr_set.attr,
+       &atkbd_attr_softrepeat.attr,
+       &atkbd_attr_softraw.attr,
+       &atkbd_attr_err_count.attr,
+       NULL
+};
+
+static struct attribute_group atkbd_attribute_group = {
+       .attrs  = atkbd_attributes,
+};
+
+static const unsigned int xl_table[] = {
+       ATKBD_RET_BAT, ATKBD_RET_ERR, ATKBD_RET_ACK,
+       ATKBD_RET_NAK, ATKBD_RET_HANJA, ATKBD_RET_HANGEUL,
+};
 
-static void atkbd_report_key(struct input_dev *dev, struct pt_regs *regs, int code, int value)
+/*
+ * Checks if we should mangle the scancode to extract 'release' bit
+ * in translated mode.
+ */
+static int atkbd_need_xlate(unsigned long xl_bit, unsigned char code)
 {
-       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);
-       input_sync(dev);
+       int i;
+
+       if (code == ATKBD_RET_EMUL0 || code == ATKBD_RET_EMUL1)
+               return 0;
+
+       for (i = 0; i < ARRAY_SIZE(xl_table); i++)
+               if (code == xl_table[i])
+                       return test_bit(i, &xl_bit);
+
+       return 1;
+}
+
+/*
+ * Calculates new value of xl_bit so the driver can distinguish
+ * between make/break pair of scancodes for select keys and PS/2
+ * protocol responses.
+ */
+static void atkbd_calculate_xl_bit(struct atkbd *atkbd, unsigned char code)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(xl_table); i++) {
+               if (!((code ^ xl_table[i]) & 0x7f)) {
+                       if (code & 0x80)
+                               __clear_bit(i, &atkbd->xl_bit);
+                       else
+                               __set_bit(i, &atkbd->xl_bit);
+                       break;
+               }
+       }
+}
+
+/*
+ * Encode the scancode, 0xe0 prefix, and high bit into a single integer,
+ * keeping kernel 2.4 compatibility for set 2
+ */
+static unsigned int atkbd_compat_scancode(struct atkbd *atkbd, unsigned int code)
+{
+       if (atkbd->set == 3) {
+               if (atkbd->emul == 1)
+                       code |= 0x100;
+        } else {
+               code = (code & 0x7f) | ((code & 0x80) << 1);
+               if (atkbd->emul == 1)
+                       code |= 0x80;
+       }
+
+       return code;
 }
 
 /*
@@ -264,12 +355,14 @@ static void atkbd_report_key(struct input_dev *dev, struct pt_regs *regs, int co
  */
 
 static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
-                       unsigned int flags, struct pt_regs *regs)
+                       unsigned int flags)
 {
        struct atkbd *atkbd = serio_get_drvdata(serio);
+       struct input_dev *dev = atkbd->dev;
        unsigned int code = data;
        int scroll = 0, hscroll = 0, click = -1;
        int value;
+       unsigned short keycode;
 
 #ifdef ATKBD_DEBUG
        printk(KERN_DEBUG "atkbd.c: Received %02x flags %02x\n", data, flags);
@@ -298,25 +391,17 @@ 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(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 || atkbd->err_xl) &&
-                    (code != ATKBD_RET_BAT || atkbd->bat_xl))) {
+               if (atkbd->emul || atkbd_need_xlate(atkbd->xl_bit, code)) {
                        atkbd->release = code >> 7;
                        code &= 0x7f;
                }
 
-               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);
-               }
+               if (!atkbd->emul)
+                       atkbd_calculate_xl_bit(atkbd, data);
        }
 
        switch (code) {
@@ -333,47 +418,44 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
                case ATKBD_RET_RELEASE:
                        atkbd->release = 1;
                        goto out;
-               case ATKBD_RET_HANGUEL:
-                       atkbd_report_key(atkbd->dev, regs, KEY_HANGUEL, 3);
-                       goto out;
-               case ATKBD_RET_HANJA:
-                       atkbd_report_key(atkbd->dev, regs, KEY_HANJA, 3);
+               case ATKBD_RET_ACK:
+               case ATKBD_RET_NAK:
+                       if (printk_ratelimit())
+                               printk(KERN_WARNING "atkbd.c: Spurious %s on %s. "
+                                      "Some program might be trying access hardware directly.\n",
+                                      data == ATKBD_RET_ACK ? "ACK" : "NAK", serio->phys);
                        goto out;
                case ATKBD_RET_ERR:
+                       atkbd->err_count++;
+#ifdef ATKBD_DEBUG
                        printk(KERN_DEBUG "atkbd.c: Keyboard on %s reports too many keys pressed.\n", serio->phys);
+#endif
                        goto out;
        }
 
-       if (atkbd->set != 3)
-               code = (code & 0x7f) | ((code & 0x80) << 1);
-       if (atkbd->emul) {
-               if (--atkbd->emul)
-                       goto out;
-               code |= (atkbd->set != 3) ? 0x80 : 0x100;
-       }
+       code = atkbd_compat_scancode(atkbd, code);
+
+       if (atkbd->emul && --atkbd->emul)
+               goto out;
 
-       if (atkbd->keycode[code] != ATKBD_KEY_NULL)
-               input_event(atkbd->dev, EV_MSC, MSC_SCAN, code);
+       keycode = atkbd->keycode[code];
 
-       switch (atkbd->keycode[code]) {
+       if (keycode != ATKBD_KEY_NULL)
+               input_event(dev, EV_MSC, MSC_SCAN, code);
+
+       switch (keycode) {
                case ATKBD_KEY_NULL:
                        break;
                case ATKBD_KEY_UNKNOWN:
-                       if (data == ATKBD_RET_ACK || data == ATKBD_RET_NAK) {
-                               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);
-                       } else {
-                               printk(KERN_WARNING "atkbd.c: Unknown key %s "
-                                      "(%s set %d, code %#x on %s).\n",
-                                      atkbd->release ? "released" : "pressed",
-                                      atkbd->translated ? "translated" : "raw",
-                                      atkbd->set, code, serio->phys);
-                               printk(KERN_WARNING "atkbd.c: Use 'setkeycodes %s%02x <keycode>' "
-                                      "to make it known.\n",
-                                      code & 0x80 ? "e0" : "", code & 0x7f);
-                       }
-                       input_sync(atkbd->dev);
+                       printk(KERN_WARNING
+                              "atkbd.c: Unknown key %s (%s set %d, code %#x on %s).\n",
+                              atkbd->release ? "released" : "pressed",
+                              atkbd->translated ? "translated" : "raw",
+                              atkbd->set, code, serio->phys);
+                       printk(KERN_WARNING
+                              "atkbd.c: Use 'setkeycodes %s%02x <keycode>' to make it known.\n",
+                              code & 0x80 ? "e0" : "", code & 0x7f);
+                       input_sync(dev);
                        break;
                case ATKBD_SCR_1:
                        scroll = 1 - atkbd->release * 2;
@@ -397,33 +479,33 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
                        hscroll = 1;
                        break;
                default:
-                       value = atkbd->release ? 0 :
-                               (1 + (!atkbd->softrepeat && test_bit(atkbd->keycode[code], atkbd->dev->key)));
-
-                       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;
-                                       break;
-                               case 2:
-                                       if (!time_after(jiffies, atkbd->time) && atkbd->last == code)
-                                               value = 1;
-                                       break;
+                       if (atkbd->release) {
+                               value = 0;
+                               atkbd->last = 0;
+                       } else if (!atkbd->softrepeat && test_bit(keycode, dev->key)) {
+                               /* Workaround Toshiba laptop multiple keypress */
+                               value = time_before(jiffies, atkbd->time) && atkbd->last == code ? 1 : 2;
+                       } else {
+                               value = 1;
+                               atkbd->last = code;
+                               atkbd->time = jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]) / 2;
                        }
 
-                       atkbd_report_key(atkbd->dev, regs, atkbd->keycode[code], value);
+                       input_event(dev, EV_KEY, keycode, value);
+                       input_sync(dev);
+
+                       if (value && test_bit(code, atkbd->force_release_mask)) {
+                               input_report_key(dev, keycode, 0);
+                               input_sync(dev);
+                       }
        }
 
        if (atkbd->scroll) {
-               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(dev, BTN_MIDDLE, click);
+               input_report_rel(dev, REL_WHEEL, scroll);
+               input_report_rel(dev, REL_HWHEEL, hscroll);
+               input_sync(dev);
        }
 
        atkbd->release = 0;
@@ -431,13 +513,7 @@ out:
        return IRQ_HANDLED;
 }
 
-/*
- * 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 void atkbd_event_work(void *data)
+static int atkbd_set_repeat_rate(struct atkbd *atkbd)
 {
        const short period[32] =
                { 33,  37,  42,  46,  50,  54,  58,  63,  67,  75,  83,  92, 100, 109, 116, 125,
@@ -445,54 +521,95 @@ static void atkbd_event_work(void *data)
        const short delay[4] =
                { 250, 500, 750, 1000 };
 
-       struct atkbd *atkbd = data;
+       struct input_dev *dev = atkbd->dev;
+       unsigned char param;
+       int i = 0, j = 0;
+
+       while (i < ARRAY_SIZE(period) - 1 && period[i] < dev->rep[REP_PERIOD])
+               i++;
+       dev->rep[REP_PERIOD] = period[i];
+
+       while (j < ARRAY_SIZE(delay) - 1 && delay[j] < dev->rep[REP_DELAY])
+               j++;
+       dev->rep[REP_DELAY] = delay[j];
+
+       param = i | (j << 5);
+       return ps2_command(&atkbd->ps2dev, &param, ATKBD_CMD_SETREP);
+}
+
+static int atkbd_set_leds(struct atkbd *atkbd)
+{
        struct input_dev *dev = atkbd->dev;
        unsigned char param[2];
-       int i, j;
 
-       mutex_lock(&atkbd->event_mutex);
+       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);
+       if (ps2_command(&atkbd->ps2dev, param, ATKBD_CMD_SETLEDS))
+               return -1;
 
-       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 (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);
+               if (ps2_command(&atkbd->ps2dev, param, ATKBD_CMD_EX_SETLEDS))
+                       return -1;
        }
 
-       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);
-       }
+       return 0;
+}
+
+/*
+ * 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 void atkbd_event_work(struct work_struct *work)
+{
+       struct atkbd *atkbd = container_of(work, struct atkbd, event_work.work);
+
+       mutex_lock(&atkbd->event_mutex);
+
+       if (test_and_clear_bit(ATKBD_LED_EVENT_BIT, &atkbd->event_mask))
+               atkbd_set_leds(atkbd);
+
+       if (test_and_clear_bit(ATKBD_REP_EVENT_BIT, &atkbd->event_mask))
+               atkbd_set_repeat_rate(atkbd);
 
        mutex_unlock(&atkbd->event_mutex);
 }
 
 /*
+ * Schedule switch for execution. We need to throttle requests,
+ * otherwise keyboard may become unresponsive.
+ */
+static void atkbd_schedule_event_work(struct atkbd *atkbd, int event_bit)
+{
+       unsigned long delay = msecs_to_jiffies(50);
+
+       if (time_after(jiffies, atkbd->event_jiffies + delay))
+               delay = 0;
+
+       atkbd->event_jiffies = jiffies;
+       set_bit(event_bit, &atkbd->event_mask);
+       wmb();
+       schedule_delayed_work(&atkbd->event_work, delay);
+}
+
+/*
  * 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)
+static int atkbd_event(struct input_dev *dev,
+                       unsigned int type, unsigned int code, int value)
 {
-       struct atkbd *atkbd = dev->private;
+       struct atkbd *atkbd = input_get_drvdata(dev);
 
        if (!atkbd->write)
                return -1;
@@ -500,19 +617,12 @@ static int atkbd_event(struct input_dev *dev, unsigned int type, unsigned int co
        switch (type) {
 
                case EV_LED:
-                       set_bit(ATKBD_LED_EVENT_BIT, &atkbd->event_mask);
-                       wmb();
-                       schedule_work(&atkbd->event_work);
+                       atkbd_schedule_event_work(atkbd, ATKBD_LED_EVENT_BIT);
                        return 0;
 
                case EV_REP:
-
-                       if (!atkbd->softrepeat) {
-                               set_bit(ATKBD_REP_EVENT_BIT, &atkbd->event_mask);
-                               wmb();
-                               schedule_work(&atkbd->event_work);
-                       }
-
+                       if (!atkbd->softrepeat)
+                               atkbd_schedule_event_work(atkbd, ATKBD_REP_EVENT_BIT);
                        return 0;
        }
 
@@ -584,9 +694,7 @@ static int atkbd_probe(struct atkbd *atkbd)
                return 0;
        }
 
-       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 */
+       if (!ps2_is_keyboard_id(param[0]))
                return -1;
 
        atkbd->id = (param[0] << 8) | param[1];
@@ -704,6 +812,8 @@ static int atkbd_activate(struct atkbd *atkbd)
 static void atkbd_cleanup(struct serio *serio)
 {
        struct atkbd *atkbd = serio_get_drvdata(serio);
+
+       atkbd_disable(atkbd);
        ps2_command(&atkbd->ps2dev, NULL, ATKBD_CMD_RESET_BAT);
 }
 
@@ -719,21 +829,103 @@ static void atkbd_disconnect(struct serio *serio)
        atkbd_disable(atkbd);
 
        /* make sure we don't have a command in flight */
-       synchronize_sched();  /* Allow atkbd_interrupt()s to complete. */
-       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);
+       cancel_delayed_work_sync(&atkbd->event_work);
 
+       sysfs_remove_group(&serio->dev.kobj, &atkbd_attribute_group);
        input_unregister_device(atkbd->dev);
        serio_close(serio);
        serio_set_drvdata(serio, NULL);
        kfree(atkbd);
 }
 
+/*
+ * generate release events for the keycodes given in data
+ */
+static void atkbd_apply_forced_release_keylist(struct atkbd* atkbd,
+                                               const void *data)
+{
+       const unsigned int *keys = data;
+       unsigned int i;
+
+       if (atkbd->set == 2)
+               for (i = 0; keys[i] != -1U; i++)
+                       __set_bit(keys[i], atkbd->force_release_mask);
+}
+
+/*
+ * Most special keys (Fn+F?) on Dell laptops do not generate release
+ * events so we have to do it ourselves.
+ */
+static unsigned int atkbd_dell_laptop_forced_release_keys[] = {
+       0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8f, 0x93, -1U
+};
+
+/*
+ * Perform fixup for HP system that doesn't generate release
+ * for its video switch
+ */
+static unsigned int atkbd_hp_forced_release_keys[] = {
+       0x94, -1U
+};
+
+/*
+ * Inventec system with broken key release on volume keys
+ */
+static unsigned int atkbd_inventec_forced_release_keys[] = {
+       0xae, 0xb0, -1U
+};
+
+/*
+ * Perform fixup for HP Pavilion ZV6100 laptop that doesn't generate release
+ * for its volume buttons
+ */
+static unsigned int atkbd_hp_zv6100_forced_release_keys[] = {
+       0xae, 0xb0, -1U
+};
+
+/*
+ * Perform fixup for HP (Compaq) Presario R4000 R4100 R4200 that don't generate
+ * release for their volume buttons
+ */
+static unsigned int atkbd_hp_r4000_forced_release_keys[] = {
+       0xae, 0xb0, -1U
+};
+
+/*
+ * Samsung NC10,NC20 with Fn+F? key release not working
+ */
+static unsigned int atkbd_samsung_forced_release_keys[] = {
+       0x82, 0x83, 0x84, 0x86, 0x88, 0x89, 0xb3, 0xf7, 0xf9, -1U
+};
+
+/*
+ * The volume up and volume down special keys on a Fujitsu Amilo PA 1510 laptop
+ * do not generate release events so we have to do it ourselves.
+ */
+static unsigned int atkbd_amilo_pa1510_forced_release_keys[] = {
+       0xb0, 0xae, -1U
+};
+
+/*
+ * Amilo Pi 3525 key release for Fn+Volume keys not working
+ */
+static unsigned int atkbd_amilo_pi3525_forced_release_keys[] = {
+       0x20, 0xa0, 0x2e, 0xae, 0x30, 0xb0, -1U
+};
+
+/*
+ * Amilo Xi 3650 key release for light touch bar not working
+ */
+static unsigned int atkbd_amilo_xi3650_forced_release_keys[] = {
+       0x67, 0xed, 0x90, 0xa2, 0x99, 0xa4, 0xae, 0xb0, -1U
+};
+
+/*
+ * Soltech TA12 system with broken key release on volume keys and mute key
+ */
+static unsigned int atkdb_soltech_ta12_forced_release_keys[] = {
+       0xa0, 0xae, 0xb0, -1U
+};
 
 /*
  * atkbd_set_keycode_table() initializes keyboard's keycode table
@@ -742,17 +934,20 @@ static void atkbd_disconnect(struct serio *serio)
 
 static void atkbd_set_keycode_table(struct atkbd *atkbd)
 {
+       unsigned int scancode;
        int i, j;
 
        memset(atkbd->keycode, 0, sizeof(atkbd->keycode));
+       bitmap_zero(atkbd->force_release_mask, ATKBD_KEYMAP_SIZE);
 
        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];
+                       scancode = atkbd_unxlate_table[i];
+                       atkbd->keycode[i] = atkbd_set2_keycode[scancode];
+                       atkbd->keycode[i | 0x80] = atkbd_set2_keycode[scancode | 0x80];
                        if (atkbd->scroll)
                                for (j = 0; j < ARRAY_SIZE(atkbd_scroll_keys); j++)
-                                       if ((atkbd_unxlate_table[i] | 0x80) == atkbd_scroll_keys[j].set2)
+                                       if ((scancode | 0x80) == atkbd_scroll_keys[j].set2)
                                                atkbd->keycode[i | 0x80] = atkbd_scroll_keys[j].keycode;
                }
        } else if (atkbd->set == 3) {
@@ -761,9 +956,29 @@ static void atkbd_set_keycode_table(struct atkbd *atkbd)
                memcpy(atkbd->keycode, atkbd_set2_keycode, sizeof(atkbd->keycode));
 
                if (atkbd->scroll)
-                       for (i = 0; i < ARRAY_SIZE(atkbd_scroll_keys); i++)
-                               atkbd->keycode[atkbd_scroll_keys[i].set2] = atkbd_scroll_keys[i].keycode;
+                       for (i = 0; i < ARRAY_SIZE(atkbd_scroll_keys); i++) {
+                               scancode = atkbd_scroll_keys[i].set2;
+                               atkbd->keycode[scancode] = atkbd_scroll_keys[i].keycode;
+               }
        }
+
+/*
+ * HANGEUL and HANJA keys do not send release events so we need to
+ * generate such events ourselves
+ */
+       scancode = atkbd_compat_scancode(atkbd, ATKBD_RET_HANGEUL);
+       atkbd->keycode[scancode] = KEY_HANGEUL;
+       __set_bit(scancode, atkbd->force_release_mask);
+
+       scancode = atkbd_compat_scancode(atkbd, ATKBD_RET_HANJA);
+       atkbd->keycode[scancode] = KEY_HANJA;
+       __set_bit(scancode, atkbd->force_release_mask);
+
+/*
+ * Perform additional fixups
+ */
+       if (atkbd_platform_fixup)
+               atkbd_platform_fixup(atkbd, atkbd_platform_fixup_data);
 }
 
 /*
@@ -776,12 +991,15 @@ static void atkbd_set_device_attrs(struct atkbd *atkbd)
        int i;
 
        if (atkbd->extra)
-               sprintf(atkbd->name, "AT Set 2 Extra keyboard");
+               snprintf(atkbd->name, sizeof(atkbd->name),
+                        "AT Set 2 Extra keyboard");
        else
-               sprintf(atkbd->name, "AT %s Set %d keyboard",
-                       atkbd->translated ? "Translated" : "Raw", atkbd->set);
+               snprintf(atkbd->name, sizeof(atkbd->name),
+                        "AT %s Set %d keyboard",
+                        atkbd->translated ? "Translated" : "Raw", atkbd->set);
 
-       sprintf(atkbd->phys, "%s/input0", atkbd->ps2dev.serio->phys);
+       snprintf(atkbd->phys, sizeof(atkbd->phys),
+                "%s/input0", atkbd->ps2dev.serio->phys);
 
        input_dev->name = atkbd->name;
        input_dev->phys = atkbd->phys;
@@ -790,40 +1008,46 @@ static void atkbd_set_device_attrs(struct atkbd *atkbd)
        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;
+       input_dev->dev.parent = &atkbd->ps2dev.serio->dev;
 
-       input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP) | BIT(EV_MSC);
+       input_set_drvdata(input_dev, atkbd);
+
+       input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) |
+               BIT_MASK(EV_MSC);
 
        if (atkbd->write) {
-               input_dev->evbit[0] |= BIT(EV_LED);
-               input_dev->ledbit[0] = BIT(LED_NUML) | BIT(LED_CAPSL) | BIT(LED_SCROLLL);
+               input_dev->evbit[0] |= BIT_MASK(EV_LED);
+               input_dev->ledbit[0] = BIT_MASK(LED_NUML) |
+                       BIT_MASK(LED_CAPSL) | BIT_MASK(LED_SCROLLL);
        }
 
        if (atkbd->extra)
-               input_dev->ledbit[0] |= BIT(LED_COMPOSE) | BIT(LED_SUSPEND) |
-                                       BIT(LED_SLEEP) | BIT(LED_MUTE) | BIT(LED_MISC);
+               input_dev->ledbit[0] |= BIT_MASK(LED_COMPOSE) |
+                       BIT_MASK(LED_SUSPEND) | BIT_MASK(LED_SLEEP) |
+                       BIT_MASK(LED_MUTE) | BIT_MASK(LED_MISC);
 
        if (!atkbd->softrepeat) {
                input_dev->rep[REP_DELAY] = 250;
                input_dev->rep[REP_PERIOD] = 33;
        }
 
-       input_dev->mscbit[0] = atkbd->softraw ? BIT(MSC_SCAN) : BIT(MSC_RAW) | BIT(MSC_SCAN);
+       input_dev->mscbit[0] = atkbd->softraw ? BIT_MASK(MSC_SCAN) :
+               BIT_MASK(MSC_RAW) | BIT_MASK(MSC_SCAN);
 
        if (atkbd->scroll) {
-               input_dev->evbit[0] |= BIT(EV_REL);
-               input_dev->relbit[0] = BIT(REL_WHEEL) | BIT(REL_HWHEEL);
-               set_bit(BTN_MIDDLE, input_dev->keybit);
+               input_dev->evbit[0] |= BIT_MASK(EV_REL);
+               input_dev->relbit[0] = BIT_MASK(REL_WHEEL) |
+                       BIT_MASK(REL_HWHEEL);
+               __set_bit(BTN_MIDDLE, input_dev->keybit);
        }
 
        input_dev->keycode = atkbd->keycode;
-       input_dev->keycodesize = sizeof(unsigned char);
+       input_dev->keycodesize = sizeof(unsigned short);
        input_dev->keycodemax = ARRAY_SIZE(atkbd_set2_keycode);
 
-       for (i = 0; i < 512; i++)
+       for (i = 0; i < ATKBD_KEYMAP_SIZE; i++)
                if (atkbd->keycode[i] && atkbd->keycode[i] < ATKBD_SPECIAL)
-                       set_bit(atkbd->keycode[i], input_dev->keybit);
+                       __set_bit(atkbd->keycode[i], input_dev->keybit);
 }
 
 /*
@@ -842,11 +1066,11 @@ static int atkbd_connect(struct serio *serio, struct serio_driver *drv)
        atkbd = kzalloc(sizeof(struct atkbd), GFP_KERNEL);
        dev = input_allocate_device();
        if (!atkbd || !dev)
-               goto fail;
+               goto fail1;
 
        atkbd->dev = dev;
        ps2_init(&atkbd->ps2dev, serio);
-       INIT_WORK(&atkbd->event_work, atkbd_event_work, atkbd);
+       INIT_DELAYED_WORK(&atkbd->event_work, atkbd_event_work);
        mutex_init(&atkbd->event_mutex);
 
        switch (serio->id.type) {
@@ -863,9 +1087,6 @@ 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;
 
@@ -873,14 +1094,13 @@ static int atkbd_connect(struct serio *serio, struct serio_driver *drv)
 
        err = serio_open(serio, drv);
        if (err)
-               goto fail;
+               goto fail2;
 
        if (atkbd->write) {
 
                if (atkbd_probe(atkbd)) {
-                       serio_close(serio);
                        err = -ENODEV;
-                       goto fail;
+                       goto fail3;
                }
 
                atkbd->set = atkbd_select_set(atkbd, atkbd_set, atkbd_extra);
@@ -894,20 +1114,22 @@ static int atkbd_connect(struct serio *serio, struct serio_driver *drv)
        atkbd_set_keycode_table(atkbd);
        atkbd_set_device_attrs(atkbd);
 
-       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);
+       err = sysfs_create_group(&serio->dev.kobj, &atkbd_attribute_group);
+       if (err)
+               goto fail3;
 
        atkbd_enable(atkbd);
 
-       input_register_device(atkbd->dev);
+       err = input_register_device(atkbd->dev);
+       if (err)
+               goto fail4;
 
        return 0;
 
- fail: serio_set_drvdata(serio, NULL);
-       input_free_device(dev);
+ fail4: sysfs_remove_group(&serio->dev.kobj, &atkbd_attribute_group);
+ fail3:        serio_close(serio);
+ fail2:        serio_set_drvdata(serio, NULL);
+ fail1:        input_free_device(dev);
        kfree(atkbd);
        return err;
 }
@@ -921,7 +1143,6 @@ static int atkbd_reconnect(struct serio *serio)
 {
        struct atkbd *atkbd = serio_get_drvdata(serio);
        struct serio_driver *drv = serio->drv;
-       unsigned char param[1];
 
        if (!atkbd || !drv) {
                printk(KERN_DEBUG "atkbd: reconnect request, but serio is disconnected, ignoring...\n");
@@ -931,10 +1152,6 @@ 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);
-
                if (atkbd_probe(atkbd))
                        return -1;
                if (atkbd->set != atkbd_select_set(atkbd, atkbd->set, atkbd->extra))
@@ -942,8 +1159,13 @@ static int atkbd_reconnect(struct serio *serio)
 
                atkbd_activate(atkbd);
 
-               if (ps2_command(&atkbd->ps2dev, param, ATKBD_CMD_SETLEDS))
-                       return -1;
+/*
+ * Restore repeat rate and LEDs (that were reset by atkbd_activate)
+ * to pre-resume state
+ */
+               if (!atkbd->softrepeat)
+                       atkbd_set_repeat_rate(atkbd);
+               atkbd_set_leds(atkbd);
        }
 
        atkbd_enable(atkbd);
@@ -1043,35 +1265,81 @@ 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;
+       struct input_dev *old_dev, *new_dev;
        unsigned long value;
-       char *rest;
+       int err;
+       unsigned char old_extra, old_set;
 
        if (!atkbd->write)
                return -EIO;
 
-       value = simple_strtoul(buf, &rest, 10);
-       if (*rest || value > 1)
+       if (strict_strtoul(buf, 10, &value) || value > 1)
                return -EINVAL;
 
        if (atkbd->extra != value) {
                /*
                 * Since device's properties will change we need to
-                * unregister old device. But allocate new one first
-                * to make sure we have it.
+                * unregister old device. But allocate and register
+                * new one first to make sure we have it.
                 */
-               if (!(new_dev = input_allocate_device()))
+               old_dev = atkbd->dev;
+               old_extra = atkbd->extra;
+               old_set = atkbd->set;
+
+               new_dev = input_allocate_device();
+               if (!new_dev)
                        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_keycode_table(atkbd);
                atkbd_set_device_attrs(atkbd);
-               input_register_device(atkbd->dev);
+
+               err = input_register_device(atkbd->dev);
+               if (err) {
+                       input_free_device(new_dev);
+
+                       atkbd->dev = old_dev;
+                       atkbd->set = atkbd_select_set(atkbd, old_set, old_extra);
+                       atkbd_set_keycode_table(atkbd);
+                       atkbd_set_device_attrs(atkbd);
+
+                       return err;
+               }
+               input_unregister_device(old_dev);
+
        }
        return count;
 }
 
+static ssize_t atkbd_show_force_release(struct atkbd *atkbd, char *buf)
+{
+       size_t len = bitmap_scnlistprintf(buf, PAGE_SIZE - 2,
+                       atkbd->force_release_mask, ATKBD_KEYMAP_SIZE);
+
+       buf[len++] = '\n';
+       buf[len] = '\0';
+
+       return len;
+}
+
+static ssize_t atkbd_set_force_release(struct atkbd *atkbd,
+                                       const char *buf, size_t count)
+{
+       /* 64 bytes on stack should be acceptable */
+       DECLARE_BITMAP(new_mask, ATKBD_KEYMAP_SIZE);
+       int err;
+
+       err = bitmap_parselist(buf, new_mask, ATKBD_KEYMAP_SIZE);
+       if (err)
+               return err;
+
+       memcpy(atkbd->force_release_mask, new_mask, sizeof(atkbd->force_release_mask));
+       return count;
+}
+
+
 static ssize_t atkbd_show_scroll(struct atkbd *atkbd, char *buf)
 {
        return sprintf(buf, "%d\n", atkbd->scroll ? 1 : 0);
@@ -1079,23 +1347,39 @@ 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;
+       struct input_dev *old_dev, *new_dev;
        unsigned long value;
-       char *rest;
+       int err;
+       unsigned char old_scroll;
 
-       value = simple_strtoul(buf, &rest, 10);
-       if (*rest || value > 1)
+       if (strict_strtoul(buf, 10, &value) || value > 1)
                return -EINVAL;
 
        if (atkbd->scroll != value) {
-               if (!(new_dev = input_allocate_device()))
+               old_dev = atkbd->dev;
+               old_scroll = atkbd->scroll;
+
+               new_dev = input_allocate_device();
+               if (!new_dev)
                        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);
+
+               err = input_register_device(atkbd->dev);
+               if (err) {
+                       input_free_device(new_dev);
+
+                       atkbd->scroll = old_scroll;
+                       atkbd->dev = old_dev;
+                       atkbd_set_keycode_table(atkbd);
+                       atkbd_set_device_attrs(atkbd);
+
+                       return err;
+               }
+               input_unregister_device(old_dev);
        }
        return count;
 }
@@ -1107,27 +1391,44 @@ 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;
+       struct input_dev *old_dev, *new_dev;
        unsigned long value;
-       char *rest;
+       int err;
+       unsigned char old_set, old_extra;
 
        if (!atkbd->write)
                return -EIO;
 
-       value = simple_strtoul(buf, &rest, 10);
-       if (*rest || (value != 2 && value != 3))
+       if (strict_strtoul(buf, 10, &value) || (value != 2 && value != 3))
                return -EINVAL;
 
        if (atkbd->set != value) {
-               if (!(new_dev = input_allocate_device()))
+               old_dev = atkbd->dev;
+               old_extra = atkbd->extra;
+               old_set = atkbd->set;
+
+               new_dev = input_allocate_device();
+               if (!new_dev)
                        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);
+
+               err = input_register_device(atkbd->dev);
+               if (err) {
+                       input_free_device(new_dev);
+
+                       atkbd->dev = old_dev;
+                       atkbd->set = atkbd_select_set(atkbd, old_set, old_extra);
+                       atkbd_set_keycode_table(atkbd);
+                       atkbd_set_device_attrs(atkbd);
+
+                       return err;
+               }
+               input_unregister_device(old_dev);
        }
        return count;
 }
@@ -1139,27 +1440,44 @@ 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;
+       struct input_dev *old_dev, *new_dev;
        unsigned long value;
-       char *rest;
+       int err;
+       unsigned char old_softrepeat, old_softraw;
 
        if (!atkbd->write)
                return -EIO;
 
-       value = simple_strtoul(buf, &rest, 10);
-       if (*rest || value > 1)
+       if (strict_strtoul(buf, 10, &value) || value > 1)
                return -EINVAL;
 
        if (atkbd->softrepeat != value) {
-               if (!(new_dev = input_allocate_device()))
+               old_dev = atkbd->dev;
+               old_softrepeat = atkbd->softrepeat;
+               old_softraw = atkbd->softraw;
+
+               new_dev = input_allocate_device();
+               if (!new_dev)
                        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);
+
+               err = input_register_device(atkbd->dev);
+               if (err) {
+                       input_free_device(new_dev);
+
+                       atkbd->dev = old_dev;
+                       atkbd->softrepeat = old_softrepeat;
+                       atkbd->softraw = old_softraw;
+                       atkbd_set_device_attrs(atkbd);
+
+                       return err;
+               }
+               input_unregister_device(old_dev);
        }
        return count;
 }
@@ -1172,33 +1490,200 @@ 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;
+       struct input_dev *old_dev, *new_dev;
        unsigned long value;
-       char *rest;
+       int err;
+       unsigned char old_softraw;
 
-       value = simple_strtoul(buf, &rest, 10);
-       if (*rest || value > 1)
+       if (strict_strtoul(buf, 10, &value) || value > 1)
                return -EINVAL;
 
        if (atkbd->softraw != value) {
-               if (!(new_dev = input_allocate_device()))
+               old_dev = atkbd->dev;
+               old_softraw = atkbd->softraw;
+
+               new_dev = input_allocate_device();
+               if (!new_dev)
                        return -ENOMEM;
-               input_unregister_device(atkbd->dev);
+
                atkbd->dev = new_dev;
                atkbd->softraw = value;
                atkbd_set_device_attrs(atkbd);
-               input_register_device(atkbd->dev);
+
+               err = input_register_device(atkbd->dev);
+               if (err) {
+                       input_free_device(new_dev);
+
+                       atkbd->dev = old_dev;
+                       atkbd->softraw = old_softraw;
+                       atkbd_set_device_attrs(atkbd);
+
+                       return err;
+               }
+               input_unregister_device(old_dev);
        }
        return count;
 }
 
+static ssize_t atkbd_show_err_count(struct atkbd *atkbd, char *buf)
+{
+       return sprintf(buf, "%lu\n", atkbd->err_count);
+}
 
-static int __init atkbd_init(void)
+static int __init atkbd_setup_forced_release(const struct dmi_system_id *id)
 {
-       serio_register_driver(&atkbd_drv);
+       atkbd_platform_fixup = atkbd_apply_forced_release_keylist;
+       atkbd_platform_fixup_data = id->driver_data;
+
        return 0;
 }
 
+static struct dmi_system_id atkbd_dmi_quirk_table[] __initdata = {
+       {
+               .ident = "Dell Laptop",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+                       DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */
+               },
+               .callback = atkbd_setup_forced_release,
+               .driver_data = atkbd_dell_laptop_forced_release_keys,
+       },
+       {
+               .ident = "Dell Laptop",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
+                       DMI_MATCH(DMI_CHASSIS_TYPE, "8"), /* Portable */
+               },
+               .callback = atkbd_setup_forced_release,
+               .driver_data = atkbd_dell_laptop_forced_release_keys,
+       },
+       {
+               .ident = "HP 2133",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "HP 2133"),
+               },
+               .callback = atkbd_setup_forced_release,
+               .driver_data = atkbd_hp_forced_release_keys,
+       },
+       {
+               .ident = "HP Pavilion ZV6100",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "Pavilion ZV6100"),
+               },
+               .callback = atkbd_setup_forced_release,
+               .driver_data = atkbd_hp_zv6100_forced_release_keys,
+       },
+       {
+               .ident = "HP Presario R4000",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "Presario R4000"),
+               },
+               .callback = atkbd_setup_forced_release,
+               .driver_data = atkbd_hp_r4000_forced_release_keys,
+       },
+       {
+               .ident = "HP Presario R4100",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "Presario R4100"),
+               },
+               .callback = atkbd_setup_forced_release,
+               .driver_data = atkbd_hp_r4000_forced_release_keys,
+       },
+       {
+               .ident = "HP Presario R4200",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "Presario R4200"),
+               },
+               .callback = atkbd_setup_forced_release,
+               .driver_data = atkbd_hp_r4000_forced_release_keys,
+       },
+       {
+               .ident = "Inventec Symphony",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "INVENTEC"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "SYMPHONY 6.0/7.0"),
+               },
+               .callback = atkbd_setup_forced_release,
+               .driver_data = atkbd_inventec_forced_release_keys,
+       },
+       {
+               .ident = "Samsung NC10",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "NC10"),
+               },
+               .callback = atkbd_setup_forced_release,
+               .driver_data = atkbd_samsung_forced_release_keys,
+       },
+       {
+               .ident = "Samsung NC20",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "NC20"),
+               },
+               .callback = atkbd_setup_forced_release,
+               .driver_data = atkbd_samsung_forced_release_keys,
+       },
+       {
+               .ident = "Samsung SQ45S70S",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "SQ45S70S"),
+               },
+               .callback = atkbd_setup_forced_release,
+               .driver_data = atkbd_samsung_forced_release_keys,
+       },
+       {
+               .ident = "Fujitsu Amilo PA 1510",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Pa 1510"),
+               },
+               .callback = atkbd_setup_forced_release,
+               .driver_data = atkbd_amilo_pa1510_forced_release_keys,
+       },
+       {
+               .ident = "Fujitsu Amilo Pi 3525",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Pi 3525"),
+               },
+               .callback = atkbd_setup_forced_release,
+               .driver_data = atkbd_amilo_pi3525_forced_release_keys,
+       },
+       {
+               .ident = "Fujitsu Amilo Xi 3650",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Xi 3650"),
+               },
+               .callback = atkbd_setup_forced_release,
+               .driver_data = atkbd_amilo_xi3650_forced_release_keys,
+       },
+       {
+               .ident = "Soltech Corporation TA12",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Soltech Corporation"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "TA12"),
+               },
+               .callback = atkbd_setup_forced_release,
+               .driver_data = atkdb_soltech_ta12_forced_release_keys,
+       },
+       { }
+};
+
+static int __init atkbd_init(void)
+{
+       dmi_check_system(atkbd_dmi_quirk_table);
+
+       return serio_register_driver(&atkbd_drv);
+}
+
 static void __exit atkbd_exit(void)
 {
        serio_unregister_driver(&atkbd_drv);