162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Windfarm PowerMac thermal control. FCU fan control 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci#undef DEBUG 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/types.h> 1062306a36Sopenharmony_ci#include <linux/errno.h> 1162306a36Sopenharmony_ci#include <linux/kernel.h> 1262306a36Sopenharmony_ci#include <linux/delay.h> 1362306a36Sopenharmony_ci#include <linux/slab.h> 1462306a36Sopenharmony_ci#include <linux/init.h> 1562306a36Sopenharmony_ci#include <linux/wait.h> 1662306a36Sopenharmony_ci#include <linux/i2c.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <asm/machdep.h> 1962306a36Sopenharmony_ci#include <asm/io.h> 2062306a36Sopenharmony_ci#include <asm/sections.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#include "windfarm.h" 2362306a36Sopenharmony_ci#include "windfarm_mpu.h" 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define VERSION "1.0" 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#ifdef DEBUG 2862306a36Sopenharmony_ci#define DBG(args...) printk(args) 2962306a36Sopenharmony_ci#else 3062306a36Sopenharmony_ci#define DBG(args...) do { } while(0) 3162306a36Sopenharmony_ci#endif 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci/* 3462306a36Sopenharmony_ci * This option is "weird" :) Basically, if you define this to 1 3562306a36Sopenharmony_ci * the control loop for the RPMs fans (not PWMs) will apply the 3662306a36Sopenharmony_ci * correction factor obtained from the PID to the actual RPM 3762306a36Sopenharmony_ci * speed read from the FCU. 3862306a36Sopenharmony_ci * 3962306a36Sopenharmony_ci * If you define the below constant to 0, then it will be 4062306a36Sopenharmony_ci * applied to the setpoint RPM speed, that is basically the 4162306a36Sopenharmony_ci * speed we proviously "asked" for. 4262306a36Sopenharmony_ci * 4362306a36Sopenharmony_ci * I'm using 0 for now which is what therm_pm72 used to do and 4462306a36Sopenharmony_ci * what Darwin -apparently- does based on observed behaviour. 4562306a36Sopenharmony_ci */ 4662306a36Sopenharmony_ci#define RPM_PID_USE_ACTUAL_SPEED 0 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci/* Default min/max for pumps */ 4962306a36Sopenharmony_ci#define CPU_PUMP_OUTPUT_MAX 3200 5062306a36Sopenharmony_ci#define CPU_PUMP_OUTPUT_MIN 1250 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci#define FCU_FAN_RPM 0 5362306a36Sopenharmony_ci#define FCU_FAN_PWM 1 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistruct wf_fcu_priv { 5662306a36Sopenharmony_ci struct kref ref; 5762306a36Sopenharmony_ci struct i2c_client *i2c; 5862306a36Sopenharmony_ci struct mutex lock; 5962306a36Sopenharmony_ci struct list_head fan_list; 6062306a36Sopenharmony_ci int rpm_shift; 6162306a36Sopenharmony_ci}; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistruct wf_fcu_fan { 6462306a36Sopenharmony_ci struct list_head link; 6562306a36Sopenharmony_ci int id; 6662306a36Sopenharmony_ci s32 min, max, target; 6762306a36Sopenharmony_ci struct wf_fcu_priv *fcu_priv; 6862306a36Sopenharmony_ci struct wf_control ctrl; 6962306a36Sopenharmony_ci}; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cistatic void wf_fcu_release(struct kref *ref) 7262306a36Sopenharmony_ci{ 7362306a36Sopenharmony_ci struct wf_fcu_priv *pv = container_of(ref, struct wf_fcu_priv, ref); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci kfree(pv); 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic void wf_fcu_fan_release(struct wf_control *ct) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci struct wf_fcu_fan *fan = ct->priv; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci kref_put(&fan->fcu_priv->ref, wf_fcu_release); 8362306a36Sopenharmony_ci kfree(fan); 8462306a36Sopenharmony_ci} 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic int wf_fcu_read_reg(struct wf_fcu_priv *pv, int reg, 8762306a36Sopenharmony_ci unsigned char *buf, int nb) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci int tries, nr, nw; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci mutex_lock(&pv->lock); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci buf[0] = reg; 9462306a36Sopenharmony_ci tries = 0; 9562306a36Sopenharmony_ci for (;;) { 9662306a36Sopenharmony_ci nw = i2c_master_send(pv->i2c, buf, 1); 9762306a36Sopenharmony_ci if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100) 9862306a36Sopenharmony_ci break; 9962306a36Sopenharmony_ci msleep(10); 10062306a36Sopenharmony_ci ++tries; 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci if (nw <= 0) { 10362306a36Sopenharmony_ci pr_err("Failure writing address to FCU: %d", nw); 10462306a36Sopenharmony_ci nr = nw; 10562306a36Sopenharmony_ci goto bail; 10662306a36Sopenharmony_ci } 10762306a36Sopenharmony_ci tries = 0; 10862306a36Sopenharmony_ci for (;;) { 10962306a36Sopenharmony_ci nr = i2c_master_recv(pv->i2c, buf, nb); 11062306a36Sopenharmony_ci if (nr > 0 || (nr < 0 && nr != -ENODEV) || tries >= 100) 11162306a36Sopenharmony_ci break; 11262306a36Sopenharmony_ci msleep(10); 11362306a36Sopenharmony_ci ++tries; 11462306a36Sopenharmony_ci } 11562306a36Sopenharmony_ci if (nr <= 0) 11662306a36Sopenharmony_ci pr_err("wf_fcu: Failure reading data from FCU: %d", nw); 11762306a36Sopenharmony_ci bail: 11862306a36Sopenharmony_ci mutex_unlock(&pv->lock); 11962306a36Sopenharmony_ci return nr; 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistatic int wf_fcu_write_reg(struct wf_fcu_priv *pv, int reg, 12362306a36Sopenharmony_ci const unsigned char *ptr, int nb) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci int tries, nw; 12662306a36Sopenharmony_ci unsigned char buf[16]; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci buf[0] = reg; 12962306a36Sopenharmony_ci memcpy(buf+1, ptr, nb); 13062306a36Sopenharmony_ci ++nb; 13162306a36Sopenharmony_ci tries = 0; 13262306a36Sopenharmony_ci for (;;) { 13362306a36Sopenharmony_ci nw = i2c_master_send(pv->i2c, buf, nb); 13462306a36Sopenharmony_ci if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100) 13562306a36Sopenharmony_ci break; 13662306a36Sopenharmony_ci msleep(10); 13762306a36Sopenharmony_ci ++tries; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci if (nw < 0) 14062306a36Sopenharmony_ci pr_err("wf_fcu: Failure writing to FCU: %d", nw); 14162306a36Sopenharmony_ci return nw; 14262306a36Sopenharmony_ci} 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic int wf_fcu_fan_set_rpm(struct wf_control *ct, s32 value) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci struct wf_fcu_fan *fan = ct->priv; 14762306a36Sopenharmony_ci struct wf_fcu_priv *pv = fan->fcu_priv; 14862306a36Sopenharmony_ci int rc, shift = pv->rpm_shift; 14962306a36Sopenharmony_ci unsigned char buf[2]; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if (value < fan->min) 15262306a36Sopenharmony_ci value = fan->min; 15362306a36Sopenharmony_ci if (value > fan->max) 15462306a36Sopenharmony_ci value = fan->max; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci fan->target = value; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci buf[0] = value >> (8 - shift); 15962306a36Sopenharmony_ci buf[1] = value << shift; 16062306a36Sopenharmony_ci rc = wf_fcu_write_reg(pv, 0x10 + (fan->id * 2), buf, 2); 16162306a36Sopenharmony_ci if (rc < 0) 16262306a36Sopenharmony_ci return -EIO; 16362306a36Sopenharmony_ci return 0; 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cistatic int wf_fcu_fan_get_rpm(struct wf_control *ct, s32 *value) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci struct wf_fcu_fan *fan = ct->priv; 16962306a36Sopenharmony_ci struct wf_fcu_priv *pv = fan->fcu_priv; 17062306a36Sopenharmony_ci int rc, reg_base, shift = pv->rpm_shift; 17162306a36Sopenharmony_ci unsigned char failure; 17262306a36Sopenharmony_ci unsigned char active; 17362306a36Sopenharmony_ci unsigned char buf[2]; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci rc = wf_fcu_read_reg(pv, 0xb, &failure, 1); 17662306a36Sopenharmony_ci if (rc != 1) 17762306a36Sopenharmony_ci return -EIO; 17862306a36Sopenharmony_ci if ((failure & (1 << fan->id)) != 0) 17962306a36Sopenharmony_ci return -EFAULT; 18062306a36Sopenharmony_ci rc = wf_fcu_read_reg(pv, 0xd, &active, 1); 18162306a36Sopenharmony_ci if (rc != 1) 18262306a36Sopenharmony_ci return -EIO; 18362306a36Sopenharmony_ci if ((active & (1 << fan->id)) == 0) 18462306a36Sopenharmony_ci return -ENXIO; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci /* Programmed value or real current speed */ 18762306a36Sopenharmony_ci#if RPM_PID_USE_ACTUAL_SPEED 18862306a36Sopenharmony_ci reg_base = 0x11; 18962306a36Sopenharmony_ci#else 19062306a36Sopenharmony_ci reg_base = 0x10; 19162306a36Sopenharmony_ci#endif 19262306a36Sopenharmony_ci rc = wf_fcu_read_reg(pv, reg_base + (fan->id * 2), buf, 2); 19362306a36Sopenharmony_ci if (rc != 2) 19462306a36Sopenharmony_ci return -EIO; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci *value = (buf[0] << (8 - shift)) | buf[1] >> shift; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci return 0; 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistatic int wf_fcu_fan_set_pwm(struct wf_control *ct, s32 value) 20262306a36Sopenharmony_ci{ 20362306a36Sopenharmony_ci struct wf_fcu_fan *fan = ct->priv; 20462306a36Sopenharmony_ci struct wf_fcu_priv *pv = fan->fcu_priv; 20562306a36Sopenharmony_ci unsigned char buf[2]; 20662306a36Sopenharmony_ci int rc; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci if (value < fan->min) 20962306a36Sopenharmony_ci value = fan->min; 21062306a36Sopenharmony_ci if (value > fan->max) 21162306a36Sopenharmony_ci value = fan->max; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci fan->target = value; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci value = (value * 2559) / 1000; 21662306a36Sopenharmony_ci buf[0] = value; 21762306a36Sopenharmony_ci rc = wf_fcu_write_reg(pv, 0x30 + (fan->id * 2), buf, 1); 21862306a36Sopenharmony_ci if (rc < 0) 21962306a36Sopenharmony_ci return -EIO; 22062306a36Sopenharmony_ci return 0; 22162306a36Sopenharmony_ci} 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_cistatic int wf_fcu_fan_get_pwm(struct wf_control *ct, s32 *value) 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci struct wf_fcu_fan *fan = ct->priv; 22662306a36Sopenharmony_ci struct wf_fcu_priv *pv = fan->fcu_priv; 22762306a36Sopenharmony_ci unsigned char failure; 22862306a36Sopenharmony_ci unsigned char active; 22962306a36Sopenharmony_ci unsigned char buf[2]; 23062306a36Sopenharmony_ci int rc; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci rc = wf_fcu_read_reg(pv, 0x2b, &failure, 1); 23362306a36Sopenharmony_ci if (rc != 1) 23462306a36Sopenharmony_ci return -EIO; 23562306a36Sopenharmony_ci if ((failure & (1 << fan->id)) != 0) 23662306a36Sopenharmony_ci return -EFAULT; 23762306a36Sopenharmony_ci rc = wf_fcu_read_reg(pv, 0x2d, &active, 1); 23862306a36Sopenharmony_ci if (rc != 1) 23962306a36Sopenharmony_ci return -EIO; 24062306a36Sopenharmony_ci if ((active & (1 << fan->id)) == 0) 24162306a36Sopenharmony_ci return -ENXIO; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci rc = wf_fcu_read_reg(pv, 0x30 + (fan->id * 2), buf, 1); 24462306a36Sopenharmony_ci if (rc != 1) 24562306a36Sopenharmony_ci return -EIO; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci *value = (((s32)buf[0]) * 1000) / 2559; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci return 0; 25062306a36Sopenharmony_ci} 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_cistatic s32 wf_fcu_fan_min(struct wf_control *ct) 25362306a36Sopenharmony_ci{ 25462306a36Sopenharmony_ci struct wf_fcu_fan *fan = ct->priv; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci return fan->min; 25762306a36Sopenharmony_ci} 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_cistatic s32 wf_fcu_fan_max(struct wf_control *ct) 26062306a36Sopenharmony_ci{ 26162306a36Sopenharmony_ci struct wf_fcu_fan *fan = ct->priv; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci return fan->max; 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_cistatic const struct wf_control_ops wf_fcu_fan_rpm_ops = { 26762306a36Sopenharmony_ci .set_value = wf_fcu_fan_set_rpm, 26862306a36Sopenharmony_ci .get_value = wf_fcu_fan_get_rpm, 26962306a36Sopenharmony_ci .get_min = wf_fcu_fan_min, 27062306a36Sopenharmony_ci .get_max = wf_fcu_fan_max, 27162306a36Sopenharmony_ci .release = wf_fcu_fan_release, 27262306a36Sopenharmony_ci .owner = THIS_MODULE, 27362306a36Sopenharmony_ci}; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_cistatic const struct wf_control_ops wf_fcu_fan_pwm_ops = { 27662306a36Sopenharmony_ci .set_value = wf_fcu_fan_set_pwm, 27762306a36Sopenharmony_ci .get_value = wf_fcu_fan_get_pwm, 27862306a36Sopenharmony_ci .get_min = wf_fcu_fan_min, 27962306a36Sopenharmony_ci .get_max = wf_fcu_fan_max, 28062306a36Sopenharmony_ci .release = wf_fcu_fan_release, 28162306a36Sopenharmony_ci .owner = THIS_MODULE, 28262306a36Sopenharmony_ci}; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_cistatic void wf_fcu_get_pump_minmax(struct wf_fcu_fan *fan) 28562306a36Sopenharmony_ci{ 28662306a36Sopenharmony_ci const struct mpu_data *mpu = wf_get_mpu(0); 28762306a36Sopenharmony_ci u16 pump_min = 0, pump_max = 0xffff; 28862306a36Sopenharmony_ci u16 tmp[4]; 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci /* Try to fetch pumps min/max infos from eeprom */ 29162306a36Sopenharmony_ci if (mpu) { 29262306a36Sopenharmony_ci memcpy(&tmp, mpu->processor_part_num, 8); 29362306a36Sopenharmony_ci if (tmp[0] != 0xffff && tmp[1] != 0xffff) { 29462306a36Sopenharmony_ci pump_min = max(pump_min, tmp[0]); 29562306a36Sopenharmony_ci pump_max = min(pump_max, tmp[1]); 29662306a36Sopenharmony_ci } 29762306a36Sopenharmony_ci if (tmp[2] != 0xffff && tmp[3] != 0xffff) { 29862306a36Sopenharmony_ci pump_min = max(pump_min, tmp[2]); 29962306a36Sopenharmony_ci pump_max = min(pump_max, tmp[3]); 30062306a36Sopenharmony_ci } 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci /* Double check the values, this _IS_ needed as the EEPROM on 30462306a36Sopenharmony_ci * some dual 2.5Ghz G5s seem, at least, to have both min & max 30562306a36Sopenharmony_ci * same to the same value ... (grrrr) 30662306a36Sopenharmony_ci */ 30762306a36Sopenharmony_ci if (pump_min == pump_max || pump_min == 0 || pump_max == 0xffff) { 30862306a36Sopenharmony_ci pump_min = CPU_PUMP_OUTPUT_MIN; 30962306a36Sopenharmony_ci pump_max = CPU_PUMP_OUTPUT_MAX; 31062306a36Sopenharmony_ci } 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci fan->min = pump_min; 31362306a36Sopenharmony_ci fan->max = pump_max; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci DBG("wf_fcu: pump min/max for %s set to: [%d..%d] RPM\n", 31662306a36Sopenharmony_ci fan->ctrl.name, pump_min, pump_max); 31762306a36Sopenharmony_ci} 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_cistatic void wf_fcu_get_rpmfan_minmax(struct wf_fcu_fan *fan) 32062306a36Sopenharmony_ci{ 32162306a36Sopenharmony_ci struct wf_fcu_priv *pv = fan->fcu_priv; 32262306a36Sopenharmony_ci const struct mpu_data *mpu0 = wf_get_mpu(0); 32362306a36Sopenharmony_ci const struct mpu_data *mpu1 = wf_get_mpu(1); 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci /* Default */ 32662306a36Sopenharmony_ci fan->min = 2400 >> pv->rpm_shift; 32762306a36Sopenharmony_ci fan->max = 56000 >> pv->rpm_shift; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci /* CPU fans have min/max in MPU */ 33062306a36Sopenharmony_ci if (mpu0 && !strcmp(fan->ctrl.name, "cpu-front-fan-0")) { 33162306a36Sopenharmony_ci fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan); 33262306a36Sopenharmony_ci fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan); 33362306a36Sopenharmony_ci goto bail; 33462306a36Sopenharmony_ci } 33562306a36Sopenharmony_ci if (mpu1 && !strcmp(fan->ctrl.name, "cpu-front-fan-1")) { 33662306a36Sopenharmony_ci fan->min = max(fan->min, (s32)mpu1->rminn_intake_fan); 33762306a36Sopenharmony_ci fan->max = min(fan->max, (s32)mpu1->rmaxn_intake_fan); 33862306a36Sopenharmony_ci goto bail; 33962306a36Sopenharmony_ci } 34062306a36Sopenharmony_ci if (mpu0 && !strcmp(fan->ctrl.name, "cpu-rear-fan-0")) { 34162306a36Sopenharmony_ci fan->min = max(fan->min, (s32)mpu0->rminn_exhaust_fan); 34262306a36Sopenharmony_ci fan->max = min(fan->max, (s32)mpu0->rmaxn_exhaust_fan); 34362306a36Sopenharmony_ci goto bail; 34462306a36Sopenharmony_ci } 34562306a36Sopenharmony_ci if (mpu1 && !strcmp(fan->ctrl.name, "cpu-rear-fan-1")) { 34662306a36Sopenharmony_ci fan->min = max(fan->min, (s32)mpu1->rminn_exhaust_fan); 34762306a36Sopenharmony_ci fan->max = min(fan->max, (s32)mpu1->rmaxn_exhaust_fan); 34862306a36Sopenharmony_ci goto bail; 34962306a36Sopenharmony_ci } 35062306a36Sopenharmony_ci /* Rackmac variants, we just use mpu0 intake */ 35162306a36Sopenharmony_ci if (!strncmp(fan->ctrl.name, "cpu-fan", 7)) { 35262306a36Sopenharmony_ci fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan); 35362306a36Sopenharmony_ci fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan); 35462306a36Sopenharmony_ci goto bail; 35562306a36Sopenharmony_ci } 35662306a36Sopenharmony_ci bail: 35762306a36Sopenharmony_ci DBG("wf_fcu: fan min/max for %s set to: [%d..%d] RPM\n", 35862306a36Sopenharmony_ci fan->ctrl.name, fan->min, fan->max); 35962306a36Sopenharmony_ci} 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_cistatic void wf_fcu_add_fan(struct wf_fcu_priv *pv, const char *name, 36262306a36Sopenharmony_ci int type, int id) 36362306a36Sopenharmony_ci{ 36462306a36Sopenharmony_ci struct wf_fcu_fan *fan; 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci fan = kzalloc(sizeof(*fan), GFP_KERNEL); 36762306a36Sopenharmony_ci if (!fan) 36862306a36Sopenharmony_ci return; 36962306a36Sopenharmony_ci fan->fcu_priv = pv; 37062306a36Sopenharmony_ci fan->id = id; 37162306a36Sopenharmony_ci fan->ctrl.name = name; 37262306a36Sopenharmony_ci fan->ctrl.priv = fan; 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci /* min/max is oddball but the code comes from 37562306a36Sopenharmony_ci * therm_pm72 which seems to work so ... 37662306a36Sopenharmony_ci */ 37762306a36Sopenharmony_ci if (type == FCU_FAN_RPM) { 37862306a36Sopenharmony_ci if (!strncmp(name, "cpu-pump", strlen("cpu-pump"))) 37962306a36Sopenharmony_ci wf_fcu_get_pump_minmax(fan); 38062306a36Sopenharmony_ci else 38162306a36Sopenharmony_ci wf_fcu_get_rpmfan_minmax(fan); 38262306a36Sopenharmony_ci fan->ctrl.type = WF_CONTROL_RPM_FAN; 38362306a36Sopenharmony_ci fan->ctrl.ops = &wf_fcu_fan_rpm_ops; 38462306a36Sopenharmony_ci } else { 38562306a36Sopenharmony_ci fan->min = 10; 38662306a36Sopenharmony_ci fan->max = 100; 38762306a36Sopenharmony_ci fan->ctrl.type = WF_CONTROL_PWM_FAN; 38862306a36Sopenharmony_ci fan->ctrl.ops = &wf_fcu_fan_pwm_ops; 38962306a36Sopenharmony_ci } 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci if (wf_register_control(&fan->ctrl)) { 39262306a36Sopenharmony_ci pr_err("wf_fcu: Failed to register fan %s\n", name); 39362306a36Sopenharmony_ci kfree(fan); 39462306a36Sopenharmony_ci return; 39562306a36Sopenharmony_ci } 39662306a36Sopenharmony_ci list_add(&fan->link, &pv->fan_list); 39762306a36Sopenharmony_ci kref_get(&pv->ref); 39862306a36Sopenharmony_ci} 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_cistatic void wf_fcu_lookup_fans(struct wf_fcu_priv *pv) 40162306a36Sopenharmony_ci{ 40262306a36Sopenharmony_ci /* Translation of device-tree location properties to 40362306a36Sopenharmony_ci * windfarm fan names 40462306a36Sopenharmony_ci */ 40562306a36Sopenharmony_ci static const struct { 40662306a36Sopenharmony_ci const char *dt_name; /* Device-tree name */ 40762306a36Sopenharmony_ci const char *ct_name; /* Control name */ 40862306a36Sopenharmony_ci } loc_trans[] = { 40962306a36Sopenharmony_ci { "BACKSIDE", "backside-fan", }, 41062306a36Sopenharmony_ci { "SYS CTRLR FAN", "backside-fan", }, 41162306a36Sopenharmony_ci { "DRIVE BAY", "drive-bay-fan", }, 41262306a36Sopenharmony_ci { "SLOT", "slots-fan", }, 41362306a36Sopenharmony_ci { "PCI FAN", "slots-fan", }, 41462306a36Sopenharmony_ci { "CPU A INTAKE", "cpu-front-fan-0", }, 41562306a36Sopenharmony_ci { "CPU A EXHAUST", "cpu-rear-fan-0", }, 41662306a36Sopenharmony_ci { "CPU B INTAKE", "cpu-front-fan-1", }, 41762306a36Sopenharmony_ci { "CPU B EXHAUST", "cpu-rear-fan-1", }, 41862306a36Sopenharmony_ci { "CPU A PUMP", "cpu-pump-0", }, 41962306a36Sopenharmony_ci { "CPU B PUMP", "cpu-pump-1", }, 42062306a36Sopenharmony_ci { "CPU A 1", "cpu-fan-a-0", }, 42162306a36Sopenharmony_ci { "CPU A 2", "cpu-fan-b-0", }, 42262306a36Sopenharmony_ci { "CPU A 3", "cpu-fan-c-0", }, 42362306a36Sopenharmony_ci { "CPU B 1", "cpu-fan-a-1", }, 42462306a36Sopenharmony_ci { "CPU B 2", "cpu-fan-b-1", }, 42562306a36Sopenharmony_ci { "CPU B 3", "cpu-fan-c-1", }, 42662306a36Sopenharmony_ci }; 42762306a36Sopenharmony_ci struct device_node *np, *fcu = pv->i2c->dev.of_node; 42862306a36Sopenharmony_ci int i; 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci DBG("Looking up FCU controls in device-tree...\n"); 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci for_each_child_of_node(fcu, np) { 43362306a36Sopenharmony_ci int id, type = -1; 43462306a36Sopenharmony_ci const char *loc; 43562306a36Sopenharmony_ci const char *name; 43662306a36Sopenharmony_ci const u32 *reg; 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci DBG(" control: %pOFn, type: %s\n", np, of_node_get_device_type(np)); 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci /* Detect control type */ 44162306a36Sopenharmony_ci if (of_node_is_type(np, "fan-rpm-control") || 44262306a36Sopenharmony_ci of_node_is_type(np, "fan-rpm")) 44362306a36Sopenharmony_ci type = FCU_FAN_RPM; 44462306a36Sopenharmony_ci if (of_node_is_type(np, "fan-pwm-control") || 44562306a36Sopenharmony_ci of_node_is_type(np, "fan-pwm")) 44662306a36Sopenharmony_ci type = FCU_FAN_PWM; 44762306a36Sopenharmony_ci /* Only care about fans for now */ 44862306a36Sopenharmony_ci if (type == -1) 44962306a36Sopenharmony_ci continue; 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci /* Lookup for a matching location */ 45262306a36Sopenharmony_ci loc = of_get_property(np, "location", NULL); 45362306a36Sopenharmony_ci reg = of_get_property(np, "reg", NULL); 45462306a36Sopenharmony_ci if (loc == NULL || reg == NULL) 45562306a36Sopenharmony_ci continue; 45662306a36Sopenharmony_ci DBG(" matching location: %s, reg: 0x%08x\n", loc, *reg); 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(loc_trans); i++) { 45962306a36Sopenharmony_ci if (strncmp(loc, loc_trans[i].dt_name, 46062306a36Sopenharmony_ci strlen(loc_trans[i].dt_name))) 46162306a36Sopenharmony_ci continue; 46262306a36Sopenharmony_ci name = loc_trans[i].ct_name; 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci DBG(" location match, name: %s\n", name); 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci if (type == FCU_FAN_RPM) 46762306a36Sopenharmony_ci id = ((*reg) - 0x10) / 2; 46862306a36Sopenharmony_ci else 46962306a36Sopenharmony_ci id = ((*reg) - 0x30) / 2; 47062306a36Sopenharmony_ci if (id > 7) { 47162306a36Sopenharmony_ci pr_warn("wf_fcu: Can't parse fan ID in device-tree for %pOF\n", np); 47262306a36Sopenharmony_ci break; 47362306a36Sopenharmony_ci } 47462306a36Sopenharmony_ci wf_fcu_add_fan(pv, name, type, id); 47562306a36Sopenharmony_ci break; 47662306a36Sopenharmony_ci } 47762306a36Sopenharmony_ci } 47862306a36Sopenharmony_ci} 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_cistatic void wf_fcu_default_fans(struct wf_fcu_priv *pv) 48162306a36Sopenharmony_ci{ 48262306a36Sopenharmony_ci /* We only support the default fans for PowerMac7,2 */ 48362306a36Sopenharmony_ci if (!of_machine_is_compatible("PowerMac7,2")) 48462306a36Sopenharmony_ci return; 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci wf_fcu_add_fan(pv, "backside-fan", FCU_FAN_PWM, 1); 48762306a36Sopenharmony_ci wf_fcu_add_fan(pv, "drive-bay-fan", FCU_FAN_RPM, 2); 48862306a36Sopenharmony_ci wf_fcu_add_fan(pv, "slots-fan", FCU_FAN_PWM, 2); 48962306a36Sopenharmony_ci wf_fcu_add_fan(pv, "cpu-front-fan-0", FCU_FAN_RPM, 3); 49062306a36Sopenharmony_ci wf_fcu_add_fan(pv, "cpu-rear-fan-0", FCU_FAN_RPM, 4); 49162306a36Sopenharmony_ci wf_fcu_add_fan(pv, "cpu-front-fan-1", FCU_FAN_RPM, 5); 49262306a36Sopenharmony_ci wf_fcu_add_fan(pv, "cpu-rear-fan-1", FCU_FAN_RPM, 6); 49362306a36Sopenharmony_ci} 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_cistatic int wf_fcu_init_chip(struct wf_fcu_priv *pv) 49662306a36Sopenharmony_ci{ 49762306a36Sopenharmony_ci unsigned char buf = 0xff; 49862306a36Sopenharmony_ci int rc; 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci rc = wf_fcu_write_reg(pv, 0xe, &buf, 1); 50162306a36Sopenharmony_ci if (rc < 0) 50262306a36Sopenharmony_ci return -EIO; 50362306a36Sopenharmony_ci rc = wf_fcu_write_reg(pv, 0x2e, &buf, 1); 50462306a36Sopenharmony_ci if (rc < 0) 50562306a36Sopenharmony_ci return -EIO; 50662306a36Sopenharmony_ci rc = wf_fcu_read_reg(pv, 0, &buf, 1); 50762306a36Sopenharmony_ci if (rc < 0) 50862306a36Sopenharmony_ci return -EIO; 50962306a36Sopenharmony_ci pv->rpm_shift = (buf == 1) ? 2 : 3; 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci pr_debug("wf_fcu: FCU Initialized, RPM fan shift is %d\n", 51262306a36Sopenharmony_ci pv->rpm_shift); 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci return 0; 51562306a36Sopenharmony_ci} 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_cistatic int wf_fcu_probe(struct i2c_client *client) 51862306a36Sopenharmony_ci{ 51962306a36Sopenharmony_ci struct wf_fcu_priv *pv; 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci pv = kzalloc(sizeof(*pv), GFP_KERNEL); 52262306a36Sopenharmony_ci if (!pv) 52362306a36Sopenharmony_ci return -ENOMEM; 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci kref_init(&pv->ref); 52662306a36Sopenharmony_ci mutex_init(&pv->lock); 52762306a36Sopenharmony_ci INIT_LIST_HEAD(&pv->fan_list); 52862306a36Sopenharmony_ci pv->i2c = client; 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci /* 53162306a36Sopenharmony_ci * First we must start the FCU which will query the 53262306a36Sopenharmony_ci * shift value to apply to RPMs 53362306a36Sopenharmony_ci */ 53462306a36Sopenharmony_ci if (wf_fcu_init_chip(pv)) { 53562306a36Sopenharmony_ci pr_err("wf_fcu: Initialization failed !\n"); 53662306a36Sopenharmony_ci kfree(pv); 53762306a36Sopenharmony_ci return -ENXIO; 53862306a36Sopenharmony_ci } 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci /* First lookup fans in the device-tree */ 54162306a36Sopenharmony_ci wf_fcu_lookup_fans(pv); 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci /* 54462306a36Sopenharmony_ci * Older machines don't have the device-tree entries 54562306a36Sopenharmony_ci * we are looking for, just hard code the list 54662306a36Sopenharmony_ci */ 54762306a36Sopenharmony_ci if (list_empty(&pv->fan_list)) 54862306a36Sopenharmony_ci wf_fcu_default_fans(pv); 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci /* Still no fans ? FAIL */ 55162306a36Sopenharmony_ci if (list_empty(&pv->fan_list)) { 55262306a36Sopenharmony_ci pr_err("wf_fcu: Failed to find fans for your machine\n"); 55362306a36Sopenharmony_ci kfree(pv); 55462306a36Sopenharmony_ci return -ENODEV; 55562306a36Sopenharmony_ci } 55662306a36Sopenharmony_ci 55762306a36Sopenharmony_ci dev_set_drvdata(&client->dev, pv); 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci return 0; 56062306a36Sopenharmony_ci} 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_cistatic void wf_fcu_remove(struct i2c_client *client) 56362306a36Sopenharmony_ci{ 56462306a36Sopenharmony_ci struct wf_fcu_priv *pv = dev_get_drvdata(&client->dev); 56562306a36Sopenharmony_ci struct wf_fcu_fan *fan; 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci while (!list_empty(&pv->fan_list)) { 56862306a36Sopenharmony_ci fan = list_first_entry(&pv->fan_list, struct wf_fcu_fan, link); 56962306a36Sopenharmony_ci list_del(&fan->link); 57062306a36Sopenharmony_ci wf_unregister_control(&fan->ctrl); 57162306a36Sopenharmony_ci } 57262306a36Sopenharmony_ci kref_put(&pv->ref, wf_fcu_release); 57362306a36Sopenharmony_ci} 57462306a36Sopenharmony_ci 57562306a36Sopenharmony_cistatic const struct i2c_device_id wf_fcu_id[] = { 57662306a36Sopenharmony_ci { "MAC,fcu", 0 }, 57762306a36Sopenharmony_ci { } 57862306a36Sopenharmony_ci}; 57962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, wf_fcu_id); 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_cistatic const struct of_device_id wf_fcu_of_id[] = { 58262306a36Sopenharmony_ci { .compatible = "fcu", }, 58362306a36Sopenharmony_ci { } 58462306a36Sopenharmony_ci}; 58562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, wf_fcu_of_id); 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_cistatic struct i2c_driver wf_fcu_driver = { 58862306a36Sopenharmony_ci .driver = { 58962306a36Sopenharmony_ci .name = "wf_fcu", 59062306a36Sopenharmony_ci .of_match_table = wf_fcu_of_id, 59162306a36Sopenharmony_ci }, 59262306a36Sopenharmony_ci .probe = wf_fcu_probe, 59362306a36Sopenharmony_ci .remove = wf_fcu_remove, 59462306a36Sopenharmony_ci .id_table = wf_fcu_id, 59562306a36Sopenharmony_ci}; 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_cimodule_i2c_driver(wf_fcu_driver); 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ciMODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); 60062306a36Sopenharmony_ciMODULE_DESCRIPTION("FCU control objects for PowerMacs thermal control"); 60162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 60262306a36Sopenharmony_ci 603