1 /* 2 * Windfarm PowerMac thermal control. SMU based controls 3 * 4 * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. 5 * <benh@kernel.crashing.org> 6 * 7 * Released under the term of the GNU GPL v2. 8 */ 9 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> 18 #include <asm/prom.h> 19 #include <asm/machdep.h> 20 #include <asm/io.h> 21 #include <asm/system.h> 22 #include <asm/sections.h> 23 #include <asm/smu.h> 24 25 #include "windfarm.h" 26 27 #define VERSION "0.3" 28 29 #undef DEBUG 30 31 #ifdef DEBUG 32 #define DBG(args...) printk(args) 33 #else 34 #define DBG(args...) do { } while(0) 35 #endif 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(comp); 58 int rc; 59 60 /* Fill SMU command structure */ 61 cmd.cmd = SMU_CMD_FAN_COMMAND; 62 cmd.data_len = 14; 63 cmd.reply_len = 16; 64 cmd.data_buf = cmd.reply_buf = buffer; 65 cmd.status = 0; 66 cmd.done = smu_done_complete; 67 cmd.misc = ∁ 68 69 /* Fill argument buffer */ 70 memset(buffer, 0, 16); 71 buffer[0] = pwm ? 0x10 : 0x00; 72 buffer[1] = 0x01 << id; 73 *((u16 *)&buffer[2 + id * 2]) = value; 74 75 rc = smu_queue_cmd(&cmd); 76 if (rc) 77 return rc; 78 wait_for_completion(&comp); 79 return cmd.status; 80 } 81 82 static void smu_fan_release(struct wf_control *ct) 83 { 84 struct smu_fan_control *fct = to_smu_fan(ct); 85 86 kfree(fct); 87 } 88 89 static int smu_fan_set(struct wf_control *ct, s32 value) 90 { 91 struct smu_fan_control *fct = to_smu_fan(ct); 92 93 if (value < fct->min) 94 value = fct->min; 95 if (value > fct->max) 96 value = fct->max; 97 fct->value = value; 98 99 return smu_set_fan(fct->fan_type, fct->reg, value); 100 } 101 102 static int smu_fan_get(struct wf_control *ct, s32 *value) 103 { 104 struct smu_fan_control *fct = to_smu_fan(ct); 105 *value = fct->value; /* todo: read from SMU */ 106 return 0; 107 } 108 109 static s32 smu_fan_min(struct wf_control *ct) 110 { 111 struct smu_fan_control *fct = to_smu_fan(ct); 112 return fct->min; 113 } 114 115 static s32 smu_fan_max(struct wf_control *ct) 116 { 117 struct smu_fan_control *fct = to_smu_fan(ct); 118 return fct->max; 119 } 120 121 static struct wf_control_ops smu_fan_ops = { 122 .set_value = smu_fan_set, 123 .get_value = smu_fan_get, 124 .get_min = smu_fan_min, 125 .get_max = smu_fan_max, 126 .release = smu_fan_release, 127 .owner = THIS_MODULE, 128 }; 129 130 static struct smu_fan_control *smu_fan_create(struct device_node *node, 131 int pwm_fan) 132 { 133 struct smu_fan_control *fct; 134 s32 *v; u32 *reg; 135 char *l; 136 137 fct = kmalloc(sizeof(struct smu_fan_control), GFP_KERNEL); 138 if (fct == NULL) 139 return NULL; 140 fct->ctrl.ops = &smu_fan_ops; 141 l = (char *)get_property(node, "location", NULL); 142 if (l == NULL) 143 goto fail; 144 145 fct->fan_type = pwm_fan; 146 fct->ctrl.type = pwm_fan ? WF_CONTROL_PWM_FAN : WF_CONTROL_RPM_FAN; 147 148 /* We use the name & location here the same way we do for SMU sensors, 149 * see the comment in windfarm_smu_sensors.c. The locations are a bit 150 * less consistent here between the iMac and the desktop models, but 151 * that is good enough for our needs for now at least. 152 * 153 * One problem though is that Apple seem to be inconsistent with case 154 * and the kernel doesn't have strcasecmp =P 155 */ 156 157 fct->ctrl.name = NULL; 158 159 /* Names used on desktop models */ 160 if (!strcmp(l, "Rear Fan 0") || !strcmp(l, "Rear Fan") || 161 !strcmp(l, "Rear fan 0") || !strcmp(l, "Rear fan")) 162 fct->ctrl.name = "cpu-rear-fan-0"; 163 else if (!strcmp(l, "Rear Fan 1") || !strcmp(l, "Rear fan 1")) 164 fct->ctrl.name = "cpu-rear-fan-1"; 165 else if (!strcmp(l, "Front Fan 0") || !strcmp(l, "Front Fan") || 166 !strcmp(l, "Front fan 0") || !strcmp(l, "Front fan")) 167 fct->ctrl.name = "cpu-front-fan-0"; 168 else if (!strcmp(l, "Front Fan 1") || !strcmp(l, "Front fan 1")) 169 fct->ctrl.name = "cpu-front-fan-1"; 170 else if (!strcmp(l, "Slots Fan") || !strcmp(l, "Slots fan")) 171 fct->ctrl.name = "slots-fan"; 172 else if (!strcmp(l, "Drive Bay") || !strcmp(l, "Drive bay")) 173 fct->ctrl.name = "drive-bay-fan"; 174 175 /* Names used on iMac models */ 176 if (!strcmp(l, "System Fan") || !strcmp(l, "System fan")) 177 fct->ctrl.name = "system-fan"; 178 else if (!strcmp(l, "CPU Fan") || !strcmp(l, "CPU fan")) 179 fct->ctrl.name = "cpu-fan"; 180 else if (!strcmp(l, "Hard Drive") || !strcmp(l, "Hard drive")) 181 fct->ctrl.name = "drive-bay-fan"; 182 183 /* Unrecognized fan, bail out */ 184 if (fct->ctrl.name == NULL) 185 goto fail; 186 187 /* Get min & max values*/ 188 v = (s32 *)get_property(node, "min-value", NULL); 189 if (v == NULL) 190 goto fail; 191 fct->min = *v; 192 v = (s32 *)get_property(node, "max-value", NULL); 193 if (v == NULL) 194 goto fail; 195 fct->max = *v; 196 197 /* Get "reg" value */ 198 reg = (u32 *)get_property(node, "reg", NULL); 199 if (reg == NULL) 200 goto fail; 201 fct->reg = *reg; 202 203 if (wf_register_control(&fct->ctrl)) 204 goto fail; 205 206 return fct; 207 fail: 208 kfree(fct); 209 return NULL; 210 } 211 212 213 static int __init smu_controls_init(void) 214 { 215 struct device_node *smu, *fans, *fan; 216 217 if (!smu_present()) 218 return -ENODEV; 219 220 smu = of_find_node_by_type(NULL, "smu"); 221 if (smu == NULL) 222 return -ENODEV; 223 224 /* Look for RPM fans */ 225 for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;) 226 if (!strcmp(fans->name, "rpm-fans")) 227 break; 228 for (fan = NULL; 229 fans && (fan = of_get_next_child(fans, fan)) != NULL;) { 230 struct smu_fan_control *fct; 231 232 fct = smu_fan_create(fan, 0); 233 if (fct == NULL) { 234 printk(KERN_WARNING "windfarm: Failed to create SMU " 235 "RPM fan %s\n", fan->name); 236 continue; 237 } 238 list_add(&fct->link, &smu_fans); 239 } 240 of_node_put(fans); 241 242 243 /* Look for PWM fans */ 244 for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;) 245 if (!strcmp(fans->name, "pwm-fans")) 246 break; 247 for (fan = NULL; 248 fans && (fan = of_get_next_child(fans, fan)) != NULL;) { 249 struct smu_fan_control *fct; 250 251 fct = smu_fan_create(fan, 1); 252 if (fct == NULL) { 253 printk(KERN_WARNING "windfarm: Failed to create SMU " 254 "PWM fan %s\n", fan->name); 255 continue; 256 } 257 list_add(&fct->link, &smu_fans); 258 } 259 of_node_put(fans); 260 of_node_put(smu); 261 262 return 0; 263 } 264 265 static void __exit smu_controls_exit(void) 266 { 267 struct smu_fan_control *fct; 268 269 while (!list_empty(&smu_fans)) { 270 fct = list_entry(smu_fans.next, struct smu_fan_control, link); 271 list_del(&fct->link); 272 wf_unregister_control(&fct->ctrl); 273 } 274 } 275 276 277 module_init(smu_controls_init); 278 module_exit(smu_controls_exit); 279 280 MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); 281 MODULE_DESCRIPTION("SMU control objects for PowerMacs thermal control"); 282 MODULE_LICENSE("GPL"); 283 284