Input: atkbd - add quirk for Fujitsu Siemens Amilo PA 1510
[safe/jmp/linux-2.6] / drivers / input / keyboard / atkbd.c
index 498e64a..f999dc6 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"
 
@@ -63,16 +63,12 @@ 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] = {
+static const unsigned short atkbd_set2_keycode[512] = {
 
 #ifdef CONFIG_KEYBOARD_ATKBD_HP_KEYCODES
 
@@ -93,7 +89,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 +99,7 @@ static unsigned char atkbd_set2_keycode[512] = {
 #endif
 };
 
-static unsigned char atkbd_set3_keycode[512] = {
+static const unsigned short atkbd_set3_keycode[512] = {
 
          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 +111,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,
@@ -165,7 +161,7 @@ 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
@@ -177,7 +173,7 @@ static unsigned char atkbd_unxlate_table[128] = {
 #define ATKBD_XL_HANGEUL       0x10
 #define ATKBD_XL_HANJA         0x20
 
-static struct {
+static const struct {
        unsigned char keycode;
        unsigned char set2;
 } atkbd_scroll_keys[] = {
@@ -204,7 +200,8 @@ struct atkbd {
        char phys[32];
 
        unsigned short id;
-       unsigned char keycode[512];
+       unsigned short keycode[512];
+       DECLARE_BITMAP(force_release_mask, 512);
        unsigned char set;
        unsigned char translated;
        unsigned char extra;
@@ -223,11 +220,18 @@ struct atkbd {
        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,
@@ -352,9 +356,9 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
        struct atkbd *atkbd = serio_get_drvdata(serio);
        struct input_dev *dev = atkbd->dev;
        unsigned int code = data;
-       int scroll = 0, hscroll = 0, click = -1, add_release_event = 0;
+       int scroll = 0, hscroll = 0, click = -1;
        int value;
-       unsigned char keycode;
+       unsigned short keycode;
 
 #ifdef ATKBD_DEBUG
        printk(KERN_DEBUG "atkbd.c: Received %02x flags %02x\n", data, flags);
@@ -412,18 +416,11 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
                        goto out;
                case ATKBD_RET_ACK:
                case ATKBD_RET_NAK:
-                       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);
+                       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_HANGEUL:
-               case ATKBD_RET_HANJA:
-                       /*
-                        * These keys do not report release and thus need to be
-                        * flagged properly
-                        */
-                       add_release_event = 1;
-                       break;
                case ATKBD_RET_ERR:
                        atkbd->err_count++;
 #ifdef ATKBD_DEBUG
@@ -493,7 +490,7 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
                        input_event(dev, EV_KEY, keycode, value);
                        input_sync(dev);
 
-                       if (value && add_release_event) {
+                       if (value && test_bit(code, atkbd->force_release_mask)) {
                                input_report_key(dev, keycode, 0);
                                input_sync(dev);
                        }
@@ -567,9 +564,9 @@ static int atkbd_set_leds(struct atkbd *atkbd)
  * interrupt context.
  */
 
-static void atkbd_event_work(void *data)
+static void atkbd_event_work(struct work_struct *work)
 {
-       struct atkbd *atkbd = data;
+       struct atkbd *atkbd = container_of(work, struct atkbd, event_work.work);
 
        mutex_lock(&atkbd->event_mutex);
 
@@ -583,14 +580,32 @@ static void atkbd_event_work(void *data)
 }
 
 /*
+ * 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;
@@ -598,19 +613,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;
        }
 
@@ -800,6 +808,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);
 }
 
@@ -815,8 +825,7 @@ 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();
+       cancel_delayed_work_sync(&atkbd->event_work);
 
        sysfs_remove_group(&serio->dev.kobj, &atkbd_attribute_group);
        input_unregister_device(atkbd->dev);
@@ -825,6 +834,65 @@ static void atkbd_disconnect(struct serio *serio)
        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
+};
+
+/*
+ * Samsung NC10 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
+};
 
 /*
  * atkbd_set_keycode_table() initializes keyboard's keycode table
@@ -833,17 +901,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, 512);
 
        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) {
@@ -852,12 +923,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;
+               }
        }
 
-       atkbd->keycode[atkbd_compat_scancode(atkbd, ATKBD_RET_HANGEUL)] = KEY_HANGUEL;
-       atkbd->keycode[atkbd_compat_scancode(atkbd, ATKBD_RET_HANJA)] = KEY_HANJA;
+/*
+ * 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);
 }
 
 /*
@@ -887,40 +975,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_set_drvdata(input_dev, atkbd);
 
-       input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP) | BIT(EV_MSC);
+       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++)
                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);
 }
 
 /*
@@ -943,7 +1037,7 @@ static int atkbd_connect(struct serio *serio, struct serio_driver *drv)
 
        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) {
@@ -1140,15 +1234,13 @@ static ssize_t atkbd_set_extra(struct atkbd *atkbd, const char *buf, size_t coun
 {
        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) {
@@ -1197,12 +1289,10 @@ static ssize_t atkbd_set_scroll(struct atkbd *atkbd, const char *buf, size_t cou
 {
        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) {
@@ -1243,15 +1333,13 @@ static ssize_t atkbd_set_set(struct atkbd *atkbd, const char *buf, size_t count)
 {
        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) {
@@ -1294,15 +1382,13 @@ static ssize_t atkbd_set_softrepeat(struct atkbd *atkbd, const char *buf, size_t
 {
        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) {
@@ -1346,12 +1432,10 @@ static ssize_t atkbd_set_softraw(struct atkbd *atkbd, const char *buf, size_t co
 {
        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) {
@@ -1386,9 +1470,85 @@ static ssize_t atkbd_show_err_count(struct atkbd *atkbd, char *buf)
        return sprintf(buf, "%lu\n", atkbd->err_count);
 }
 
+static int __init atkbd_setup_forced_release(const struct dmi_system_id *id)
+{
+       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 = "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 = "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,
+       },
+       { }
+};
 
 static int __init atkbd_init(void)
 {
+       dmi_check_system(atkbd_dmi_quirk_table);
+
        return serio_register_driver(&atkbd_drv);
 }