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