1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Windfarm PowerMac thermal control. SMU based controls 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.4" 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 static int smu_supports_new_fans_ops = 1; 36 37 /* 38 * SMU fans control object 39 */ 40 41 static LIST_HEAD(smu_fans); 42 43 struct smu_fan_control { 44 struct list_head link; 45 int fan_type; /* 0 = rpm, 1 = pwm */ 46 u32 reg; /* index in SMU */ 47 s32 value; /* current value */ 48 s32 min, max; /* min/max values */ 49 struct wf_control ctrl; 50 }; 51 #define to_smu_fan(c) container_of(c, struct smu_fan_control, ctrl) 52 53 static int smu_set_fan(int pwm, u8 id, u16 value) 54 { 55 struct smu_cmd cmd; 56 u8 buffer[16]; 57 DECLARE_COMPLETION_ONSTACK(comp); 58 int rc; 59 60 /* Fill SMU command structure */ 61 cmd.cmd = SMU_CMD_FAN_COMMAND; 62 63 /* The SMU has an "old" and a "new" way of setting the fan speed 64 * Unfortunately, I found no reliable way to know which one works 65 * on a given machine model. After some investigations it appears 66 * that MacOS X just tries the new one, and if it fails fallbacks 67 * to the old ones ... Ugh. 68 */ 69 retry: 70 if (smu_supports_new_fans_ops) { 71 buffer[0] = 0x30; 72 buffer[1] = id; 73 *((u16 *)(&buffer[2])) = value; 74 cmd.data_len = 4; 75 } else { 76 if (id > 7) 77 return -EINVAL; 78 /* Fill argument buffer */ 79 memset(buffer, 0, 16); 80 buffer[0] = pwm ? 0x10 : 0x00; 81 buffer[1] = 0x01 << id; 82 *((u16 *)&buffer[2 + id * 2]) = value; 83 cmd.data_len = 14; 84 } 85 86 cmd.reply_len = 16; 87 cmd.data_buf = cmd.reply_buf = buffer; 88 cmd.status = 0; 89 cmd.done = smu_done_complete; 90 cmd.misc = ∁ 91 92 rc = smu_queue_cmd(&cmd); 93 if (rc) 94 return rc; 95 wait_for_completion(&comp); 96 97 /* Handle fallback (see coment above) */ 98 if (cmd.status != 0 && smu_supports_new_fans_ops) { 99 printk(KERN_WARNING "windfarm: SMU failed new fan command " 100 "falling back to old method\n"); 101 smu_supports_new_fans_ops = 0; 102 goto retry; 103 } 104 105 return cmd.status; 106 } 107 108 static void smu_fan_release(struct wf_control *ct) 109 { 110 struct smu_fan_control *fct = to_smu_fan(ct); 111 112 kfree(fct); 113 } 114 115 static int smu_fan_set(struct wf_control *ct, s32 value) 116 { 117 struct smu_fan_control *fct = to_smu_fan(ct); 118 119 if (value < fct->min) 120 value = fct->min; 121 if (value > fct->max) 122 value = fct->max; 123 fct->value = value; 124 125 return smu_set_fan(fct->fan_type, fct->reg, value); 126 } 127 128 static int smu_fan_get(struct wf_control *ct, s32 *value) 129 { 130 struct smu_fan_control *fct = to_smu_fan(ct); 131 *value = fct->value; /* todo: read from SMU */ 132 return 0; 133 } 134 135 static s32 smu_fan_min(struct wf_control *ct) 136 { 137 struct smu_fan_control *fct = to_smu_fan(ct); 138 return fct->min; 139 } 140 141 static s32 smu_fan_max(struct wf_control *ct) 142 { 143 struct smu_fan_control *fct = to_smu_fan(ct); 144 return fct->max; 145 } 146 147 static const struct wf_control_ops smu_fan_ops = { 148 .set_value = smu_fan_set, 149 .get_value = smu_fan_get, 150 .get_min = smu_fan_min, 151 .get_max = smu_fan_max, 152 .release = smu_fan_release, 153 .owner = THIS_MODULE, 154 }; 155 156 static struct smu_fan_control *smu_fan_create(struct device_node *node, 157 int pwm_fan) 158 { 159 struct smu_fan_control *fct; 160 const s32 *v; 161 const u32 *reg; 162 const char *l; 163 164 fct = kmalloc(sizeof(struct smu_fan_control), GFP_KERNEL); 165 if (fct == NULL) 166 return NULL; 167 fct->ctrl.ops = &smu_fan_ops; 168 l = of_get_property(node, "location", NULL); 169 if (l == NULL) 170 goto fail; 171 172 fct->fan_type = pwm_fan; 173 fct->ctrl.type = pwm_fan ? WF_CONTROL_PWM_FAN : WF_CONTROL_RPM_FAN; 174 175 /* We use the name & location here the same way we do for SMU sensors, 176 * see the comment in windfarm_smu_sensors.c. The locations are a bit 177 * less consistent here between the iMac and the desktop models, but 178 * that is good enough for our needs for now at least. 179 * 180 * One problem though is that Apple seem to be inconsistent with case 181 * and the kernel doesn't have strcasecmp =P 182 */ 183 184 fct->ctrl.name = NULL; 185 186 /* Names used on desktop models */ 187 if (!strcmp(l, "Rear Fan 0") || !strcmp(l, "Rear Fan") || 188 !strcmp(l, "Rear fan 0") || !strcmp(l, "Rear fan") || 189 !strcmp(l, "CPU A EXHAUST")) 190 fct->ctrl.name = "cpu-rear-fan-0"; 191 else if (!strcmp(l, "Rear Fan 1") || !strcmp(l, "Rear fan 1") || 192 !strcmp(l, "CPU B EXHAUST")) 193 fct->ctrl.name = "cpu-rear-fan-1"; 194 else if (!strcmp(l, "Front Fan 0") || !strcmp(l, "Front Fan") || 195 !strcmp(l, "Front fan 0") || !strcmp(l, "Front fan") || 196 !strcmp(l, "CPU A INTAKE")) 197 fct->ctrl.name = "cpu-front-fan-0"; 198 else if (!strcmp(l, "Front Fan 1") || !strcmp(l, "Front fan 1") || 199 !strcmp(l, "CPU B INTAKE")) 200 fct->ctrl.name = "cpu-front-fan-1"; 201 else if (!strcmp(l, "CPU A PUMP")) 202 fct->ctrl.name = "cpu-pump-0"; 203 else if (!strcmp(l, "CPU B PUMP")) 204 fct->ctrl.name = "cpu-pump-1"; 205 else if (!strcmp(l, "Slots Fan") || !strcmp(l, "Slots fan") || 206 !strcmp(l, "EXPANSION SLOTS INTAKE")) 207 fct->ctrl.name = "slots-fan"; 208 else if (!strcmp(l, "Drive Bay") || !strcmp(l, "Drive bay") || 209 !strcmp(l, "DRIVE BAY A INTAKE")) 210 fct->ctrl.name = "drive-bay-fan"; 211 else if (!strcmp(l, "BACKSIDE")) 212 fct->ctrl.name = "backside-fan"; 213 214 /* Names used on iMac models */ 215 if (!strcmp(l, "System Fan") || !strcmp(l, "System fan")) 216 fct->ctrl.name = "system-fan"; 217 else if (!strcmp(l, "CPU Fan") || !strcmp(l, "CPU fan")) 218 fct->ctrl.name = "cpu-fan"; 219 else if (!strcmp(l, "Hard Drive") || !strcmp(l, "Hard drive")) 220 fct->ctrl.name = "drive-bay-fan"; 221 else if (!strcmp(l, "HDD Fan")) /* seen on iMac G5 iSight */ 222 fct->ctrl.name = "hard-drive-fan"; 223 else if (!strcmp(l, "ODD Fan")) /* same */ 224 fct->ctrl.name = "optical-drive-fan"; 225 226 /* Unrecognized fan, bail out */ 227 if (fct->ctrl.name == NULL) 228 goto fail; 229 230 /* Get min & max values*/ 231 v = of_get_property(node, "min-value", NULL); 232 if (v == NULL) 233 goto fail; 234 fct->min = *v; 235 v = of_get_property(node, "max-value", NULL); 236 if (v == NULL) 237 goto fail; 238 fct->max = *v; 239 240 /* Get "reg" value */ 241 reg = of_get_property(node, "reg", NULL); 242 if (reg == NULL) 243 goto fail; 244 fct->reg = *reg; 245 246 if (wf_register_control(&fct->ctrl)) 247 goto fail; 248 249 return fct; 250 fail: 251 kfree(fct); 252 return NULL; 253 } 254 255 256 static int __init smu_controls_init(void) 257 { 258 struct device_node *smu, *fans, *fan; 259 260 if (!smu_present()) 261 return -ENODEV; 262 263 smu = of_find_node_by_type(NULL, "smu"); 264 if (smu == NULL) 265 return -ENODEV; 266 267 /* Look for RPM fans */ 268 for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;) 269 if (of_node_name_eq(fans, "rpm-fans") || 270 of_device_is_compatible(fans, "smu-rpm-fans")) 271 break; 272 for (fan = NULL; 273 fans && (fan = of_get_next_child(fans, fan)) != NULL;) { 274 struct smu_fan_control *fct; 275 276 fct = smu_fan_create(fan, 0); 277 if (fct == NULL) { 278 printk(KERN_WARNING "windfarm: Failed to create SMU " 279 "RPM fan %pOFn\n", fan); 280 continue; 281 } 282 list_add(&fct->link, &smu_fans); 283 } 284 of_node_put(fans); 285 286 287 /* Look for PWM fans */ 288 for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;) 289 if (of_node_name_eq(fans, "pwm-fans")) 290 break; 291 for (fan = NULL; 292 fans && (fan = of_get_next_child(fans, fan)) != NULL;) { 293 struct smu_fan_control *fct; 294 295 fct = smu_fan_create(fan, 1); 296 if (fct == NULL) { 297 printk(KERN_WARNING "windfarm: Failed to create SMU " 298 "PWM fan %pOFn\n", fan); 299 continue; 300 } 301 list_add(&fct->link, &smu_fans); 302 } 303 of_node_put(fans); 304 of_node_put(smu); 305 306 return 0; 307 } 308 309 static void __exit smu_controls_exit(void) 310 { 311 struct smu_fan_control *fct; 312 313 while (!list_empty(&smu_fans)) { 314 fct = list_entry(smu_fans.next, struct smu_fan_control, link); 315 list_del(&fct->link); 316 wf_unregister_control(&fct->ctrl); 317 } 318 } 319 320 321 module_init(smu_controls_init); 322 module_exit(smu_controls_exit); 323 324 MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); 325 MODULE_DESCRIPTION("SMU control objects for PowerMacs thermal control"); 326 MODULE_LICENSE("GPL"); 327 328