18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Windfarm PowerMac thermal control. SMU based controls 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. 68c2ecf20Sopenharmony_ci * <benh@kernel.crashing.org> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/types.h> 108c2ecf20Sopenharmony_ci#include <linux/errno.h> 118c2ecf20Sopenharmony_ci#include <linux/kernel.h> 128c2ecf20Sopenharmony_ci#include <linux/delay.h> 138c2ecf20Sopenharmony_ci#include <linux/slab.h> 148c2ecf20Sopenharmony_ci#include <linux/init.h> 158c2ecf20Sopenharmony_ci#include <linux/wait.h> 168c2ecf20Sopenharmony_ci#include <linux/completion.h> 178c2ecf20Sopenharmony_ci#include <asm/prom.h> 188c2ecf20Sopenharmony_ci#include <asm/machdep.h> 198c2ecf20Sopenharmony_ci#include <asm/io.h> 208c2ecf20Sopenharmony_ci#include <asm/sections.h> 218c2ecf20Sopenharmony_ci#include <asm/smu.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#include "windfarm.h" 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#define VERSION "0.4" 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#undef DEBUG 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#ifdef DEBUG 308c2ecf20Sopenharmony_ci#define DBG(args...) printk(args) 318c2ecf20Sopenharmony_ci#else 328c2ecf20Sopenharmony_ci#define DBG(args...) do { } while(0) 338c2ecf20Sopenharmony_ci#endif 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_cistatic int smu_supports_new_fans_ops = 1; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci/* 388c2ecf20Sopenharmony_ci * SMU fans control object 398c2ecf20Sopenharmony_ci */ 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic LIST_HEAD(smu_fans); 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistruct smu_fan_control { 448c2ecf20Sopenharmony_ci struct list_head link; 458c2ecf20Sopenharmony_ci int fan_type; /* 0 = rpm, 1 = pwm */ 468c2ecf20Sopenharmony_ci u32 reg; /* index in SMU */ 478c2ecf20Sopenharmony_ci s32 value; /* current value */ 488c2ecf20Sopenharmony_ci s32 min, max; /* min/max values */ 498c2ecf20Sopenharmony_ci struct wf_control ctrl; 508c2ecf20Sopenharmony_ci}; 518c2ecf20Sopenharmony_ci#define to_smu_fan(c) container_of(c, struct smu_fan_control, ctrl) 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistatic int smu_set_fan(int pwm, u8 id, u16 value) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci struct smu_cmd cmd; 568c2ecf20Sopenharmony_ci u8 buffer[16]; 578c2ecf20Sopenharmony_ci DECLARE_COMPLETION_ONSTACK(comp); 588c2ecf20Sopenharmony_ci int rc; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci /* Fill SMU command structure */ 618c2ecf20Sopenharmony_ci cmd.cmd = SMU_CMD_FAN_COMMAND; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci /* The SMU has an "old" and a "new" way of setting the fan speed 648c2ecf20Sopenharmony_ci * Unfortunately, I found no reliable way to know which one works 658c2ecf20Sopenharmony_ci * on a given machine model. After some investigations it appears 668c2ecf20Sopenharmony_ci * that MacOS X just tries the new one, and if it fails fallbacks 678c2ecf20Sopenharmony_ci * to the old ones ... Ugh. 688c2ecf20Sopenharmony_ci */ 698c2ecf20Sopenharmony_ci retry: 708c2ecf20Sopenharmony_ci if (smu_supports_new_fans_ops) { 718c2ecf20Sopenharmony_ci buffer[0] = 0x30; 728c2ecf20Sopenharmony_ci buffer[1] = id; 738c2ecf20Sopenharmony_ci *((u16 *)(&buffer[2])) = value; 748c2ecf20Sopenharmony_ci cmd.data_len = 4; 758c2ecf20Sopenharmony_ci } else { 768c2ecf20Sopenharmony_ci if (id > 7) 778c2ecf20Sopenharmony_ci return -EINVAL; 788c2ecf20Sopenharmony_ci /* Fill argument buffer */ 798c2ecf20Sopenharmony_ci memset(buffer, 0, 16); 808c2ecf20Sopenharmony_ci buffer[0] = pwm ? 0x10 : 0x00; 818c2ecf20Sopenharmony_ci buffer[1] = 0x01 << id; 828c2ecf20Sopenharmony_ci *((u16 *)&buffer[2 + id * 2]) = value; 838c2ecf20Sopenharmony_ci cmd.data_len = 14; 848c2ecf20Sopenharmony_ci } 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci cmd.reply_len = 16; 878c2ecf20Sopenharmony_ci cmd.data_buf = cmd.reply_buf = buffer; 888c2ecf20Sopenharmony_ci cmd.status = 0; 898c2ecf20Sopenharmony_ci cmd.done = smu_done_complete; 908c2ecf20Sopenharmony_ci cmd.misc = ∁ 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci rc = smu_queue_cmd(&cmd); 938c2ecf20Sopenharmony_ci if (rc) 948c2ecf20Sopenharmony_ci return rc; 958c2ecf20Sopenharmony_ci wait_for_completion(&comp); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci /* Handle fallback (see coment above) */ 988c2ecf20Sopenharmony_ci if (cmd.status != 0 && smu_supports_new_fans_ops) { 998c2ecf20Sopenharmony_ci printk(KERN_WARNING "windfarm: SMU failed new fan command " 1008c2ecf20Sopenharmony_ci "falling back to old method\n"); 1018c2ecf20Sopenharmony_ci smu_supports_new_fans_ops = 0; 1028c2ecf20Sopenharmony_ci goto retry; 1038c2ecf20Sopenharmony_ci } 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci return cmd.status; 1068c2ecf20Sopenharmony_ci} 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic void smu_fan_release(struct wf_control *ct) 1098c2ecf20Sopenharmony_ci{ 1108c2ecf20Sopenharmony_ci struct smu_fan_control *fct = to_smu_fan(ct); 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci kfree(fct); 1138c2ecf20Sopenharmony_ci} 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_cistatic int smu_fan_set(struct wf_control *ct, s32 value) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci struct smu_fan_control *fct = to_smu_fan(ct); 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci if (value < fct->min) 1208c2ecf20Sopenharmony_ci value = fct->min; 1218c2ecf20Sopenharmony_ci if (value > fct->max) 1228c2ecf20Sopenharmony_ci value = fct->max; 1238c2ecf20Sopenharmony_ci fct->value = value; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci return smu_set_fan(fct->fan_type, fct->reg, value); 1268c2ecf20Sopenharmony_ci} 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_cistatic int smu_fan_get(struct wf_control *ct, s32 *value) 1298c2ecf20Sopenharmony_ci{ 1308c2ecf20Sopenharmony_ci struct smu_fan_control *fct = to_smu_fan(ct); 1318c2ecf20Sopenharmony_ci *value = fct->value; /* todo: read from SMU */ 1328c2ecf20Sopenharmony_ci return 0; 1338c2ecf20Sopenharmony_ci} 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_cistatic s32 smu_fan_min(struct wf_control *ct) 1368c2ecf20Sopenharmony_ci{ 1378c2ecf20Sopenharmony_ci struct smu_fan_control *fct = to_smu_fan(ct); 1388c2ecf20Sopenharmony_ci return fct->min; 1398c2ecf20Sopenharmony_ci} 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_cistatic s32 smu_fan_max(struct wf_control *ct) 1428c2ecf20Sopenharmony_ci{ 1438c2ecf20Sopenharmony_ci struct smu_fan_control *fct = to_smu_fan(ct); 1448c2ecf20Sopenharmony_ci return fct->max; 1458c2ecf20Sopenharmony_ci} 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_cistatic const struct wf_control_ops smu_fan_ops = { 1488c2ecf20Sopenharmony_ci .set_value = smu_fan_set, 1498c2ecf20Sopenharmony_ci .get_value = smu_fan_get, 1508c2ecf20Sopenharmony_ci .get_min = smu_fan_min, 1518c2ecf20Sopenharmony_ci .get_max = smu_fan_max, 1528c2ecf20Sopenharmony_ci .release = smu_fan_release, 1538c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1548c2ecf20Sopenharmony_ci}; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_cistatic struct smu_fan_control *smu_fan_create(struct device_node *node, 1578c2ecf20Sopenharmony_ci int pwm_fan) 1588c2ecf20Sopenharmony_ci{ 1598c2ecf20Sopenharmony_ci struct smu_fan_control *fct; 1608c2ecf20Sopenharmony_ci const s32 *v; 1618c2ecf20Sopenharmony_ci const u32 *reg; 1628c2ecf20Sopenharmony_ci const char *l; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci fct = kmalloc(sizeof(struct smu_fan_control), GFP_KERNEL); 1658c2ecf20Sopenharmony_ci if (fct == NULL) 1668c2ecf20Sopenharmony_ci return NULL; 1678c2ecf20Sopenharmony_ci fct->ctrl.ops = &smu_fan_ops; 1688c2ecf20Sopenharmony_ci l = of_get_property(node, "location", NULL); 1698c2ecf20Sopenharmony_ci if (l == NULL) 1708c2ecf20Sopenharmony_ci goto fail; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci fct->fan_type = pwm_fan; 1738c2ecf20Sopenharmony_ci fct->ctrl.type = pwm_fan ? WF_CONTROL_PWM_FAN : WF_CONTROL_RPM_FAN; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci /* We use the name & location here the same way we do for SMU sensors, 1768c2ecf20Sopenharmony_ci * see the comment in windfarm_smu_sensors.c. The locations are a bit 1778c2ecf20Sopenharmony_ci * less consistent here between the iMac and the desktop models, but 1788c2ecf20Sopenharmony_ci * that is good enough for our needs for now at least. 1798c2ecf20Sopenharmony_ci * 1808c2ecf20Sopenharmony_ci * One problem though is that Apple seem to be inconsistent with case 1818c2ecf20Sopenharmony_ci * and the kernel doesn't have strcasecmp =P 1828c2ecf20Sopenharmony_ci */ 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci fct->ctrl.name = NULL; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci /* Names used on desktop models */ 1878c2ecf20Sopenharmony_ci if (!strcmp(l, "Rear Fan 0") || !strcmp(l, "Rear Fan") || 1888c2ecf20Sopenharmony_ci !strcmp(l, "Rear fan 0") || !strcmp(l, "Rear fan") || 1898c2ecf20Sopenharmony_ci !strcmp(l, "CPU A EXHAUST")) 1908c2ecf20Sopenharmony_ci fct->ctrl.name = "cpu-rear-fan-0"; 1918c2ecf20Sopenharmony_ci else if (!strcmp(l, "Rear Fan 1") || !strcmp(l, "Rear fan 1") || 1928c2ecf20Sopenharmony_ci !strcmp(l, "CPU B EXHAUST")) 1938c2ecf20Sopenharmony_ci fct->ctrl.name = "cpu-rear-fan-1"; 1948c2ecf20Sopenharmony_ci else if (!strcmp(l, "Front Fan 0") || !strcmp(l, "Front Fan") || 1958c2ecf20Sopenharmony_ci !strcmp(l, "Front fan 0") || !strcmp(l, "Front fan") || 1968c2ecf20Sopenharmony_ci !strcmp(l, "CPU A INTAKE")) 1978c2ecf20Sopenharmony_ci fct->ctrl.name = "cpu-front-fan-0"; 1988c2ecf20Sopenharmony_ci else if (!strcmp(l, "Front Fan 1") || !strcmp(l, "Front fan 1") || 1998c2ecf20Sopenharmony_ci !strcmp(l, "CPU B INTAKE")) 2008c2ecf20Sopenharmony_ci fct->ctrl.name = "cpu-front-fan-1"; 2018c2ecf20Sopenharmony_ci else if (!strcmp(l, "CPU A PUMP")) 2028c2ecf20Sopenharmony_ci fct->ctrl.name = "cpu-pump-0"; 2038c2ecf20Sopenharmony_ci else if (!strcmp(l, "CPU B PUMP")) 2048c2ecf20Sopenharmony_ci fct->ctrl.name = "cpu-pump-1"; 2058c2ecf20Sopenharmony_ci else if (!strcmp(l, "Slots Fan") || !strcmp(l, "Slots fan") || 2068c2ecf20Sopenharmony_ci !strcmp(l, "EXPANSION SLOTS INTAKE")) 2078c2ecf20Sopenharmony_ci fct->ctrl.name = "slots-fan"; 2088c2ecf20Sopenharmony_ci else if (!strcmp(l, "Drive Bay") || !strcmp(l, "Drive bay") || 2098c2ecf20Sopenharmony_ci !strcmp(l, "DRIVE BAY A INTAKE")) 2108c2ecf20Sopenharmony_ci fct->ctrl.name = "drive-bay-fan"; 2118c2ecf20Sopenharmony_ci else if (!strcmp(l, "BACKSIDE")) 2128c2ecf20Sopenharmony_ci fct->ctrl.name = "backside-fan"; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci /* Names used on iMac models */ 2158c2ecf20Sopenharmony_ci if (!strcmp(l, "System Fan") || !strcmp(l, "System fan")) 2168c2ecf20Sopenharmony_ci fct->ctrl.name = "system-fan"; 2178c2ecf20Sopenharmony_ci else if (!strcmp(l, "CPU Fan") || !strcmp(l, "CPU fan")) 2188c2ecf20Sopenharmony_ci fct->ctrl.name = "cpu-fan"; 2198c2ecf20Sopenharmony_ci else if (!strcmp(l, "Hard Drive") || !strcmp(l, "Hard drive")) 2208c2ecf20Sopenharmony_ci fct->ctrl.name = "drive-bay-fan"; 2218c2ecf20Sopenharmony_ci else if (!strcmp(l, "HDD Fan")) /* seen on iMac G5 iSight */ 2228c2ecf20Sopenharmony_ci fct->ctrl.name = "hard-drive-fan"; 2238c2ecf20Sopenharmony_ci else if (!strcmp(l, "ODD Fan")) /* same */ 2248c2ecf20Sopenharmony_ci fct->ctrl.name = "optical-drive-fan"; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci /* Unrecognized fan, bail out */ 2278c2ecf20Sopenharmony_ci if (fct->ctrl.name == NULL) 2288c2ecf20Sopenharmony_ci goto fail; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci /* Get min & max values*/ 2318c2ecf20Sopenharmony_ci v = of_get_property(node, "min-value", NULL); 2328c2ecf20Sopenharmony_ci if (v == NULL) 2338c2ecf20Sopenharmony_ci goto fail; 2348c2ecf20Sopenharmony_ci fct->min = *v; 2358c2ecf20Sopenharmony_ci v = of_get_property(node, "max-value", NULL); 2368c2ecf20Sopenharmony_ci if (v == NULL) 2378c2ecf20Sopenharmony_ci goto fail; 2388c2ecf20Sopenharmony_ci fct->max = *v; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci /* Get "reg" value */ 2418c2ecf20Sopenharmony_ci reg = of_get_property(node, "reg", NULL); 2428c2ecf20Sopenharmony_ci if (reg == NULL) 2438c2ecf20Sopenharmony_ci goto fail; 2448c2ecf20Sopenharmony_ci fct->reg = *reg; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci if (wf_register_control(&fct->ctrl)) 2478c2ecf20Sopenharmony_ci goto fail; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci return fct; 2508c2ecf20Sopenharmony_ci fail: 2518c2ecf20Sopenharmony_ci kfree(fct); 2528c2ecf20Sopenharmony_ci return NULL; 2538c2ecf20Sopenharmony_ci} 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_cistatic int __init smu_controls_init(void) 2578c2ecf20Sopenharmony_ci{ 2588c2ecf20Sopenharmony_ci struct device_node *smu, *fans, *fan; 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci if (!smu_present()) 2618c2ecf20Sopenharmony_ci return -ENODEV; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci smu = of_find_node_by_type(NULL, "smu"); 2648c2ecf20Sopenharmony_ci if (smu == NULL) 2658c2ecf20Sopenharmony_ci return -ENODEV; 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci /* Look for RPM fans */ 2688c2ecf20Sopenharmony_ci for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;) 2698c2ecf20Sopenharmony_ci if (of_node_name_eq(fans, "rpm-fans") || 2708c2ecf20Sopenharmony_ci of_device_is_compatible(fans, "smu-rpm-fans")) 2718c2ecf20Sopenharmony_ci break; 2728c2ecf20Sopenharmony_ci for (fan = NULL; 2738c2ecf20Sopenharmony_ci fans && (fan = of_get_next_child(fans, fan)) != NULL;) { 2748c2ecf20Sopenharmony_ci struct smu_fan_control *fct; 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci fct = smu_fan_create(fan, 0); 2778c2ecf20Sopenharmony_ci if (fct == NULL) { 2788c2ecf20Sopenharmony_ci printk(KERN_WARNING "windfarm: Failed to create SMU " 2798c2ecf20Sopenharmony_ci "RPM fan %pOFn\n", fan); 2808c2ecf20Sopenharmony_ci continue; 2818c2ecf20Sopenharmony_ci } 2828c2ecf20Sopenharmony_ci list_add(&fct->link, &smu_fans); 2838c2ecf20Sopenharmony_ci } 2848c2ecf20Sopenharmony_ci of_node_put(fans); 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci /* Look for PWM fans */ 2888c2ecf20Sopenharmony_ci for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;) 2898c2ecf20Sopenharmony_ci if (of_node_name_eq(fans, "pwm-fans")) 2908c2ecf20Sopenharmony_ci break; 2918c2ecf20Sopenharmony_ci for (fan = NULL; 2928c2ecf20Sopenharmony_ci fans && (fan = of_get_next_child(fans, fan)) != NULL;) { 2938c2ecf20Sopenharmony_ci struct smu_fan_control *fct; 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci fct = smu_fan_create(fan, 1); 2968c2ecf20Sopenharmony_ci if (fct == NULL) { 2978c2ecf20Sopenharmony_ci printk(KERN_WARNING "windfarm: Failed to create SMU " 2988c2ecf20Sopenharmony_ci "PWM fan %pOFn\n", fan); 2998c2ecf20Sopenharmony_ci continue; 3008c2ecf20Sopenharmony_ci } 3018c2ecf20Sopenharmony_ci list_add(&fct->link, &smu_fans); 3028c2ecf20Sopenharmony_ci } 3038c2ecf20Sopenharmony_ci of_node_put(fans); 3048c2ecf20Sopenharmony_ci of_node_put(smu); 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci return 0; 3078c2ecf20Sopenharmony_ci} 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_cistatic void __exit smu_controls_exit(void) 3108c2ecf20Sopenharmony_ci{ 3118c2ecf20Sopenharmony_ci struct smu_fan_control *fct; 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci while (!list_empty(&smu_fans)) { 3148c2ecf20Sopenharmony_ci fct = list_entry(smu_fans.next, struct smu_fan_control, link); 3158c2ecf20Sopenharmony_ci list_del(&fct->link); 3168c2ecf20Sopenharmony_ci wf_unregister_control(&fct->ctrl); 3178c2ecf20Sopenharmony_ci } 3188c2ecf20Sopenharmony_ci} 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_cimodule_init(smu_controls_init); 3228c2ecf20Sopenharmony_cimodule_exit(smu_controls_exit); 3238c2ecf20Sopenharmony_ci 3248c2ecf20Sopenharmony_ciMODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); 3258c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SMU control objects for PowerMacs thermal control"); 3268c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 3278c2ecf20Sopenharmony_ci 328