BugLink: https://bugs.launchpad.net/ubuntu/+source/linux/+bug/568611

New acpi_brightness_hook_register lets another driver (e.g. i915) override
the often-dysfunctional native acpi brightness control methods.

Signed-off-by: Kamal Mostafa <ka...@canonical.com>
---
 drivers/acpi/video.c |  149 ++++++++++++++++++++++++++++++++++++++++++++++----
 include/acpi/video.h |   10 +++
 2 files changed, 147 insertions(+), 12 deletions(-)

diff --git a/drivers/acpi/video.c b/drivers/acpi/video.c
index bd9843a..331fdcc 100644
--- a/drivers/acpi/video.c
+++ b/drivers/acpi/video.c
@@ -481,6 +481,12 @@ acpi_video_device_set_state(struct acpi_video_device 
*device, int state)
        return status;
 }
 
+static unsigned int (*acpi_brightness_hook_routine)
+                       (void *dev, unsigned int brightness) = NULL;
+static char *acpi_brightness_hook_driver;
+static void *acpi_brightness_hook_dev;
+static unsigned int acpi_brightness_hook_max;
+
 static int
 acpi_video_device_lcd_query_levels(struct acpi_video_device *device,
                                   union acpi_object **levels)
@@ -520,13 +526,25 @@ acpi_video_device_lcd_set_level(struct acpi_video_device 
*device, int level)
        struct acpi_object_list args = { 1, &arg0 };
        int state;
 
-       arg0.integer.value = level;
+       /* If another driver has registered a brightness hook override,
+        * set the brightness using that method, otherwise set the brightness
+        * using the acpi _BCM method. */
+       if ( acpi_brightness_hook_routine ) {
+               status = acpi_brightness_hook_routine(acpi_brightness_hook_dev,
+                               level * acpi_brightness_hook_max / 100);
+               if ( status != 0 ) {
+                       ACPI_ERROR((AE_INFO, "brightness hook failed"));
+                       return -EIO;
+               }
+       } else {
+               arg0.integer.value = level;
 
-       status = acpi_evaluate_object(device->dev->handle, "_BCM",
-                                     &args, NULL);
-       if (ACPI_FAILURE(status)) {
-               ACPI_ERROR((AE_INFO, "Evaluating _BCM failed"));
-               return -EIO;
+               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;
@@ -607,7 +625,10 @@ acpi_video_device_lcd_get_level_current(struct 
acpi_video_device *device,
        acpi_status status = AE_OK;
        int i;
 
-       if (device->cap._BQC || device->cap._BCQ) {
+       /* Try to get the current brightness using the _BQC/_BCQ method, only
+        * if another driver has not registered a brightness hook override. */
+       if (!acpi_brightness_hook_routine
+                       && (device->cap._BQC || device->cap._BCQ)) {
                char *buf = device->cap._BQC ? "_BQC" : "_BCQ";
 
                status = acpi_evaluate_integer(device->dev->handle, buf,
@@ -784,7 +805,7 @@ acpi_video_cmp_level(const void *a, const void *b)
  *     device  : video output device (LCD, CRT, ..)
  *
  *  Return Value:
- *     Maximum brightness level
+ *     0 on success, error code on failure.
  *
  *  Allocate and initialize device->brightness.
  */
@@ -944,6 +965,99 @@ out:
  *     device  : video output device (LCD, CRT, ..)
  *
  *  Return Value:
+ *     0 on success, error code on failure.
+ *
+ *  Allocate and initialize device->brightness. when a driver has registered
+ *  a brightness hook override via acpi_brightness_hook_register.
+ *
+ *  Cobbles up a fake brightness 'levels' array (emulating a _BCL list) and
+ *  sets max brightness -- effectively what acpi_video_init_brightness does
+ *  for the native acpi brightness methods
+ */
+static int
+acpi_brightness_hook_init(struct acpi_video_device *device)
+{
+       struct acpi_video_device_brightness *br = NULL;
+       int result = -EINVAL;
+       int nsteps = 10;
+       int count, i;
+       static int initialized = 0;
+
+       if ( initialized )
+           return 1;
+       initialized = 1;
+
+       device->brightness = NULL;
+
+       br = kzalloc(sizeof(*br), GFP_KERNEL);
+       if (!br) {
+               printk(KERN_ERR "can't allocate memory\n");
+               result = -ENOMEM;
+               return result;
+       }
+       br->levels = kmalloc((nsteps + 2) * sizeof *(br->levels),
+                               GFP_KERNEL);
+       if (!br->levels) {
+               result = -ENOMEM;
+               kfree(br);
+               return result;
+       }
+
+       for (count=2, i = 1; i <= nsteps; i++)
+               br->levels[count++] = (u32) i * 100 / nsteps;
+       br->levels[0] = 100;
+       br->levels[1] = br->levels[2+(nsteps/2)];
+       br->count = count;
+       device->brightness = br;
+
+       result = acpi_video_device_lcd_set_level(device, 100);
+       if (result) {
+           kfree(br->levels);
+           kfree(br);
+           device->brightness = NULL;
+           return result;
+       }
+
+       /* Switch off acpi's native brightness switch control */
+       brightness_switch_enabled = 0;
+
+       ACPI_DEBUG_PRINT((ACPI_DB_INFO,
+                         "set up %d brightness levels\n", nsteps));
+
+       return 0;
+}
+
+/*
+ *  Arg:
+ *     driver_name     : name of the registering driver
+ *     set_brightness_routine: pointer to the new set_brightness method
+ *     dev             : arbitrary pointer passed to set_brightness_routine
+ *     max_brightness  : set_brightnes_routines' maximum brightness value
+ *
+ *  Return Value:
+ *     None
+ *
+ *  Register a brightness hook override method, which ACPI will use
+ *  instead of its native _BCM/_BCL/_BQC methods.
+ */
+void acpi_brightness_hook_register(
+               char *driver_name,
+               unsigned int (*set_brightness_routine)
+                               (void *dev, unsigned int brightness),
+               void *dev, int max_brightness)
+{
+       acpi_brightness_hook_routine = set_brightness_routine;
+       acpi_brightness_hook_driver = driver_name,
+       acpi_brightness_hook_dev = dev;
+       acpi_brightness_hook_max = max_brightness;
+}
+EXPORT_SYMBOL(acpi_brightness_hook_register);
+
+/*
+ *  Arg:
+ *     device  : video output device (LCD, CRT, ..)
+ *
+ *  Return Value:
  *     None
  *
  *  Find out all required AML methods defined under the output
@@ -984,22 +1098,33 @@ static void acpi_video_device_find_cap(struct 
acpi_video_device *device)
                device->cap._DSS = 1;
        }
 
-       if (acpi_video_backlight_support()) {
+       if (acpi_brightness_hook_routine || acpi_video_backlight_support()) {
                int result;
                static int count = 0;
                char *name;
 
-               result = acpi_video_init_brightness(device);
+               /* If another driver has registered a brightness hook override,
+                * call the brightness_hook init, otherwise call the native
+                * acpi_video brightness init. */
+               if (acpi_brightness_hook_routine)
+                       result = acpi_brightness_hook_init(device);
+               else
+                       result = acpi_video_init_brightness(device);
                if (result)
                        return;
                name = kzalloc(MAX_NAME_LEN, GFP_KERNEL);
                if (!name)
                        return;
 
-               sprintf(name, "acpi_video%d", count++);
+               if (acpi_brightness_hook_routine)
+                       sprintf(name, "%s", acpi_brightness_hook_driver);
+               else
+                       sprintf(name, "acpi_video%d", count++);
                device->backlight = backlight_device_register(name,
                        NULL, device, &acpi_backlight_ops);
                device->backlight->props.max_brightness = 
device->brightness->count-3;
+               dev_info(&device->dev->dev,
+                               "registered as backlight/%s\n", name);
                kfree(name);
 
                result = sysfs_create_link(&device->backlight->dev.kobj,
diff --git a/include/acpi/video.h b/include/acpi/video.h
index cf7be3d..645b574 100644
--- a/include/acpi/video.h
+++ b/include/acpi/video.h
@@ -2,9 +2,19 @@
 #define __ACPI_VIDEO_H
 
 #if (defined CONFIG_ACPI_VIDEO || defined CONFIG_ACPI_VIDEO_MODULE)
+extern void acpi_brightness_hook_register(
+               char *driver_name,
+               unsigned int (*set_brightness_routine)
+                               (void *dev, unsigned int brightness),
+               void *dev, unsigned int max_brightness);
 extern int acpi_video_register(void);
 extern void acpi_video_unregister(void);
 #else
+static inline acpi_brightness_hook_register(
+               char *driver_name,
+               unsigned int (*set_brightness_routine)
+                               (void *dev, unsigned int brightness),
+               void *dev, unsigned int max_brightness) { return; }
 static inline int acpi_video_register(void) { return 0; }
 static inline void acpi_video_unregister(void) { return; }
 #endif
-- 
1.7.0.4

_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/intel-gfx

Reply via email to