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 */ 39 static struct smu_sdbp_cpuvcp *cpuvcp; 40 static int cpuvcp_version; 41 static struct smu_sdbp_cpudiode *cpudiode; 42 static struct smu_sdbp_slotspow *slotspow; 43 static u8 *debugswitches; 44 45 /* 46 * SMU basic sensors objects 47 */ 48 49 static LIST_HEAD(smu_ads); 50 51 struct 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 58 static 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 65 static 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 87 static 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 110 static 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 131 static 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 152 static 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 174 static const struct wf_sensor_ops smu_cputemp_ops = { 175 .get_value = smu_cputemp_get, 176 .release = smu_ads_release, 177 .owner = THIS_MODULE, 178 }; 179 static const struct wf_sensor_ops smu_cpuamp_ops = { 180 .get_value = smu_cpuamp_get, 181 .release = smu_ads_release, 182 .owner = THIS_MODULE, 183 }; 184 static const struct wf_sensor_ops smu_cpuvolt_ops = { 185 .get_value = smu_cpuvolt_get, 186 .release = smu_ads_release, 187 .owner = THIS_MODULE, 188 }; 189 static 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 196 static 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 272 struct 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 282 static struct smu_cpu_power_sensor *smu_cpu_power; 283 284 static 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 295 static 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, &s); 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 328 static 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 335 static struct smu_cpu_power_sensor * 336 smu_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 380 static 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 408 static 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 456 static 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 473 module_init(smu_sensors_init); 474 module_exit(smu_sensors_exit); 475 476 MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); 477 MODULE_DESCRIPTION("SMU sensor objects for PowerMacs thermal control"); 478 MODULE_LICENSE("GPL"); 479 480