powerpc/windfarm: Add ad7417 sensor
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>
Wed, 18 Apr 2012 22:16:52 +0000 (22:16 +0000)
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>
Mon, 30 Apr 2012 05:37:21 +0000 (15:37 +1000)
For user by the upcoming windfarm_pm72 and windfarm_rm31

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
drivers/macintosh/windfarm_ad7417_sensor.c [new file with mode: 0644]

diff --git a/drivers/macintosh/windfarm_ad7417_sensor.c b/drivers/macintosh/windfarm_ad7417_sensor.c
new file mode 100644 (file)
index 0000000..ac3f243
--- /dev/null
@@ -0,0 +1,347 @@
+/*
+ * Windfarm PowerMac thermal control. AD7417 sensors
+ *
+ * Copyright 2012 Benjamin Herrenschmidt, IBM Corp.
+ *
+ * Released under the term of the GNU GPL v2.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/i2c.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/sections.h>
+
+#include "windfarm.h"
+#include "windfarm_mpu.h"
+
+#define VERSION "1.0"
+
+struct wf_ad7417_priv {
+       struct kref             ref;
+       struct i2c_client       *i2c;
+       u8                      config;
+       u8                      cpu;
+       const struct mpu_data   *mpu;
+       struct wf_sensor        sensors[5];
+       struct mutex            lock;
+};
+
+static int wf_ad7417_temp_get(struct wf_sensor *sr, s32 *value)
+{
+       struct wf_ad7417_priv *pv = sr->priv;
+       u8 buf[2];
+       s16 raw;
+       int rc;
+
+       *value = 0;
+       mutex_lock(&pv->lock);
+
+       /* Read temp register */
+       buf[0] = 0;
+       rc = i2c_master_send(pv->i2c, buf, 1);
+       if (rc < 0)
+               goto error;
+       rc = i2c_master_recv(pv->i2c, buf, 2);
+       if (rc < 0)
+               goto error;
+
+       /* Read a a 16-bit signed value */
+       raw = be16_to_cpup((__le16 *)buf);
+
+       /* Convert 8.8-bit to 16.16 fixed point */
+       *value = ((s32)raw) << 8;
+
+       mutex_unlock(&pv->lock);
+       return 0;
+
+error:
+       mutex_unlock(&pv->lock);
+       return -1;
+}
+
+/*
+ * Scaling factors for the AD7417 ADC converters (except
+ * for the CPU diode which is obtained from the EEPROM).
+ * Those values are obtained from the property list of
+ * the darwin driver
+ */
+#define ADC_12V_CURRENT_SCALE  0x0320  /* _AD2 */
+#define ADC_CPU_VOLTAGE_SCALE  0x00a0  /* _AD3 */
+#define ADC_CPU_CURRENT_SCALE  0x1f40  /* _AD4 */
+
+static void wf_ad7417_adc_convert(struct wf_ad7417_priv *pv,
+                                 int chan, s32 raw, s32 *value)
+{
+       switch(chan) {
+       case 1: /* Diode */
+               *value = (raw * (s32)pv->mpu->mdiode +
+                       ((s32)pv->mpu->bdiode << 12)) >> 2;
+               break;
+       case 2: /* 12v current */
+               *value = raw * ADC_12V_CURRENT_SCALE;
+               break;
+       case 3: /* core voltage */
+               *value = raw * ADC_CPU_VOLTAGE_SCALE;
+               break;
+       case 4: /* core current */
+               *value = raw * ADC_CPU_CURRENT_SCALE;
+               break;
+       }
+}
+
+static int wf_ad7417_adc_get(struct wf_sensor *sr, s32 *value)
+{
+       struct wf_ad7417_priv *pv = sr->priv;
+       int chan = sr - pv->sensors;
+       int i, rc;
+       u8 buf[2];
+       u16 raw;
+
+       *value = 0;
+       mutex_lock(&pv->lock);
+       for (i = 0; i < 10; i++) {
+               /* Set channel */
+               buf[0] = 1;
+               buf[1] = (pv->config & 0x1f) | (chan << 5);
+               rc = i2c_master_send(pv->i2c, buf, 2);
+               if (rc < 0)
+                       goto error;
+
+               /* Wait for conversion */
+               msleep(1);
+
+               /* Switch to data register */
+               buf[0] = 4;
+               rc = i2c_master_send(pv->i2c, buf, 1);
+               if (rc < 0)
+                       goto error;
+
+               /* Read result */
+               rc = i2c_master_recv(pv->i2c, buf, 2);
+               if (rc < 0)
+                       goto error;
+
+               /* Read a a 16-bit signed value */
+               raw = be16_to_cpup((__le16 *)buf) >> 6;
+               wf_ad7417_adc_convert(pv, chan, raw, value);
+
+               dev_vdbg(&pv->i2c->dev, "ADC chan %d [%s]"
+                        " raw value: 0x%x, conv to: 0x%08x\n",
+                        chan, sr->name, raw, *value);
+
+               mutex_unlock(&pv->lock);
+               return 0;
+
+       error:
+               dev_dbg(&pv->i2c->dev,
+                         "Error reading ADC, try %d...\n", i);
+               if (i < 9)
+                       msleep(10);
+       }
+       mutex_unlock(&pv->lock);
+       return -1;
+}
+
+static void wf_ad7417_release(struct kref *ref)
+{
+       struct wf_ad7417_priv *pv = container_of(ref,
+                                                struct wf_ad7417_priv, ref);
+       kfree(pv);
+}
+
+static void wf_ad7417_sensor_release(struct wf_sensor *sr)
+{
+       struct wf_ad7417_priv *pv = sr->priv;
+
+       kfree(sr->name);
+       kref_put(&pv->ref, wf_ad7417_release);
+}
+
+static const struct wf_sensor_ops wf_ad7417_temp_ops = {
+       .get_value      = wf_ad7417_temp_get,
+       .release        = wf_ad7417_sensor_release,
+       .owner          = THIS_MODULE,
+};
+
+static const struct wf_sensor_ops wf_ad7417_adc_ops = {
+       .get_value      = wf_ad7417_adc_get,
+       .release        = wf_ad7417_sensor_release,
+       .owner          = THIS_MODULE,
+};
+
+static void __devinit wf_ad7417_add_sensor(struct wf_ad7417_priv *pv,
+                                          int index, const char *name,
+                                          const struct wf_sensor_ops *ops)
+{
+       pv->sensors[index].name = kasprintf(GFP_KERNEL, "%s-%d", name, pv->cpu);
+       pv->sensors[index].priv = pv;
+       pv->sensors[index].ops = ops;
+       if (!wf_register_sensor(&pv->sensors[index]))
+               kref_get(&pv->ref);
+}
+
+static void __devinit wf_ad7417_init_chip(struct wf_ad7417_priv *pv)
+{
+       int rc;
+       u8 buf[2];
+       u8 config = 0;
+
+       /*
+        * Read ADC the configuration register and cache it. We
+        * also make sure Config2 contains proper values, I've seen
+        * cases where we got stale grabage in there, thus preventing
+        * proper reading of conv. values
+        */
+
+       /* Clear Config2 */
+       buf[0] = 5;
+       buf[1] = 0;
+       i2c_master_send(pv->i2c, buf, 2);
+
+       /* Read & cache Config1 */
+       buf[0] = 1;
+       rc = i2c_master_send(pv->i2c, buf, 1);
+       if (rc > 0) {
+               rc = i2c_master_recv(pv->i2c, buf, 1);
+               if (rc > 0) {
+                       config = buf[0];
+
+                       dev_dbg(&pv->i2c->dev, "ADC config reg: %02x\n",
+                               config);
+
+                       /* Disable shutdown mode */
+                       config &= 0xfe;
+                       buf[0] = 1;
+                       buf[1] = config;
+                       rc = i2c_master_send(pv->i2c, buf, 2);
+               }
+       }
+       if (rc <= 0)
+               dev_err(&pv->i2c->dev, "Error reading ADC config\n");
+
+       pv->config = config;
+}
+
+static int __devinit wf_ad7417_probe(struct i2c_client *client,
+                                    const struct i2c_device_id *id)
+{
+       struct wf_ad7417_priv *pv;
+       const struct mpu_data *mpu;
+       const char *loc;
+       int cpu_nr;
+
+       loc = of_get_property(client->dev.of_node, "hwsensor-location", NULL);
+       if (!loc) {
+               dev_warn(&client->dev, "Missing hwsensor-location property!\n");
+               return -ENXIO;
+       }
+
+       /*
+        * Identify which CPU we belong to by looking at the first entry
+        * in the hwsensor-location list
+        */
+       if (!strncmp(loc, "CPU A", 5))
+               cpu_nr = 0;
+       else if (!strncmp(loc, "CPU B", 5))
+               cpu_nr = 1;
+       else {
+               pr_err("wf_ad7417: Can't identify location %s\n", loc);
+               return -ENXIO;
+       }
+       mpu = wf_get_mpu(cpu_nr);
+       if (!mpu) {
+               dev_err(&client->dev, "Failed to retrieve MPU data\n");
+               return -ENXIO;
+       }
+
+       pv = kzalloc(sizeof(struct wf_ad7417_priv), GFP_KERNEL);
+       if (pv == NULL)
+               return -ENODEV;
+
+       kref_init(&pv->ref);
+       mutex_init(&pv->lock);
+       pv->i2c = client;
+       pv->cpu = cpu_nr;
+       pv->mpu = mpu;
+       dev_set_drvdata(&client->dev, pv);
+
+       /* Initialize the chip */
+       wf_ad7417_init_chip(pv);
+
+       /*
+        * We cannot rely on Apple device-tree giving us child
+        * node with the names of the individual sensors so we
+        * just hard code what we know about them
+        */
+       wf_ad7417_add_sensor(pv, 0, "cpu-amb-temp", &wf_ad7417_temp_ops);
+       wf_ad7417_add_sensor(pv, 1, "cpu-diode-temp", &wf_ad7417_adc_ops);
+       wf_ad7417_add_sensor(pv, 2, "cpu-12v-current", &wf_ad7417_adc_ops);
+       wf_ad7417_add_sensor(pv, 3, "cpu-voltage", &wf_ad7417_adc_ops);
+       wf_ad7417_add_sensor(pv, 4, "cpu-current", &wf_ad7417_adc_ops);
+
+       return 0;
+}
+
+static int __devexit wf_ad7417_remove(struct i2c_client *client)
+{
+       struct wf_ad7417_priv *pv = dev_get_drvdata(&client->dev);
+       int i;
+
+       /* Mark client detached */
+       pv->i2c = NULL;
+
+       /* Release sensor */
+       for (i = 0; i < 5; i++)
+               wf_unregister_sensor(&pv->sensors[i]);
+
+       kref_put(&pv->ref, wf_ad7417_release);
+
+       return 0;
+}
+
+static const struct i2c_device_id wf_ad7417_id[] = {
+       { "MAC,ad7417", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, wf_ad7417_id);
+
+static struct i2c_driver wf_ad7417_driver = {
+       .driver = {
+               .name   = "wf_ad7417",
+       },
+       .probe          = wf_ad7417_probe,
+       .remove         = wf_ad7417_remove,
+       .id_table       = wf_ad7417_id,
+};
+
+static int __devinit wf_ad7417_init(void)
+{
+       /* This is only supported on these machines */
+       if (!of_machine_is_compatible("PowerMac7,2") &&
+           !of_machine_is_compatible("PowerMac7,3") &&
+           !of_machine_is_compatible("RackMac3,1"))
+               return -ENODEV;
+
+       return i2c_add_driver(&wf_ad7417_driver);
+}
+
+static void __devexit wf_ad7417_exit(void)
+{
+       i2c_del_driver(&wf_ad7417_driver);
+}
+
+module_init(wf_ad7417_init);
+module_exit(wf_ad7417_exit);
+
+MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
+MODULE_DESCRIPTION("ad7417 sensor driver for PowerMacs");
+MODULE_LICENSE("GPL");
+