ACPI video: support _BCL packages that don't export brightness levels when machine...
[deliverable/linux.git] / drivers / acpi / video.c
index baa4419297209efd6d79aaedb2dae13c2d1a6f92..398b3eee37fbb00b552f16bbeca53c0d4605ed58 100644 (file)
@@ -36,6 +36,7 @@
 #include <linux/backlight.h>
 #include <linux/thermal.h>
 #include <linux/video_output.h>
+#include <linux/sort.h>
 #include <asm/uaccess.h>
 
 #include <acpi/acpi_bus.h>
@@ -167,10 +168,15 @@ struct acpi_video_device_cap {
        u8 _DSS:1;              /*Device state set */
 };
 
+struct acpi_video_brightness_flags {
+       u8 _BCL_no_ac_battery_levels:1; /* no AC/Battery levels in _BCL */
+};
+
 struct acpi_video_device_brightness {
        int curr;
        int count;
        int *levels;
+       struct acpi_video_brightness_flags flags;
 };
 
 struct acpi_video_device {
@@ -293,7 +299,7 @@ static int acpi_video_device_lcd_get_level_current(
                        unsigned long long *level);
 static int acpi_video_get_next_level(struct acpi_video_device *device,
                                     u32 level_current, u32 event);
-static void acpi_video_switch_brightness(struct acpi_video_device *device,
+static int acpi_video_switch_brightness(struct acpi_video_device *device,
                                         int event);
 static int acpi_video_device_get_state(struct acpi_video_device *device,
                            unsigned long long *state);
@@ -307,7 +313,9 @@ static int acpi_video_get_brightness(struct backlight_device *bd)
        int i;
        struct acpi_video_device *vd =
                (struct acpi_video_device *)bl_get_data(bd);
-       acpi_video_device_lcd_get_level_current(vd, &cur_level);
+
+       if (acpi_video_device_lcd_get_level_current(vd, &cur_level))
+               return -EINVAL;
        for (i = 2; i < vd->brightness->count; i++) {
                if (vd->brightness->levels[i] == cur_level)
                        /* The first two entries are special - see page 575
@@ -319,12 +327,12 @@ static int acpi_video_get_brightness(struct backlight_device *bd)
 
 static int acpi_video_set_brightness(struct backlight_device *bd)
 {
-       int request_level = bd->props.brightness+2;
+       int request_level = bd->props.brightness + 2;
        struct acpi_video_device *vd =
                (struct acpi_video_device *)bl_get_data(bd);
-       acpi_video_device_lcd_set_level(vd,
-                                       vd->brightness->levels[request_level]);
-       return 0;
+
+       return acpi_video_device_lcd_set_level(vd,
+                               vd->brightness->levels[request_level]);
 }
 
 static struct backlight_ops acpi_backlight_ops = {
@@ -372,7 +380,8 @@ static int video_get_cur_state(struct thermal_cooling_device *cdev, char *buf)
        unsigned long long level;
        int state;
 
-       acpi_video_device_lcd_get_level_current(video, &level);
+       if (acpi_video_device_lcd_get_level_current(video, &level))
+               return -EINVAL;
        for (state = 2; state < video->brightness->count; state++)
                if (level == video->brightness->levels[state])
                        return sprintf(buf, "%d\n",
@@ -478,29 +487,58 @@ acpi_video_device_lcd_query_levels(struct acpi_video_device *device,
 static int
 acpi_video_device_lcd_set_level(struct acpi_video_device *device, int level)
 {
-       int status = AE_OK;
+       int status;
        union acpi_object arg0 = { ACPI_TYPE_INTEGER };
        struct acpi_object_list args = { 1, &arg0 };
-
+       int state;
 
        arg0.integer.value = level;
 
-       if (device->cap._BCM)
-               status = acpi_evaluate_object(device->dev->handle, "_BCM",
-                                             &args, NULL);
+       status = acpi_evaluate_object(device->dev->handle, "_BCM",
+                                     &args, NULL);
+       if (ACPI_FAILURE(status)) {
+               ACPI_ERROR((AE_INFO, "Evaluating _BCM failed"));
+               return -EIO;
+       }
+
        device->brightness->curr = level;
-       return status;
+       for (state = 2; state < device->brightness->count; state++)
+               if (level == device->brightness->levels[state]) {
+                       device->backlight->props.brightness = state - 2;
+                       return 0;
+               }
+
+       ACPI_ERROR((AE_INFO, "Current brightness invalid"));
+       return -EINVAL;
 }
 
 static int
 acpi_video_device_lcd_get_level_current(struct acpi_video_device *device,
                                        unsigned long long *level)
 {
-       if (device->cap._BQC)
-               return acpi_evaluate_integer(device->dev->handle, "_BQC", NULL,
-                                            level);
+       acpi_status status = AE_OK;
+
+       if (device->cap._BQC) {
+               status = acpi_evaluate_integer(device->dev->handle, "_BQC",
+                                               NULL, level);
+               if (ACPI_SUCCESS(status)) {
+                       device->brightness->curr = *level;
+                       return 0;
+               } else {
+                       /* Fixme:
+                        * should we return an error or ignore this failure?
+                        * dev->brightness->curr is a cached value which stores
+                        * the correct current backlight level in most cases.
+                        * ACPI video backlight still works w/ buggy _BQC.
+                        * http://bugzilla.kernel.org/show_bug.cgi?id=12233
+                        */
+                       ACPI_WARNING((AE_INFO, "Evaluating _BQC failed"));
+                       device->cap._BQC = 0;
+               }
+       }
+
        *level = device->brightness->curr;
-       return AE_OK;
+       return 0;
 }
 
 static int
@@ -625,6 +663,16 @@ acpi_video_bus_DOS(struct acpi_video_bus *video, int bios_flag, int lcd_flag)
        return status;
 }
 
+/*
+ * Simple comparison function used to sort backlight levels.
+ */
+
+static int
+acpi_video_cmp_level(const void *a, const void *b)
+{
+       return *(int *)a - *(int *)b;
+}
+
 /*
  *  Arg:       
  *     device  : video output device (LCD, CRT, ..)
@@ -639,7 +687,7 @@ static int
 acpi_video_init_brightness(struct acpi_video_device *device)
 {
        union acpi_object *obj = NULL;
-       int i, max_level = 0, count = 0;
+       int i, max_level = 0, count = 0, level_ac_battery = 0;
        union acpi_object *o;
        struct acpi_video_device_brightness *br = NULL;
 
@@ -658,7 +706,7 @@ acpi_video_init_brightness(struct acpi_video_device *device)
                goto out;
        }
 
-       br->levels = kmalloc(obj->package.count * sizeof *(br->levels),
+       br->levels = kmalloc((obj->package.count + 2) * sizeof *(br->levels),
                                GFP_KERNEL);
        if (!br->levels)
                goto out_free;
@@ -676,12 +724,34 @@ acpi_video_init_brightness(struct acpi_video_device *device)
                count++;
        }
 
-       if (count < 2)
-               goto out_free_levels;
+       /*
+        * some buggy BIOS don't export the levels
+        * when machine is on AC/Battery in _BCL package.
+        * In this case, the first two elements in _BCL packages
+        * are also supported brightness levels that OS should take care of.
+        */
+       for (i = 2; i < count; i++)
+               if (br->levels[i] == br->levels[0] ||
+                   br->levels[i] == br->levels[1])
+                       level_ac_battery++;
+
+       if (level_ac_battery < 2) {
+               level_ac_battery = 2 - level_ac_battery;
+               br->flags._BCL_no_ac_battery_levels = 1;
+               for (i = (count - 1 + level_ac_battery); i >= 2; i--)
+                       br->levels[i] = br->levels[i - level_ac_battery];
+               count += level_ac_battery;
+       } else if (level_ac_battery > 2)
+               ACPI_ERROR((AE_INFO, "Too many duplicates in _BCL package\n"));
+
+       /* sort all the supported brightness levels */
+       sort(&br->levels[2], count - 2, sizeof(br->levels[2]),
+               acpi_video_cmp_level, NULL);
 
        br->count = count;
        device->brightness = br;
-       ACPI_DEBUG_PRINT((ACPI_DB_INFO, "found %d brightness levels\n", count));
+       ACPI_DEBUG_PRINT((ACPI_DB_INFO,
+                         "found %d brightness levels\n", count - 2));
        kfree(obj);
        return max_level;
 
@@ -1000,7 +1070,7 @@ acpi_video_device_brightness_seq_show(struct seq_file *seq, void *offset)
        }
 
        seq_printf(seq, "levels: ");
-       for (i = 0; i < dev->brightness->count; i++)
+       for (i = 2; i < dev->brightness->count; i++)
                seq_printf(seq, " %d", dev->brightness->levels[i]);
        seq_printf(seq, "\ncurrent: %d\n", dev->brightness->curr);
 
@@ -1039,15 +1109,14 @@ acpi_video_device_write_brightness(struct file *file,
                return -EFAULT;
 
        /* validate through the list of available levels */
-       for (i = 0; i < dev->brightness->count; i++)
+       for (i = 2; i < dev->brightness->count; i++)
                if (level == dev->brightness->levels[i]) {
-                       if (ACPI_SUCCESS
-                           (acpi_video_device_lcd_set_level(dev, level)))
-                               dev->brightness->curr = level;
+                       if (!acpi_video_device_lcd_set_level(dev, level))
+                               return count;
                        break;
                }
 
-       return count;
+       return -EINVAL;
 }
 
 static int acpi_video_device_EDID_seq_show(struct seq_file *seq, void *offset)
@@ -1240,7 +1309,7 @@ static int acpi_video_bus_POST_info_seq_show(struct seq_file *seq, void *offset)
                        printk(KERN_WARNING PREFIX
                               "This indicates a BIOS bug. Please contact the manufacturer.\n");
                }
-               printk("%llx\n", options);
+               printk(KERN_WARNING "%llx\n", options);
                seq_printf(seq, "can POST: <integrated video>");
                if (options & 2)
                        seq_printf(seq, " <PCI video>");
@@ -1692,7 +1761,7 @@ acpi_video_get_next_level(struct acpi_video_device *device,
        max = max_below = 0;
        min = min_above = 255;
        /* Find closest level to level_current */
-       for (i = 0; i < device->brightness->count; i++) {
+       for (i = 2; i < device->brightness->count; i++) {
                l = device->brightness->levels[i];
                if (abs(l - level_current) < abs(delta)) {
                        delta = l - level_current;
@@ -1702,7 +1771,7 @@ acpi_video_get_next_level(struct acpi_video_device *device,
        }
        /* Ajust level_current to closest available level */
        level_current += delta;
-       for (i = 0; i < device->brightness->count; i++) {
+       for (i = 2; i < device->brightness->count; i++) {
                l = device->brightness->levels[i];
                if (l < min)
                        min = l;
@@ -1729,15 +1798,29 @@ acpi_video_get_next_level(struct acpi_video_device *device,
        }
 }
 
-static void
+static int
 acpi_video_switch_brightness(struct acpi_video_device *device, int event)
 {
        unsigned long long level_current, level_next;
+       int result = -EINVAL;
+
        if (!device->brightness)
-               return;
-       acpi_video_device_lcd_get_level_current(device, &level_current);
+               goto out;
+
+       result = acpi_video_device_lcd_get_level_current(device,
+                                                        &level_current);
+       if (result)
+               goto out;
+
        level_next = acpi_video_get_next_level(device, level_current, event);
-       acpi_video_device_lcd_set_level(device, level_next);
+
+       result = acpi_video_device_lcd_set_level(device, level_next);
+
+out:
+       if (result)
+               printk(KERN_ERR PREFIX "Failed to switch the brightness\n");
+
+       return result;
 }
 
 static int
@@ -1986,6 +2069,12 @@ static int acpi_video_bus_add(struct acpi_device *device)
                        device->pnp.bus_id[3] = '0' + instance;
                instance ++;
        }
+       /* a hack to fix the duplicate name "VGA" problem on Pa 3553 */
+       if (!strcmp(device->pnp.bus_id, "VGA")) {
+               if (instance)
+                       device->pnp.bus_id[3] = '0' + instance;
+               instance++;
+       }
 
        video->device = device;
        strcpy(acpi_device_name(device), ACPI_VIDEO_BUS_NAME);
This page took 0.0311 seconds and 5 git commands to generate.