sony-laptop: report failures on setting LCD brightness
[linux-2.6.git] / drivers / platform / x86 / sony-laptop.c
index 3f71a60..9d80ae4 100644 (file)
@@ -58,6 +58,7 @@
 #include <linux/kfifo.h>
 #include <linux/workqueue.h>
 #include <linux/acpi.h>
+#include <linux/slab.h>
 #include <acpi/acpi_drivers.h>
 #include <acpi/acpi_bus.h>
 #include <asm/uaccess.h>
@@ -70,8 +71,9 @@
 #endif
 
 #define DRV_PFX                        "sony-laptop: "
-#define dprintk(msg...)                do {                    \
-       if (debug) printk(KERN_WARNING DRV_PFX  msg);   \
+#define dprintk(msg...)                do {    \
+       if (debug)                      \
+               pr_warn(DRV_PFX msg);   \
 } while (0)
 
 #define SONY_LAPTOP_DRIVER_VERSION     "0.6"
@@ -123,6 +125,21 @@ MODULE_PARM_DESC(minor,
                 "default is -1 (automatic)");
 #endif
 
+static int kbd_backlight;      /* = 1 */
+module_param(kbd_backlight, int, 0444);
+MODULE_PARM_DESC(kbd_backlight,
+                "set this to 0 to disable keyboard backlight, "
+                "1 to enable it (default: 0)");
+
+static int kbd_backlight_timeout;      /* = 0 */
+module_param(kbd_backlight_timeout, int, 0444);
+MODULE_PARM_DESC(kbd_backlight_timeout,
+                "set this to 0 to set the default 10 seconds timeout, "
+                "1 for 30 seconds, 2 for 60 seconds and 3 to disable timeout "
+                "(default: 0)");
+
+static void sony_nc_kbd_backlight_resume(void);
+
 enum sony_nc_rfkill {
        SONY_WIFI,
        SONY_BLUETOOTH,
@@ -145,7 +162,7 @@ struct sony_laptop_input_s {
        struct input_dev        *key_dev;
        struct kfifo            fifo;
        spinlock_t              fifo_lock;
-       struct workqueue_struct *wq;
+       struct timer_list       release_key_timer;
 };
 
 static struct sony_laptop_input_s sony_laptop_input = {
@@ -234,6 +251,7 @@ static int sony_laptop_input_index[] = {
        57,     /* 70 SONYPI_EVENT_VOLUME_DEC_PRESSED */
        -1,     /* 71 SONYPI_EVENT_BRIGHTNESS_PRESSED */
        58,     /* 72 SONYPI_EVENT_MEDIA_PRESSED */
+       59,     /* 72 SONYPI_EVENT_VENDOR_PRESSED */
 };
 
 static int sony_laptop_input_keycode_map[] = {
@@ -296,23 +314,30 @@ static int sony_laptop_input_keycode_map[] = {
        KEY_VOLUMEUP,   /* 56 SONYPI_EVENT_VOLUME_INC_PRESSED */
        KEY_VOLUMEDOWN, /* 57 SONYPI_EVENT_VOLUME_DEC_PRESSED */
        KEY_MEDIA,      /* 58 SONYPI_EVENT_MEDIA_PRESSED */
+       KEY_VENDOR,     /* 59 SONYPI_EVENT_VENDOR_PRESSED */
 };
 
 /* release buttons after a short delay if pressed */
-static void do_sony_laptop_release_key(struct work_struct *work)
+static void do_sony_laptop_release_key(unsigned long unused)
 {
        struct sony_laptop_keypress kp;
+       unsigned long flags;
 
-       while (kfifo_out_locked(&sony_laptop_input.fifo, (unsigned char *)&kp,
-                       sizeof(kp), &sony_laptop_input.fifo_lock)
-                       == sizeof(kp)) {
-               msleep(10);
+       spin_lock_irqsave(&sony_laptop_input.fifo_lock, flags);
+
+       if (kfifo_out(&sony_laptop_input.fifo,
+                     (unsigned char *)&kp, sizeof(kp)) == sizeof(kp)) {
                input_report_key(kp.dev, kp.key, 0);
                input_sync(kp.dev);
        }
+
+       /* If there is something in the fifo schedule next release. */
+       if (kfifo_len(&sony_laptop_input.fifo) != 0)
+               mod_timer(&sony_laptop_input.release_key_timer,
+                         jiffies + msecs_to_jiffies(10));
+
+       spin_unlock_irqrestore(&sony_laptop_input.fifo_lock, flags);
 }
-static DECLARE_WORK(sony_laptop_release_key_work,
-               do_sony_laptop_release_key);
 
 /* forward event to the input subsystem */
 static void sony_laptop_report_input_event(u8 event)
@@ -366,13 +391,13 @@ static void sony_laptop_report_input_event(u8 event)
                /* we emit the scancode so we can always remap the key */
                input_event(kp.dev, EV_MSC, MSC_SCAN, event);
                input_sync(kp.dev);
-               kfifo_in_locked(&sony_laptop_input.fifo,
-                         (unsigned char *)&kp, sizeof(kp),
-                         &sony_laptop_input.fifo_lock);
 
-               if (!work_pending(&sony_laptop_release_key_work))
-                       queue_work(sony_laptop_input.wq,
-                                       &sony_laptop_release_key_work);
+               /* schedule key release */
+               kfifo_in_locked(&sony_laptop_input.fifo,
+                               (unsigned char *)&kp, sizeof(kp),
+                               &sony_laptop_input.fifo_lock);
+               mod_timer(&sony_laptop_input.release_key_timer,
+                         jiffies + msecs_to_jiffies(10));
        } else
                dprintk("unknown input event %.2x\n", event);
 }
@@ -390,27 +415,21 @@ static int sony_laptop_setup_input(struct acpi_device *acpi_device)
 
        /* kfifo */
        spin_lock_init(&sony_laptop_input.fifo_lock);
-       error =
-        kfifo_alloc(&sony_laptop_input.fifo, SONY_LAPTOP_BUF_SIZE, GFP_KERNEL);
+       error = kfifo_alloc(&sony_laptop_input.fifo,
+                           SONY_LAPTOP_BUF_SIZE, GFP_KERNEL);
        if (error) {
-               printk(KERN_ERR DRV_PFX "kfifo_alloc failed\n");
+               pr_err(DRV_PFX "kfifo_alloc failed\n");
                goto err_dec_users;
        }
 
-       /* init workqueue */
-       sony_laptop_input.wq = create_singlethread_workqueue("sony-laptop");
-       if (!sony_laptop_input.wq) {
-               printk(KERN_ERR DRV_PFX
-                               "Unable to create workqueue.\n");
-               error = -ENXIO;
-               goto err_free_kfifo;
-       }
+       setup_timer(&sony_laptop_input.release_key_timer,
+                   do_sony_laptop_release_key, 0);
 
        /* input keys */
        key_dev = input_allocate_device();
        if (!key_dev) {
                error = -ENOMEM;
-               goto err_destroy_wq;
+               goto err_free_kfifo;
        }
 
        key_dev->name = "Sony Vaio Keys";
@@ -419,18 +438,15 @@ static int sony_laptop_setup_input(struct acpi_device *acpi_device)
        key_dev->dev.parent = &acpi_device->dev;
 
        /* Initialize the Input Drivers: special keys */
-       set_bit(EV_KEY, key_dev->evbit);
-       set_bit(EV_MSC, key_dev->evbit);
-       set_bit(MSC_SCAN, key_dev->mscbit);
+       input_set_capability(key_dev, EV_MSC, MSC_SCAN);
+
+       __set_bit(EV_KEY, key_dev->evbit);
        key_dev->keycodesize = sizeof(sony_laptop_input_keycode_map[0]);
        key_dev->keycodemax = ARRAY_SIZE(sony_laptop_input_keycode_map);
        key_dev->keycode = &sony_laptop_input_keycode_map;
-       for (i = 0; i < ARRAY_SIZE(sony_laptop_input_keycode_map); i++) {
-               if (sony_laptop_input_keycode_map[i] != KEY_RESERVED) {
-                       set_bit(sony_laptop_input_keycode_map[i],
-                               key_dev->keybit);
-               }
-       }
+       for (i = 0; i < ARRAY_SIZE(sony_laptop_input_keycode_map); i++)
+               __set_bit(sony_laptop_input_keycode_map[i], key_dev->keybit);
+       __clear_bit(KEY_RESERVED, key_dev->keybit);
 
        error = input_register_device(key_dev);
        if (error)
@@ -450,9 +466,8 @@ static int sony_laptop_setup_input(struct acpi_device *acpi_device)
        jog_dev->id.vendor = PCI_VENDOR_ID_SONY;
        key_dev->dev.parent = &acpi_device->dev;
 
-       jog_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
-       jog_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_MIDDLE);
-       jog_dev->relbit[0] = BIT_MASK(REL_WHEEL);
+       input_set_capability(jog_dev, EV_KEY, BTN_MIDDLE);
+       input_set_capability(jog_dev, EV_REL, REL_WHEEL);
 
        error = input_register_device(jog_dev);
        if (error)
@@ -473,9 +488,6 @@ err_unregister_keydev:
 err_free_keydev:
        input_free_device(key_dev);
 
-err_destroy_wq:
-       destroy_workqueue(sony_laptop_input.wq);
-
 err_free_kfifo:
        kfifo_free(&sony_laptop_input.fifo);
 
@@ -486,12 +498,23 @@ err_dec_users:
 
 static void sony_laptop_remove_input(void)
 {
-       /* cleanup only after the last user has gone */
+       struct sony_laptop_keypress kp = { NULL };
+
+       /* Cleanup only after the last user has gone */
        if (!atomic_dec_and_test(&sony_laptop_input.users))
                return;
 
-       /* flush workqueue first */
-       flush_workqueue(sony_laptop_input.wq);
+       del_timer_sync(&sony_laptop_input.release_key_timer);
+
+       /*
+        * Generate key-up events for remaining keys. Note that we don't
+        * need locking since nobody is adding new events to the kfifo.
+        */
+       while (kfifo_out(&sony_laptop_input.fifo,
+                        (unsigned char *)&kp, sizeof(kp)) == sizeof(kp)) {
+               input_report_key(kp.dev, kp.key, 0);
+               input_sync(kp.dev);
+       }
 
        /* destroy input devs */
        input_unregister_device(sony_laptop_input.key_dev);
@@ -502,7 +525,6 @@ static void sony_laptop_remove_input(void)
                sony_laptop_input.jog_dev = NULL;
        }
 
-       destroy_workqueue(sony_laptop_input.wq);
        kfifo_free(&sony_laptop_input.fifo);
 }
 
@@ -557,8 +579,7 @@ static void sony_pf_remove(void)
        if (!atomic_dec_and_test(&sony_pf_users))
                return;
 
-       platform_device_del(sony_pf_device);
-       platform_device_put(sony_pf_device);
+       platform_device_unregister(sony_pf_device);
        platform_driver_unregister(&sony_pf_driver);
 }
 
@@ -586,7 +607,7 @@ struct sony_nc_value {
        int value;              /* current setting */
        int valid;              /* Has ever been set */
        int debug;              /* active only in debug mode ? */
-       struct device_attribute devattr;        /* sysfs atribute */
+       struct device_attribute devattr;        /* sysfs attribute */
 };
 
 #define SNC_HANDLE_NAMES(_name, _values...) \
@@ -681,7 +702,7 @@ static int acpi_callgetfunc(acpi_handle handle, char *name, int *result)
                return 0;
        }
 
-       printk(KERN_WARNING DRV_PFX "acpi_callreadfunc failed\n");
+       pr_warn(DRV_PFX "acpi_callreadfunc failed\n");
 
        return -1;
 }
@@ -707,7 +728,7 @@ static int acpi_callsetfunc(acpi_handle handle, char *name, int value,
        if (status == AE_OK) {
                if (result != NULL) {
                        if (out_obj.type != ACPI_TYPE_INTEGER) {
-                               printk(KERN_WARNING DRV_PFX "acpi_evaluate_object bad "
+                               pr_warn(DRV_PFX "acpi_evaluate_object bad "
                                       "return type\n");
                                return -1;
                        }
@@ -716,34 +737,111 @@ static int acpi_callsetfunc(acpi_handle handle, char *name, int value,
                return 0;
        }
 
-       printk(KERN_WARNING DRV_PFX "acpi_evaluate_object failed\n");
+       pr_warn(DRV_PFX "acpi_evaluate_object failed\n");
 
        return -1;
 }
 
-static int sony_find_snc_handle(int handle)
+struct sony_nc_handles {
+       u16 cap[0x10];
+       struct device_attribute devattr;
+};
+
+static struct sony_nc_handles *handles;
+
+static ssize_t sony_nc_handles_show(struct device *dev,
+               struct device_attribute *attr, char *buffer)
+{
+       ssize_t len = 0;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(handles->cap); i++) {
+               len += snprintf(buffer + len, PAGE_SIZE - len, "0x%.4x ",
+                               handles->cap[i]);
+       }
+       len += snprintf(buffer + len, PAGE_SIZE - len, "\n");
+
+       return len;
+}
+
+static int sony_nc_handles_setup(struct platform_device *pd)
 {
        int i;
        int result;
 
-       for (i = 0x20; i < 0x30; i++) {
-               acpi_callsetfunc(sony_nc_acpi_handle, "SN00", i, &result);
-               if (result == handle)
-                       return i-0x20;
+       handles = kzalloc(sizeof(*handles), GFP_KERNEL);
+       if (!handles)
+               return -ENOMEM;
+
+       for (i = 0; i < ARRAY_SIZE(handles->cap); i++) {
+               if (!acpi_callsetfunc(sony_nc_acpi_handle,
+                                       "SN00", i + 0x20, &result)) {
+                       dprintk("caching handle 0x%.4x (offset: 0x%.2x)\n",
+                                       result, i);
+                       handles->cap[i] = result;
+               }
+       }
+
+       if (debug) {
+               sysfs_attr_init(&handles->devattr.attr);
+               handles->devattr.attr.name = "handles";
+               handles->devattr.attr.mode = S_IRUGO;
+               handles->devattr.show = sony_nc_handles_show;
+
+               /* allow reading capabilities via sysfs */
+               if (device_create_file(&pd->dev, &handles->devattr)) {
+                       kfree(handles);
+                       handles = NULL;
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+static int sony_nc_handles_cleanup(struct platform_device *pd)
+{
+       if (handles) {
+               if (debug)
+                       device_remove_file(&pd->dev, &handles->devattr);
+               kfree(handles);
+               handles = NULL;
        }
+       return 0;
+}
+
+static int sony_find_snc_handle(int handle)
+{
+       int i;
 
+       /* not initialized yet, return early */
+       if (!handles)
+               return -1;
+
+       for (i = 0; i < 0x10; i++) {
+               if (handles->cap[i] == handle) {
+                       dprintk("found handle 0x%.4x (offset: 0x%.2x)\n",
+                                       handle, i);
+                       return i;
+               }
+       }
+       dprintk("handle 0x%.4x not found\n", handle);
        return -1;
 }
 
 static int sony_call_snc_handle(int handle, int argument, int *result)
 {
+       int ret = 0;
        int offset = sony_find_snc_handle(handle);
 
        if (offset < 0)
                return -1;
 
-       return acpi_callsetfunc(sony_nc_acpi_handle, "SN07", offset | argument,
-                               result);
+       ret = acpi_callsetfunc(sony_nc_acpi_handle, "SN07", offset | argument,
+                       result);
+       dprintk("called SN07 with 0x%.4x (result: 0x%.4x)\n", offset | argument,
+                       *result);
+       return ret;
 }
 
 /*
@@ -852,11 +950,40 @@ static int sony_backlight_get_brightness(struct backlight_device *bd)
        return value - 1;
 }
 
-static struct backlight_device *sony_backlight_device;
-static struct backlight_ops sony_backlight_ops = {
+static int sony_nc_get_brightness_ng(struct backlight_device *bd)
+{
+       int result;
+       int *handle = (int *)bl_get_data(bd);
+
+       sony_call_snc_handle(*handle, 0x0200, &result);
+
+       return result & 0xff;
+}
+
+static int sony_nc_update_status_ng(struct backlight_device *bd)
+{
+       int value, result;
+       int *handle = (int *)bl_get_data(bd);
+
+       value = bd->props.brightness;
+       if (sony_call_snc_handle(*handle, 0x0100 | (value << 16), &result))
+               return -EIO;
+
+       return value;
+}
+
+static const struct backlight_ops sony_backlight_ops = {
+       .options = BL_CORE_SUSPENDRESUME,
        .update_status = sony_backlight_update_status,
        .get_brightness = sony_backlight_get_brightness,
 };
+static const struct backlight_ops sony_backlight_ng_ops = {
+       .options = BL_CORE_SUSPENDRESUME,
+       .update_status = sony_nc_update_status_ng,
+       .get_brightness = sony_nc_get_brightness_ng,
+};
+static int backlight_ng_handle;
+static struct backlight_device *sony_backlight_device;
 
 /*
  * New SNC-only Vaios event mapping to driver known keys
@@ -891,10 +1018,18 @@ static struct sony_nc_event sony_100_events[] = {
        { 0x0A, SONYPI_EVENT_FNKEY_RELEASED },
        { 0x8C, SONYPI_EVENT_FNKEY_F12 },
        { 0x0C, SONYPI_EVENT_FNKEY_RELEASED },
+       { 0x9d, SONYPI_EVENT_ZOOM_PRESSED },
+       { 0x1d, SONYPI_EVENT_ANYBUTTON_RELEASED },
        { 0x9f, SONYPI_EVENT_CD_EJECT_PRESSED },
        { 0x1f, SONYPI_EVENT_ANYBUTTON_RELEASED },
        { 0xa1, SONYPI_EVENT_MEDIA_PRESSED },
        { 0x21, SONYPI_EVENT_ANYBUTTON_RELEASED },
+       { 0xa4, SONYPI_EVENT_CD_EJECT_PRESSED },
+       { 0x24, SONYPI_EVENT_ANYBUTTON_RELEASED },
+       { 0xa5, SONYPI_EVENT_VENDOR_PRESSED },
+       { 0x25, SONYPI_EVENT_ANYBUTTON_RELEASED },
+       { 0xa6, SONYPI_EVENT_HELP_PRESSED },
+       { 0x26, SONYPI_EVENT_ANYBUTTON_RELEASED },
        { 0, 0 },
 };
 
@@ -959,7 +1094,7 @@ static void sony_nc_notify(struct acpi_device *device, u32 event)
                                }
 
                                if (!key_event->data)
-                                       printk(KERN_INFO DRV_PFX
+                                       pr_info(DRV_PFX
                                                        "Unknown event: 0x%x 0x%x\n",
                                                        key_handle,
                                                        ev);
@@ -983,7 +1118,7 @@ static acpi_status sony_walk_callback(acpi_handle handle, u32 level,
        struct acpi_device_info *info;
 
        if (ACPI_SUCCESS(acpi_get_object_info(handle, &info))) {
-               printk(KERN_WARNING DRV_PFX "method: name: %4.4s, args %X\n",
+               pr_warn(DRV_PFX "method: name: %4.4s, args %X\n",
                        (char *)&info->name, info->param_count);
 
                kfree(info);
@@ -1024,7 +1159,7 @@ static int sony_nc_resume(struct acpi_device *device)
                ret = acpi_callsetfunc(sony_nc_acpi_handle, *item->acpiset,
                                       item->value, NULL);
                if (ret < 0) {
-                       printk("%s: %d\n", __func__, ret);
+                       pr_err(DRV_PFX "%s: %d\n", __func__, ret);
                        break;
                }
        }
@@ -1041,14 +1176,12 @@ static int sony_nc_resume(struct acpi_device *device)
                sony_nc_function_setup(device);
        }
 
-       /* set the last requested brightness level */
-       if (sony_backlight_device &&
-                       sony_backlight_update_status(sony_backlight_device) < 0)
-               printk(KERN_WARNING DRV_PFX "unable to restore brightness level\n");
-
        /* re-read rfkill state */
        sony_nc_rfkill_update();
 
+       /* restore kbd backlight states */
+       sony_nc_kbd_backlight_resume();
+
        return 0;
 }
 
@@ -1128,7 +1261,7 @@ static int sony_nc_setup_rfkill(struct acpi_device *device,
        return err;
 }
 
-static void sony_nc_rfkill_update()
+static void sony_nc_rfkill_update(void)
 {
        enum sony_nc_rfkill i;
        int result;
@@ -1192,8 +1325,12 @@ static void sony_nc_rfkill_setup(struct acpi_device *device)
        }
 
        device_enum = (union acpi_object *) buffer.pointer;
-       if (!device_enum || device_enum->type != ACPI_TYPE_BUFFER) {
-               printk(KERN_ERR "Invalid SN06 return object 0x%.2x\n",
+       if (!device_enum) {
+               pr_err(DRV_PFX "No SN06 return object.");
+               goto out_no_enum;
+       }
+       if (device_enum->type != ACPI_TYPE_BUFFER) {
+               pr_err(DRV_PFX "Invalid SN06 return object 0x%.2x\n",
                                device_enum->type);
                goto out_no_enum;
        }
@@ -1228,6 +1365,237 @@ out_no_enum:
        return;
 }
 
+/* Keyboard backlight feature */
+#define KBDBL_HANDLER  0x137
+#define KBDBL_PRESENT  0xB00
+#define        SET_MODE        0xC00
+#define SET_STATE      0xD00
+#define SET_TIMEOUT    0xE00
+
+struct kbd_backlight {
+       int mode;
+       int timeout;
+       struct device_attribute mode_attr;
+       struct device_attribute timeout_attr;
+};
+
+static struct kbd_backlight *kbdbl_handle;
+
+static ssize_t __sony_nc_kbd_backlight_mode_set(u8 value)
+{
+       int result;
+
+       if (value > 1)
+               return -EINVAL;
+
+       if (sony_call_snc_handle(KBDBL_HANDLER,
+                               (value << 0x10) | SET_MODE, &result))
+               return -EIO;
+
+       /* Try to turn the light on/off immediately */
+       sony_call_snc_handle(KBDBL_HANDLER, (value << 0x10) | SET_STATE,
+                       &result);
+
+       kbdbl_handle->mode = value;
+
+       return 0;
+}
+
+static ssize_t sony_nc_kbd_backlight_mode_store(struct device *dev,
+               struct device_attribute *attr,
+               const char *buffer, size_t count)
+{
+       int ret = 0;
+       unsigned long value;
+
+       if (count > 31)
+               return -EINVAL;
+
+       if (strict_strtoul(buffer, 10, &value))
+               return -EINVAL;
+
+       ret = __sony_nc_kbd_backlight_mode_set(value);
+       if (ret < 0)
+               return ret;
+
+       return count;
+}
+
+static ssize_t sony_nc_kbd_backlight_mode_show(struct device *dev,
+               struct device_attribute *attr, char *buffer)
+{
+       ssize_t count = 0;
+       count = snprintf(buffer, PAGE_SIZE, "%d\n", kbdbl_handle->mode);
+       return count;
+}
+
+static int __sony_nc_kbd_backlight_timeout_set(u8 value)
+{
+       int result;
+
+       if (value > 3)
+               return -EINVAL;
+
+       if (sony_call_snc_handle(KBDBL_HANDLER,
+                               (value << 0x10) | SET_TIMEOUT, &result))
+               return -EIO;
+
+       kbdbl_handle->timeout = value;
+
+       return 0;
+}
+
+static ssize_t sony_nc_kbd_backlight_timeout_store(struct device *dev,
+               struct device_attribute *attr,
+               const char *buffer, size_t count)
+{
+       int ret = 0;
+       unsigned long value;
+
+       if (count > 31)
+               return -EINVAL;
+
+       if (strict_strtoul(buffer, 10, &value))
+               return -EINVAL;
+
+       ret = __sony_nc_kbd_backlight_timeout_set(value);
+       if (ret < 0)
+               return ret;
+
+       return count;
+}
+
+static ssize_t sony_nc_kbd_backlight_timeout_show(struct device *dev,
+               struct device_attribute *attr, char *buffer)
+{
+       ssize_t count = 0;
+       count = snprintf(buffer, PAGE_SIZE, "%d\n", kbdbl_handle->timeout);
+       return count;
+}
+
+static int sony_nc_kbd_backlight_setup(struct platform_device *pd)
+{
+       int result;
+
+       if (sony_call_snc_handle(KBDBL_HANDLER, KBDBL_PRESENT, &result))
+               return 0;
+       if (!(result & 0x02))
+               return 0;
+
+       kbdbl_handle = kzalloc(sizeof(*kbdbl_handle), GFP_KERNEL);
+       if (!kbdbl_handle)
+               return -ENOMEM;
+
+       sysfs_attr_init(&kbdbl_handle->mode_attr.attr);
+       kbdbl_handle->mode_attr.attr.name = "kbd_backlight";
+       kbdbl_handle->mode_attr.attr.mode = S_IRUGO | S_IWUSR;
+       kbdbl_handle->mode_attr.show = sony_nc_kbd_backlight_mode_show;
+       kbdbl_handle->mode_attr.store = sony_nc_kbd_backlight_mode_store;
+
+       sysfs_attr_init(&kbdbl_handle->timeout_attr.attr);
+       kbdbl_handle->timeout_attr.attr.name = "kbd_backlight_timeout";
+       kbdbl_handle->timeout_attr.attr.mode = S_IRUGO | S_IWUSR;
+       kbdbl_handle->timeout_attr.show = sony_nc_kbd_backlight_timeout_show;
+       kbdbl_handle->timeout_attr.store = sony_nc_kbd_backlight_timeout_store;
+
+       if (device_create_file(&pd->dev, &kbdbl_handle->mode_attr))
+               goto outkzalloc;
+
+       if (device_create_file(&pd->dev, &kbdbl_handle->timeout_attr))
+               goto outmode;
+
+       __sony_nc_kbd_backlight_mode_set(kbd_backlight);
+       __sony_nc_kbd_backlight_timeout_set(kbd_backlight_timeout);
+
+       return 0;
+
+outmode:
+       device_remove_file(&pd->dev, &kbdbl_handle->mode_attr);
+outkzalloc:
+       kfree(kbdbl_handle);
+       kbdbl_handle = NULL;
+       return -1;
+}
+
+static int sony_nc_kbd_backlight_cleanup(struct platform_device *pd)
+{
+       if (kbdbl_handle) {
+               int result;
+
+               device_remove_file(&pd->dev, &kbdbl_handle->mode_attr);
+               device_remove_file(&pd->dev, &kbdbl_handle->timeout_attr);
+
+               /* restore the default hw behaviour */
+               sony_call_snc_handle(KBDBL_HANDLER, 0x1000 | SET_MODE, &result);
+               sony_call_snc_handle(KBDBL_HANDLER, SET_TIMEOUT, &result);
+
+               kfree(kbdbl_handle);
+       }
+       return 0;
+}
+
+static void sony_nc_kbd_backlight_resume(void)
+{
+       int ignore = 0;
+
+       if (!kbdbl_handle)
+               return;
+
+       if (kbdbl_handle->mode == 0)
+               sony_call_snc_handle(KBDBL_HANDLER, SET_MODE, &ignore);
+
+       if (kbdbl_handle->timeout != 0)
+               sony_call_snc_handle(KBDBL_HANDLER,
+                               (kbdbl_handle->timeout << 0x10) | SET_TIMEOUT,
+                               &ignore);
+}
+
+static void sony_nc_backlight_setup(void)
+{
+       acpi_handle unused;
+       int max_brightness = 0;
+       const struct backlight_ops *ops = NULL;
+       struct backlight_properties props;
+
+       if (sony_find_snc_handle(0x12f) != -1) {
+               backlight_ng_handle = 0x12f;
+               ops = &sony_backlight_ng_ops;
+               max_brightness = 0xff;
+
+       } else if (sony_find_snc_handle(0x137) != -1) {
+               backlight_ng_handle = 0x137;
+               ops = &sony_backlight_ng_ops;
+               max_brightness = 0xff;
+
+       } else if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "GBRT",
+                                               &unused))) {
+               ops = &sony_backlight_ops;
+               max_brightness = SONY_MAX_BRIGHTNESS - 1;
+
+       } else
+               return;
+
+       memset(&props, 0, sizeof(struct backlight_properties));
+       props.type = BACKLIGHT_PLATFORM;
+       props.max_brightness = max_brightness;
+       sony_backlight_device = backlight_device_register("sony", NULL,
+                                                         &backlight_ng_handle,
+                                                         ops, &props);
+
+       if (IS_ERR(sony_backlight_device)) {
+               pr_warning(DRV_PFX "unable to register backlight device\n");
+               sony_backlight_device = NULL;
+       } else
+               sony_backlight_device->props.brightness =
+                   ops->get_brightness(sony_backlight_device);
+}
+
+static void sony_nc_backlight_cleanup(void)
+{
+       if (sony_backlight_device)
+               backlight_device_unregister(sony_backlight_device);
+}
+
 static int sony_nc_add(struct acpi_device *device)
 {
        acpi_status status;
@@ -1235,8 +1603,8 @@ static int sony_nc_add(struct acpi_device *device)
        acpi_handle handle;
        struct sony_nc_value *item;
 
-       printk(KERN_INFO DRV_PFX "%s v%s.\n",
-               SONY_NC_DRIVER_NAME, SONY_LAPTOP_DRIVER_VERSION);
+       pr_info(DRV_PFX "%s v%s.\n", SONY_NC_DRIVER_NAME,
+                       SONY_LAPTOP_DRIVER_VERSION);
 
        sony_nc_acpi_device = device;
        strcpy(acpi_device_class(device), "sony/hotkey");
@@ -1252,13 +1620,18 @@ static int sony_nc_add(struct acpi_device *device)
                goto outwalk;
        }
 
+       result = sony_pf_add();
+       if (result)
+               goto outpresent;
+
        if (debug) {
-               status = acpi_walk_namespace(ACPI_TYPE_METHOD, sony_nc_acpi_handle,
-                                            1, sony_walk_callback, NULL, NULL, NULL);
+               status = acpi_walk_namespace(ACPI_TYPE_METHOD,
+                               sony_nc_acpi_handle, 1, sony_walk_callback,
+                               NULL, NULL, NULL);
                if (ACPI_FAILURE(status)) {
-                       printk(KERN_WARNING DRV_PFX "unable to walk acpi resources\n");
+                       pr_warn(DRV_PFX "unable to walk acpi resources\n");
                        result = -ENODEV;
-                       goto outwalk;
+                       goto outpresent;
                }
        }
 
@@ -1271,6 +1644,12 @@ static int sony_nc_add(struct acpi_device *device)
        if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "SN00",
                                         &handle))) {
                dprintk("Doing SNC setup\n");
+               result = sony_nc_handles_setup(sony_pf_device);
+               if (result)
+                       goto outpresent;
+               result = sony_nc_kbd_backlight_setup(sony_pf_device);
+               if (result)
+                       goto outsnc;
                sony_nc_function_setup(device);
                sony_nc_rfkill_setup(device);
        }
@@ -1278,37 +1657,17 @@ static int sony_nc_add(struct acpi_device *device)
        /* setup input devices and helper fifo */
        result = sony_laptop_setup_input(device);
        if (result) {
-               printk(KERN_ERR DRV_PFX
-                               "Unable to create input devices.\n");
-               goto outwalk;
+               pr_err(DRV_PFX "Unable to create input devices.\n");
+               goto outkbdbacklight;
        }
 
        if (acpi_video_backlight_support()) {
-               printk(KERN_INFO DRV_PFX "brightness ignored, must be "
+               pr_info(DRV_PFX "brightness ignored, must be "
                       "controlled by ACPI video driver\n");
-       } else if (ACPI_SUCCESS(acpi_get_handle(sony_nc_acpi_handle, "GBRT",
-                                               &handle))) {
-               sony_backlight_device = backlight_device_register("sony", NULL,
-                                                                 NULL,
-                                                                 &sony_backlight_ops);
-
-               if (IS_ERR(sony_backlight_device)) {
-                       printk(KERN_WARNING DRV_PFX "unable to register backlight device\n");
-                       sony_backlight_device = NULL;
-               } else {
-                       sony_backlight_device->props.brightness =
-                           sony_backlight_get_brightness
-                           (sony_backlight_device);
-                       sony_backlight_device->props.max_brightness =
-                           SONY_MAX_BRIGHTNESS - 1;
-               }
-
+       } else {
+               sony_nc_backlight_setup();
        }
 
-       result = sony_pf_add();
-       if (result)
-               goto outbacklight;
-
        /* create sony_pf sysfs attributes related to the SNC device */
        for (item = sony_nc_values; item->name; ++item) {
 
@@ -1354,14 +1713,19 @@ static int sony_nc_add(struct acpi_device *device)
        for (item = sony_nc_values; item->name; ++item) {
                device_remove_file(&sony_pf_device->dev, &item->devattr);
        }
-       sony_pf_remove();
-
-      outbacklight:
-       if (sony_backlight_device)
-               backlight_device_unregister(sony_backlight_device);
+       sony_nc_backlight_cleanup();
 
        sony_laptop_remove_input();
 
+      outkbdbacklight:
+       sony_nc_kbd_backlight_cleanup(sony_pf_device);
+
+      outsnc:
+       sony_nc_handles_cleanup(sony_pf_device);
+
+      outpresent:
+       sony_pf_remove();
+
       outwalk:
        sony_nc_rfkill_cleanup();
        return result;
@@ -1371,8 +1735,7 @@ static int sony_nc_remove(struct acpi_device *device, int type)
 {
        struct sony_nc_value *item;
 
-       if (sony_backlight_device)
-               backlight_device_unregister(sony_backlight_device);
+       sony_nc_backlight_cleanup();
 
        sony_nc_acpi_device = NULL;
 
@@ -1380,6 +1743,8 @@ static int sony_nc_remove(struct acpi_device *device, int type)
                device_remove_file(&sony_pf_device->dev, &item->devattr);
        }
 
+       sony_nc_kbd_backlight_cleanup(sony_pf_device);
+       sony_nc_handles_cleanup(sony_pf_device);
        sony_pf_remove();
        sony_laptop_remove_input();
        sony_nc_rfkill_cleanup();
@@ -1418,7 +1783,6 @@ static struct acpi_driver sony_nc_driver = {
 #define SONYPI_DEVICE_TYPE1    0x00000001
 #define SONYPI_DEVICE_TYPE2    0x00000002
 #define SONYPI_DEVICE_TYPE3    0x00000004
-#define SONYPI_DEVICE_TYPE4    0x00000008
 
 #define SONYPI_TYPE1_OFFSET    0x04
 #define SONYPI_TYPE2_OFFSET    0x12
@@ -1564,8 +1928,8 @@ static struct sonypi_event sonypi_blueev[] = {
 
 /* The set of possible wireless events */
 static struct sonypi_event sonypi_wlessev[] = {
-       { 0x59, SONYPI_EVENT_WIRELESS_ON },
-       { 0x5a, SONYPI_EVENT_WIRELESS_OFF },
+       { 0x59, SONYPI_EVENT_IGNORE },
+       { 0x5a, SONYPI_EVENT_IGNORE },
        { 0, 0 }
 };
 
@@ -1822,7 +2186,7 @@ out:
        if (pcidev)
                pci_dev_put(pcidev);
 
-       printk(KERN_INFO DRV_PFX "detected Type%d model\n",
+       pr_info(DRV_PFX "detected Type%d model\n",
                        dev->model == SONYPI_DEVICE_TYPE1 ? 1 :
                        dev->model == SONYPI_DEVICE_TYPE2 ? 2 : 3);
 }
@@ -1870,7 +2234,7 @@ static int __sony_pic_camera_ready(void)
 static int __sony_pic_camera_off(void)
 {
        if (!camera) {
-               printk(KERN_WARNING DRV_PFX "camera control not enabled\n");
+               pr_warn(DRV_PFX "camera control not enabled\n");
                return -ENODEV;
        }
 
@@ -1890,7 +2254,7 @@ static int __sony_pic_camera_on(void)
        int i, j, x;
 
        if (!camera) {
-               printk(KERN_WARNING DRV_PFX "camera control not enabled\n");
+               pr_warn(DRV_PFX "camera control not enabled\n");
                return -ENODEV;
        }
 
@@ -1913,7 +2277,7 @@ static int __sony_pic_camera_on(void)
        }
 
        if (j == 0) {
-               printk(KERN_WARNING DRV_PFX "failed to power on camera\n");
+               pr_warn(DRV_PFX "failed to power on camera\n");
                return -ENODEV;
        }
 
@@ -1969,7 +2333,7 @@ int sony_pic_camera_command(int command, u8 value)
                                ITERATIONS_SHORT);
                break;
        default:
-               printk(KERN_ERR DRV_PFX "sony_pic_camera_command invalid: %d\n",
+               pr_err(DRV_PFX "sony_pic_camera_command invalid: %d\n",
                       command);
                break;
        }
@@ -2351,6 +2715,7 @@ static const struct file_operations sonypi_misc_fops = {
        .release        = sonypi_misc_release,
        .fasync         = sonypi_misc_fasync,
        .unlocked_ioctl = sonypi_misc_ioctl,
+       .llseek         = noop_llseek,
 };
 
 static struct miscdevice sonypi_misc_device = {
@@ -2375,7 +2740,7 @@ static int sonypi_compat_init(void)
        error =
         kfifo_alloc(&sonypi_compat.fifo, SONY_LAPTOP_BUF_SIZE, GFP_KERNEL);
        if (error) {
-               printk(KERN_ERR DRV_PFX "kfifo_alloc failed\n");
+               pr_err(DRV_PFX "kfifo_alloc failed\n");
                return error;
        }
 
@@ -2385,11 +2750,11 @@ static int sonypi_compat_init(void)
                sonypi_misc_device.minor = minor;
        error = misc_register(&sonypi_misc_device);
        if (error) {
-               printk(KERN_ERR DRV_PFX "misc_register failed\n");
+               pr_err(DRV_PFX "misc_register failed\n");
                goto err_free_kfifo;
        }
        if (minor == -1)
-               printk(KERN_INFO DRV_PFX "device allocated minor is %d\n",
+               pr_info(DRV_PFX "device allocated minor is %d\n",
                       sonypi_misc_device.minor);
 
        return 0;
@@ -2449,8 +2814,7 @@ sony_pic_read_possible_resource(struct acpi_resource *resource, void *context)
                        }
                        for (i = 0; i < p->interrupt_count; i++) {
                                if (!p->interrupts[i]) {
-                                       printk(KERN_WARNING DRV_PFX
-                                                       "Invalid IRQ %d\n",
+                                       pr_warn(DRV_PFX "Invalid IRQ %d\n",
                                                        p->interrupts[i]);
                                        continue;
                                }
@@ -2489,7 +2853,7 @@ sony_pic_read_possible_resource(struct acpi_resource *resource, void *context)
                                                ioport->io2.address_length);
                        }
                        else {
-                               printk(KERN_ERR DRV_PFX "Unknown SPIC Type, more than 2 IO Ports\n");
+                               pr_err(DRV_PFX "Unknown SPIC Type, more than 2 IO Ports\n");
                                return AE_ERROR;
                        }
                        return AE_OK;
@@ -2517,7 +2881,7 @@ static int sony_pic_possible_resources(struct acpi_device *device)
        dprintk("Evaluating _STA\n");
        result = acpi_bus_get_status(device);
        if (result) {
-               printk(KERN_WARNING DRV_PFX "Unable to read status\n");
+               pr_warn(DRV_PFX "Unable to read status\n");
                goto end;
        }
 
@@ -2533,8 +2897,7 @@ static int sony_pic_possible_resources(struct acpi_device *device)
        status = acpi_walk_resources(device->handle, METHOD_NAME__PRS,
                        sony_pic_read_possible_resource, &spic_dev);
        if (ACPI_FAILURE(status)) {
-               printk(KERN_WARNING DRV_PFX
-                               "Failure evaluating %s\n",
+               pr_warn(DRV_PFX "Failure evaluating %s\n",
                                METHOD_NAME__PRS);
                result = -ENODEV;
        }
@@ -2648,7 +3011,7 @@ static int sony_pic_enable(struct acpi_device *device,
 
        /* check for total failure */
        if (ACPI_FAILURE(status)) {
-               printk(KERN_ERR DRV_PFX "Error evaluating _SRS\n");
+               pr_err(DRV_PFX "Error evaluating _SRS\n");
                result = -ENODEV;
                goto end;
        }
@@ -2704,6 +3067,9 @@ static irqreturn_t sony_pic_irq(int irq, void *dev_id)
                        if (ev == dev->event_types[i].events[j].data) {
                                device_event =
                                        dev->event_types[i].events[j].event;
+                               /* some events may require ignoring */
+                               if (!device_event)
+                                       return IRQ_HANDLED;
                                goto found;
                        }
                }
@@ -2723,7 +3089,6 @@ found:
        sony_laptop_report_input_event(device_event);
        acpi_bus_generate_proc_event(dev->acpi_dev, 1, device_event);
        sonypi_compat_report_event(device_event);
-
        return IRQ_HANDLED;
 }
 
@@ -2738,7 +3103,7 @@ static int sony_pic_remove(struct acpi_device *device, int type)
        struct sony_pic_irq *irq, *tmp_irq;
 
        if (sony_pic_disable(device)) {
-               printk(KERN_ERR DRV_PFX "Couldn't disable device.\n");
+               pr_err(DRV_PFX "Couldn't disable device.\n");
                return -ENXIO;
        }
 
@@ -2778,8 +3143,8 @@ static int sony_pic_add(struct acpi_device *device)
        struct sony_pic_ioport *io, *tmp_io;
        struct sony_pic_irq *irq, *tmp_irq;
 
-       printk(KERN_INFO DRV_PFX "%s v%s.\n",
-               SONY_PIC_DRIVER_NAME, SONY_LAPTOP_DRIVER_VERSION);
+       pr_info(DRV_PFX "%s v%s.\n", SONY_PIC_DRIVER_NAME,
+                       SONY_LAPTOP_DRIVER_VERSION);
 
        spic_dev.acpi_dev = device;
        strcpy(acpi_device_class(device), "sony/hotkey");
@@ -2789,16 +3154,14 @@ static int sony_pic_add(struct acpi_device *device)
        /* read _PRS resources */
        result = sony_pic_possible_resources(device);
        if (result) {
-               printk(KERN_ERR DRV_PFX
-                               "Unable to read possible resources.\n");
+               pr_err(DRV_PFX "Unable to read possible resources.\n");
                goto err_free_resources;
        }
 
        /* setup input devices and helper fifo */
        result = sony_laptop_setup_input(device);
        if (result) {
-               printk(KERN_ERR DRV_PFX
-                               "Unable to create input devices.\n");
+               pr_err(DRV_PFX "Unable to create input devices.\n");
                goto err_free_resources;
        }
 
@@ -2808,7 +3171,7 @@ static int sony_pic_add(struct acpi_device *device)
        /* request io port */
        list_for_each_entry_reverse(io, &spic_dev.ioports, list) {
                if (request_region(io->io1.minimum, io->io1.address_length,
-                                       "Sony Programable I/O Device")) {
+                                       "Sony Programmable I/O Device")) {
                        dprintk("I/O port1: 0x%.4x (0x%.4x) + 0x%.2x\n",
                                        io->io1.minimum, io->io1.maximum,
                                        io->io1.address_length);
@@ -2816,7 +3179,7 @@ static int sony_pic_add(struct acpi_device *device)
                        if (io->io2.minimum) {
                                if (request_region(io->io2.minimum,
                                                io->io2.address_length,
-                                               "Sony Programable I/O Device")) {
+                                               "Sony Programmable I/O Device")) {
                                        dprintk("I/O port2: 0x%.4x (0x%.4x) + 0x%.2x\n",
                                                        io->io2.minimum, io->io2.maximum,
                                                        io->io2.address_length);
@@ -2839,7 +3202,7 @@ static int sony_pic_add(struct acpi_device *device)
                }
        }
        if (!spic_dev.cur_ioport) {
-               printk(KERN_ERR DRV_PFX "Failed to request_region.\n");
+               pr_err(DRV_PFX "Failed to request_region.\n");
                result = -ENODEV;
                goto err_remove_compat;
        }
@@ -2859,7 +3222,7 @@ static int sony_pic_add(struct acpi_device *device)
                }
        }
        if (!spic_dev.cur_irq) {
-               printk(KERN_ERR DRV_PFX "Failed to request_irq.\n");
+               pr_err(DRV_PFX "Failed to request_irq.\n");
                result = -ENODEV;
                goto err_release_region;
        }
@@ -2867,7 +3230,7 @@ static int sony_pic_add(struct acpi_device *device)
        /* set resource status _SRS */
        result = sony_pic_enable(device, spic_dev.cur_ioport, spic_dev.cur_irq);
        if (result) {
-               printk(KERN_ERR DRV_PFX "Couldn't enable device.\n");
+               pr_err(DRV_PFX "Couldn't enable device.\n");
                goto err_free_irq;
        }
 
@@ -2976,8 +3339,7 @@ static int __init sony_laptop_init(void)
        if (!no_spic && dmi_check_system(sonypi_dmi_table)) {
                result = acpi_bus_register_driver(&sony_pic_driver);
                if (result) {
-                       printk(KERN_ERR DRV_PFX
-                                       "Unable to register SPIC driver.");
+                       pr_err(DRV_PFX "Unable to register SPIC driver.");
                        goto out;
                }
                spic_drv_registered = 1;
@@ -2985,7 +3347,7 @@ static int __init sony_laptop_init(void)
 
        result = acpi_bus_register_driver(&sony_nc_driver);
        if (result) {
-               printk(KERN_ERR DRV_PFX "Unable to register SNC driver.");
+               pr_err(DRV_PFX "Unable to register SNC driver.");
                goto out_unregister_pic;
        }