linux/drivers/macintosh/windfarm_smu_sensors.c
<<
>>
Prefs
   1/*
   2 * Windfarm PowerMac thermal control. SMU based sensors
   3 *
   4 * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
   5 *                    <benh@kernel.crashing.org>
   6 *
   7 * Released under the term of the GNU GPL v2.
   8 */
   9
  10#include <linux/types.h>
  11#include <linux/errno.h>
  12#include <linux/kernel.h>
  13#include <linux/delay.h>
  14#include <linux/slab.h>
  15#include <linux/init.h>
  16#include <linux/wait.h>
  17#include <linux/completion.h>
  18#include <asm/prom.h>
  19#include <asm/machdep.h>
  20#include <asm/io.h>
  21#include <asm/sections.h>
  22#include <asm/smu.h>
  23
  24#include "windfarm.h"
  25
  26#define VERSION "0.2"
  27
  28#undef DEBUG
  29
  30#ifdef DEBUG
  31#define DBG(args...)    printk(args)
  32#else
  33#define DBG(args...)    do { } while(0)
  34#endif
  35
  36/*
  37 * Various SMU "partitions" calibration objects for which we
  38 * keep pointers here for use by bits & pieces of the driver
  39 */
  40static struct smu_sdbp_cpuvcp *cpuvcp;
  41static int  cpuvcp_version;
  42static struct smu_sdbp_cpudiode *cpudiode;
  43static struct smu_sdbp_slotspow *slotspow;
  44static u8 *debugswitches;
  45
  46/*
  47 * SMU basic sensors objects
  48 */
  49
  50static LIST_HEAD(smu_ads);
  51
  52struct smu_ad_sensor {
  53        struct list_head        link;
  54        u32                     reg;            /* index in SMU */
  55        struct wf_sensor        sens;
  56};
  57#define to_smu_ads(c) container_of(c, struct smu_ad_sensor, sens)
  58
  59static void smu_ads_release(struct wf_sensor *sr)
  60{
  61        struct smu_ad_sensor *ads = to_smu_ads(sr);
  62
  63        kfree(ads);
  64}
  65
  66static int smu_read_adc(u8 id, s32 *value)
  67{
  68        struct smu_simple_cmd   cmd;
  69        DECLARE_COMPLETION_ONSTACK(comp);
  70        int rc;
  71
  72        rc = smu_queue_simple(&cmd, SMU_CMD_READ_ADC, 1,
  73                              smu_done_complete, &comp, id);
  74        if (rc)
  75                return rc;
  76        wait_for_completion(&comp);
  77        if (cmd.cmd.status != 0)
  78                return cmd.cmd.status;
  79        if (cmd.cmd.reply_len != 2) {
  80                printk(KERN_ERR "winfarm: read ADC 0x%x returned %d bytes !\n",
  81                       id, cmd.cmd.reply_len);
  82                return -EIO;
  83        }
  84        *value = *((u16 *)cmd.buffer);
  85        return 0;
  86}
  87
  88static int smu_cputemp_get(struct wf_sensor *sr, s32 *value)
  89{
  90        struct smu_ad_sensor *ads = to_smu_ads(sr);
  91        int rc;
  92        s32 val;
  93        s64 scaled;
  94
  95        rc = smu_read_adc(ads->reg, &val);
  96        if (rc) {
  97                printk(KERN_ERR "windfarm: read CPU temp failed, err %d\n",
  98                       rc);
  99                return rc;
 100        }
 101
 102        /* Ok, we have to scale & adjust, taking units into account */
 103        scaled = (s64)(((u64)val) * (u64)cpudiode->m_value);
 104        scaled >>= 3;
 105        scaled += ((s64)cpudiode->b_value) << 9;
 106        *value = (s32)(scaled << 1);
 107
 108        return 0;
 109}
 110
 111static int smu_cpuamp_get(struct wf_sensor *sr, s32 *value)
 112{
 113        struct smu_ad_sensor *ads = to_smu_ads(sr);
 114        s32 val, scaled;
 115        int rc;
 116
 117        rc = smu_read_adc(ads->reg, &val);
 118        if (rc) {
 119                printk(KERN_ERR "windfarm: read CPU current failed, err %d\n",
 120                       rc);
 121                return rc;
 122        }
 123
 124        /* Ok, we have to scale & adjust, taking units into account */
 125        scaled = (s32)(val * (u32)cpuvcp->curr_scale);
 126        scaled += (s32)cpuvcp->curr_offset;
 127        *value = scaled << 4;
 128
 129        return 0;
 130}
 131
 132static int smu_cpuvolt_get(struct wf_sensor *sr, s32 *value)
 133{
 134        struct smu_ad_sensor *ads = to_smu_ads(sr);
 135        s32 val, scaled;
 136        int rc;
 137
 138        rc = smu_read_adc(ads->reg, &val);
 139        if (rc) {
 140                printk(KERN_ERR "windfarm: read CPU voltage failed, err %d\n",
 141                       rc);
 142                return rc;
 143        }
 144
 145        /* Ok, we have to scale & adjust, taking units into account */
 146        scaled = (s32)(val * (u32)cpuvcp->volt_scale);
 147        scaled += (s32)cpuvcp->volt_offset;
 148        *value = scaled << 4;
 149
 150        return 0;
 151}
 152
 153static int smu_slotspow_get(struct wf_sensor *sr, s32 *value)
 154{
 155        struct smu_ad_sensor *ads = to_smu_ads(sr);
 156        s32 val, scaled;
 157        int rc;
 158
 159        rc = smu_read_adc(ads->reg, &val);
 160        if (rc) {
 161                printk(KERN_ERR "windfarm: read slots power failed, err %d\n",
 162                       rc);
 163                return rc;
 164        }
 165
 166        /* Ok, we have to scale & adjust, taking units into account */
 167        scaled = (s32)(val * (u32)slotspow->pow_scale);
 168        scaled += (s32)slotspow->pow_offset;
 169        *value = scaled << 4;
 170
 171        return 0;
 172}
 173
 174
 175static struct wf_sensor_ops smu_cputemp_ops = {
 176        .get_value      = smu_cputemp_get,
 177        .release        = smu_ads_release,
 178        .owner          = THIS_MODULE,
 179};
 180static struct wf_sensor_ops smu_cpuamp_ops = {
 181        .get_value      = smu_cpuamp_get,
 182        .release        = smu_ads_release,
 183        .owner          = THIS_MODULE,
 184};
 185static struct wf_sensor_ops smu_cpuvolt_ops = {
 186        .get_value      = smu_cpuvolt_get,
 187        .release        = smu_ads_release,
 188        .owner          = THIS_MODULE,
 189};
 190static struct wf_sensor_ops smu_slotspow_ops = {
 191        .get_value      = smu_slotspow_get,
 192        .release        = smu_ads_release,
 193        .owner          = THIS_MODULE,
 194};
 195
 196
 197static struct smu_ad_sensor *smu_ads_create(struct device_node *node)
 198{
 199        struct smu_ad_sensor *ads;
 200        const char *c, *l;
 201        const u32 *v;
 202
 203        ads = kmalloc(sizeof(struct smu_ad_sensor), GFP_KERNEL);
 204        if (ads == NULL)
 205                return NULL;
 206        c = of_get_property(node, "device_type", NULL);
 207        l = of_get_property(node, "location", NULL);
 208        if (c == NULL || l == NULL)
 209                goto fail;
 210
 211        /* We currently pick the sensors based on the OF name and location
 212         * properties, while Darwin uses the sensor-id's.
 213         * The problem with the IDs is that they are model specific while it
 214         * looks like apple has been doing a reasonably good job at keeping
 215         * the names and locations consistents so I'll stick with the names
 216         * and locations for now.
 217         */
 218        if (!strcmp(c, "temp-sensor") &&
 219            !strcmp(l, "CPU T-Diode")) {
 220                ads->sens.ops = &smu_cputemp_ops;
 221                ads->sens.name = "cpu-temp";
 222                if (cpudiode == NULL) {
 223                        DBG("wf: cpudiode partition (%02x) not found\n",
 224                            SMU_SDB_CPUDIODE_ID);
 225                        goto fail;
 226                }
 227        } else if (!strcmp(c, "current-sensor") &&
 228                   !strcmp(l, "CPU Current")) {
 229                ads->sens.ops = &smu_cpuamp_ops;
 230                ads->sens.name = "cpu-current";
 231                if (cpuvcp == NULL) {
 232                        DBG("wf: cpuvcp partition (%02x) not found\n",
 233                            SMU_SDB_CPUVCP_ID);
 234                        goto fail;
 235                }
 236        } else if (!strcmp(c, "voltage-sensor") &&
 237                   !strcmp(l, "CPU Voltage")) {
 238                ads->sens.ops = &smu_cpuvolt_ops;
 239                ads->sens.name = "cpu-voltage";
 240                if (cpuvcp == NULL) {
 241                        DBG("wf: cpuvcp partition (%02x) not found\n",
 242                            SMU_SDB_CPUVCP_ID);
 243                        goto fail;
 244                }
 245        } else if (!strcmp(c, "power-sensor") &&
 246                   !strcmp(l, "Slots Power")) {
 247                ads->sens.ops = &smu_slotspow_ops;
 248                ads->sens.name = "slots-power";
 249                if (slotspow == NULL) {
 250                        DBG("wf: slotspow partition (%02x) not found\n",
 251                            SMU_SDB_SLOTSPOW_ID);
 252                        goto fail;
 253                }
 254        } else
 255                goto fail;
 256
 257        v = of_get_property(node, "reg", NULL);
 258        if (v == NULL)
 259                goto fail;
 260        ads->reg = *v;
 261
 262        if (wf_register_sensor(&ads->sens))
 263                goto fail;
 264        return ads;
 265 fail:
 266        kfree(ads);
 267        return NULL;
 268}
 269
 270/*
 271 * SMU Power combo sensor object
 272 */
 273
 274struct smu_cpu_power_sensor {
 275        struct list_head        link;
 276        struct wf_sensor        *volts;
 277        struct wf_sensor        *amps;
 278        int                     fake_volts : 1;
 279        int                     quadratic : 1;
 280        struct wf_sensor        sens;
 281};
 282#define to_smu_cpu_power(c) container_of(c, struct smu_cpu_power_sensor, sens)
 283
 284static struct smu_cpu_power_sensor *smu_cpu_power;
 285
 286static void smu_cpu_power_release(struct wf_sensor *sr)
 287{
 288        struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);
 289
 290        if (pow->volts)
 291                wf_put_sensor(pow->volts);
 292        if (pow->amps)
 293                wf_put_sensor(pow->amps);
 294        kfree(pow);
 295}
 296
 297static int smu_cpu_power_get(struct wf_sensor *sr, s32 *value)
 298{
 299        struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);
 300        s32 volts, amps, power;
 301        u64 tmps, tmpa, tmpb;
 302        int rc;
 303
 304        rc = pow->amps->ops->get_value(pow->amps, &amps);
 305        if (rc)
 306                return rc;
 307
 308        if (pow->fake_volts) {
 309                *value = amps * 12 - 0x30000;
 310                return 0;
 311        }
 312
 313        rc = pow->volts->ops->get_value(pow->volts, &volts);
 314        if (rc)
 315                return rc;
 316
 317        power = (s32)((((u64)volts) * ((u64)amps)) >> 16);
 318        if (!pow->quadratic) {
 319                *value = power;
 320                return 0;
 321        }
 322        tmps = (((u64)power) * ((u64)power)) >> 16;
 323        tmpa = ((u64)cpuvcp->power_quads[0]) * tmps;
 324        tmpb = ((u64)cpuvcp->power_quads[1]) * ((u64)power);
 325        *value = (tmpa >> 28) + (tmpb >> 28) + (cpuvcp->power_quads[2] >> 12);
 326
 327        return 0;
 328}
 329
 330static struct wf_sensor_ops smu_cpu_power_ops = {
 331        .get_value      = smu_cpu_power_get,
 332        .release        = smu_cpu_power_release,
 333        .owner          = THIS_MODULE,
 334};
 335
 336
 337static struct smu_cpu_power_sensor *
 338smu_cpu_power_create(struct wf_sensor *volts, struct wf_sensor *amps)
 339{
 340        struct smu_cpu_power_sensor *pow;
 341
 342        pow = kmalloc(sizeof(struct smu_cpu_power_sensor), GFP_KERNEL);
 343        if (pow == NULL)
 344                return NULL;
 345        pow->sens.ops = &smu_cpu_power_ops;
 346        pow->sens.name = "cpu-power";
 347
 348        wf_get_sensor(volts);
 349        pow->volts = volts;
 350        wf_get_sensor(amps);
 351        pow->amps = amps;
 352
 353        /* Some early machines need a faked voltage */
 354        if (debugswitches && ((*debugswitches) & 0x80)) {
 355                printk(KERN_INFO "windfarm: CPU Power sensor using faked"
 356                       " voltage !\n");
 357                pow->fake_volts = 1;
 358        } else
 359                pow->fake_volts = 0;
 360
 361        /* Try to use quadratic transforms on PowerMac8,1 and 9,1 for now,
 362         * I yet have to figure out what's up with 8,2 and will have to
 363         * adjust for later, unless we can 100% trust the SDB partition...
 364         */
 365        if ((of_machine_is_compatible("PowerMac8,1") ||
 366             of_machine_is_compatible("PowerMac8,2") ||
 367             of_machine_is_compatible("PowerMac9,1")) &&
 368            cpuvcp_version >= 2) {
 369                pow->quadratic = 1;
 370                DBG("windfarm: CPU Power using quadratic transform\n");
 371        } else
 372                pow->quadratic = 0;
 373
 374        if (wf_register_sensor(&pow->sens))
 375                goto fail;
 376        return pow;
 377 fail:
 378        kfree(pow);
 379        return NULL;
 380}
 381
 382static void smu_fetch_param_partitions(void)
 383{
 384        const struct smu_sdbp_header *hdr;
 385
 386        /* Get CPU voltage/current/power calibration data */
 387        hdr = smu_get_sdb_partition(SMU_SDB_CPUVCP_ID, NULL);
 388        if (hdr != NULL) {
 389                cpuvcp = (struct smu_sdbp_cpuvcp *)&hdr[1];
 390                /* Keep version around */
 391                cpuvcp_version = hdr->version;
 392        }
 393
 394        /* Get CPU diode calibration data */
 395        hdr = smu_get_sdb_partition(SMU_SDB_CPUDIODE_ID, NULL);
 396        if (hdr != NULL)
 397                cpudiode = (struct smu_sdbp_cpudiode *)&hdr[1];
 398
 399        /* Get slots power calibration data if any */
 400        hdr = smu_get_sdb_partition(SMU_SDB_SLOTSPOW_ID, NULL);
 401        if (hdr != NULL)
 402                slotspow = (struct smu_sdbp_slotspow *)&hdr[1];
 403
 404        /* Get debug switches if any */
 405        hdr = smu_get_sdb_partition(SMU_SDB_DEBUG_SWITCHES_ID, NULL);
 406        if (hdr != NULL)
 407                debugswitches = (u8 *)&hdr[1];
 408}
 409
 410static int __init smu_sensors_init(void)
 411{
 412        struct device_node *smu, *sensors, *s;
 413        struct smu_ad_sensor *volt_sensor = NULL, *curr_sensor = NULL;
 414
 415        if (!smu_present())
 416                return -ENODEV;
 417
 418        /* Get parameters partitions */
 419        smu_fetch_param_partitions();
 420
 421        smu = of_find_node_by_type(NULL, "smu");
 422        if (smu == NULL)
 423                return -ENODEV;
 424
 425        /* Look for sensors subdir */
 426        for (sensors = NULL;
 427             (sensors = of_get_next_child(smu, sensors)) != NULL;)
 428                if (!strcmp(sensors->name, "sensors"))
 429                        break;
 430
 431        of_node_put(smu);
 432
 433        /* Create basic sensors */
 434        for (s = NULL;
 435             sensors && (s = of_get_next_child(sensors, s)) != NULL;) {
 436                struct smu_ad_sensor *ads;
 437
 438                ads = smu_ads_create(s);
 439                if (ads == NULL)
 440                        continue;
 441                list_add(&ads->link, &smu_ads);
 442                /* keep track of cpu voltage & current */
 443                if (!strcmp(ads->sens.name, "cpu-voltage"))
 444                        volt_sensor = ads;
 445                else if (!strcmp(ads->sens.name, "cpu-current"))
 446                        curr_sensor = ads;
 447        }
 448
 449        of_node_put(sensors);
 450
 451        /* Create CPU power sensor if possible */
 452        if (volt_sensor && curr_sensor)
 453                smu_cpu_power = smu_cpu_power_create(&volt_sensor->sens,
 454                                                     &curr_sensor->sens);
 455
 456        return 0;
 457}
 458
 459static void __exit smu_sensors_exit(void)
 460{
 461        struct smu_ad_sensor *ads;
 462
 463        /* dispose of power sensor */
 464        if (smu_cpu_power)
 465                wf_unregister_sensor(&smu_cpu_power->sens);
 466
 467        /* dispose of basic sensors */
 468        while (!list_empty(&smu_ads)) {
 469                ads = list_entry(smu_ads.next, struct smu_ad_sensor, link);
 470                list_del(&ads->link);
 471                wf_unregister_sensor(&ads->sens);
 472        }
 473}
 474
 475
 476module_init(smu_sensors_init);
 477module_exit(smu_sensors_exit);
 478
 479MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
 480MODULE_DESCRIPTION("SMU sensor objects for PowerMacs thermal control");
 481MODULE_LICENSE("GPL");
 482
 483