18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Windfarm PowerMac thermal control. FCU fan control 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci#undef DEBUG 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/types.h> 108c2ecf20Sopenharmony_ci#include <linux/errno.h> 118c2ecf20Sopenharmony_ci#include <linux/kernel.h> 128c2ecf20Sopenharmony_ci#include <linux/delay.h> 138c2ecf20Sopenharmony_ci#include <linux/slab.h> 148c2ecf20Sopenharmony_ci#include <linux/init.h> 158c2ecf20Sopenharmony_ci#include <linux/wait.h> 168c2ecf20Sopenharmony_ci#include <linux/i2c.h> 178c2ecf20Sopenharmony_ci#include <asm/prom.h> 188c2ecf20Sopenharmony_ci#include <asm/machdep.h> 198c2ecf20Sopenharmony_ci#include <asm/io.h> 208c2ecf20Sopenharmony_ci#include <asm/sections.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#include "windfarm.h" 238c2ecf20Sopenharmony_ci#include "windfarm_mpu.h" 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#define VERSION "1.0" 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#ifdef DEBUG 288c2ecf20Sopenharmony_ci#define DBG(args...) printk(args) 298c2ecf20Sopenharmony_ci#else 308c2ecf20Sopenharmony_ci#define DBG(args...) do { } while(0) 318c2ecf20Sopenharmony_ci#endif 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci/* 348c2ecf20Sopenharmony_ci * This option is "weird" :) Basically, if you define this to 1 358c2ecf20Sopenharmony_ci * the control loop for the RPMs fans (not PWMs) will apply the 368c2ecf20Sopenharmony_ci * correction factor obtained from the PID to the actual RPM 378c2ecf20Sopenharmony_ci * speed read from the FCU. 388c2ecf20Sopenharmony_ci * 398c2ecf20Sopenharmony_ci * If you define the below constant to 0, then it will be 408c2ecf20Sopenharmony_ci * applied to the setpoint RPM speed, that is basically the 418c2ecf20Sopenharmony_ci * speed we proviously "asked" for. 428c2ecf20Sopenharmony_ci * 438c2ecf20Sopenharmony_ci * I'm using 0 for now which is what therm_pm72 used to do and 448c2ecf20Sopenharmony_ci * what Darwin -apparently- does based on observed behaviour. 458c2ecf20Sopenharmony_ci */ 468c2ecf20Sopenharmony_ci#define RPM_PID_USE_ACTUAL_SPEED 0 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci/* Default min/max for pumps */ 498c2ecf20Sopenharmony_ci#define CPU_PUMP_OUTPUT_MAX 3200 508c2ecf20Sopenharmony_ci#define CPU_PUMP_OUTPUT_MIN 1250 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci#define FCU_FAN_RPM 0 538c2ecf20Sopenharmony_ci#define FCU_FAN_PWM 1 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistruct wf_fcu_priv { 568c2ecf20Sopenharmony_ci struct kref ref; 578c2ecf20Sopenharmony_ci struct i2c_client *i2c; 588c2ecf20Sopenharmony_ci struct mutex lock; 598c2ecf20Sopenharmony_ci struct list_head fan_list; 608c2ecf20Sopenharmony_ci int rpm_shift; 618c2ecf20Sopenharmony_ci}; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_cistruct wf_fcu_fan { 648c2ecf20Sopenharmony_ci struct list_head link; 658c2ecf20Sopenharmony_ci int id; 668c2ecf20Sopenharmony_ci s32 min, max, target; 678c2ecf20Sopenharmony_ci struct wf_fcu_priv *fcu_priv; 688c2ecf20Sopenharmony_ci struct wf_control ctrl; 698c2ecf20Sopenharmony_ci}; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistatic void wf_fcu_release(struct kref *ref) 728c2ecf20Sopenharmony_ci{ 738c2ecf20Sopenharmony_ci struct wf_fcu_priv *pv = container_of(ref, struct wf_fcu_priv, ref); 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci kfree(pv); 768c2ecf20Sopenharmony_ci} 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistatic void wf_fcu_fan_release(struct wf_control *ct) 798c2ecf20Sopenharmony_ci{ 808c2ecf20Sopenharmony_ci struct wf_fcu_fan *fan = ct->priv; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci kref_put(&fan->fcu_priv->ref, wf_fcu_release); 838c2ecf20Sopenharmony_ci kfree(fan); 848c2ecf20Sopenharmony_ci} 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic int wf_fcu_read_reg(struct wf_fcu_priv *pv, int reg, 878c2ecf20Sopenharmony_ci unsigned char *buf, int nb) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci int tries, nr, nw; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci mutex_lock(&pv->lock); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci buf[0] = reg; 948c2ecf20Sopenharmony_ci tries = 0; 958c2ecf20Sopenharmony_ci for (;;) { 968c2ecf20Sopenharmony_ci nw = i2c_master_send(pv->i2c, buf, 1); 978c2ecf20Sopenharmony_ci if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100) 988c2ecf20Sopenharmony_ci break; 998c2ecf20Sopenharmony_ci msleep(10); 1008c2ecf20Sopenharmony_ci ++tries; 1018c2ecf20Sopenharmony_ci } 1028c2ecf20Sopenharmony_ci if (nw <= 0) { 1038c2ecf20Sopenharmony_ci pr_err("Failure writing address to FCU: %d", nw); 1048c2ecf20Sopenharmony_ci nr = nw; 1058c2ecf20Sopenharmony_ci goto bail; 1068c2ecf20Sopenharmony_ci } 1078c2ecf20Sopenharmony_ci tries = 0; 1088c2ecf20Sopenharmony_ci for (;;) { 1098c2ecf20Sopenharmony_ci nr = i2c_master_recv(pv->i2c, buf, nb); 1108c2ecf20Sopenharmony_ci if (nr > 0 || (nr < 0 && nr != -ENODEV) || tries >= 100) 1118c2ecf20Sopenharmony_ci break; 1128c2ecf20Sopenharmony_ci msleep(10); 1138c2ecf20Sopenharmony_ci ++tries; 1148c2ecf20Sopenharmony_ci } 1158c2ecf20Sopenharmony_ci if (nr <= 0) 1168c2ecf20Sopenharmony_ci pr_err("wf_fcu: Failure reading data from FCU: %d", nw); 1178c2ecf20Sopenharmony_ci bail: 1188c2ecf20Sopenharmony_ci mutex_unlock(&pv->lock); 1198c2ecf20Sopenharmony_ci return nr; 1208c2ecf20Sopenharmony_ci} 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_cistatic int wf_fcu_write_reg(struct wf_fcu_priv *pv, int reg, 1238c2ecf20Sopenharmony_ci const unsigned char *ptr, int nb) 1248c2ecf20Sopenharmony_ci{ 1258c2ecf20Sopenharmony_ci int tries, nw; 1268c2ecf20Sopenharmony_ci unsigned char buf[16]; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci buf[0] = reg; 1298c2ecf20Sopenharmony_ci memcpy(buf+1, ptr, nb); 1308c2ecf20Sopenharmony_ci ++nb; 1318c2ecf20Sopenharmony_ci tries = 0; 1328c2ecf20Sopenharmony_ci for (;;) { 1338c2ecf20Sopenharmony_ci nw = i2c_master_send(pv->i2c, buf, nb); 1348c2ecf20Sopenharmony_ci if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100) 1358c2ecf20Sopenharmony_ci break; 1368c2ecf20Sopenharmony_ci msleep(10); 1378c2ecf20Sopenharmony_ci ++tries; 1388c2ecf20Sopenharmony_ci } 1398c2ecf20Sopenharmony_ci if (nw < 0) 1408c2ecf20Sopenharmony_ci pr_err("wf_fcu: Failure writing to FCU: %d", nw); 1418c2ecf20Sopenharmony_ci return nw; 1428c2ecf20Sopenharmony_ci} 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_cistatic int wf_fcu_fan_set_rpm(struct wf_control *ct, s32 value) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci struct wf_fcu_fan *fan = ct->priv; 1478c2ecf20Sopenharmony_ci struct wf_fcu_priv *pv = fan->fcu_priv; 1488c2ecf20Sopenharmony_ci int rc, shift = pv->rpm_shift; 1498c2ecf20Sopenharmony_ci unsigned char buf[2]; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci if (value < fan->min) 1528c2ecf20Sopenharmony_ci value = fan->min; 1538c2ecf20Sopenharmony_ci if (value > fan->max) 1548c2ecf20Sopenharmony_ci value = fan->max; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci fan->target = value; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci buf[0] = value >> (8 - shift); 1598c2ecf20Sopenharmony_ci buf[1] = value << shift; 1608c2ecf20Sopenharmony_ci rc = wf_fcu_write_reg(pv, 0x10 + (fan->id * 2), buf, 2); 1618c2ecf20Sopenharmony_ci if (rc < 0) 1628c2ecf20Sopenharmony_ci return -EIO; 1638c2ecf20Sopenharmony_ci return 0; 1648c2ecf20Sopenharmony_ci} 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_cistatic int wf_fcu_fan_get_rpm(struct wf_control *ct, s32 *value) 1678c2ecf20Sopenharmony_ci{ 1688c2ecf20Sopenharmony_ci struct wf_fcu_fan *fan = ct->priv; 1698c2ecf20Sopenharmony_ci struct wf_fcu_priv *pv = fan->fcu_priv; 1708c2ecf20Sopenharmony_ci int rc, reg_base, shift = pv->rpm_shift; 1718c2ecf20Sopenharmony_ci unsigned char failure; 1728c2ecf20Sopenharmony_ci unsigned char active; 1738c2ecf20Sopenharmony_ci unsigned char buf[2]; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci rc = wf_fcu_read_reg(pv, 0xb, &failure, 1); 1768c2ecf20Sopenharmony_ci if (rc != 1) 1778c2ecf20Sopenharmony_ci return -EIO; 1788c2ecf20Sopenharmony_ci if ((failure & (1 << fan->id)) != 0) 1798c2ecf20Sopenharmony_ci return -EFAULT; 1808c2ecf20Sopenharmony_ci rc = wf_fcu_read_reg(pv, 0xd, &active, 1); 1818c2ecf20Sopenharmony_ci if (rc != 1) 1828c2ecf20Sopenharmony_ci return -EIO; 1838c2ecf20Sopenharmony_ci if ((active & (1 << fan->id)) == 0) 1848c2ecf20Sopenharmony_ci return -ENXIO; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci /* Programmed value or real current speed */ 1878c2ecf20Sopenharmony_ci#if RPM_PID_USE_ACTUAL_SPEED 1888c2ecf20Sopenharmony_ci reg_base = 0x11; 1898c2ecf20Sopenharmony_ci#else 1908c2ecf20Sopenharmony_ci reg_base = 0x10; 1918c2ecf20Sopenharmony_ci#endif 1928c2ecf20Sopenharmony_ci rc = wf_fcu_read_reg(pv, reg_base + (fan->id * 2), buf, 2); 1938c2ecf20Sopenharmony_ci if (rc != 2) 1948c2ecf20Sopenharmony_ci return -EIO; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci *value = (buf[0] << (8 - shift)) | buf[1] >> shift; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci return 0; 1998c2ecf20Sopenharmony_ci} 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_cistatic int wf_fcu_fan_set_pwm(struct wf_control *ct, s32 value) 2028c2ecf20Sopenharmony_ci{ 2038c2ecf20Sopenharmony_ci struct wf_fcu_fan *fan = ct->priv; 2048c2ecf20Sopenharmony_ci struct wf_fcu_priv *pv = fan->fcu_priv; 2058c2ecf20Sopenharmony_ci unsigned char buf[2]; 2068c2ecf20Sopenharmony_ci int rc; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci if (value < fan->min) 2098c2ecf20Sopenharmony_ci value = fan->min; 2108c2ecf20Sopenharmony_ci if (value > fan->max) 2118c2ecf20Sopenharmony_ci value = fan->max; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci fan->target = value; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci value = (value * 2559) / 1000; 2168c2ecf20Sopenharmony_ci buf[0] = value; 2178c2ecf20Sopenharmony_ci rc = wf_fcu_write_reg(pv, 0x30 + (fan->id * 2), buf, 1); 2188c2ecf20Sopenharmony_ci if (rc < 0) 2198c2ecf20Sopenharmony_ci return -EIO; 2208c2ecf20Sopenharmony_ci return 0; 2218c2ecf20Sopenharmony_ci} 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_cistatic int wf_fcu_fan_get_pwm(struct wf_control *ct, s32 *value) 2248c2ecf20Sopenharmony_ci{ 2258c2ecf20Sopenharmony_ci struct wf_fcu_fan *fan = ct->priv; 2268c2ecf20Sopenharmony_ci struct wf_fcu_priv *pv = fan->fcu_priv; 2278c2ecf20Sopenharmony_ci unsigned char failure; 2288c2ecf20Sopenharmony_ci unsigned char active; 2298c2ecf20Sopenharmony_ci unsigned char buf[2]; 2308c2ecf20Sopenharmony_ci int rc; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci rc = wf_fcu_read_reg(pv, 0x2b, &failure, 1); 2338c2ecf20Sopenharmony_ci if (rc != 1) 2348c2ecf20Sopenharmony_ci return -EIO; 2358c2ecf20Sopenharmony_ci if ((failure & (1 << fan->id)) != 0) 2368c2ecf20Sopenharmony_ci return -EFAULT; 2378c2ecf20Sopenharmony_ci rc = wf_fcu_read_reg(pv, 0x2d, &active, 1); 2388c2ecf20Sopenharmony_ci if (rc != 1) 2398c2ecf20Sopenharmony_ci return -EIO; 2408c2ecf20Sopenharmony_ci if ((active & (1 << fan->id)) == 0) 2418c2ecf20Sopenharmony_ci return -ENXIO; 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci rc = wf_fcu_read_reg(pv, 0x30 + (fan->id * 2), buf, 1); 2448c2ecf20Sopenharmony_ci if (rc != 1) 2458c2ecf20Sopenharmony_ci return -EIO; 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci *value = (((s32)buf[0]) * 1000) / 2559; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci return 0; 2508c2ecf20Sopenharmony_ci} 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_cistatic s32 wf_fcu_fan_min(struct wf_control *ct) 2538c2ecf20Sopenharmony_ci{ 2548c2ecf20Sopenharmony_ci struct wf_fcu_fan *fan = ct->priv; 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci return fan->min; 2578c2ecf20Sopenharmony_ci} 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_cistatic s32 wf_fcu_fan_max(struct wf_control *ct) 2608c2ecf20Sopenharmony_ci{ 2618c2ecf20Sopenharmony_ci struct wf_fcu_fan *fan = ct->priv; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci return fan->max; 2648c2ecf20Sopenharmony_ci} 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_cistatic const struct wf_control_ops wf_fcu_fan_rpm_ops = { 2678c2ecf20Sopenharmony_ci .set_value = wf_fcu_fan_set_rpm, 2688c2ecf20Sopenharmony_ci .get_value = wf_fcu_fan_get_rpm, 2698c2ecf20Sopenharmony_ci .get_min = wf_fcu_fan_min, 2708c2ecf20Sopenharmony_ci .get_max = wf_fcu_fan_max, 2718c2ecf20Sopenharmony_ci .release = wf_fcu_fan_release, 2728c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2738c2ecf20Sopenharmony_ci}; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_cistatic const struct wf_control_ops wf_fcu_fan_pwm_ops = { 2768c2ecf20Sopenharmony_ci .set_value = wf_fcu_fan_set_pwm, 2778c2ecf20Sopenharmony_ci .get_value = wf_fcu_fan_get_pwm, 2788c2ecf20Sopenharmony_ci .get_min = wf_fcu_fan_min, 2798c2ecf20Sopenharmony_ci .get_max = wf_fcu_fan_max, 2808c2ecf20Sopenharmony_ci .release = wf_fcu_fan_release, 2818c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2828c2ecf20Sopenharmony_ci}; 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_cistatic void wf_fcu_get_pump_minmax(struct wf_fcu_fan *fan) 2858c2ecf20Sopenharmony_ci{ 2868c2ecf20Sopenharmony_ci const struct mpu_data *mpu = wf_get_mpu(0); 2878c2ecf20Sopenharmony_ci u16 pump_min = 0, pump_max = 0xffff; 2888c2ecf20Sopenharmony_ci u16 tmp[4]; 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci /* Try to fetch pumps min/max infos from eeprom */ 2918c2ecf20Sopenharmony_ci if (mpu) { 2928c2ecf20Sopenharmony_ci memcpy(&tmp, mpu->processor_part_num, 8); 2938c2ecf20Sopenharmony_ci if (tmp[0] != 0xffff && tmp[1] != 0xffff) { 2948c2ecf20Sopenharmony_ci pump_min = max(pump_min, tmp[0]); 2958c2ecf20Sopenharmony_ci pump_max = min(pump_max, tmp[1]); 2968c2ecf20Sopenharmony_ci } 2978c2ecf20Sopenharmony_ci if (tmp[2] != 0xffff && tmp[3] != 0xffff) { 2988c2ecf20Sopenharmony_ci pump_min = max(pump_min, tmp[2]); 2998c2ecf20Sopenharmony_ci pump_max = min(pump_max, tmp[3]); 3008c2ecf20Sopenharmony_ci } 3018c2ecf20Sopenharmony_ci } 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci /* Double check the values, this _IS_ needed as the EEPROM on 3048c2ecf20Sopenharmony_ci * some dual 2.5Ghz G5s seem, at least, to have both min & max 3058c2ecf20Sopenharmony_ci * same to the same value ... (grrrr) 3068c2ecf20Sopenharmony_ci */ 3078c2ecf20Sopenharmony_ci if (pump_min == pump_max || pump_min == 0 || pump_max == 0xffff) { 3088c2ecf20Sopenharmony_ci pump_min = CPU_PUMP_OUTPUT_MIN; 3098c2ecf20Sopenharmony_ci pump_max = CPU_PUMP_OUTPUT_MAX; 3108c2ecf20Sopenharmony_ci } 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ci fan->min = pump_min; 3138c2ecf20Sopenharmony_ci fan->max = pump_max; 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ci DBG("wf_fcu: pump min/max for %s set to: [%d..%d] RPM\n", 3168c2ecf20Sopenharmony_ci fan->ctrl.name, pump_min, pump_max); 3178c2ecf20Sopenharmony_ci} 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_cistatic void wf_fcu_get_rpmfan_minmax(struct wf_fcu_fan *fan) 3208c2ecf20Sopenharmony_ci{ 3218c2ecf20Sopenharmony_ci struct wf_fcu_priv *pv = fan->fcu_priv; 3228c2ecf20Sopenharmony_ci const struct mpu_data *mpu0 = wf_get_mpu(0); 3238c2ecf20Sopenharmony_ci const struct mpu_data *mpu1 = wf_get_mpu(1); 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci /* Default */ 3268c2ecf20Sopenharmony_ci fan->min = 2400 >> pv->rpm_shift; 3278c2ecf20Sopenharmony_ci fan->max = 56000 >> pv->rpm_shift; 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci /* CPU fans have min/max in MPU */ 3308c2ecf20Sopenharmony_ci if (mpu0 && !strcmp(fan->ctrl.name, "cpu-front-fan-0")) { 3318c2ecf20Sopenharmony_ci fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan); 3328c2ecf20Sopenharmony_ci fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan); 3338c2ecf20Sopenharmony_ci goto bail; 3348c2ecf20Sopenharmony_ci } 3358c2ecf20Sopenharmony_ci if (mpu1 && !strcmp(fan->ctrl.name, "cpu-front-fan-1")) { 3368c2ecf20Sopenharmony_ci fan->min = max(fan->min, (s32)mpu1->rminn_intake_fan); 3378c2ecf20Sopenharmony_ci fan->max = min(fan->max, (s32)mpu1->rmaxn_intake_fan); 3388c2ecf20Sopenharmony_ci goto bail; 3398c2ecf20Sopenharmony_ci } 3408c2ecf20Sopenharmony_ci if (mpu0 && !strcmp(fan->ctrl.name, "cpu-rear-fan-0")) { 3418c2ecf20Sopenharmony_ci fan->min = max(fan->min, (s32)mpu0->rminn_exhaust_fan); 3428c2ecf20Sopenharmony_ci fan->max = min(fan->max, (s32)mpu0->rmaxn_exhaust_fan); 3438c2ecf20Sopenharmony_ci goto bail; 3448c2ecf20Sopenharmony_ci } 3458c2ecf20Sopenharmony_ci if (mpu1 && !strcmp(fan->ctrl.name, "cpu-rear-fan-1")) { 3468c2ecf20Sopenharmony_ci fan->min = max(fan->min, (s32)mpu1->rminn_exhaust_fan); 3478c2ecf20Sopenharmony_ci fan->max = min(fan->max, (s32)mpu1->rmaxn_exhaust_fan); 3488c2ecf20Sopenharmony_ci goto bail; 3498c2ecf20Sopenharmony_ci } 3508c2ecf20Sopenharmony_ci /* Rackmac variants, we just use mpu0 intake */ 3518c2ecf20Sopenharmony_ci if (!strncmp(fan->ctrl.name, "cpu-fan", 7)) { 3528c2ecf20Sopenharmony_ci fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan); 3538c2ecf20Sopenharmony_ci fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan); 3548c2ecf20Sopenharmony_ci goto bail; 3558c2ecf20Sopenharmony_ci } 3568c2ecf20Sopenharmony_ci bail: 3578c2ecf20Sopenharmony_ci DBG("wf_fcu: fan min/max for %s set to: [%d..%d] RPM\n", 3588c2ecf20Sopenharmony_ci fan->ctrl.name, fan->min, fan->max); 3598c2ecf20Sopenharmony_ci} 3608c2ecf20Sopenharmony_ci 3618c2ecf20Sopenharmony_cistatic void wf_fcu_add_fan(struct wf_fcu_priv *pv, const char *name, 3628c2ecf20Sopenharmony_ci int type, int id) 3638c2ecf20Sopenharmony_ci{ 3648c2ecf20Sopenharmony_ci struct wf_fcu_fan *fan; 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci fan = kzalloc(sizeof(*fan), GFP_KERNEL); 3678c2ecf20Sopenharmony_ci if (!fan) 3688c2ecf20Sopenharmony_ci return; 3698c2ecf20Sopenharmony_ci fan->fcu_priv = pv; 3708c2ecf20Sopenharmony_ci fan->id = id; 3718c2ecf20Sopenharmony_ci fan->ctrl.name = name; 3728c2ecf20Sopenharmony_ci fan->ctrl.priv = fan; 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_ci /* min/max is oddball but the code comes from 3758c2ecf20Sopenharmony_ci * therm_pm72 which seems to work so ... 3768c2ecf20Sopenharmony_ci */ 3778c2ecf20Sopenharmony_ci if (type == FCU_FAN_RPM) { 3788c2ecf20Sopenharmony_ci if (!strncmp(name, "cpu-pump", strlen("cpu-pump"))) 3798c2ecf20Sopenharmony_ci wf_fcu_get_pump_minmax(fan); 3808c2ecf20Sopenharmony_ci else 3818c2ecf20Sopenharmony_ci wf_fcu_get_rpmfan_minmax(fan); 3828c2ecf20Sopenharmony_ci fan->ctrl.type = WF_CONTROL_RPM_FAN; 3838c2ecf20Sopenharmony_ci fan->ctrl.ops = &wf_fcu_fan_rpm_ops; 3848c2ecf20Sopenharmony_ci } else { 3858c2ecf20Sopenharmony_ci fan->min = 10; 3868c2ecf20Sopenharmony_ci fan->max = 100; 3878c2ecf20Sopenharmony_ci fan->ctrl.type = WF_CONTROL_PWM_FAN; 3888c2ecf20Sopenharmony_ci fan->ctrl.ops = &wf_fcu_fan_pwm_ops; 3898c2ecf20Sopenharmony_ci } 3908c2ecf20Sopenharmony_ci 3918c2ecf20Sopenharmony_ci if (wf_register_control(&fan->ctrl)) { 3928c2ecf20Sopenharmony_ci pr_err("wf_fcu: Failed to register fan %s\n", name); 3938c2ecf20Sopenharmony_ci kfree(fan); 3948c2ecf20Sopenharmony_ci return; 3958c2ecf20Sopenharmony_ci } 3968c2ecf20Sopenharmony_ci list_add(&fan->link, &pv->fan_list); 3978c2ecf20Sopenharmony_ci kref_get(&pv->ref); 3988c2ecf20Sopenharmony_ci} 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_cistatic void wf_fcu_lookup_fans(struct wf_fcu_priv *pv) 4018c2ecf20Sopenharmony_ci{ 4028c2ecf20Sopenharmony_ci /* Translation of device-tree location properties to 4038c2ecf20Sopenharmony_ci * windfarm fan names 4048c2ecf20Sopenharmony_ci */ 4058c2ecf20Sopenharmony_ci static const struct { 4068c2ecf20Sopenharmony_ci const char *dt_name; /* Device-tree name */ 4078c2ecf20Sopenharmony_ci const char *ct_name; /* Control name */ 4088c2ecf20Sopenharmony_ci } loc_trans[] = { 4098c2ecf20Sopenharmony_ci { "BACKSIDE", "backside-fan", }, 4108c2ecf20Sopenharmony_ci { "SYS CTRLR FAN", "backside-fan", }, 4118c2ecf20Sopenharmony_ci { "DRIVE BAY", "drive-bay-fan", }, 4128c2ecf20Sopenharmony_ci { "SLOT", "slots-fan", }, 4138c2ecf20Sopenharmony_ci { "PCI FAN", "slots-fan", }, 4148c2ecf20Sopenharmony_ci { "CPU A INTAKE", "cpu-front-fan-0", }, 4158c2ecf20Sopenharmony_ci { "CPU A EXHAUST", "cpu-rear-fan-0", }, 4168c2ecf20Sopenharmony_ci { "CPU B INTAKE", "cpu-front-fan-1", }, 4178c2ecf20Sopenharmony_ci { "CPU B EXHAUST", "cpu-rear-fan-1", }, 4188c2ecf20Sopenharmony_ci { "CPU A PUMP", "cpu-pump-0", }, 4198c2ecf20Sopenharmony_ci { "CPU B PUMP", "cpu-pump-1", }, 4208c2ecf20Sopenharmony_ci { "CPU A 1", "cpu-fan-a-0", }, 4218c2ecf20Sopenharmony_ci { "CPU A 2", "cpu-fan-b-0", }, 4228c2ecf20Sopenharmony_ci { "CPU A 3", "cpu-fan-c-0", }, 4238c2ecf20Sopenharmony_ci { "CPU B 1", "cpu-fan-a-1", }, 4248c2ecf20Sopenharmony_ci { "CPU B 2", "cpu-fan-b-1", }, 4258c2ecf20Sopenharmony_ci { "CPU B 3", "cpu-fan-c-1", }, 4268c2ecf20Sopenharmony_ci }; 4278c2ecf20Sopenharmony_ci struct device_node *np, *fcu = pv->i2c->dev.of_node; 4288c2ecf20Sopenharmony_ci int i; 4298c2ecf20Sopenharmony_ci 4308c2ecf20Sopenharmony_ci DBG("Looking up FCU controls in device-tree...\n"); 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci for_each_child_of_node(fcu, np) { 4338c2ecf20Sopenharmony_ci int id, type = -1; 4348c2ecf20Sopenharmony_ci const char *loc; 4358c2ecf20Sopenharmony_ci const char *name; 4368c2ecf20Sopenharmony_ci const u32 *reg; 4378c2ecf20Sopenharmony_ci 4388c2ecf20Sopenharmony_ci DBG(" control: %pOFn, type: %s\n", np, of_node_get_device_type(np)); 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_ci /* Detect control type */ 4418c2ecf20Sopenharmony_ci if (of_node_is_type(np, "fan-rpm-control") || 4428c2ecf20Sopenharmony_ci of_node_is_type(np, "fan-rpm")) 4438c2ecf20Sopenharmony_ci type = FCU_FAN_RPM; 4448c2ecf20Sopenharmony_ci if (of_node_is_type(np, "fan-pwm-control") || 4458c2ecf20Sopenharmony_ci of_node_is_type(np, "fan-pwm")) 4468c2ecf20Sopenharmony_ci type = FCU_FAN_PWM; 4478c2ecf20Sopenharmony_ci /* Only care about fans for now */ 4488c2ecf20Sopenharmony_ci if (type == -1) 4498c2ecf20Sopenharmony_ci continue; 4508c2ecf20Sopenharmony_ci 4518c2ecf20Sopenharmony_ci /* Lookup for a matching location */ 4528c2ecf20Sopenharmony_ci loc = of_get_property(np, "location", NULL); 4538c2ecf20Sopenharmony_ci reg = of_get_property(np, "reg", NULL); 4548c2ecf20Sopenharmony_ci if (loc == NULL || reg == NULL) 4558c2ecf20Sopenharmony_ci continue; 4568c2ecf20Sopenharmony_ci DBG(" matching location: %s, reg: 0x%08x\n", loc, *reg); 4578c2ecf20Sopenharmony_ci 4588c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(loc_trans); i++) { 4598c2ecf20Sopenharmony_ci if (strncmp(loc, loc_trans[i].dt_name, 4608c2ecf20Sopenharmony_ci strlen(loc_trans[i].dt_name))) 4618c2ecf20Sopenharmony_ci continue; 4628c2ecf20Sopenharmony_ci name = loc_trans[i].ct_name; 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ci DBG(" location match, name: %s\n", name); 4658c2ecf20Sopenharmony_ci 4668c2ecf20Sopenharmony_ci if (type == FCU_FAN_RPM) 4678c2ecf20Sopenharmony_ci id = ((*reg) - 0x10) / 2; 4688c2ecf20Sopenharmony_ci else 4698c2ecf20Sopenharmony_ci id = ((*reg) - 0x30) / 2; 4708c2ecf20Sopenharmony_ci if (id > 7) { 4718c2ecf20Sopenharmony_ci pr_warn("wf_fcu: Can't parse fan ID in device-tree for %pOF\n", np); 4728c2ecf20Sopenharmony_ci break; 4738c2ecf20Sopenharmony_ci } 4748c2ecf20Sopenharmony_ci wf_fcu_add_fan(pv, name, type, id); 4758c2ecf20Sopenharmony_ci break; 4768c2ecf20Sopenharmony_ci } 4778c2ecf20Sopenharmony_ci } 4788c2ecf20Sopenharmony_ci} 4798c2ecf20Sopenharmony_ci 4808c2ecf20Sopenharmony_cistatic void wf_fcu_default_fans(struct wf_fcu_priv *pv) 4818c2ecf20Sopenharmony_ci{ 4828c2ecf20Sopenharmony_ci /* We only support the default fans for PowerMac7,2 */ 4838c2ecf20Sopenharmony_ci if (!of_machine_is_compatible("PowerMac7,2")) 4848c2ecf20Sopenharmony_ci return; 4858c2ecf20Sopenharmony_ci 4868c2ecf20Sopenharmony_ci wf_fcu_add_fan(pv, "backside-fan", FCU_FAN_PWM, 1); 4878c2ecf20Sopenharmony_ci wf_fcu_add_fan(pv, "drive-bay-fan", FCU_FAN_RPM, 2); 4888c2ecf20Sopenharmony_ci wf_fcu_add_fan(pv, "slots-fan", FCU_FAN_PWM, 2); 4898c2ecf20Sopenharmony_ci wf_fcu_add_fan(pv, "cpu-front-fan-0", FCU_FAN_RPM, 3); 4908c2ecf20Sopenharmony_ci wf_fcu_add_fan(pv, "cpu-rear-fan-0", FCU_FAN_RPM, 4); 4918c2ecf20Sopenharmony_ci wf_fcu_add_fan(pv, "cpu-front-fan-1", FCU_FAN_RPM, 5); 4928c2ecf20Sopenharmony_ci wf_fcu_add_fan(pv, "cpu-rear-fan-1", FCU_FAN_RPM, 6); 4938c2ecf20Sopenharmony_ci} 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_cistatic int wf_fcu_init_chip(struct wf_fcu_priv *pv) 4968c2ecf20Sopenharmony_ci{ 4978c2ecf20Sopenharmony_ci unsigned char buf = 0xff; 4988c2ecf20Sopenharmony_ci int rc; 4998c2ecf20Sopenharmony_ci 5008c2ecf20Sopenharmony_ci rc = wf_fcu_write_reg(pv, 0xe, &buf, 1); 5018c2ecf20Sopenharmony_ci if (rc < 0) 5028c2ecf20Sopenharmony_ci return -EIO; 5038c2ecf20Sopenharmony_ci rc = wf_fcu_write_reg(pv, 0x2e, &buf, 1); 5048c2ecf20Sopenharmony_ci if (rc < 0) 5058c2ecf20Sopenharmony_ci return -EIO; 5068c2ecf20Sopenharmony_ci rc = wf_fcu_read_reg(pv, 0, &buf, 1); 5078c2ecf20Sopenharmony_ci if (rc < 0) 5088c2ecf20Sopenharmony_ci return -EIO; 5098c2ecf20Sopenharmony_ci pv->rpm_shift = (buf == 1) ? 2 : 3; 5108c2ecf20Sopenharmony_ci 5118c2ecf20Sopenharmony_ci pr_debug("wf_fcu: FCU Initialized, RPM fan shift is %d\n", 5128c2ecf20Sopenharmony_ci pv->rpm_shift); 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_ci return 0; 5158c2ecf20Sopenharmony_ci} 5168c2ecf20Sopenharmony_ci 5178c2ecf20Sopenharmony_cistatic int wf_fcu_probe(struct i2c_client *client, 5188c2ecf20Sopenharmony_ci const struct i2c_device_id *id) 5198c2ecf20Sopenharmony_ci{ 5208c2ecf20Sopenharmony_ci struct wf_fcu_priv *pv; 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci pv = kzalloc(sizeof(*pv), GFP_KERNEL); 5238c2ecf20Sopenharmony_ci if (!pv) 5248c2ecf20Sopenharmony_ci return -ENOMEM; 5258c2ecf20Sopenharmony_ci 5268c2ecf20Sopenharmony_ci kref_init(&pv->ref); 5278c2ecf20Sopenharmony_ci mutex_init(&pv->lock); 5288c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&pv->fan_list); 5298c2ecf20Sopenharmony_ci pv->i2c = client; 5308c2ecf20Sopenharmony_ci 5318c2ecf20Sopenharmony_ci /* 5328c2ecf20Sopenharmony_ci * First we must start the FCU which will query the 5338c2ecf20Sopenharmony_ci * shift value to apply to RPMs 5348c2ecf20Sopenharmony_ci */ 5358c2ecf20Sopenharmony_ci if (wf_fcu_init_chip(pv)) { 5368c2ecf20Sopenharmony_ci pr_err("wf_fcu: Initialization failed !\n"); 5378c2ecf20Sopenharmony_ci kfree(pv); 5388c2ecf20Sopenharmony_ci return -ENXIO; 5398c2ecf20Sopenharmony_ci } 5408c2ecf20Sopenharmony_ci 5418c2ecf20Sopenharmony_ci /* First lookup fans in the device-tree */ 5428c2ecf20Sopenharmony_ci wf_fcu_lookup_fans(pv); 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_ci /* 5458c2ecf20Sopenharmony_ci * Older machines don't have the device-tree entries 5468c2ecf20Sopenharmony_ci * we are looking for, just hard code the list 5478c2ecf20Sopenharmony_ci */ 5488c2ecf20Sopenharmony_ci if (list_empty(&pv->fan_list)) 5498c2ecf20Sopenharmony_ci wf_fcu_default_fans(pv); 5508c2ecf20Sopenharmony_ci 5518c2ecf20Sopenharmony_ci /* Still no fans ? FAIL */ 5528c2ecf20Sopenharmony_ci if (list_empty(&pv->fan_list)) { 5538c2ecf20Sopenharmony_ci pr_err("wf_fcu: Failed to find fans for your machine\n"); 5548c2ecf20Sopenharmony_ci kfree(pv); 5558c2ecf20Sopenharmony_ci return -ENODEV; 5568c2ecf20Sopenharmony_ci } 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ci dev_set_drvdata(&client->dev, pv); 5598c2ecf20Sopenharmony_ci 5608c2ecf20Sopenharmony_ci return 0; 5618c2ecf20Sopenharmony_ci} 5628c2ecf20Sopenharmony_ci 5638c2ecf20Sopenharmony_cistatic int wf_fcu_remove(struct i2c_client *client) 5648c2ecf20Sopenharmony_ci{ 5658c2ecf20Sopenharmony_ci struct wf_fcu_priv *pv = dev_get_drvdata(&client->dev); 5668c2ecf20Sopenharmony_ci struct wf_fcu_fan *fan; 5678c2ecf20Sopenharmony_ci 5688c2ecf20Sopenharmony_ci while (!list_empty(&pv->fan_list)) { 5698c2ecf20Sopenharmony_ci fan = list_first_entry(&pv->fan_list, struct wf_fcu_fan, link); 5708c2ecf20Sopenharmony_ci list_del(&fan->link); 5718c2ecf20Sopenharmony_ci wf_unregister_control(&fan->ctrl); 5728c2ecf20Sopenharmony_ci } 5738c2ecf20Sopenharmony_ci kref_put(&pv->ref, wf_fcu_release); 5748c2ecf20Sopenharmony_ci return 0; 5758c2ecf20Sopenharmony_ci} 5768c2ecf20Sopenharmony_ci 5778c2ecf20Sopenharmony_cistatic const struct i2c_device_id wf_fcu_id[] = { 5788c2ecf20Sopenharmony_ci { "MAC,fcu", 0 }, 5798c2ecf20Sopenharmony_ci { } 5808c2ecf20Sopenharmony_ci}; 5818c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, wf_fcu_id); 5828c2ecf20Sopenharmony_ci 5838c2ecf20Sopenharmony_cistatic const struct of_device_id wf_fcu_of_id[] = { 5848c2ecf20Sopenharmony_ci { .compatible = "fcu", }, 5858c2ecf20Sopenharmony_ci { } 5868c2ecf20Sopenharmony_ci}; 5878c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, wf_fcu_of_id); 5888c2ecf20Sopenharmony_ci 5898c2ecf20Sopenharmony_cistatic struct i2c_driver wf_fcu_driver = { 5908c2ecf20Sopenharmony_ci .driver = { 5918c2ecf20Sopenharmony_ci .name = "wf_fcu", 5928c2ecf20Sopenharmony_ci .of_match_table = wf_fcu_of_id, 5938c2ecf20Sopenharmony_ci }, 5948c2ecf20Sopenharmony_ci .probe = wf_fcu_probe, 5958c2ecf20Sopenharmony_ci .remove = wf_fcu_remove, 5968c2ecf20Sopenharmony_ci .id_table = wf_fcu_id, 5978c2ecf20Sopenharmony_ci}; 5988c2ecf20Sopenharmony_ci 5998c2ecf20Sopenharmony_cimodule_i2c_driver(wf_fcu_driver); 6008c2ecf20Sopenharmony_ci 6018c2ecf20Sopenharmony_ciMODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); 6028c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("FCU control objects for PowerMacs thermal control"); 6038c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 6048c2ecf20Sopenharmony_ci 605