|
@@ -37,6 +37,8 @@
|
|
|
#include <linux/thermal.h>
|
|
|
#include <linux/video_output.h>
|
|
|
#include <linux/sort.h>
|
|
|
+#include <linux/pci.h>
|
|
|
+#include <linux/pci_ids.h>
|
|
|
#include <asm/uaccess.h>
|
|
|
|
|
|
#include <acpi/acpi_bus.h>
|
|
@@ -162,16 +164,26 @@ struct acpi_video_device_cap {
|
|
|
u8 _BCL:1; /*Query list of brightness control levels supported */
|
|
|
u8 _BCM:1; /*Set the brightness level */
|
|
|
u8 _BQC:1; /* Get current brightness level */
|
|
|
+ u8 _BCQ:1; /* Some buggy BIOS uses _BCQ instead of _BQC */
|
|
|
u8 _DDC:1; /*Return the EDID for this device */
|
|
|
u8 _DCS:1; /*Return status of output device */
|
|
|
u8 _DGS:1; /*Query graphics state */
|
|
|
u8 _DSS:1; /*Device state set */
|
|
|
};
|
|
|
|
|
|
+struct acpi_video_brightness_flags {
|
|
|
+ u8 _BCL_no_ac_battery_levels:1; /* no AC/Battery levels in _BCL */
|
|
|
+ u8 _BCL_reversed:1; /* _BCL package is in a reversed order*/
|
|
|
+ u8 _BCL_use_index:1; /* levels in _BCL are index values */
|
|
|
+ u8 _BCM_use_index:1; /* input of _BCM is an index value */
|
|
|
+ u8 _BQC_use_index:1; /* _BQC returns an index value */
|
|
|
+};
|
|
|
+
|
|
|
struct acpi_video_device_brightness {
|
|
|
int curr;
|
|
|
int count;
|
|
|
int *levels;
|
|
|
+ struct acpi_video_brightness_flags flags;
|
|
|
};
|
|
|
|
|
|
struct acpi_video_device {
|
|
@@ -294,7 +306,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);
|
|
@@ -308,7 +320,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
|
|
@@ -320,12 +334,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 = {
|
|
@@ -376,7 +390,8 @@ static int video_get_cur_state(struct thermal_cooling_device *cdev, unsigned
|
|
|
unsigned long long level;
|
|
|
int offset;
|
|
|
|
|
|
- acpi_video_device_lcd_get_level_current(video, &level);
|
|
|
+ if (acpi_video_device_lcd_get_level_current(video, &level))
|
|
|
+ return -EINVAL;
|
|
|
for (offset = 2; offset < video->brightness->count; offset++)
|
|
|
if (level == video->brightness->levels[offset]) {
|
|
|
*state = video->brightness->count - offset - 1;
|
|
@@ -483,34 +498,68 @@ 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;
|
|
|
for (state = 2; state < device->brightness->count; state++)
|
|
|
- if (level == device->brightness->levels[state])
|
|
|
- device->backlight->props.brightness = state - 2;
|
|
|
+ if (level == device->brightness->levels[state]) {
|
|
|
+ if (device->backlight)
|
|
|
+ device->backlight->props.brightness = state - 2;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
|
|
|
- return status;
|
|
|
+ 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 || device->cap._BCQ) {
|
|
|
+ char *buf = device->cap._BQC ? "_BQC" : "_BCQ";
|
|
|
+
|
|
|
+ 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];
|
|
|
+
|
|
|
+ }
|
|
|
+ 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 %s failed", buf));
|
|
|
+ device->cap._BQC = device->cap._BCQ = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
*level = device->brightness->curr;
|
|
|
- return AE_OK;
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
static int
|
|
@@ -659,9 +708,11 @@ 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;
|
|
|
+ unsigned long long level, level_old;
|
|
|
union acpi_object *o;
|
|
|
struct acpi_video_device_brightness *br = NULL;
|
|
|
+ int result = -EINVAL;
|
|
|
|
|
|
if (!ACPI_SUCCESS(acpi_video_device_lcd_query_levels(device, &obj))) {
|
|
|
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Could not query available "
|
|
@@ -675,13 +726,16 @@ acpi_video_init_brightness(struct acpi_video_device *device)
|
|
|
br = kzalloc(sizeof(*br), GFP_KERNEL);
|
|
|
if (!br) {
|
|
|
printk(KERN_ERR "can't allocate memory\n");
|
|
|
+ result = -ENOMEM;
|
|
|
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)
|
|
|
+ if (!br->levels) {
|
|
|
+ result = -ENOMEM;
|
|
|
goto out_free;
|
|
|
+ }
|
|
|
|
|
|
for (i = 0; i < obj->package.count; i++) {
|
|
|
o = (union acpi_object *)&obj->package.elements[i];
|
|
@@ -696,18 +750,86 @@ acpi_video_init_brightness(struct acpi_video_device *device)
|
|
|
count++;
|
|
|
}
|
|
|
|
|
|
- /* don't sort the first two brightness levels */
|
|
|
- sort(&br->levels[2], count - 2, sizeof(br->levels[2]),
|
|
|
- acpi_video_cmp_level, NULL);
|
|
|
-
|
|
|
- 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"));
|
|
|
+
|
|
|
+ /* Check if the _BCL package is in a reversed order */
|
|
|
+ if (max_level == br->levels[2]) {
|
|
|
+ br->flags._BCL_reversed = 1;
|
|
|
+ sort(&br->levels[2], count - 2, sizeof(br->levels[2]),
|
|
|
+ acpi_video_cmp_level, NULL);
|
|
|
+ } else if (max_level != br->levels[count - 1])
|
|
|
+ ACPI_ERROR((AE_INFO,
|
|
|
+ "Found unordered _BCL package\n"));
|
|
|
|
|
|
br->count = count;
|
|
|
device->brightness = br;
|
|
|
- ACPI_DEBUG_PRINT((ACPI_DB_INFO, "found %d brightness levels\n", count));
|
|
|
+
|
|
|
+ /* Check the input/output of _BQC/_BCL/_BCM */
|
|
|
+ if ((max_level < 100) && (max_level <= (count - 2)))
|
|
|
+ br->flags._BCL_use_index = 1;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * _BCM is always consistent with _BCL,
|
|
|
+ * at least for all the laptops we have ever seen.
|
|
|
+ */
|
|
|
+ br->flags._BCM_use_index = br->flags._BCL_use_index;
|
|
|
+
|
|
|
+ /* _BQC uses INDEX while _BCL uses VALUE in some laptops */
|
|
|
+ br->curr = max_level;
|
|
|
+ result = acpi_video_device_lcd_get_level_current(device, &level_old);
|
|
|
+ if (result)
|
|
|
+ goto out_free_levels;
|
|
|
+
|
|
|
+ result = acpi_video_device_lcd_set_level(device, br->curr);
|
|
|
+ if (result)
|
|
|
+ goto out_free_levels;
|
|
|
+
|
|
|
+ result = acpi_video_device_lcd_get_level_current(device, &level);
|
|
|
+ if (result)
|
|
|
+ goto out_free_levels;
|
|
|
+
|
|
|
+ if ((level != level_old) && !br->flags._BCM_use_index) {
|
|
|
+ /* Note:
|
|
|
+ * This piece of code does not work correctly if the current
|
|
|
+ * brightness levels is 0.
|
|
|
+ * But I guess boxes that boot with such a dark screen are rare
|
|
|
+ * and no more code is needed to cover this specifial case.
|
|
|
+ */
|
|
|
+
|
|
|
+ if (level_ac_battery != 2) {
|
|
|
+ /*
|
|
|
+ * For now, we don't support the _BCL like this:
|
|
|
+ * 16, 15, 0, 1, 2, 3, ..., 14, 15, 16
|
|
|
+ * because we may mess up the index returned by _BQC.
|
|
|
+ * Plus: we have not got a box like this.
|
|
|
+ */
|
|
|
+ ACPI_ERROR((AE_INFO, "_BCL not supported\n"));
|
|
|
+ }
|
|
|
+ br->flags._BQC_use_index = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO,
|
|
|
+ "found %d brightness levels\n", count - 2));
|
|
|
kfree(obj);
|
|
|
- return max_level;
|
|
|
+ return result;
|
|
|
|
|
|
out_free_levels:
|
|
|
kfree(br->levels);
|
|
@@ -716,7 +838,7 @@ out_free:
|
|
|
out:
|
|
|
device->brightness = NULL;
|
|
|
kfree(obj);
|
|
|
- return 0;
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -733,7 +855,6 @@ out:
|
|
|
static void acpi_video_device_find_cap(struct acpi_video_device *device)
|
|
|
{
|
|
|
acpi_handle h_dummy1;
|
|
|
- u32 max_level = 0;
|
|
|
|
|
|
|
|
|
memset(&device->cap, 0, sizeof(device->cap));
|
|
@@ -749,6 +870,12 @@ static void acpi_video_device_find_cap(struct acpi_video_device *device)
|
|
|
}
|
|
|
if (ACPI_SUCCESS(acpi_get_handle(device->dev->handle,"_BQC",&h_dummy1)))
|
|
|
device->cap._BQC = 1;
|
|
|
+ else if (ACPI_SUCCESS(acpi_get_handle(device->dev->handle, "_BCQ",
|
|
|
+ &h_dummy1))) {
|
|
|
+ printk(KERN_WARNING FW_BUG "_BCQ is used instead of _BQC\n");
|
|
|
+ device->cap._BCQ = 1;
|
|
|
+ }
|
|
|
+
|
|
|
if (ACPI_SUCCESS(acpi_get_handle(device->dev->handle, "_DDC", &h_dummy1))) {
|
|
|
device->cap._DDC = 1;
|
|
|
}
|
|
@@ -762,13 +889,14 @@ static void acpi_video_device_find_cap(struct acpi_video_device *device)
|
|
|
device->cap._DSS = 1;
|
|
|
}
|
|
|
|
|
|
- if (acpi_video_backlight_support())
|
|
|
- max_level = acpi_video_init_brightness(device);
|
|
|
-
|
|
|
- if (device->cap._BCL && device->cap._BCM && max_level > 0) {
|
|
|
+ if (acpi_video_backlight_support()) {
|
|
|
int result;
|
|
|
static int count = 0;
|
|
|
char *name;
|
|
|
+
|
|
|
+ result = acpi_video_init_brightness(device);
|
|
|
+ if (result)
|
|
|
+ return;
|
|
|
name = kzalloc(MAX_NAME_LEN, GFP_KERNEL);
|
|
|
if (!name)
|
|
|
return;
|
|
@@ -777,18 +905,6 @@ static void acpi_video_device_find_cap(struct acpi_video_device *device)
|
|
|
device->backlight = backlight_device_register(name,
|
|
|
NULL, device, &acpi_backlight_ops);
|
|
|
device->backlight->props.max_brightness = device->brightness->count-3;
|
|
|
- /*
|
|
|
- * If there exists the _BQC object, the _BQC object will be
|
|
|
- * called to get the current backlight brightness. Otherwise
|
|
|
- * the brightness will be set to the maximum.
|
|
|
- */
|
|
|
- if (device->cap._BQC)
|
|
|
- device->backlight->props.brightness =
|
|
|
- acpi_video_get_brightness(device->backlight);
|
|
|
- else
|
|
|
- device->backlight->props.brightness =
|
|
|
- device->backlight->props.max_brightness;
|
|
|
- backlight_update_status(device->backlight);
|
|
|
kfree(name);
|
|
|
|
|
|
device->cdev = thermal_cooling_device_register("LCD",
|
|
@@ -1065,13 +1181,12 @@ acpi_video_device_write_brightness(struct file *file,
|
|
|
/* validate through the list of available levels */
|
|
|
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)
|
|
@@ -1753,15 +1868,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
|
|
@@ -2128,7 +2257,27 @@ static int acpi_video_bus_remove(struct acpi_device *device, int type)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static int __init acpi_video_init(void)
|
|
|
+static int __init intel_opregion_present(void)
|
|
|
+{
|
|
|
+#if defined(CONFIG_DRM_I915) || defined(CONFIG_DRM_I915_MODULE)
|
|
|
+ struct pci_dev *dev = NULL;
|
|
|
+ u32 address;
|
|
|
+
|
|
|
+ for_each_pci_dev(dev) {
|
|
|
+ if ((dev->class >> 8) != PCI_CLASS_DISPLAY_VGA)
|
|
|
+ continue;
|
|
|
+ if (dev->vendor != PCI_VENDOR_ID_INTEL)
|
|
|
+ continue;
|
|
|
+ pci_read_config_dword(dev, 0xfc, &address);
|
|
|
+ if (!address)
|
|
|
+ continue;
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+#endif
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int acpi_video_register(void)
|
|
|
{
|
|
|
int result = 0;
|
|
|
|
|
@@ -2145,6 +2294,22 @@ static int __init acpi_video_init(void)
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
+EXPORT_SYMBOL(acpi_video_register);
|
|
|
+
|
|
|
+/*
|
|
|
+ * This is kind of nasty. Hardware using Intel chipsets may require
|
|
|
+ * the video opregion code to be run first in order to initialise
|
|
|
+ * state before any ACPI video calls are made. To handle this we defer
|
|
|
+ * registration of the video class until the opregion code has run.
|
|
|
+ */
|
|
|
+
|
|
|
+static int __init acpi_video_init(void)
|
|
|
+{
|
|
|
+ if (intel_opregion_present())
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ return acpi_video_register();
|
|
|
+}
|
|
|
|
|
|
static void __exit acpi_video_exit(void)
|
|
|
{
|