]> rtime.felk.cvut.cz Git - linux-imx.git/blobdiff - drivers/acpi/video.c
ACPI: video: correct acpi_video_bus_add error processing
[linux-imx.git] / drivers / acpi / video.c
index 313f959413dc09a89c8a8262957b35081b1e2b0b..c3932d0876e0059c4a7c7aee7342b7123388e27c 100644 (file)
@@ -167,7 +167,8 @@ struct acpi_video_device_flags {
        u8 dvi:1;
        u8 bios:1;
        u8 unknown:1;
-       u8 reserved:2;
+       u8 notify:1;
+       u8 reserved:1;
 };
 
 struct acpi_video_device_cap {
@@ -222,7 +223,7 @@ static int acpi_video_device_lcd_set_level(struct acpi_video_device *device,
                        int level);
 static int acpi_video_device_lcd_get_level_current(
                        struct acpi_video_device *device,
-                       unsigned long long *level, int init);
+                       unsigned long long *level, bool raw);
 static int acpi_video_get_next_level(struct acpi_video_device *device,
                                     u32 level_current, u32 event);
 static int acpi_video_switch_brightness(struct acpi_video_device *device,
@@ -236,7 +237,7 @@ static int acpi_video_get_brightness(struct backlight_device *bd)
        struct acpi_video_device *vd =
                (struct acpi_video_device *)bl_get_data(bd);
 
-       if (acpi_video_device_lcd_get_level_current(vd, &cur_level, 0))
+       if (acpi_video_device_lcd_get_level_current(vd, &cur_level, false))
                return -EINVAL;
        for (i = 2; i < vd->brightness->count; i++) {
                if (vd->brightness->levels[i] == cur_level)
@@ -281,7 +282,7 @@ static int video_get_cur_state(struct thermal_cooling_device *cooling_dev, unsig
        unsigned long long level;
        int offset;
 
-       if (acpi_video_device_lcd_get_level_current(video, &level, 0))
+       if (acpi_video_device_lcd_get_level_current(video, &level, false))
                return -EINVAL;
        for (offset = 2; offset < video->brightness->count; offset++)
                if (level == video->brightness->levels[offset]) {
@@ -447,12 +448,45 @@ static struct dmi_system_id video_dmi_table[] __initdata = {
                DMI_MATCH(DMI_PRODUCT_NAME, "HP Folio 13 - 2000 Notebook PC"),
                },
        },
+       {
+        .callback = video_ignore_initial_backlight,
+        .ident = "HP Pavilion dm4",
+        .matches = {
+               DMI_MATCH(DMI_BOARD_VENDOR, "Hewlett-Packard"),
+               DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion dm4 Notebook PC"),
+               },
+       },
        {}
 };
 
+static unsigned long long
+acpi_video_bqc_value_to_level(struct acpi_video_device *device,
+                             unsigned long long bqc_value)
+{
+       unsigned long long level;
+
+       if (device->brightness->flags._BQC_use_index) {
+               /*
+                * _BQC returns an index that doesn't account for
+                * the first 2 items with special meaning, so we need
+                * to compensate for that by offsetting ourselves
+                */
+               if (device->brightness->flags._BCL_reversed)
+                       bqc_value = device->brightness->count - 3 - bqc_value;
+
+               level = device->brightness->levels[bqc_value + 2];
+       } else {
+               level = bqc_value;
+       }
+
+       level += bqc_offset_aml_bug_workaround;
+
+       return level;
+}
+
 static int
 acpi_video_device_lcd_get_level_current(struct acpi_video_device *device,
-                                       unsigned long long *level, int init)
+                                       unsigned long long *level, bool raw)
 {
        acpi_status status = AE_OK;
        int i;
@@ -463,29 +497,30 @@ acpi_video_device_lcd_get_level_current(struct acpi_video_device *device,
                status = acpi_evaluate_integer(device->dev->handle, buf,
                                                NULL, level);
                if (ACPI_SUCCESS(status)) {
-                       if (device->brightness->flags._BQC_use_index) {
-                               if (device->brightness->flags._BCL_reversed)
-                                       *level = device->brightness->count
-                                                                - 3 - (*level);
-                               *level = device->brightness->levels[*level + 2];
-
+                       if (raw) {
+                               /*
+                                * Caller has indicated he wants the raw
+                                * value returned by _BQC, so don't furtherly
+                                * mess with the value.
+                                */
+                               return 0;
                        }
-                       *level += bqc_offset_aml_bug_workaround;
+
+                       *level = acpi_video_bqc_value_to_level(device, *level);
+
                        for (i = 2; i < device->brightness->count; i++)
                                if (device->brightness->levels[i] == *level) {
                                        device->brightness->curr = *level;
                                        return 0;
                        }
-                       if (!init) {
-                               /*
-                                * BQC returned an invalid level.
-                                * Stop using it.
-                                */
-                               ACPI_WARNING((AE_INFO,
-                                             "%s returned an invalid level",
-                                             buf));
-                               device->cap._BQC = device->cap._BCQ = 0;
-                       }
+                       /*
+                        * BQC returned an invalid level.
+                        * Stop using it.
+                        */
+                       ACPI_WARNING((AE_INFO,
+                                     "%s returned an invalid level",
+                                     buf));
+                       device->cap._BQC = device->cap._BCQ = 0;
                } else {
                        /* Fixme:
                         * should we return an error or ignore this failure?
@@ -597,6 +632,56 @@ acpi_video_cmp_level(const void *a, const void *b)
        return *(int *)a - *(int *)b;
 }
 
+/*
+ * Decides if _BQC/_BCQ for this system is usable
+ *
+ * We do this by changing the level first and then read out the current
+ * brightness level, if the value does not match, find out if it is using
+ * index. If not, clear the _BQC/_BCQ capability.
+ */
+static int acpi_video_bqc_quirk(struct acpi_video_device *device,
+                               int max_level, int current_level)
+{
+       struct acpi_video_device_brightness *br = device->brightness;
+       int result;
+       unsigned long long level;
+       int test_level;
+
+       /* don't mess with existing known broken systems */
+       if (bqc_offset_aml_bug_workaround)
+               return 0;
+
+       /*
+        * Some systems always report current brightness level as maximum
+        * through _BQC, we need to test another value for them.
+        */
+       test_level = current_level == max_level ? br->levels[2] : max_level;
+
+       result = acpi_video_device_lcd_set_level(device, test_level);
+       if (result)
+               return result;
+
+       result = acpi_video_device_lcd_get_level_current(device, &level, true);
+       if (result)
+               return result;
+
+       if (level != test_level) {
+               /* buggy _BQC found, need to find out if it uses index */
+               if (level < br->count) {
+                       if (br->flags._BCL_reversed)
+                               level = br->count - 3 - level;
+                       if (br->levels[level + 2] == test_level)
+                               br->flags._BQC_use_index = 1;
+               }
+
+               if (!br->flags._BQC_use_index)
+                       device->cap._BQC = device->cap._BCQ = 0;
+       }
+
+       return 0;
+}
+
+
 /*
  *  Arg:       
  *     device  : video output device (LCD, CRT, ..)
@@ -703,42 +788,36 @@ acpi_video_init_brightness(struct acpi_video_device *device)
        if (!device->cap._BQC)
                goto set_level;
 
-       result = acpi_video_device_lcd_get_level_current(device, &level_old, 1);
-       if (result)
-               goto out_free_levels;
-
-       /*
-        * Set the level to maximum and check if _BQC uses indexed value
-        */
-       result = acpi_video_device_lcd_set_level(device, max_level);
+       result = acpi_video_device_lcd_get_level_current(device,
+                                                        &level_old, true);
        if (result)
                goto out_free_levels;
 
-       result = acpi_video_device_lcd_get_level_current(device, &level, 0);
+       result = acpi_video_bqc_quirk(device, max_level, level_old);
        if (result)
                goto out_free_levels;
+       /*
+        * cap._BQC may get cleared due to _BQC is found to be broken
+        * in acpi_video_bqc_quirk, so check again here.
+        */
+       if (!device->cap._BQC)
+               goto set_level;
 
-       br->flags._BQC_use_index = (level == max_level ? 0 : 1);
-
-       if (!br->flags._BQC_use_index) {
+       if (use_bios_initial_backlight) {
+               level = acpi_video_bqc_value_to_level(device, level_old);
                /*
-                * Set the backlight to the initial state.
-                * On some buggy laptops, _BQC returns an uninitialized value
-                * when invoked for the first time, i.e. level_old is invalid.
-                * set the backlight to max_level in this case
+                * On some buggy laptops, _BQC returns an uninitialized
+                * value when invoked for the first time, i.e.
+                * level_old is invalid (no matter whether it's a level
+                * or an index). Set the backlight to max_level in this case.
                 */
-               if (use_bios_initial_backlight) {
-                       for (i = 2; i < br->count; i++)
-                               if (level_old == br->levels[i])
-                                       level = level_old;
-               }
-               goto set_level;
+               for (i = 2; i < br->count; i++)
+                       if (level_old == br->levels[i])
+                               break;
+               if (i == br->count)
+                       level = max_level;
        }
 
-       if (br->flags._BCL_reversed)
-               level_old = (br->count - 1) - level_old;
-       level = br->levels[level_old];
-
 set_level:
        result = acpi_video_device_lcd_set_level(device, level);
        if (result)
@@ -996,53 +1075,51 @@ acpi_video_bus_get_one_device(struct acpi_device *device,
        struct acpi_video_device *data;
        struct acpi_video_device_attrib* attribute;
 
-       if (!device || !video)
-               return -EINVAL;
-
        status =
            acpi_evaluate_integer(device->handle, "_ADR", NULL, &device_id);
-       if (ACPI_SUCCESS(status)) {
-
-               data = kzalloc(sizeof(struct acpi_video_device), GFP_KERNEL);
-               if (!data)
-                       return -ENOMEM;
-
-               strcpy(acpi_device_name(device), ACPI_VIDEO_DEVICE_NAME);
-               strcpy(acpi_device_class(device), ACPI_VIDEO_CLASS);
-               device->driver_data = data;
-
-               data->device_id = device_id;
-               data->video = video;
-               data->dev = device;
+       /* Some device omits _ADR, we skip them instead of fail */
+       if (ACPI_FAILURE(status))
+               return 0;
 
-               attribute = acpi_video_get_device_attr(video, device_id);
+       data = kzalloc(sizeof(struct acpi_video_device), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
 
-               if((attribute != NULL) && attribute->device_id_scheme) {
-                       switch (attribute->display_type) {
-                       case ACPI_VIDEO_DISPLAY_CRT:
-                               data->flags.crt = 1;
-                               break;
-                       case ACPI_VIDEO_DISPLAY_TV:
-                               data->flags.tvout = 1;
-                               break;
-                       case ACPI_VIDEO_DISPLAY_DVI:
-                               data->flags.dvi = 1;
-                               break;
-                       case ACPI_VIDEO_DISPLAY_LCD:
-                               data->flags.lcd = 1;
-                               break;
-                       default:
-                               data->flags.unknown = 1;
-                               break;
-                       }
-                       if(attribute->bios_can_detect)
-                               data->flags.bios = 1;
-               } else {
-                       /* Check for legacy IDs */
-                       device_type = acpi_video_get_device_type(video,
-                                                                device_id);
-                       /* Ignore bits 16 and 18-20 */
-                       switch (device_type & 0xffe2ffff) {
+       strcpy(acpi_device_name(device), ACPI_VIDEO_DEVICE_NAME);
+       strcpy(acpi_device_class(device), ACPI_VIDEO_CLASS);
+       device->driver_data = data;
+
+       data->device_id = device_id;
+       data->video = video;
+       data->dev = device;
+
+       attribute = acpi_video_get_device_attr(video, device_id);
+
+       if((attribute != NULL) && attribute->device_id_scheme) {
+               switch (attribute->display_type) {
+               case ACPI_VIDEO_DISPLAY_CRT:
+                       data->flags.crt = 1;
+                       break;
+               case ACPI_VIDEO_DISPLAY_TV:
+                       data->flags.tvout = 1;
+                       break;
+               case ACPI_VIDEO_DISPLAY_DVI:
+                       data->flags.dvi = 1;
+                       break;
+               case ACPI_VIDEO_DISPLAY_LCD:
+                       data->flags.lcd = 1;
+                       break;
+               default:
+                       data->flags.unknown = 1;
+                       break;
+               }
+               if(attribute->bios_can_detect)
+                       data->flags.bios = 1;
+       } else {
+               /* Check for legacy IDs */
+               device_type = acpi_video_get_device_type(video, device_id);
+               /* Ignore bits 16 and 18-20 */
+               switch (device_type & 0xffe2ffff) {
                        case ACPI_VIDEO_DISPLAY_LEGACY_MONITOR:
                                data->flags.crt = 1;
                                break;
@@ -1054,34 +1131,24 @@ acpi_video_bus_get_one_device(struct acpi_device *device,
                                break;
                        default:
                                data->flags.unknown = 1;
-                       }
                }
+       }
 
-               acpi_video_device_bind(video, data);
-               acpi_video_device_find_cap(data);
-
-               status = acpi_install_notify_handler(device->handle,
-                                                    ACPI_DEVICE_NOTIFY,
-                                                    acpi_video_device_notify,
-                                                    data);
-               if (ACPI_FAILURE(status)) {
-                       printk(KERN_ERR PREFIX
-                                         "Error installing notify handler\n");
-                       if(data->brightness)
-                               kfree(data->brightness->levels);
-                       kfree(data->brightness);
-                       kfree(data);
-                       return -ENODEV;
-               }
+       acpi_video_device_bind(video, data);
+       acpi_video_device_find_cap(data);
 
-               mutex_lock(&video->device_list_lock);
-               list_add_tail(&data->entry, &video->video_device_list);
-               mutex_unlock(&video->device_list_lock);
+       status = acpi_install_notify_handler(device->handle, ACPI_DEVICE_NOTIFY,
+                                            acpi_video_device_notify, data);
+       if (ACPI_FAILURE(status))
+               dev_err(&device->dev, "Error installing notify handler\n");
+       else
+               data->flags.notify = 1;
 
-               return 0;
-       }
+       mutex_lock(&video->device_list_lock);
+       list_add_tail(&data->entry, &video->video_device_list);
+       mutex_unlock(&video->device_list_lock);
 
-       return -ENOENT;
+       return status;
 }
 
 /*
@@ -1268,7 +1335,8 @@ acpi_video_switch_brightness(struct acpi_video_device *device, int event)
                goto out;
 
        result = acpi_video_device_lcd_get_level_current(device,
-                                                        &level_current, 0);
+                                                        &level_current,
+                                                        false);
        if (result)
                goto out;
 
@@ -1373,9 +1441,8 @@ acpi_video_bus_get_devices(struct acpi_video_bus *video,
 
                status = acpi_video_bus_get_one_device(dev, video);
                if (status) {
-                       printk(KERN_WARNING PREFIX
-                                       "Can't attach device\n");
-                       continue;
+                       dev_err(&dev->dev, "Can't attach device\n");
+                       break;
                }
        }
        return status;
@@ -1388,13 +1455,14 @@ static int acpi_video_bus_put_one_device(struct acpi_video_device *device)
        if (!device || !device->video)
                return -ENOENT;
 
-       status = acpi_remove_notify_handler(device->dev->handle,
-                                           ACPI_DEVICE_NOTIFY,
-                                           acpi_video_device_notify);
-       if (ACPI_FAILURE(status)) {
-               printk(KERN_WARNING PREFIX
-                      "Can't remove video notify handler\n");
+       if (device->flags.notify) {
+               status = acpi_remove_notify_handler(device->dev->handle,
+                               ACPI_DEVICE_NOTIFY, acpi_video_device_notify);
+               if (ACPI_FAILURE(status))
+                       dev_err(&device->dev->dev,
+                                       "Can't remove video notify handler\n");
        }
+
        if (device->backlight) {
                backlight_device_unregister(device->backlight);
                device->backlight = NULL;
@@ -1676,7 +1744,7 @@ static int acpi_video_bus_add(struct acpi_device *device)
 
        error = acpi_video_bus_get_devices(video, device);
        if (error)
-               goto err_free_video;
+               goto err_put_video;
 
        video->input = input = input_allocate_device();
        if (!input) {