2  * Windfarm PowerMac thermal control. SMU based sensors
 
   4  * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
 
   5  *                    <benh@kernel.crashing.org>
 
   7  * Released under the term of the GNU GPL v2.
 
  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>
 
  19 #include <asm/machdep.h>
 
  21 #include <asm/system.h>
 
  22 #include <asm/sections.h>
 
  32 #define DBG(args...)    printk(args)
 
  34 #define DBG(args...)    do { } while(0)
 
  38  * Various SMU "partitions" calibration objects for which we
 
  39  * keep pointers here for use by bits & pieces of the driver
 
  41 static struct smu_sdbp_cpuvcp *cpuvcp;
 
  42 static int  cpuvcp_version;
 
  43 static struct smu_sdbp_cpudiode *cpudiode;
 
  44 static struct smu_sdbp_slotspow *slotspow;
 
  45 static u8 *debugswitches;
 
  48  * SMU basic sensors objects
 
  51 static LIST_HEAD(smu_ads);
 
  53 struct smu_ad_sensor {
 
  54         struct list_head        link;
 
  55         u32                     reg;            /* index in SMU */
 
  56         struct wf_sensor        sens;
 
  58 #define to_smu_ads(c) container_of(c, struct smu_ad_sensor, sens)
 
  60 static void smu_ads_release(struct wf_sensor *sr)
 
  62         struct smu_ad_sensor *ads = to_smu_ads(sr);
 
  67 static int smu_read_adc(u8 id, s32 *value)
 
  69         struct smu_simple_cmd   cmd;
 
  70         DECLARE_COMPLETION_ONSTACK(comp);
 
  73         rc = smu_queue_simple(&cmd, SMU_CMD_READ_ADC, 1,
 
  74                               smu_done_complete, &comp, id);
 
  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);
 
  85         *value = *((u16 *)cmd.buffer);
 
  89 static int smu_cputemp_get(struct wf_sensor *sr, s32 *value)
 
  91         struct smu_ad_sensor *ads = to_smu_ads(sr);
 
  96         rc = smu_read_adc(ads->reg, &val);
 
  98                 printk(KERN_ERR "windfarm: read CPU temp failed, err %d\n",
 
 103         /* Ok, we have to scale & adjust, taking units into account */
 
 104         scaled = (s64)(((u64)val) * (u64)cpudiode->m_value);
 
 106         scaled += ((s64)cpudiode->b_value) << 9;
 
 107         *value = (s32)(scaled << 1);
 
 112 static int smu_cpuamp_get(struct wf_sensor *sr, s32 *value)
 
 114         struct smu_ad_sensor *ads = to_smu_ads(sr);
 
 118         rc = smu_read_adc(ads->reg, &val);
 
 120                 printk(KERN_ERR "windfarm: read CPU current failed, err %d\n",
 
 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;
 
 133 static int smu_cpuvolt_get(struct wf_sensor *sr, s32 *value)
 
 135         struct smu_ad_sensor *ads = to_smu_ads(sr);
 
 139         rc = smu_read_adc(ads->reg, &val);
 
 141                 printk(KERN_ERR "windfarm: read CPU voltage failed, err %d\n",
 
 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;
 
 154 static int smu_slotspow_get(struct wf_sensor *sr, s32 *value)
 
 156         struct smu_ad_sensor *ads = to_smu_ads(sr);
 
 160         rc = smu_read_adc(ads->reg, &val);
 
 162                 printk(KERN_ERR "windfarm: read slots power failed, err %d\n",
 
 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;
 
 176 static struct wf_sensor_ops smu_cputemp_ops = {
 
 177         .get_value      = smu_cputemp_get,
 
 178         .release        = smu_ads_release,
 
 179         .owner          = THIS_MODULE,
 
 181 static struct wf_sensor_ops smu_cpuamp_ops = {
 
 182         .get_value      = smu_cpuamp_get,
 
 183         .release        = smu_ads_release,
 
 184         .owner          = THIS_MODULE,
 
 186 static struct wf_sensor_ops smu_cpuvolt_ops = {
 
 187         .get_value      = smu_cpuvolt_get,
 
 188         .release        = smu_ads_release,
 
 189         .owner          = THIS_MODULE,
 
 191 static struct wf_sensor_ops smu_slotspow_ops = {
 
 192         .get_value      = smu_slotspow_get,
 
 193         .release        = smu_ads_release,
 
 194         .owner          = THIS_MODULE,
 
 198 static struct smu_ad_sensor *smu_ads_create(struct device_node *node)
 
 200         struct smu_ad_sensor *ads;
 
 204         ads = kmalloc(sizeof(struct smu_ad_sensor), GFP_KERNEL);
 
 207         c = of_get_property(node, "device_type", NULL);
 
 208         l = of_get_property(node, "location", NULL);
 
 209         if (c == NULL || l == NULL)
 
 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.
 
 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);
 
 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",
 
 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",
 
 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);
 
 258         v = of_get_property(node, "reg", NULL);
 
 263         if (wf_register_sensor(&ads->sens))
 
 272  * SMU Power combo sensor object
 
 275 struct smu_cpu_power_sensor {
 
 276         struct list_head        link;
 
 277         struct wf_sensor        *volts;
 
 278         struct wf_sensor        *amps;
 
 281         struct wf_sensor        sens;
 
 283 #define to_smu_cpu_power(c) container_of(c, struct smu_cpu_power_sensor, sens)
 
 285 static struct smu_cpu_power_sensor *smu_cpu_power;
 
 287 static void smu_cpu_power_release(struct wf_sensor *sr)
 
 289         struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);
 
 292                 wf_put_sensor(pow->volts);
 
 294                 wf_put_sensor(pow->amps);
 
 298 static int smu_cpu_power_get(struct wf_sensor *sr, s32 *value)
 
 300         struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);
 
 301         s32 volts, amps, power;
 
 302         u64 tmps, tmpa, tmpb;
 
 305         rc = pow->amps->ops->get_value(pow->amps, &s);
 
 309         if (pow->fake_volts) {
 
 310                 *value = amps * 12 - 0x30000;
 
 314         rc = pow->volts->ops->get_value(pow->volts, &volts);
 
 318         power = (s32)((((u64)volts) * ((u64)amps)) >> 16);
 
 319         if (!pow->quadratic) {
 
 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);
 
 331 static 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,
 
 338 static struct smu_cpu_power_sensor *
 
 339 smu_cpu_power_create(struct wf_sensor *volts, struct wf_sensor *amps)
 
 341         struct smu_cpu_power_sensor *pow;
 
 343         pow = kmalloc(sizeof(struct smu_cpu_power_sensor), GFP_KERNEL);
 
 346         pow->sens.ops = &smu_cpu_power_ops;
 
 347         pow->sens.name = "cpu-power";
 
 349         wf_get_sensor(volts);
 
 354         /* Some early machines need a faked voltage */
 
 355         if (debugswitches && ((*debugswitches) & 0x80)) {
 
 356                 printk(KERN_INFO "windfarm: CPU Power sensor using faked"
 
 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...
 
 366         if ((machine_is_compatible("PowerMac8,1") ||
 
 367              machine_is_compatible("PowerMac8,2") ||
 
 368              machine_is_compatible("PowerMac9,1")) &&
 
 369             cpuvcp_version >= 2) {
 
 371                 DBG("windfarm: CPU Power using quadratic transform\n");
 
 375         if (wf_register_sensor(&pow->sens))
 
 383 static void smu_fetch_param_partitions(void)
 
 385         const struct smu_sdbp_header *hdr;
 
 387         /* Get CPU voltage/current/power calibration data */
 
 388         hdr = smu_get_sdb_partition(SMU_SDB_CPUVCP_ID, NULL);
 
 390                 cpuvcp = (struct smu_sdbp_cpuvcp *)&hdr[1];
 
 391                 /* Keep version around */
 
 392                 cpuvcp_version = hdr->version;
 
 395         /* Get CPU diode calibration data */
 
 396         hdr = smu_get_sdb_partition(SMU_SDB_CPUDIODE_ID, NULL);
 
 398                 cpudiode = (struct smu_sdbp_cpudiode *)&hdr[1];
 
 400         /* Get slots power calibration data if any */
 
 401         hdr = smu_get_sdb_partition(SMU_SDB_SLOTSPOW_ID, NULL);
 
 403                 slotspow = (struct smu_sdbp_slotspow *)&hdr[1];
 
 405         /* Get debug switches if any */
 
 406         hdr = smu_get_sdb_partition(SMU_SDB_DEBUG_SWITCHES_ID, NULL);
 
 408                 debugswitches = (u8 *)&hdr[1];
 
 411 static int __init smu_sensors_init(void)
 
 413         struct device_node *smu, *sensors, *s;
 
 414         struct smu_ad_sensor *volt_sensor = NULL, *curr_sensor = NULL;
 
 419         /* Get parameters partitions */
 
 420         smu_fetch_param_partitions();
 
 422         smu = of_find_node_by_type(NULL, "smu");
 
 426         /* Look for sensors subdir */
 
 428              (sensors = of_get_next_child(smu, sensors)) != NULL;)
 
 429                 if (!strcmp(sensors->name, "sensors"))
 
 434         /* Create basic sensors */
 
 436              sensors && (s = of_get_next_child(sensors, s)) != NULL;) {
 
 437                 struct smu_ad_sensor *ads;
 
 439                 ads = smu_ads_create(s);
 
 442                 list_add(&ads->link, &smu_ads);
 
 443                 /* keep track of cpu voltage & current */
 
 444                 if (!strcmp(ads->sens.name, "cpu-voltage"))
 
 446                 else if (!strcmp(ads->sens.name, "cpu-current"))
 
 450         of_node_put(sensors);
 
 452         /* Create CPU power sensor if possible */
 
 453         if (volt_sensor && curr_sensor)
 
 454                 smu_cpu_power = smu_cpu_power_create(&volt_sensor->sens,
 
 460 static void __exit smu_sensors_exit(void)
 
 462         struct smu_ad_sensor *ads;
 
 464         /* dispose of power sensor */
 
 466                 wf_unregister_sensor(&smu_cpu_power->sens);
 
 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);
 
 477 module_init(smu_sensors_init);
 
 478 module_exit(smu_sensors_exit);
 
 480 MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
 
 481 MODULE_DESCRIPTION("SMU sensor objects for PowerMacs thermal control");
 
 482 MODULE_LICENSE("GPL");