hwmon: (fschmd) Read voltage scaling factors from BIOS DMI
authorHans de Goede <j.w.r.degoede@hhs.nl>
Thu, 20 Dec 2007 15:42:59 +0000 (16:42 +0100)
committerMark M. Hoffman <mhoffman@lightlink.com>
Fri, 8 Feb 2008 01:39:42 +0000 (20:39 -0500)
This patch adds support to the fschmd driver for reading the voltage scaling
factors from BIOS DMI tables, as specified in the Siemens datasheet.

Signed-off-by: Hans de Goede <j.w.r.degoede@hhs.nl>
Acked-by: Jean Delvare <khali@linux-fr.org>
Signed-off-by: Mark M. Hoffman <mhoffman@lightlink.com>
drivers/hwmon/fschmd.c

index d427ea5623fb2ac24760dd27de0dfbe292939f08..b7c9eef0f9283fe0d77c3b19086dcf8462302d0c 100644 (file)
@@ -41,6 +41,7 @@
 #include <linux/err.h>
 #include <linux/mutex.h>
 #include <linux/sysfs.h>
+#include <linux/dmi.h>
 
 /* Addresses to scan */
 static unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END };
@@ -210,6 +211,13 @@ struct fschmd_data {
        u8 fan_ripple[6];       /* divider for rps */
 };
 
+/* Global variables to hold information read from special DMI tables, which are
+   available on FSC machines with an fscher or later chip. */
+static int dmi_mult[3] = { 490, 200, 100 };
+static int dmi_offset[3] = { 0, 0, 0 };
+static int dmi_vref = -1;
+
+
 /*
  * Sysfs attr show / store functions
  */
@@ -221,8 +229,13 @@ static ssize_t show_in_value(struct device *dev,
        int index = to_sensor_dev_attr(devattr)->index;
        struct fschmd_data *data = fschmd_update_device(dev);
 
-       return sprintf(buf, "%d\n", (data->volt[index] *
-               max_reading[index] + 128) / 255);
+       /* fscher / fschrc - 1 as data->kind is an array index, not a chips */
+       if (data->kind == (fscher - 1) || data->kind >= (fschrc - 1))
+               return sprintf(buf, "%d\n", (data->volt[index] * dmi_vref *
+                       dmi_mult[index]) / 255 + dmi_offset[index]);
+       else
+               return sprintf(buf, "%d\n", (data->volt[index] *
+                       max_reading[index] + 128) / 255);
 }
 
 
@@ -525,6 +538,68 @@ static struct sensor_device_attribute fschmd_fan_attr[] = {
  * Real code
  */
 
+/* DMI decode routine to read voltage scaling factors from special DMI tables,
+   which are available on FSC machines with an fscher or later chip. */
+static void fschmd_dmi_decode(const struct dmi_header *header)
+{
+       int i, mult[3] = { 0 }, offset[3] = { 0 }, vref = 0, found = 0;
+
+       /* dmi code ugliness, we get passed the address of the contents of
+          a complete DMI record, but in the form of a dmi_header pointer, in
+          reality this address holds header->length bytes of which the header
+          are the first 4 bytes */
+       u8 *dmi_data = (u8 *)header;
+
+       /* We are looking for OEM-specific type 185 */
+       if (header->type != 185)
+               return;
+
+       /* we are looking for what Siemens calls "subtype" 19, the subtype
+          is stored in byte 5 of the dmi block */
+       if (header->length < 5 || dmi_data[4] != 19)
+               return;
+
+       /* After the subtype comes 1 unknown byte and then blocks of 5 bytes,
+          consisting of what Siemens calls an "Entity" number, followed by
+          2 16-bit words in LSB first order */
+       for (i = 6; (i + 4) < header->length; i += 5) {
+               /* entity 1 - 3: voltage multiplier and offset */
+               if (dmi_data[i] >= 1 && dmi_data[i] <= 3) {
+                       /* Our in sensors order and the DMI order differ */
+                       const int shuffle[3] = { 1, 0, 2 };
+                       int in = shuffle[dmi_data[i] - 1];
+
+                       /* Check for twice the same entity */
+                       if (found & (1 << in))
+                               return;
+
+                       mult[in] = dmi_data[i + 1] | (dmi_data[i + 2] << 8);
+                       offset[in] = dmi_data[i + 3] | (dmi_data[i + 4] << 8);
+
+                       found |= 1 << in;
+               }
+
+               /* entity 7: reference voltage */
+               if (dmi_data[i] == 7) {
+                       /* Check for twice the same entity */
+                       if (found & 0x08)
+                               return;
+
+                       vref = dmi_data[i + 1] | (dmi_data[i + 2] << 8);
+
+                       found |= 0x08;
+               }
+       }
+
+       if (found == 0x0F) {
+               for (i = 0; i < 3; i++) {
+                       dmi_mult[i] = mult[i] * 10;
+                       dmi_offset[i] = offset[i] * 10;
+               }
+               dmi_vref = vref;
+       }
+}
+
 static int fschmd_detect(struct i2c_adapter *adapter, int address, int kind)
 {
        struct i2c_client *client;
@@ -586,6 +661,17 @@ static int fschmd_detect(struct i2c_adapter *adapter, int address, int kind)
                data->temp_max[2] = 50 + 128;
        }
 
+       /* Read the special DMI table for fscher and newer chips */
+       if (kind == fscher || kind >= fschrc) {
+               dmi_walk(fschmd_dmi_decode);
+               if (dmi_vref == -1) {
+                       printk(KERN_WARNING FSCHMD_NAME
+                               ": Couldn't get voltage scaling factors from "
+                               "BIOS DMI table, using builtin defaults\n");
+                       dmi_vref = 33;
+               }
+       }
+
        /* i2c kind goes from 1-5, we want from 0-4 to address arrays */
        data->kind = kind - 1;
        strlcpy(client->name, client_names[data->kind], I2C_NAME_SIZE);