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