ACPI: ibm-acpi: Implement direct-ec-access thermal reading modes for up to 16 sensors
[powerpc.git] / drivers / acpi / ibm_acpi.c
index 15fc124..1703c61 100644 (file)
@@ -78,7 +78,9 @@
 #include <linux/init.h>
 #include <linux/types.h>
 #include <linux/proc_fs.h>
+#include <linux/backlight.h>
 #include <asm/uaccess.h>
+#include <linux/dmi.h>
 
 #include <acpi/acpi_drivers.h>
 #include <acpi/acnamesp.h>
@@ -216,6 +218,21 @@ IBM_HANDLE(sfan, ec, "SFAN",       /* 570 */
 #define IBM_HKEY_HID   "IBM0068"
 #define IBM_PCI_HID    "PNP0A03"
 
+enum thermal_access_mode {
+       IBMACPI_THERMAL_NONE = 0,       /* No thermal support */
+       IBMACPI_THERMAL_ACPI_TMP07,     /* Use ACPI TMP0-7 */
+       IBMACPI_THERMAL_ACPI_UPDT,      /* Use ACPI TMP0-7 with UPDT */
+       IBMACPI_THERMAL_TPEC_8,         /* Use ACPI EC regs, 8 sensors */
+       IBMACPI_THERMAL_TPEC_16,        /* Use ACPI EC regs, 16 sensors */
+};
+
+#define IBMACPI_MAX_THERMAL_SENSORS 16 /* Max thermal sensors supported */
+struct ibm_thermal_sensors_struct {
+       s32 temp[IBMACPI_MAX_THERMAL_SENSORS];
+};
+
+static int ibm_thinkpad_ec_found;
+
 struct ibm_struct {
        char *name;
        char param[32];
@@ -243,6 +260,8 @@ struct ibm_struct {
 
 static struct proc_dir_entry *proc_dir = NULL;
 
+static struct backlight_device *ibm_backlight_device;
+
 #define onoff(status,bit) ((status) & (1 << (bit)) ? "on" : "off")
 #define enabled(status,bit) ((status) & (1 << (bit)) ? "enabled" : "disabled")
 #define strlencmp(a,b) (strncmp((a), (b), strlen(b)))
@@ -581,8 +600,7 @@ static int wan_status(void)
 {
        int status;
 
-       if (!wan_supported ||
-           !acpi_evalf(hkey_handle, &status, "GWAN", "d"))
+       if (!wan_supported || !acpi_evalf(hkey_handle, &status, "GWAN", "d"))
                status = 0;
 
        return status;
@@ -907,6 +925,7 @@ static int _sta(acpi_handle handle)
 
        return status;
 }
+
 #ifdef CONFIG_ACPI_IBM_DOCK
 #define dock_docked() (_sta(dock_handle) & 1)
 
@@ -1272,50 +1291,142 @@ static int acpi_ec_write(int i, u8 v)
        return 1;
 }
 
-static int thermal_tmp_supported;
-static int thermal_updt_supported;
+static enum thermal_access_mode thermal_read_mode;
 
 static int thermal_init(void)
 {
-       /* temperatures not supported on 570, G4x, R30, R31, R32 */
-       thermal_tmp_supported = acpi_evalf(ec_handle, NULL, "TMP7", "qv");
+       u8 t, ta1, ta2;
+       int i;
+       int acpi_tmp7 = acpi_evalf(ec_handle, NULL, "TMP7", "qv");
 
-       /* 600e/x, 770e, 770x */
-       thermal_updt_supported = acpi_evalf(ec_handle, NULL, "UPDT", "qv");
+       if (ibm_thinkpad_ec_found && experimental) {
+               /*
+                * Direct EC access mode: sensors at registers
+                * 0x78-0x7F, 0xC0-0xC7.  Registers return 0x00 for
+                * non-implemented, thermal sensors return 0x80 when
+                * not available
+                */
+
+               ta1 = ta2 = 0;
+               for (i = 0; i < 8; i++) {
+                       if (likely(acpi_ec_read(0x78 + i, &t))) {
+                               ta1 |= t;
+                       } else {
+                               ta1 = 0;
+                               break;
+                       }
+                       if (likely(acpi_ec_read(0xC0 + i, &t))) {
+                               ta2 |= t;
+                       } else {
+                               ta1 = 0;
+                               break;
+                       }
+               }
+               if (ta1 == 0) {
+                       /* This is sheer paranoia, but we handle it anyway */
+                       if (acpi_tmp7) {
+                               printk(IBM_ERR
+                                      "ThinkPad ACPI EC access misbehaving, "
+                                      "falling back to ACPI TMPx access mode\n");
+                               thermal_read_mode = IBMACPI_THERMAL_ACPI_TMP07;
+                       } else {
+                               printk(IBM_ERR
+                                      "ThinkPad ACPI EC access misbehaving, "
+                                      "disabling thermal sensors access\n");
+                               thermal_read_mode = IBMACPI_THERMAL_NONE;
+                       }
+               } else {
+                       thermal_read_mode =
+                           (ta2 != 0) ?
+                           IBMACPI_THERMAL_TPEC_16 : IBMACPI_THERMAL_TPEC_8;
+               }
+       } else if (acpi_tmp7) {
+               if (acpi_evalf(ec_handle, NULL, "UPDT", "qv")) {
+                       /* 600e/x, 770e, 770x */
+                       thermal_read_mode = IBMACPI_THERMAL_ACPI_UPDT;
+               } else {
+                       /* Standard ACPI TMPx access, max 8 sensors */
+                       thermal_read_mode = IBMACPI_THERMAL_ACPI_TMP07;
+               }
+       } else {
+               /* temperatures not supported on 570, G4x, R30, R31, R32 */
+               thermal_read_mode = IBMACPI_THERMAL_NONE;
+       }
 
        return 0;
 }
 
-static int thermal_read(char *p)
+static int thermal_get_sensors(struct ibm_thermal_sensors_struct *s)
 {
-       int len = 0;
+       int i, t;
+       s8 tmp;
+       char tmpi[] = "TMPi";
 
-       if (!thermal_tmp_supported)
-               len += sprintf(p + len, "temperatures:\tnot supported\n");
-       else {
-               int i, t;
-               char tmpi[] = "TMPi";
-               s8 tmp[8];
+       if (!s)
+               return -EINVAL;
 
-               if (thermal_updt_supported)
-                       if (!acpi_evalf(ec_handle, NULL, "UPDT", "v"))
+       switch (thermal_read_mode) {
+#if IBMACPI_MAX_THERMAL_SENSORS >= 16
+       case IBMACPI_THERMAL_TPEC_16:
+               for (i = 0; i < 8; i++) {
+                       if (!acpi_ec_read(0xC0 + i, &tmp))
+                               return -EIO;
+                       s->temp[i + 8] = tmp * 1000;
+               }
+               /* fallthrough */
+#endif
+       case IBMACPI_THERMAL_TPEC_8:
+               for (i = 0; i < 8; i++) {
+                       if (!acpi_ec_read(0x78 + i, &tmp))
                                return -EIO;
+                       s->temp[i] = tmp * 1000;
+               }
+               return (thermal_read_mode == IBMACPI_THERMAL_TPEC_16) ? 16 : 8;
 
+       case IBMACPI_THERMAL_ACPI_UPDT:
+               if (!acpi_evalf(ec_handle, NULL, "UPDT", "v"))
+                       return -EIO;
                for (i = 0; i < 8; i++) {
                        tmpi[3] = '0' + i;
                        if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
                                return -EIO;
-                       if (thermal_updt_supported)
-                               tmp[i] = (t - 2732 + 5) / 10;
-                       else
-                               tmp[i] = t;
+                       s->temp[i] = (t - 2732) * 100;
                }
+               return 8;
 
-               len += sprintf(p + len,
-                              "temperatures:\t%d %d %d %d %d %d %d %d\n",
-                              tmp[0], tmp[1], tmp[2], tmp[3],
-                              tmp[4], tmp[5], tmp[6], tmp[7]);
+       case IBMACPI_THERMAL_ACPI_TMP07:
+               for (i = 0; i < 8; i++) {
+                       tmpi[3] = '0' + i;
+                       if (!acpi_evalf(ec_handle, &t, tmpi, "d"))
+                               return -EIO;
+                       s->temp[i] = t * 1000;
+               }
+               return 8;
+
+       case IBMACPI_THERMAL_NONE:
+       default:
+               return 0;
        }
+}
+
+static int thermal_read(char *p)
+{
+       int len = 0;
+       int n, i;
+       struct ibm_thermal_sensors_struct t;
+
+       n = thermal_get_sensors(&t);
+       if (unlikely(n < 0))
+               return n;
+
+       len += sprintf(p + len, "temperatures:\t");
+
+       if (n > 0) {
+               for (i = 0; i < (n - 1); i++)
+                       len += sprintf(p + len, "%d ", t.temp[i] / 1000);
+               len += sprintf(p + len, "%d\n", t.temp[i] / 1000);
+       } else
+               len += sprintf(p + len, "not supported\n");
 
        return len;
 }
@@ -1381,12 +1492,22 @@ static int ecdump_write(char *buf)
 
 static int brightness_offset = 0x31;
 
+static int brightness_get(struct backlight_device *bd)
+{
+       u8 level;
+       if (!acpi_ec_read(brightness_offset, &level))
+               return -EIO;
+
+       level &= 0x7;
+       return level;
+}
+
 static int brightness_read(char *p)
 {
        int len = 0;
-       u8 level;
+       int level;
 
-       if (!acpi_ec_read(brightness_offset, &level)) {
+       if ((level = brightness_get(NULL)) < 0) {
                len += sprintf(p + len, "level:\t\tunreadable\n");
        } else {
                len += sprintf(p + len, "level:\t\t%d\n", level & 0x7);
@@ -1401,16 +1522,34 @@ static int brightness_read(char *p)
 #define BRIGHTNESS_UP  4
 #define BRIGHTNESS_DOWN        5
 
-static int brightness_write(char *buf)
+static int brightness_set(int value)
 {
        int cmos_cmd, inc, i;
-       u8 level;
+       int current_value = brightness_get(NULL);
+
+       value &= 7;
+
+       cmos_cmd = value > current_value ? BRIGHTNESS_UP : BRIGHTNESS_DOWN;
+       inc = value > current_value ? 1 : -1;
+       for (i = current_value; i != value; i += inc) {
+               if (!cmos_eval(cmos_cmd))
+                       return -EIO;
+               if (!acpi_ec_write(brightness_offset, i + inc))
+                       return -EIO;
+       }
+
+       return 0;
+}
+
+static int brightness_write(char *buf)
+{
+       int level;
        int new_level;
        char *cmd;
 
        while ((cmd = next_cmd(&buf))) {
-               if (!acpi_ec_read(brightness_offset, &level))
-                       return -EIO;
+               if ((level = brightness_get(NULL)) < 0)
+                       return level;
                level &= 7;
 
                if (strlencmp(cmd, "up") == 0) {
@@ -1423,19 +1562,17 @@ static int brightness_write(char *buf)
                } else
                        return -EINVAL;
 
-               cmos_cmd = new_level > level ? BRIGHTNESS_UP : BRIGHTNESS_DOWN;
-               inc = new_level > level ? 1 : -1;
-               for (i = level; i != new_level; i += inc) {
-                       if (!cmos_eval(cmos_cmd))
-                               return -EIO;
-                       if (!acpi_ec_write(brightness_offset, i + inc))
-                               return -EIO;
-               }
+               brightness_set(new_level);
        }
 
        return 0;
 }
 
+static int brightness_update_status(struct backlight_device *bd)
+{
+       return brightness_set(bd->props->brightness);
+}
+
 static int volume_offset = 0x30;
 
 static int volume_read(char *p)
@@ -1702,13 +1839,11 @@ static struct ibm_struct ibms[] = {
         .name = "brightness",
         .read = brightness_read,
         .write = brightness_write,
-        .experimental = 1,
         },
        {
         .name = "volume",
         .read = volume_read,
         .write = volume_write,
-        .experimental = 1,
         },
        {
         .name = "fan",
@@ -1827,7 +1962,7 @@ static int __init register_driver(struct ibm_struct *ibm)
        }
 
        memset(ibm->driver, 0, sizeof(struct acpi_driver));
-       sprintf(ibm->driver->name, "%s/%s", IBM_NAME, ibm->name);
+       sprintf(ibm->driver->name, "%s_%s", IBM_NAME, ibm->name);
        ibm->driver->ids = ibm->hid;
        ibm->driver->ops.add = &ibm_device_add;
 
@@ -1965,16 +2100,44 @@ IBM_PARAM(brightness);
 IBM_PARAM(volume);
 IBM_PARAM(fan);
 
+static struct backlight_properties ibm_backlight_data = {
+       .owner = THIS_MODULE,
+       .get_brightness = brightness_get,
+       .update_status = brightness_update_status,
+       .max_brightness = 7,
+};
+
 static void acpi_ibm_exit(void)
 {
        int i;
 
+       if (ibm_backlight_device)
+               backlight_device_unregister(ibm_backlight_device);
+
        for (i = ARRAY_SIZE(ibms) - 1; i >= 0; i--)
                ibm_exit(&ibms[i]);
 
        remove_proc_entry(IBM_DIR, acpi_root_dir);
 }
 
+static int __init check_dmi_for_ec(void)
+{
+       struct dmi_device *dev = NULL;
+
+       /*
+        * ThinkPad T23 or newer, A31 or newer, R50e or newer,
+        * X32 or newer, all Z series;  Some models must have an
+        * up-to-date BIOS or they will not be detected.
+        *
+        * See http://thinkwiki.org/wiki/List_of_DMI_IDs
+        */
+       while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) {
+               if (strstr(dev->name, "IBM ThinkPad Embedded Controller"))
+                       return 1;
+       }
+       return 0;
+}
+
 static int __init acpi_ibm_init(void)
 {
        int ret, i;
@@ -1994,6 +2157,9 @@ static int __init acpi_ibm_init(void)
                return -ENODEV;
        }
 
+       /* Models with newer firmware report the EC in DMI */
+       ibm_thinkpad_ec_found = check_dmi_for_ec();
+
        /* these handles are not required */
        IBM_HANDLE_INIT(vid);
        IBM_HANDLE_INIT(vid2);
@@ -2036,6 +2202,14 @@ static int __init acpi_ibm_init(void)
                }
        }
 
+       ibm_backlight_device = backlight_device_register("ibm", NULL,
+                                                        &ibm_backlight_data);
+       if (IS_ERR(ibm_backlight_device)) {
+               printk(IBM_ERR "Could not register ibm backlight device\n");
+               ibm_backlight_device = NULL;
+               acpi_ibm_exit();
+       }
+
        return 0;
 }