CHROMIUM: backlight: ACPI interface for chromeos_keyboard_bl
authorDuncan Laurie <dlaurie@chromium.org>
Mon, 30 Jul 2012 21:33:19 +0000 (14:33 -0700)
committerGerrit <chrome-bot@google.com>
Tue, 7 Aug 2012 21:00:49 +0000 (14:00 -0700)
In order to shield the kernel from EC interface changes we added
an ACPI abstraction to the keyboard backlight.  The firmware defines
a new top-level ACPI device called "KBLT" which has methods to
get (KBQC) and set (KBCM) the backlight value.

This driver is changed to use the ACPI interface instead of
talking to the EC directly.

When the firmware does not support this interface the backlight
driver will not load.  When the firmware does support this
interface but the EC does not then the ACPI methods will fail
but nothing else.

I also added a KConfig dependency on ACPI since it is now required
for this driver to operate.

BUG=chromium-os:33054
TEST=manual

1) install EC with support for EC RAM and that exports the
keyboard backlight via EC RAM byte 3
2) install coreboot with this change to add keyboard backlight
interface via acpi
3) install kernel with keyboard backlight driver that uses
the new acpi interface
4) ensure the kernel driver loads
5) ensure the backlight can be read in sysfs:
> cat /sys/class/backlight/keyboard_backlight/actual_brightness
100
6) ensure the backlight can be set via sysfs:
> echo 50 > /sys/class/backlight/keyboard_backlight/brightness
> cat /sys/class/backlight/keyboard_backlight/actual_brightness
50
7) test with firmware that does not have this device to ensure
that the driver notices this and does not load
8) test with EC that does not support this interface to ensure
that the driver can cope with the ACPI methods failing to talk
to the EC properly.  In this setup the EC state machine can get
unhappy as it does not support the ACPI protocol fully and the
device power sequencing can get confused.  The EC and BIOS update
will be released together so this should not be an issue.

Change-Id: I6bfa58563dc1da5ade458e90929cd5c2985548e6
Signed-off-by: Duncan Laurie <dlaurie@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/28723
Reviewed-by: Olof Johansson <olofj@chromium.org>
drivers/video/backlight/Kconfig
drivers/video/backlight/chromeos_keyboard_bl.c

index bc1e474..5f05a87 100644 (file)
@@ -371,7 +371,7 @@ config BACKLIGHT_PANDORA
 
 config BACKLIGHT_CHROMEOS_KEYBOARD
        tristate "EC-based Keyboard Backlight for Chrome OS"
-       depends on BACKLIGHT_CLASS_DEVICE
+       depends on BACKLIGHT_CLASS_DEVICE && ACPI
        help
          If you have a EC-based keyboard backlight say Y to enable the
          backlight driver.
index 902b5c8..f1b94aa 100644 (file)
  *
  */
 
+#include <linux/acpi.h>
 #include <linux/backlight.h>
 #include <linux/delay.h>
 #include <linux/err.h>
-#include <linux/io.h>
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/kernel.h>
 #include <linux/platform_device.h>
 #include <linux/slab.h>
 
-/* I/O addresses for LPC commands */
-#define EC_ADDR_DATA          0x62
-#define EC_ADDR_CMD           0x66
-#define EC_ADDR_PARAM        0x800
-#define EC_PARAM_SIZE          128  /* Size of each param area in bytes */
-
-/*
- * LPC command status byte masks
- * Host has written a command/data byte and the EC hasn't read it yet
- */
-#define EC_LPC_STATUS_FROM_HOST   0x02
-/* EC is processing a command */
-#define EC_LPC_STATUS_PROCESSING  0x04
-
-/* EC is busy.  This covers both the EC processing a command, and the host has
- * written a new command but the EC hasn't picked it up yet. */
-#define EC_LPC_STATUS_BUSY_MASK \
-       (EC_LPC_STATUS_FROM_HOST | EC_LPC_STATUS_PROCESSING)
-
-/* EC PWM commands */
-#define EC_CMD_PWM_GET_KEYBOARD_BACKLIGHT 0x22
-#define EC_CMD_PWM_SET_KEYBOARD_BACKLIGHT 0x23
-
-/* Waits for the EC to be unbusy.  Returns 0 if unbusy, non-zero if
- * timeout. */
-static int wait_for_ec(void)
-{
-       int i;
-       for (i = 0; i < 1000000; i += 10) {
-               udelay(10);  /* Delay first, in case we just sent a command */
-               if (!(inb(EC_ADDR_CMD) & EC_LPC_STATUS_BUSY_MASK))
-                       return 0;
-       }
-       return -1;  /* Timeout */
-}
-
-/* Sends a command to the EC.  Returns the command status code, or
- * -1 if other error. */
-static int ec_command(int command, const u8 *indata, int insize,
-                     u8 *outdata, int outsize)
-{
-       int i;
-
-       if (insize > EC_PARAM_SIZE || outsize > EC_PARAM_SIZE) {
-               printk(KERN_ERR "Data size too big\n");
-               return -1;
-       }
-
-       if (wait_for_ec()) {
-               printk(KERN_ERR "Timeout waiting for EC ready\n");
-               return -1;
-       }
-
-       /* Write data, if any */
-       /* TODO: optimized copy using outl() */
-       for (i = 0; i < insize; i++)
-               outb(indata[i], EC_ADDR_PARAM + i);
-
-       outb(command, EC_ADDR_CMD);
-
-       if (wait_for_ec()) {
-               printk(KERN_ERR "Timeout waiting for EC response\n");
-               return -1;
-       }
-
-       /* Check result */
-       i = inb(EC_ADDR_DATA);
-       if (i) {
-               printk(KERN_ERR "EC returned error result code %d\n", i);
-               return i;
-       }
-
-       /* Read data, if any */
-       /* TODO: optimized copy using outl() */
-       for (i = 0; i < outsize; i++)
-               outdata[i] = inb(EC_ADDR_PARAM + i);
-
-       return 0;
-}
+/* Keyboard Backlight ACPI Device must be defined in firmware */
+#define ACPI_KEYBOARD_BACKLIGHT_DEVICE "\\_SB.KBLT"
+#define ACPI_KEYBOARD_BACKLIGHT_READ   ACPI_KEYBOARD_BACKLIGHT_DEVICE ".KBQC"
+#define ACPI_KEYBOARD_BACKLIGHT_WRITE  ACPI_KEYBOARD_BACKLIGHT_DEVICE ".KBCM"
 
 static int keyboard_backlight_update_status(struct backlight_device *device)
 {
-       u8 brightness = device->props.brightness;
-       int ret = ec_command(EC_CMD_PWM_SET_KEYBOARD_BACKLIGHT, &brightness,
-                            sizeof(brightness), NULL, 0);
-       if (ret) {
-               printk(KERN_ERR "Error setting keyboard backlight value.");
-               return ret;
+       union acpi_object param;
+       struct acpi_object_list input;
+       acpi_status status;
+
+       param.type = ACPI_TYPE_INTEGER;
+       param.integer.value = (u8)device->props.brightness;
+       input.count = 1;
+       input.pointer = &param;
+
+       status = acpi_evaluate_object(NULL, ACPI_KEYBOARD_BACKLIGHT_WRITE,
+                                     &input, NULL);
+       if (ACPI_FAILURE(status)) {
+               dev_err(&device->dev, "Error setting keyboard backlight value");
+               return -1;
        }
        return 0;
 }
 
 static int keyboard_backlight_get_brightness(struct backlight_device *device)
 {
-       u8 brightness;
-       int ret = ec_command(EC_CMD_PWM_GET_KEYBOARD_BACKLIGHT, NULL, 0,
-                            &brightness, sizeof(brightness));
-       if (ret) {
-               printk(KERN_ERR "Error reading keyboard backlight value.");
+       unsigned long long brightness;
+       acpi_status status;
+
+       status = acpi_evaluate_integer(NULL, ACPI_KEYBOARD_BACKLIGHT_READ,
+                                      NULL, &brightness);
+
+       if (ACPI_FAILURE(status)) {
+               dev_err(&device->dev, "Error reading keyboard backlight value");
                return -1;
        }
-       return brightness;
+       return (int)brightness;
 }
 
 static const struct backlight_ops keyboard_backlight_ops = {
@@ -144,6 +80,18 @@ static int keyboard_backlight_probe(struct platform_device *pdev)
        struct backlight_properties props = {
                .type = BACKLIGHT_FIRMWARE, .max_brightness = 100
        };
+       acpi_handle handle;
+       acpi_status status;
+
+       /* Look for the keyboard backlight ACPI Device */
+       status = acpi_get_handle(ACPI_ROOT_OBJECT,
+                                ACPI_KEYBOARD_BACKLIGHT_DEVICE,
+                                &handle);
+       if (ACPI_FAILURE(status)) {
+               dev_err(&pdev->dev, "Unable fo find ACPI device %s\n",
+                       ACPI_KEYBOARD_BACKLIGHT_DEVICE);
+               return -ENODEV;
+       }
 
        bl = backlight_device_register("keyboard_backlight", &pdev->dev, NULL,
                                       &keyboard_backlight_ops, &props);