diff options
| author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2012-04-18 18:16:55 -0400 |
|---|---|---|
| committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2012-04-30 01:37:25 -0400 |
| commit | a78a4a03a75466ff859d989a1a00110ebd0165b0 (patch) | |
| tree | af7748ecde199305ab493a32954f7163a4579914 /drivers/macintosh | |
| parent | d839ba2ab2f3270fe4f067e082a7233ba06bcf9c (diff) | |
powerpc/windfarm: Add Fan Control Unit controls for G5s
The FCU operates the fans on the earlier generation G5 machines,
this module will be used by upcoming windfarm drivers.
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'drivers/macintosh')
| -rw-r--r-- | drivers/macintosh/windfarm_fcu_controls.c | 617 |
1 files changed, 617 insertions, 0 deletions
diff --git a/drivers/macintosh/windfarm_fcu_controls.c b/drivers/macintosh/windfarm_fcu_controls.c new file mode 100644 index 000000000000..871f8b4cf367 --- /dev/null +++ b/drivers/macintosh/windfarm_fcu_controls.c | |||
| @@ -0,0 +1,617 @@ | |||
| 1 | /* | ||
| 2 | * Windfarm PowerMac thermal control. FCU fan control | ||
| 3 | * | ||
| 4 | * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. | ||
| 5 | * | ||
| 6 | * Released under the term of the GNU GPL v2. | ||
| 7 | */ | ||
| 8 | #undef DEBUG | ||
| 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/i2c.h> | ||
| 18 | #include <asm/prom.h> | ||
| 19 | #include <asm/machdep.h> | ||
| 20 | #include <asm/io.h> | ||
| 21 | #include <asm/sections.h> | ||
| 22 | |||
| 23 | #include "windfarm.h" | ||
| 24 | #include "windfarm_mpu.h" | ||
| 25 | |||
| 26 | #define VERSION "1.0" | ||
| 27 | |||
| 28 | #ifdef DEBUG | ||
| 29 | #define DBG(args...) printk(args) | ||
| 30 | #else | ||
| 31 | #define DBG(args...) do { } while(0) | ||
| 32 | #endif | ||
| 33 | |||
| 34 | /* | ||
| 35 | * This option is "weird" :) Basically, if you define this to 1 | ||
| 36 | * the control loop for the RPMs fans (not PWMs) will apply the | ||
| 37 | * correction factor obtained from the PID to the actual RPM | ||
| 38 | * speed read from the FCU. | ||
| 39 | * | ||
| 40 | * If you define the below constant to 0, then it will be | ||
| 41 | * applied to the setpoint RPM speed, that is basically the | ||
| 42 | * speed we proviously "asked" for. | ||
| 43 | * | ||
| 44 | * I'm not sure which of these Apple's algorithm is supposed | ||
| 45 | * to use | ||
| 46 | */ | ||
| 47 | #define RPM_PID_USE_ACTUAL_SPEED 1 | ||
| 48 | |||
| 49 | /* Default min/max for pumps */ | ||
| 50 | #define CPU_PUMP_OUTPUT_MAX 3200 | ||
| 51 | #define CPU_PUMP_OUTPUT_MIN 1250 | ||
| 52 | |||
| 53 | #define FCU_FAN_RPM 0 | ||
| 54 | #define FCU_FAN_PWM 1 | ||
| 55 | |||
| 56 | struct wf_fcu_priv { | ||
| 57 | struct kref ref; | ||
| 58 | struct i2c_client *i2c; | ||
| 59 | struct mutex lock; | ||
| 60 | struct list_head fan_list; | ||
| 61 | int rpm_shift; | ||
| 62 | }; | ||
| 63 | |||
| 64 | struct wf_fcu_fan { | ||
| 65 | struct list_head link; | ||
| 66 | int id; | ||
| 67 | s32 min, max, target; | ||
| 68 | struct wf_fcu_priv *fcu_priv; | ||
| 69 | struct wf_control ctrl; | ||
| 70 | }; | ||
| 71 | |||
| 72 | static void wf_fcu_release(struct kref *ref) | ||
| 73 | { | ||
| 74 | struct wf_fcu_priv *pv = container_of(ref, struct wf_fcu_priv, ref); | ||
| 75 | |||
| 76 | kfree(pv); | ||
| 77 | } | ||
| 78 | |||
| 79 | static void wf_fcu_fan_release(struct wf_control *ct) | ||
| 80 | { | ||
| 81 | struct wf_fcu_fan *fan = ct->priv; | ||
| 82 | |||
| 83 | kref_put(&fan->fcu_priv->ref, wf_fcu_release); | ||
| 84 | kfree(fan); | ||
| 85 | } | ||
| 86 | |||
| 87 | static int wf_fcu_read_reg(struct wf_fcu_priv *pv, int reg, | ||
| 88 | unsigned char *buf, int nb) | ||
| 89 | { | ||
| 90 | int tries, nr, nw; | ||
| 91 | |||
| 92 | mutex_lock(&pv->lock); | ||
| 93 | |||
| 94 | buf[0] = reg; | ||
| 95 | tries = 0; | ||
| 96 | for (;;) { | ||
| 97 | nw = i2c_master_send(pv->i2c, buf, 1); | ||
| 98 | if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100) | ||
| 99 | break; | ||
| 100 | msleep(10); | ||
| 101 | ++tries; | ||
| 102 | } | ||
| 103 | if (nw <= 0) { | ||
| 104 | pr_err("Failure writing address to FCU: %d", nw); | ||
| 105 | nr = nw; | ||
| 106 | goto bail; | ||
| 107 | } | ||
| 108 | tries = 0; | ||
| 109 | for (;;) { | ||
| 110 | nr = i2c_master_recv(pv->i2c, buf, nb); | ||
| 111 | if (nr > 0 || (nr < 0 && nr != -ENODEV) || tries >= 100) | ||
| 112 | break; | ||
| 113 | msleep(10); | ||
| 114 | ++tries; | ||
| 115 | } | ||
| 116 | if (nr <= 0) | ||
| 117 | pr_err("wf_fcu: Failure reading data from FCU: %d", nw); | ||
| 118 | bail: | ||
| 119 | mutex_unlock(&pv->lock); | ||
| 120 | return nr; | ||
| 121 | } | ||
| 122 | |||
| 123 | static int wf_fcu_write_reg(struct wf_fcu_priv *pv, int reg, | ||
| 124 | const unsigned char *ptr, int nb) | ||
| 125 | { | ||
| 126 | int tries, nw; | ||
| 127 | unsigned char buf[16]; | ||
| 128 | |||
| 129 | buf[0] = reg; | ||
| 130 | memcpy(buf+1, ptr, nb); | ||
| 131 | ++nb; | ||
| 132 | tries = 0; | ||
| 133 | for (;;) { | ||
| 134 | nw = i2c_master_send(pv->i2c, buf, nb); | ||
| 135 | if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100) | ||
| 136 | break; | ||
| 137 | msleep(10); | ||
| 138 | ++tries; | ||
| 139 | } | ||
| 140 | if (nw < 0) | ||
| 141 | pr_err("wf_fcu: Failure writing to FCU: %d", nw); | ||
| 142 | return nw; | ||
| 143 | } | ||
| 144 | |||
| 145 | static int wf_fcu_fan_set_rpm(struct wf_control *ct, s32 value) | ||
| 146 | { | ||
| 147 | struct wf_fcu_fan *fan = ct->priv; | ||
| 148 | struct wf_fcu_priv *pv = fan->fcu_priv; | ||
| 149 | int rc, shift = pv->rpm_shift; | ||
| 150 | unsigned char buf[2]; | ||
| 151 | |||
| 152 | if (value < fan->min) | ||
| 153 | value = fan->min; | ||
| 154 | if (value > fan->max) | ||
| 155 | value = fan->max; | ||
| 156 | |||
| 157 | if (fan->target && fan->target == value) | ||
| 158 | return 0; | ||
| 159 | fan->target = value; | ||
| 160 | |||
| 161 | buf[0] = value >> (8 - shift); | ||
| 162 | buf[1] = value << shift; | ||
| 163 | rc = wf_fcu_write_reg(pv, 0x10 + (fan->id * 2), buf, 2); | ||
| 164 | if (rc < 0) | ||
| 165 | return -EIO; | ||
| 166 | return 0; | ||
| 167 | } | ||
| 168 | |||
| 169 | static int wf_fcu_fan_get_rpm(struct wf_control *ct, s32 *value) | ||
| 170 | { | ||
| 171 | struct wf_fcu_fan *fan = ct->priv; | ||
| 172 | struct wf_fcu_priv *pv = fan->fcu_priv; | ||
| 173 | int rc, reg_base, shift = pv->rpm_shift; | ||
| 174 | unsigned char failure; | ||
| 175 | unsigned char active; | ||
| 176 | unsigned char buf[2]; | ||
| 177 | |||
| 178 | rc = wf_fcu_read_reg(pv, 0xb, &failure, 1); | ||
| 179 | if (rc != 1) | ||
| 180 | return -EIO; | ||
| 181 | if ((failure & (1 << fan->id)) != 0) | ||
| 182 | return -EFAULT; | ||
| 183 | rc = wf_fcu_read_reg(pv, 0xd, &active, 1); | ||
| 184 | if (rc != 1) | ||
| 185 | return -EIO; | ||
| 186 | if ((active & (1 << fan->id)) == 0) | ||
| 187 | return -ENXIO; | ||
| 188 | |||
| 189 | /* Programmed value or real current speed */ | ||
| 190 | #if RPM_PID_USE_ACTUAL_SPEED | ||
| 191 | reg_base = 0x11; | ||
| 192 | #else | ||
| 193 | reg_base = 0x10; | ||
| 194 | #endif | ||
| 195 | rc = wf_fcu_read_reg(pv, reg_base + (fan->id * 2), buf, 2); | ||
| 196 | if (rc != 2) | ||
| 197 | return -EIO; | ||
| 198 | |||
| 199 | *value = (buf[0] << (8 - shift)) | buf[1] >> shift; | ||
| 200 | |||
| 201 | return 0; | ||
| 202 | } | ||
| 203 | |||
| 204 | static int wf_fcu_fan_set_pwm(struct wf_control *ct, s32 value) | ||
| 205 | { | ||
| 206 | struct wf_fcu_fan *fan = ct->priv; | ||
| 207 | struct wf_fcu_priv *pv = fan->fcu_priv; | ||
| 208 | unsigned char buf[2]; | ||
| 209 | int rc; | ||
| 210 | |||
| 211 | if (value < fan->min) | ||
| 212 | value = fan->min; | ||
| 213 | if (value > fan->max) | ||
| 214 | value = fan->max; | ||
| 215 | |||
| 216 | if (fan->target && fan->target == value) | ||
| 217 | return 0; | ||
| 218 | fan->target = value; | ||
| 219 | |||
| 220 | value = (value * 2559) / 1000; | ||
| 221 | buf[0] = value; | ||
| 222 | rc = wf_fcu_write_reg(pv, 0x30 + (fan->id * 2), buf, 1); | ||
| 223 | if (rc < 0) | ||
| 224 | return -EIO; | ||
| 225 | return 0; | ||
| 226 | } | ||
| 227 | |||
| 228 | static int wf_fcu_fan_get_pwm(struct wf_control *ct, s32 *value) | ||
| 229 | { | ||
| 230 | struct wf_fcu_fan *fan = ct->priv; | ||
| 231 | struct wf_fcu_priv *pv = fan->fcu_priv; | ||
| 232 | unsigned char failure; | ||
| 233 | unsigned char active; | ||
| 234 | unsigned char buf[2]; | ||
| 235 | int rc; | ||
| 236 | |||
| 237 | rc = wf_fcu_read_reg(pv, 0x2b, &failure, 1); | ||
| 238 | if (rc != 1) | ||
| 239 | return -EIO; | ||
| 240 | if ((failure & (1 << fan->id)) != 0) | ||
| 241 | return -EFAULT; | ||
| 242 | rc = wf_fcu_read_reg(pv, 0x2d, &active, 1); | ||
| 243 | if (rc != 1) | ||
| 244 | return -EIO; | ||
| 245 | if ((active & (1 << fan->id)) == 0) | ||
| 246 | return -ENXIO; | ||
| 247 | |||
| 248 | rc = wf_fcu_read_reg(pv, 0x30 + (fan->id * 2), buf, 1); | ||
| 249 | if (rc != 1) | ||
| 250 | return -EIO; | ||
| 251 | |||
| 252 | *value = (((s32)buf[0]) * 1000) / 2559; | ||
| 253 | |||
| 254 | return 0; | ||
| 255 | } | ||
| 256 | |||
| 257 | static s32 wf_fcu_fan_min(struct wf_control *ct) | ||
| 258 | { | ||
| 259 | struct wf_fcu_fan *fan = ct->priv; | ||
| 260 | |||
| 261 | return fan->min; | ||
| 262 | } | ||
| 263 | |||
| 264 | static s32 wf_fcu_fan_max(struct wf_control *ct) | ||
| 265 | { | ||
| 266 | struct wf_fcu_fan *fan = ct->priv; | ||
| 267 | |||
| 268 | return fan->max; | ||
| 269 | } | ||
| 270 | |||
| 271 | static const struct wf_control_ops wf_fcu_fan_rpm_ops = { | ||
| 272 | .set_value = wf_fcu_fan_set_rpm, | ||
| 273 | .get_value = wf_fcu_fan_get_rpm, | ||
| 274 | .get_min = wf_fcu_fan_min, | ||
| 275 | .get_max = wf_fcu_fan_max, | ||
| 276 | .release = wf_fcu_fan_release, | ||
| 277 | .owner = THIS_MODULE, | ||
| 278 | }; | ||
| 279 | |||
| 280 | static const struct wf_control_ops wf_fcu_fan_pwm_ops = { | ||
| 281 | .set_value = wf_fcu_fan_set_pwm, | ||
| 282 | .get_value = wf_fcu_fan_get_pwm, | ||
| 283 | .get_min = wf_fcu_fan_min, | ||
| 284 | .get_max = wf_fcu_fan_max, | ||
| 285 | .release = wf_fcu_fan_release, | ||
| 286 | .owner = THIS_MODULE, | ||
| 287 | }; | ||
| 288 | |||
| 289 | static void __devinit wf_fcu_get_pump_minmax(struct wf_fcu_fan *fan) | ||
| 290 | { | ||
| 291 | const struct mpu_data *mpu = wf_get_mpu(0); | ||
| 292 | u16 pump_min = 0, pump_max = 0xffff; | ||
| 293 | u16 tmp[4]; | ||
| 294 | |||
| 295 | /* Try to fetch pumps min/max infos from eeprom */ | ||
| 296 | if (mpu) { | ||
| 297 | memcpy(&tmp, mpu->processor_part_num, 8); | ||
| 298 | if (tmp[0] != 0xffff && tmp[1] != 0xffff) { | ||
| 299 | pump_min = max(pump_min, tmp[0]); | ||
| 300 | pump_max = min(pump_max, tmp[1]); | ||
| 301 | } | ||
| 302 | if (tmp[2] != 0xffff && tmp[3] != 0xffff) { | ||
| 303 | pump_min = max(pump_min, tmp[2]); | ||
| 304 | pump_max = min(pump_max, tmp[3]); | ||
| 305 | } | ||
| 306 | } | ||
| 307 | |||
| 308 | /* Double check the values, this _IS_ needed as the EEPROM on | ||
| 309 | * some dual 2.5Ghz G5s seem, at least, to have both min & max | ||
| 310 | * same to the same value ... (grrrr) | ||
| 311 | */ | ||
| 312 | if (pump_min == pump_max || pump_min == 0 || pump_max == 0xffff) { | ||
| 313 | pump_min = CPU_PUMP_OUTPUT_MIN; | ||
| 314 | pump_max = CPU_PUMP_OUTPUT_MAX; | ||
| 315 | } | ||
| 316 | |||
| 317 | fan->min = pump_min; | ||
| 318 | fan->max = pump_max; | ||
| 319 | |||
| 320 | DBG("wf_fcu: pump min/max for %s set to: [%d..%d] RPM\n", | ||
| 321 | fan->ctrl.name, pump_min, pump_max); | ||
| 322 | } | ||
| 323 | |||
| 324 | static void __devinit wf_fcu_get_rpmfan_minmax(struct wf_fcu_fan *fan) | ||
| 325 | { | ||
| 326 | struct wf_fcu_priv *pv = fan->fcu_priv; | ||
| 327 | const struct mpu_data *mpu0 = wf_get_mpu(0); | ||
| 328 | const struct mpu_data *mpu1 = wf_get_mpu(1); | ||
| 329 | |||
| 330 | /* Default */ | ||
| 331 | fan->min = 2400 >> pv->rpm_shift; | ||
| 332 | fan->max = 56000 >> pv->rpm_shift; | ||
| 333 | |||
| 334 | /* CPU fans have min/max in MPU */ | ||
| 335 | if (mpu0 && !strcmp(fan->ctrl.name, "cpu-front-fan-0")) { | ||
| 336 | fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan); | ||
| 337 | fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan); | ||
| 338 | goto bail; | ||
| 339 | } | ||
| 340 | if (mpu1 && !strcmp(fan->ctrl.name, "cpu-front-fan-1")) { | ||
| 341 | fan->min = max(fan->min, (s32)mpu1->rminn_intake_fan); | ||
| 342 | fan->max = min(fan->max, (s32)mpu1->rmaxn_intake_fan); | ||
| 343 | goto bail; | ||
| 344 | } | ||
| 345 | if (mpu0 && !strcmp(fan->ctrl.name, "cpu-rear-fan-0")) { | ||
| 346 | fan->min = max(fan->min, (s32)mpu0->rminn_exhaust_fan); | ||
| 347 | fan->max = min(fan->max, (s32)mpu0->rmaxn_exhaust_fan); | ||
| 348 | goto bail; | ||
| 349 | } | ||
| 350 | if (mpu1 && !strcmp(fan->ctrl.name, "cpu-rear-fan-1")) { | ||
| 351 | fan->min = max(fan->min, (s32)mpu1->rminn_exhaust_fan); | ||
| 352 | fan->max = min(fan->max, (s32)mpu1->rmaxn_exhaust_fan); | ||
| 353 | goto bail; | ||
| 354 | } | ||
| 355 | /* Rackmac variants, we just use mpu0 intake */ | ||
| 356 | if (!strncmp(fan->ctrl.name, "cpu-fan", 7)) { | ||
| 357 | fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan); | ||
| 358 | fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan); | ||
| 359 | goto bail; | ||
| 360 | } | ||
| 361 | bail: | ||
| 362 | DBG("wf_fcu: fan min/max for %s set to: [%d..%d] RPM\n", | ||
| 363 | fan->ctrl.name, fan->min, fan->max); | ||
| 364 | } | ||
| 365 | |||
| 366 | static void __devinit wf_fcu_add_fan(struct wf_fcu_priv *pv, | ||
| 367 | const char *name, | ||
| 368 | int type, int id) | ||
| 369 | { | ||
| 370 | struct wf_fcu_fan *fan; | ||
| 371 | |||
| 372 | fan = kzalloc(sizeof(*fan), GFP_KERNEL); | ||
| 373 | if (!fan) | ||
| 374 | return; | ||
| 375 | fan->fcu_priv = pv; | ||
| 376 | fan->id = id; | ||
| 377 | fan->ctrl.name = name; | ||
| 378 | fan->ctrl.priv = fan; | ||
| 379 | |||
| 380 | /* min/max is oddball but the code comes from | ||
| 381 | * therm_pm72 which seems to work so ... | ||
| 382 | */ | ||
| 383 | if (type == FCU_FAN_RPM) { | ||
| 384 | if (!strncmp(name, "cpu-pump", strlen("cpu-pump"))) | ||
| 385 | wf_fcu_get_pump_minmax(fan); | ||
| 386 | else | ||
| 387 | wf_fcu_get_rpmfan_minmax(fan); | ||
| 388 | fan->ctrl.type = WF_CONTROL_RPM_FAN; | ||
| 389 | fan->ctrl.ops = &wf_fcu_fan_rpm_ops; | ||
| 390 | } else { | ||
| 391 | fan->min = 10; | ||
| 392 | fan->max = 100; | ||
| 393 | fan->ctrl.type = WF_CONTROL_PWM_FAN; | ||
| 394 | fan->ctrl.ops = &wf_fcu_fan_pwm_ops; | ||
| 395 | } | ||
| 396 | |||
| 397 | if (wf_register_control(&fan->ctrl)) { | ||
| 398 | pr_err("wf_fcu: Failed to register fan %s\n", name); | ||
| 399 | kfree(fan); | ||
| 400 | return; | ||
| 401 | } | ||
| 402 | list_add(&fan->link, &pv->fan_list); | ||
| 403 | kref_get(&pv->ref); | ||
| 404 | } | ||
| 405 | |||
| 406 | static void __devinit wf_fcu_lookup_fans(struct wf_fcu_priv *pv) | ||
| 407 | { | ||
| 408 | /* Translation of device-tree location properties to | ||
| 409 | * windfarm fan names | ||
| 410 | */ | ||
| 411 | static const struct { | ||
| 412 | const char *dt_name; /* Device-tree name */ | ||
| 413 | const char *ct_name; /* Control name */ | ||
| 414 | } loc_trans[] = { | ||
| 415 | { "BACKSIDE", "backside-fan", }, | ||
| 416 | { "SYS CTRLR FAN", "backside-fan", }, | ||
| 417 | { "DRIVE BAY", "drive-bay-fan", }, | ||
| 418 | { "SLOT", "slots-fan", }, | ||
| 419 | { "PCI FAN", "slots-fan", }, | ||
| 420 | { "CPU A INTAKE", "cpu-front-fan-0", }, | ||
| 421 | { "CPU A EXHAUST", "cpu-rear-fan-0", }, | ||
| 422 | { "CPU B INTAKE", "cpu-front-fan-1", }, | ||
| 423 | { "CPU B EXHAUST", "cpu-rear-fan-1", }, | ||
| 424 | { "CPU A PUMP", "cpu-pump-0", }, | ||
| 425 | { "CPU B PUMP", "cpu-pump-1", }, | ||
| 426 | { "CPU A 1", "cpu-fan-a-0", }, | ||
| 427 | { "CPU A 2", "cpu-fan-b-0", }, | ||
| 428 | { "CPU A 3", "cpu-fan-c-0", }, | ||
| 429 | { "CPU B 1", "cpu-fan-a-1", }, | ||
| 430 | { "CPU B 2", "cpu-fan-b-1", }, | ||
| 431 | { "CPU B 3", "cpu-fan-c-1", }, | ||
| 432 | }; | ||
| 433 | struct device_node *np = NULL, *fcu = pv->i2c->dev.of_node; | ||
| 434 | int i; | ||
| 435 | |||
| 436 | DBG("Looking up FCU controls in device-tree...\n"); | ||
| 437 | |||
| 438 | while ((np = of_get_next_child(fcu, np)) != NULL) { | ||
| 439 | int id, type = -1; | ||
| 440 | const char *loc; | ||
| 441 | const char *name; | ||
| 442 | const u32 *reg; | ||
| 443 | |||
| 444 | DBG(" control: %s, type: %s\n", np->name, np->type); | ||
| 445 | |||
| 446 | /* Detect control type */ | ||
| 447 | if (!strcmp(np->type, "fan-rpm-control") || | ||
| 448 | !strcmp(np->type, "fan-rpm")) | ||
| 449 | type = FCU_FAN_RPM; | ||
| 450 | if (!strcmp(np->type, "fan-pwm-control") || | ||
| 451 | !strcmp(np->type, "fan-pwm")) | ||
| 452 | type = FCU_FAN_PWM; | ||
| 453 | /* Only care about fans for now */ | ||
| 454 | if (type == -1) | ||
| 455 | continue; | ||
| 456 | |||
| 457 | /* Lookup for a matching location */ | ||
| 458 | loc = of_get_property(np, "location", NULL); | ||
| 459 | reg = of_get_property(np, "reg", NULL); | ||
| 460 | if (loc == NULL || reg == NULL) | ||
| 461 | continue; | ||
| 462 | DBG(" matching location: %s, reg: 0x%08x\n", loc, *reg); | ||
| 463 | |||
| 464 | for (i = 0; i < ARRAY_SIZE(loc_trans); i++) { | ||
| 465 | if (strncmp(loc, loc_trans[i].dt_name, | ||
| 466 | strlen(loc_trans[i].dt_name))) | ||
| 467 | continue; | ||
| 468 | name = loc_trans[i].ct_name; | ||
| 469 | |||
| 470 | DBG(" location match, name: %s\n", name); | ||
| 471 | |||
| 472 | if (type == FCU_FAN_RPM) | ||
| 473 | id = ((*reg) - 0x10) / 2; | ||
| 474 | else | ||
| 475 | id = ((*reg) - 0x30) / 2; | ||
| 476 | if (id > 7) { | ||
| 477 | pr_warning("wf_fcu: Can't parse " | ||
| 478 | "fan ID in device-tree for %s\n", | ||
| 479 | np->full_name); | ||
| 480 | break; | ||
| 481 | } | ||
| 482 | wf_fcu_add_fan(pv, name, type, id); | ||
| 483 | break; | ||
| 484 | } | ||
| 485 | } | ||
| 486 | } | ||
| 487 | |||
| 488 | static void __devinit wf_fcu_default_fans(struct wf_fcu_priv *pv) | ||
| 489 | { | ||
| 490 | /* We only support the default fans for PowerMac7,2 */ | ||
| 491 | if (!of_machine_is_compatible("PowerMac7,2")) | ||
| 492 | return; | ||
| 493 | |||
| 494 | wf_fcu_add_fan(pv, "backside-fan", FCU_FAN_PWM, 1); | ||
| 495 | wf_fcu_add_fan(pv, "drive-bay-fan", FCU_FAN_RPM, 2); | ||
| 496 | wf_fcu_add_fan(pv, "slots-fan", FCU_FAN_PWM, 2); | ||
| 497 | wf_fcu_add_fan(pv, "cpu-front-fan-0", FCU_FAN_RPM, 3); | ||
| 498 | wf_fcu_add_fan(pv, "cpu-rear-fan-0", FCU_FAN_RPM, 4); | ||
| 499 | wf_fcu_add_fan(pv, "cpu-front-fan-1", FCU_FAN_RPM, 5); | ||
| 500 | wf_fcu_add_fan(pv, "cpu-rear-fan-1", FCU_FAN_RPM, 6); | ||
| 501 | } | ||
| 502 | |||
| 503 | static int __devinit wf_fcu_init_chip(struct wf_fcu_priv *pv) | ||
| 504 | { | ||
| 505 | unsigned char buf = 0xff; | ||
| 506 | int rc; | ||
| 507 | |||
| 508 | rc = wf_fcu_write_reg(pv, 0xe, &buf, 1); | ||
| 509 | if (rc < 0) | ||
| 510 | return -EIO; | ||
| 511 | rc = wf_fcu_write_reg(pv, 0x2e, &buf, 1); | ||
| 512 | if (rc < 0) | ||
| 513 | return -EIO; | ||
| 514 | rc = wf_fcu_read_reg(pv, 0, &buf, 1); | ||
| 515 | if (rc < 0) | ||
| 516 | return -EIO; | ||
| 517 | pv->rpm_shift = (buf == 1) ? 2 : 3; | ||
| 518 | |||
| 519 | pr_debug("wf_fcu: FCU Initialized, RPM fan shift is %d\n", | ||
| 520 | pv->rpm_shift); | ||
| 521 | |||
| 522 | return 0; | ||
| 523 | } | ||
| 524 | |||
| 525 | static int __devinit wf_fcu_probe(struct i2c_client *client, | ||
| 526 | const struct i2c_device_id *id) | ||
| 527 | { | ||
| 528 | struct wf_fcu_priv *pv; | ||
| 529 | |||
| 530 | pv = kzalloc(sizeof(*pv), GFP_KERNEL); | ||
| 531 | if (!pv) | ||
| 532 | return -ENOMEM; | ||
| 533 | |||
| 534 | kref_init(&pv->ref); | ||
| 535 | mutex_init(&pv->lock); | ||
| 536 | INIT_LIST_HEAD(&pv->fan_list); | ||
| 537 | pv->i2c = client; | ||
| 538 | |||
| 539 | /* | ||
| 540 | * First we must start the FCU which will query the | ||
| 541 | * shift value to apply to RPMs | ||
| 542 | */ | ||
| 543 | if (wf_fcu_init_chip(pv)) { | ||
| 544 | pr_err("wf_fcu: Initialization failed !\n"); | ||
| 545 | kfree(pv); | ||
| 546 | return -ENXIO; | ||
| 547 | } | ||
| 548 | |||
| 549 | /* First lookup fans in the device-tree */ | ||
| 550 | wf_fcu_lookup_fans(pv); | ||
| 551 | |||
| 552 | /* | ||
| 553 | * Older machines don't have the device-tree entries | ||
| 554 | * we are looking for, just hard code the list | ||
| 555 | */ | ||
| 556 | if (list_empty(&pv->fan_list)) | ||
| 557 | wf_fcu_default_fans(pv); | ||
| 558 | |||
| 559 | /* Still no fans ? FAIL */ | ||
| 560 | if (list_empty(&pv->fan_list)) { | ||
| 561 | pr_err("wf_fcu: Failed to find fans for your machine\n"); | ||
| 562 | kfree(pv); | ||
| 563 | return -ENODEV; | ||
| 564 | } | ||
| 565 | |||
| 566 | dev_set_drvdata(&client->dev, pv); | ||
| 567 | |||
| 568 | return 0; | ||
| 569 | } | ||
| 570 | |||
| 571 | static int __devexit wf_fcu_remove(struct i2c_client *client) | ||
| 572 | { | ||
| 573 | struct wf_fcu_priv *pv = dev_get_drvdata(&client->dev); | ||
| 574 | struct wf_fcu_fan *fan; | ||
| 575 | |||
| 576 | while (!list_empty(&pv->fan_list)) { | ||
| 577 | fan = list_first_entry(&pv->fan_list, struct wf_fcu_fan, link); | ||
| 578 | list_del(&fan->link); | ||
| 579 | wf_unregister_control(&fan->ctrl); | ||
| 580 | } | ||
| 581 | kref_put(&pv->ref, wf_fcu_release); | ||
| 582 | return 0; | ||
| 583 | } | ||
| 584 | |||
| 585 | static const struct i2c_device_id wf_fcu_id[] = { | ||
| 586 | { "MAC,fcu", 0 }, | ||
| 587 | { } | ||
| 588 | }; | ||
| 589 | MODULE_DEVICE_TABLE(i2c, wf_fcu_id); | ||
| 590 | |||
| 591 | static struct i2c_driver wf_fcu_driver = { | ||
| 592 | .driver = { | ||
| 593 | .name = "wf_fcu", | ||
| 594 | }, | ||
| 595 | .probe = wf_fcu_probe, | ||
| 596 | .remove = wf_fcu_remove, | ||
| 597 | .id_table = wf_fcu_id, | ||
| 598 | }; | ||
| 599 | |||
| 600 | static int __init wf_fcu_init(void) | ||
| 601 | { | ||
| 602 | return i2c_add_driver(&wf_fcu_driver); | ||
| 603 | } | ||
| 604 | |||
| 605 | static void __exit wf_fcu_exit(void) | ||
| 606 | { | ||
| 607 | i2c_del_driver(&wf_fcu_driver); | ||
| 608 | } | ||
| 609 | |||
| 610 | |||
| 611 | module_init(wf_fcu_init); | ||
| 612 | module_exit(wf_fcu_exit); | ||
| 613 | |||
| 614 | MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); | ||
| 615 | MODULE_DESCRIPTION("FCU control objects for PowerMacs thermal control"); | ||
| 616 | MODULE_LICENSE("GPL"); | ||
| 617 | |||
