thinkpad-acpi: rework brightness support
authorHenrique de Moraes Holschuh <hmh@hmh.eng.br>
Sat, 4 Apr 2009 04:25:53 +0000 (04:25 +0000)
committerLen Brown <len.brown@intel.com>
Sat, 4 Apr 2009 07:14:53 +0000 (03:14 -0400)
Refactor and redesign the brightness control backend...

In order to fix bugzilla #11750...

Add a new brightness control mode: support direct NVRAM checkpointing
of the backlight level (i.e. store directly to NVRAM without the need
for UCMS calls), and use that together with the EC-based control.
Disallow UCMS+EC, thus avoiding races with the SMM firmware.

Switch the models that define HBRV (EC Brightness Value) in the DSDT
to the new mode.  These are: T40-T43, R50-R52, R50e, R51e, X31-X41.

Change the default for all other IBM ThinkPads to UCMS-only.  The
Lenovo models already default to UCMS-only.

Reported-by: Alexey Fisher <bug-track@fisher-privat.net>
Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: Len Brown <len.brown@intel.com>
Documentation/laptops/thinkpad-acpi.txt
drivers/platform/x86/thinkpad_acpi.c

index 25ed43d..3d76507 100644 (file)
@@ -1157,10 +1157,15 @@ display backlight brightness control methods have 16 levels, ranging
 from 0 to 15.
 
 There are two interfaces to the firmware for direct brightness control,
-EC and CMOS.  To select which one should be used, use the
+EC and UCMS (or CMOS).  To select which one should be used, use the
 brightness_mode module parameter: brightness_mode=1 selects EC mode,
-brightness_mode=2 selects CMOS mode, brightness_mode=3 selects both EC
-and CMOS.  The driver tries to auto-detect which interface to use.
+brightness_mode=2 selects UCMS mode, brightness_mode=3 selects EC
+mode with NVRAM backing (so that brightness changes are remembered
+across shutdown/reboot).
+
+The driver tries to select which interface to use from a table of
+defaults for each ThinkPad model.  If it makes a wrong choice, please
+report this as a bug, so that we can fix it.
 
 When display backlight brightness controls are available through the
 standard ACPI interface, it is best to use it instead of this direct
@@ -1498,6 +1503,7 @@ to enable more than one output class, just add their values.
                                (bluetooth, WWAN, UWB...)
        0x0008                  HKEY event interface, hotkeys
        0x0010                  Fan control
+       0x0020                  Backlight brightness
 
 There is also a kernel build option to enable more debugging
 information, which may be necessary to debug driver problems.
index 4eec770..ba3682c 100644 (file)
@@ -192,6 +192,7 @@ enum {
 #define TPACPI_DBG_RFKILL      0x0004
 #define TPACPI_DBG_HKEY                0x0008
 #define TPACPI_DBG_FAN         0x0010
+#define TPACPI_DBG_BRGHT       0x0020
 
 #define onoff(status, bit) ((status) & (1 << (bit)) ? "on" : "off")
 #define enabled(status, bit) ((status) & (1 << (bit)) ? "enabled" : "disabled")
@@ -274,7 +275,6 @@ static struct {
 
 static struct {
        u16 hotkey_mask_ff:1;
-       u16 bright_cmos_ec_unsync:1;
 } tp_warned;
 
 struct thinkpad_id_data {
@@ -5526,6 +5526,20 @@ static struct ibm_struct ecdump_driver_data = {
 
 #define TPACPI_BACKLIGHT_DEV_NAME "thinkpad_screen"
 
+/*
+ * ThinkPads can read brightness from two places: EC HBRV (0x31), or
+ * CMOS NVRAM byte 0x5E, bits 0-3.
+ *
+ * EC HBRV (0x31) has the following layout
+ *   Bit 7: unknown function
+ *   Bit 6: unknown function
+ *   Bit 5: Z: honour scale changes, NZ: ignore scale changes
+ *   Bit 4: must be set to zero to avoid problems
+ *   Bit 3-0: backlight brightness level
+ *
+ * brightness_get_raw returns status data in the HBRV layout
+ */
+
 enum {
        TP_EC_BACKLIGHT = 0x31,
 
@@ -5535,108 +5549,164 @@ enum {
        TP_EC_BACKLIGHT_MAPSW = 0x20,
 };
 
+enum tpacpi_brightness_access_mode {
+       TPACPI_BRGHT_MODE_AUTO = 0,     /* Not implemented yet */
+       TPACPI_BRGHT_MODE_EC,           /* EC control */
+       TPACPI_BRGHT_MODE_UCMS_STEP,    /* UCMS step-based control */
+       TPACPI_BRGHT_MODE_ECNVRAM,      /* EC control w/ NVRAM store */
+       TPACPI_BRGHT_MODE_MAX
+};
+
 static struct backlight_device *ibm_backlight_device;
-static int brightness_mode;
+
+static enum tpacpi_brightness_access_mode brightness_mode =
+               TPACPI_BRGHT_MODE_MAX;
+
 static unsigned int brightness_enable = 2; /* 2 = auto, 0 = no, 1 = yes */
 
 static struct mutex brightness_mutex;
 
-/*
- * ThinkPads can read brightness from two places: EC 0x31, or
- * CMOS NVRAM byte 0x5E, bits 0-3.
- *
- * EC 0x31 has the following layout
- *   Bit 7: unknown function
- *   Bit 6: unknown function
- *   Bit 5: Z: honour scale changes, NZ: ignore scale changes
- *   Bit 4: must be set to zero to avoid problems
- *   Bit 3-0: backlight brightness level
- *
- * brightness_get_raw returns status data in the EC 0x31 layout
- */
-static int brightness_get_raw(int *status)
+/* NVRAM brightness access,
+ * call with brightness_mutex held! */
+static unsigned int tpacpi_brightness_nvram_get(void)
 {
-       u8 lec = 0, lcmos = 0, level = 0;
+       u8 lnvram;
 
-       if (brightness_mode & 1) {
-               if (!acpi_ec_read(TP_EC_BACKLIGHT, &lec))
-                       return -EIO;
-               level = lec & TP_EC_BACKLIGHT_LVLMSK;
-       };
-       if (brightness_mode & 2) {
-               lcmos = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
-                        & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
-                       >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
-               lcmos &= (tp_features.bright_16levels)? 0x0f : 0x07;
-               level = lcmos;
-       }
-
-       if (brightness_mode == 3) {
-               *status = lec;  /* Prefer EC, CMOS is just a backing store */
-               lec &= TP_EC_BACKLIGHT_LVLMSK;
-               if (lec == lcmos)
-                       tp_warned.bright_cmos_ec_unsync = 0;
-               else {
-                       if (!tp_warned.bright_cmos_ec_unsync) {
-                               printk(TPACPI_ERR
-                                       "CMOS NVRAM (%u) and EC (%u) do not "
-                                       "agree on display brightness level\n",
-                                       (unsigned int) lcmos,
-                                       (unsigned int) lec);
-                               tp_warned.bright_cmos_ec_unsync = 1;
-                       }
+       lnvram = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
+                 & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
+                 >> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
+       lnvram &= (tp_features.bright_16levels) ? 0x0f : 0x07;
+
+       return lnvram;
+}
+
+static void tpacpi_brightness_checkpoint_nvram(void)
+{
+       u8 lec = 0;
+       u8 b_nvram;
+
+       if (brightness_mode != TPACPI_BRGHT_MODE_ECNVRAM)
+               return;
+
+       vdbg_printk(TPACPI_DBG_BRGHT,
+               "trying to checkpoint backlight level to NVRAM...\n");
+
+       if (mutex_lock_killable(&brightness_mutex) < 0)
+               return;
+
+       if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec)))
+               goto unlock;
+       lec &= TP_EC_BACKLIGHT_LVLMSK;
+       b_nvram = nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS);
+
+       if (lec != ((b_nvram & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
+                            >> TP_NVRAM_POS_LEVEL_BRIGHTNESS)) {
+               /* NVRAM needs update */
+               b_nvram &= ~(TP_NVRAM_MASK_LEVEL_BRIGHTNESS <<
+                               TP_NVRAM_POS_LEVEL_BRIGHTNESS);
+               b_nvram |= lec;
+               nvram_write_byte(b_nvram, TP_NVRAM_ADDR_BRIGHTNESS);
+               dbg_printk(TPACPI_DBG_BRGHT,
+                          "updated NVRAM backlight level to %u (0x%02x)\n",
+                          (unsigned int) lec, (unsigned int) b_nvram);
+       } else
+               vdbg_printk(TPACPI_DBG_BRGHT,
+                          "NVRAM backlight level already is %u (0x%02x)\n",
+                          (unsigned int) lec, (unsigned int) b_nvram);
+
+unlock:
+       mutex_unlock(&brightness_mutex);
+}
+
+
+/* call with brightness_mutex held! */
+static int tpacpi_brightness_get_raw(int *status)
+{
+       u8 lec = 0;
+
+       switch (brightness_mode) {
+       case TPACPI_BRGHT_MODE_UCMS_STEP:
+               *status = tpacpi_brightness_nvram_get();
+               return 0;
+       case TPACPI_BRGHT_MODE_EC:
+       case TPACPI_BRGHT_MODE_ECNVRAM:
+               if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec)))
                        return -EIO;
-               }
-       } else {
-               *status = level;
+               *status = lec;
+               return 0;
+       default:
+               return -ENXIO;
        }
+}
+
+/* call with brightness_mutex held! */
+/* do NOT call with illegal backlight level value */
+static int tpacpi_brightness_set_ec(unsigned int value)
+{
+       u8 lec = 0;
+
+       if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec)))
+               return -EIO;
+
+       if (unlikely(!acpi_ec_write(TP_EC_BACKLIGHT,
+                               (lec & TP_EC_BACKLIGHT_CMDMSK) |
+                               (value & TP_EC_BACKLIGHT_LVLMSK))))
+               return -EIO;
+
+       return 0;
+}
+
+/* call with brightness_mutex held! */
+static int tpacpi_brightness_set_ucmsstep(unsigned int value)
+{
+       int cmos_cmd, inc;
+       unsigned int current_value, i;
+
+       current_value = tpacpi_brightness_nvram_get();
+
+       if (value == current_value)
+               return 0;
+
+       cmos_cmd = (value > current_value) ?
+                       TP_CMOS_BRIGHTNESS_UP :
+                       TP_CMOS_BRIGHTNESS_DOWN;
+       inc = (value > current_value) ? 1 : -1;
+
+       for (i = current_value; i != value; i += inc)
+               if (issue_thinkpad_cmos_command(cmos_cmd))
+                       return -EIO;
 
        return 0;
 }
 
 /* May return EINTR which can always be mapped to ERESTARTSYS */
-static int brightness_set(int value)
+static int brightness_set(unsigned int value)
 {
-       int cmos_cmd, inc, i, res;
-       int current_value;
-       int command_bits;
+       int res;
 
        if (value > ((tp_features.bright_16levels)? 15 : 7) ||
            value < 0)
                return -EINVAL;
 
+       vdbg_printk(TPACPI_DBG_BRGHT,
+                       "set backlight level to %d\n", value);
+
        res = mutex_lock_killable(&brightness_mutex);
        if (res < 0)
                return res;
 
-       res = brightness_get_raw(&current_value);
-       if (res < 0)
-               goto errout;
-
-       command_bits = current_value & TP_EC_BACKLIGHT_CMDMSK;
-       current_value &= TP_EC_BACKLIGHT_LVLMSK;
-
-       cmos_cmd = value > current_value ?
-                       TP_CMOS_BRIGHTNESS_UP :
-                       TP_CMOS_BRIGHTNESS_DOWN;
-       inc = (value > current_value)? 1 : -1;
-
-       res = 0;
-       for (i = current_value; i != value; i += inc) {
-               if ((brightness_mode & 2) &&
-                   issue_thinkpad_cmos_command(cmos_cmd)) {
-                       res = -EIO;
-                       goto errout;
-               }
-               if ((brightness_mode & 1) &&
-                   !acpi_ec_write(TP_EC_BACKLIGHT,
-                                  (i + inc) | command_bits)) {
-                       res = -EIO;
-                       goto errout;;
-               }
+       switch (brightness_mode) {
+       case TPACPI_BRGHT_MODE_EC:
+       case TPACPI_BRGHT_MODE_ECNVRAM:
+               res = tpacpi_brightness_set_ec(value);
+               break;
+       case TPACPI_BRGHT_MODE_UCMS_STEP:
+               res = tpacpi_brightness_set_ucmsstep(value);
+               break;
+       default:
+               res = -ENXIO;
        }
 
-errout:
        mutex_unlock(&brightness_mutex);
        return res;
 }
@@ -5645,21 +5715,34 @@ errout:
 
 static int brightness_update_status(struct backlight_device *bd)
 {
-       /* it is the backlight class's job (caller) to handle
-        * EINTR and other errors properly */
-       return brightness_set(
+       unsigned int level =
                (bd->props.fb_blank == FB_BLANK_UNBLANK &&
                 bd->props.power == FB_BLANK_UNBLANK) ?
-                               bd->props.brightness : 0);
+                               bd->props.brightness : 0;
+
+       dbg_printk(TPACPI_DBG_BRGHT,
+                       "backlight: attempt to set level to %d\n",
+                       level);
+
+       /* it is the backlight class's job (caller) to handle
+        * EINTR and other errors properly */
+       return brightness_set(level);
 }
 
 static int brightness_get(struct backlight_device *bd)
 {
        int status, res;
 
-       res = brightness_get_raw(&status);
+       res = mutex_lock_killable(&brightness_mutex);
        if (res < 0)
-               return 0; /* FIXME: teach backlight about error handling */
+               return 0;
+
+       res = tpacpi_brightness_get_raw(&status);
+
+       mutex_unlock(&brightness_mutex);
+
+       if (res < 0)
+               return 0;
 
        return status & TP_EC_BACKLIGHT_LVLMSK;
 }
@@ -5709,7 +5792,7 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
        }
 
        if (!brightness_enable) {
-               dbg_printk(TPACPI_DBG_INIT,
+               dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
                           "brightness support disabled by "
                           "module parameter\n");
                return 1;
@@ -5724,20 +5807,38 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
        if (b == 16)
                tp_features.bright_16levels = 1;
 
-       if (!brightness_mode) {
-               if (thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO)
-                       brightness_mode = 2;
-               else
-                       brightness_mode = 3;
+       /*
+        * Check for module parameter bogosity, note that we
+        * init brightness_mode to TPACPI_BRGHT_MODE_MAX in order to be
+        * able to detect "unspecified"
+        */
+       if (brightness_mode > TPACPI_BRGHT_MODE_MAX)
+               return -EINVAL;
 
-               dbg_printk(TPACPI_DBG_INIT, "selected brightness_mode=%d\n",
-                       brightness_mode);
-       }
+       /* TPACPI_BRGHT_MODE_AUTO not implemented yet, just use default */
+       if (brightness_mode == TPACPI_BRGHT_MODE_AUTO ||
+           brightness_mode == TPACPI_BRGHT_MODE_MAX) {
+               if (thinkpad_id.vendor == PCI_VENDOR_ID_IBM) {
+                       /*
+                        * IBM models that define HBRV probably have
+                        * EC-based backlight level control
+                        */
+                       if (acpi_evalf(ec_handle, NULL, "HBRV", "qd"))
+                               /* T40-T43, R50-R52, R50e, R51e, X31-X41 */
+                               brightness_mode = TPACPI_BRGHT_MODE_ECNVRAM;
+                       else
+                               /* all other IBM ThinkPads */
+                               brightness_mode = TPACPI_BRGHT_MODE_UCMS_STEP;
+               } else
+                       /* All Lenovo ThinkPads */
+                       brightness_mode = TPACPI_BRGHT_MODE_UCMS_STEP;
 
-       if (brightness_mode > 3)
-               return -EINVAL;
+               dbg_printk(TPACPI_DBG_BRGHT,
+                          "selected brightness_mode=%d\n",
+                          brightness_mode);
+       }
 
-       if (brightness_get_raw(&b) < 0)
+       if (tpacpi_brightness_get_raw(&b) < 0)
                return 1;
 
        if (tp_features.bright_16levels)
@@ -5751,7 +5852,8 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
                printk(TPACPI_ERR "Could not register backlight device\n");
                return PTR_ERR(ibm_backlight_device);
        }
-       vdbg_printk(TPACPI_DBG_INIT, "brightness is supported\n");
+       vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_BRGHT,
+                       "brightness is supported\n");
 
        ibm_backlight_device->props.max_brightness =
                                (tp_features.bright_16levels)? 15 : 7;
@@ -5761,13 +5863,25 @@ static int __init brightness_init(struct ibm_init_struct *iibm)
        return 0;
 }
 
+static void brightness_suspend(pm_message_t state)
+{
+       tpacpi_brightness_checkpoint_nvram();
+}
+
+static void brightness_shutdown(void)
+{
+       tpacpi_brightness_checkpoint_nvram();
+}
+
 static void brightness_exit(void)
 {
        if (ibm_backlight_device) {
-               vdbg_printk(TPACPI_DBG_EXIT,
+               vdbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_BRGHT,
                            "calling backlight_device_unregister()\n");
                backlight_device_unregister(ibm_backlight_device);
        }
+
+       tpacpi_brightness_checkpoint_nvram();
 }
 
 static int brightness_read(char *p)
@@ -5814,6 +5928,9 @@ static int brightness_write(char *buf)
                        return -EINVAL;
        }
 
+       tpacpi_disclose_usertask("procfs brightness",
+                       "set level to %d\n", level);
+
        /*
         * Now we know what the final level should be, so we try to set it.
         * Doing it this way makes the syscall restartable in case of EINTR
@@ -5827,6 +5944,8 @@ static struct ibm_struct brightness_driver_data = {
        .read = brightness_read,
        .write = brightness_write,
        .exit = brightness_exit,
+       .suspend = brightness_suspend,
+       .shutdown = brightness_shutdown,
 };
 
 /*************************************************************************
@@ -7465,10 +7584,10 @@ module_param_named(fan_control, fan_control_allowed, bool, 0);
 MODULE_PARM_DESC(fan_control,
                 "Enables setting fan parameters features when true");
 
-module_param_named(brightness_mode, brightness_mode, int, 0);
+module_param_named(brightness_mode, brightness_mode, uint, 0);
 MODULE_PARM_DESC(brightness_mode,
                 "Selects brightness control strategy: "
-                "0=auto, 1=EC, 2=CMOS, 3=both");
+                "0=auto, 1=EC, 2=UCMS, 3=EC+NVRAM");
 
 module_param(brightness_enable, uint, 0);
 MODULE_PARM_DESC(brightness_enable,