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