Merge branch 'bugfixes' of git://git.linux-nfs.org/projects/trondmy/nfs-2.6
[pandora-kernel.git] / drivers / platform / x86 / sony-laptop.c
index 89d66c7..8f709ae 100644 (file)
@@ -125,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,
@@ -592,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...) \
@@ -727,20 +742,89 @@ static int acpi_callsetfunc(acpi_handle handle, char *name, int value,
        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) {
-                       dprintk("found handle 0x%.4x (offset: 0x%.2x)\n",
-                                       handle, i - 0x20);
-                       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;
 }
@@ -866,11 +950,39 @@ static int sony_backlight_get_brightness(struct backlight_device *bd)
        return value - 1;
 }
 
-static struct backlight_device *sony_backlight_device;
+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;
+       sony_call_snc_handle(*handle, 0x0100 | (value << 16), &result);
+
+       return sony_nc_get_brightness_ng(bd);
+}
+
 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
@@ -1063,14 +1175,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)
-               pr_warn(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;
 }
 
@@ -1214,7 +1324,11 @@ 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) {
+       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;
@@ -1250,6 +1364,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;
@@ -1274,6 +1619,10 @@ 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,
@@ -1281,7 +1630,7 @@ static int sony_nc_add(struct acpi_device *device)
                if (ACPI_FAILURE(status)) {
                        pr_warn(DRV_PFX "unable to walk acpi resources\n");
                        result = -ENODEV;
-                       goto outwalk;
+                       goto outpresent;
                }
        }
 
@@ -1294,6 +1643,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);
        }
@@ -1302,38 +1657,16 @@ static int sony_nc_add(struct acpi_device *device)
        result = sony_laptop_setup_input(device);
        if (result) {
                pr_err(DRV_PFX "Unable to create input devices.\n");
-               goto outwalk;
+               goto outkbdbacklight;
        }
 
        if (acpi_video_backlight_support()) {
                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))) {
-               struct backlight_properties props;
-               memset(&props, 0, sizeof(struct backlight_properties));
-               props.type = BACKLIGHT_PLATFORM;
-               props.max_brightness = SONY_MAX_BRIGHTNESS - 1;
-               sony_backlight_device = backlight_device_register("sony", NULL,
-                                                                 NULL,
-                                                                 &sony_backlight_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 =
-                           sony_backlight_get_brightness
-                           (sony_backlight_device);
-               }
-
+       } 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) {
 
@@ -1379,14 +1712,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;
@@ -1396,8 +1734,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;
 
@@ -1405,6 +1742,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();
@@ -1443,7 +1782,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
@@ -2832,7 +3170,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);
@@ -2840,7 +3178,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);