diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2006-07-06 18:03:06 +1000 |
---|---|---|
committer | Paul Mackerras <paulus@samba.org> | 2006-07-28 10:42:49 +1000 |
commit | 8279d2e6062673f31c59ad9e2104024123154267 (patch) | |
tree | 8410692303bae0ee8d38c5c7f01e489c5c63ccd1 /drivers | |
parent | 30f30e13055d1e018c70d2b1b3ff86f8c25c511b (diff) | |
download | lwn-8279d2e6062673f31c59ad9e2104024123154267.tar.gz lwn-8279d2e6062673f31c59ad9e2104024123154267.zip |
[POWERPC] Xserve G5 thermal control fixes
The thermal control for the Xserve G5s had a few issues. For one, the
way to program the RPM fans speeds into the FCU is different between it
and the desktop models, which I didn't figure out until recently, and it
was missing a control loop for the slots fan, running it too fast. Both
of those problems were causing the machine to be much more noisy than
necessary. This patch also changes the fixed value of the slots fan for
desktop G5s to 40% instead of 50%. It seems to still have a pretty good
airflow that way and is much less noisy.
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Paul Mackerras <paulus@samba.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/macintosh/therm_pm72.c | 218 | ||||
-rw-r--r-- | drivers/macintosh/therm_pm72.h | 33 |
2 files changed, 234 insertions, 17 deletions
diff --git a/drivers/macintosh/therm_pm72.c b/drivers/macintosh/therm_pm72.c index c1fe0b368f76..20bf67244e2c 100644 --- a/drivers/macintosh/therm_pm72.c +++ b/drivers/macintosh/therm_pm72.c @@ -95,6 +95,17 @@ * - Use min/max macros here or there * - Latest darwin updated U3H min fan speed to 20% PWM * + * July. 06, 2006 : 1.3 + * - Fix setting of RPM fans on Xserve G5 (they were going too fast) + * - Add missing slots fan control loop for Xserve G5 + * - Lower fixed slots fan speed from 50% to 40% on desktop G5s. We + * still can't properly implement the control loop for these, so let's + * reduce the noise a little bit, it appears that 40% still gives us + * a pretty good air flow + * - Add code to "tickle" the FCU regulary so it doesn't think that + * we are gone while in fact, the machine just didn't need any fan + * speed change lately + * */ #include <linux/types.h> @@ -121,7 +132,7 @@ #include "therm_pm72.h" -#define VERSION "1.2b2" +#define VERSION "1.3" #undef DEBUG @@ -146,6 +157,7 @@ static struct basckside_pid_params backside_params; static struct backside_pid_state backside_state; static struct drives_pid_state drives_state; static struct dimm_pid_state dimms_state; +static struct slots_pid_state slots_state; static int state; static int cpu_count; static int cpu_pid_type; @@ -154,7 +166,8 @@ static struct completion ctrl_complete; static int critical_state; static int rackmac; static s32 dimm_output_clamp; - +static int fcu_rpm_shift; +static int fcu_tickle_ticks; static DECLARE_MUTEX(driver_lock); /* @@ -495,13 +508,20 @@ static int start_fcu(void) rc = fan_write_reg(0x2e, &buf, 1); if (rc < 0) return -EIO; + rc = fan_read_reg(0, &buf, 1); + if (rc < 0) + return -EIO; + fcu_rpm_shift = (buf == 1) ? 2 : 3; + printk(KERN_DEBUG "FCU Initialized, RPM fan shift is %d\n", + fcu_rpm_shift); + return 0; } static int set_rpm_fan(int fan_index, int rpm) { unsigned char buf[2]; - int rc, id; + int rc, id, min, max; if (fcu_fans[fan_index].type != FCU_FAN_RPM) return -EINVAL; @@ -509,12 +529,15 @@ static int set_rpm_fan(int fan_index, int rpm) if (id == FCU_FAN_ABSENT_ID) return -EINVAL; - if (rpm < 300) - rpm = 300; - else if (rpm > 8191) - rpm = 8191; - buf[0] = rpm >> 5; - buf[1] = rpm << 3; + min = 2400 >> fcu_rpm_shift; + max = 56000 >> fcu_rpm_shift; + + if (rpm < min) + rpm = min; + else if (rpm > max) + rpm = max; + buf[0] = rpm >> (8 - fcu_rpm_shift); + buf[1] = rpm << fcu_rpm_shift; rc = fan_write_reg(0x10 + (id * 2), buf, 2); if (rc < 0) return -EIO; @@ -551,7 +574,7 @@ static int get_rpm_fan(int fan_index, int programmed) if (rc != 2) return -EIO; - return (buf[0] << 5) | buf[1] >> 3; + return (buf[0] << (8 - fcu_rpm_shift)) | buf[1] >> fcu_rpm_shift; } static int set_pwm_fan(int fan_index, int pwm) @@ -609,6 +632,26 @@ static int get_pwm_fan(int fan_index) return (buf[0] * 1000) / 2559; } +static void tickle_fcu(void) +{ + int pwm; + + pwm = get_pwm_fan(SLOTS_FAN_PWM_INDEX); + + DBG("FCU Tickle, slots fan is: %d\n", pwm); + if (pwm < 0) + pwm = 100; + + if (!rackmac) { + pwm = SLOTS_FAN_DEFAULT_PWM; + } else if (pwm < SLOTS_PID_OUTPUT_MIN) + pwm = SLOTS_PID_OUTPUT_MIN; + + /* That is hopefully enough to make the FCU happy */ + set_pwm_fan(SLOTS_FAN_PWM_INDEX, pwm); +} + + /* * Utility routine to read the CPU calibration EEPROM data * from the device-tree @@ -715,6 +758,9 @@ BUILD_SHOW_FUNC_INT(backside_fan_pwm, backside_state.pwm) BUILD_SHOW_FUNC_FIX(drives_temperature, drives_state.last_temp) BUILD_SHOW_FUNC_INT(drives_fan_rpm, drives_state.rpm) +BUILD_SHOW_FUNC_FIX(slots_temperature, slots_state.last_temp) +BUILD_SHOW_FUNC_INT(slots_fan_pwm, slots_state.pwm) + BUILD_SHOW_FUNC_FIX(dimms_temperature, dimms_state.last_temp) static DEVICE_ATTR(cpu0_temperature,S_IRUGO,show_cpu0_temperature,NULL); @@ -735,6 +781,9 @@ static DEVICE_ATTR(backside_fan_pwm,S_IRUGO,show_backside_fan_pwm,NULL); static DEVICE_ATTR(drives_temperature,S_IRUGO,show_drives_temperature,NULL); static DEVICE_ATTR(drives_fan_rpm,S_IRUGO,show_drives_fan_rpm,NULL); +static DEVICE_ATTR(slots_temperature,S_IRUGO,show_slots_temperature,NULL); +static DEVICE_ATTR(slots_fan_pwm,S_IRUGO,show_slots_fan_pwm,NULL); + static DEVICE_ATTR(dimms_temperature,S_IRUGO,show_dimms_temperature,NULL); /* @@ -1076,6 +1125,9 @@ static void do_monitor_cpu_rack(struct cpu_pid_state *state) fan_min = dimm_output_clamp; fan_min = max(fan_min, (int)state->mpu.rminn_intake_fan); + DBG(" CPU min mpu = %d, min dimm = %d\n", + state->mpu.rminn_intake_fan, dimm_output_clamp); + state->rpm = max(state->rpm, (int)fan_min); state->rpm = min(state->rpm, (int)state->mpu.rmaxn_intake_fan); state->intake_rpm = state->rpm; @@ -1374,7 +1426,8 @@ static void do_monitor_drives(struct drives_pid_state *state) DBG(" current rpm: %d\n", state->rpm); /* Get some sensor readings */ - temp = le16_to_cpu(i2c_smbus_read_word_data(state->monitor, DS1775_TEMP)) << 8; + temp = le16_to_cpu(i2c_smbus_read_word_data(state->monitor, + DS1775_TEMP)) << 8; state->last_temp = temp; DBG(" temp: %d.%03d, target: %d.%03d\n", FIX32TOPRINT(temp), FIX32TOPRINT(DRIVES_PID_INPUT_TARGET)); @@ -1575,7 +1628,7 @@ static int init_dimms_state(struct dimm_pid_state *state) } /* - * Dispose of the state data for the drives control loop + * Dispose of the state data for the DIMM control loop */ static void dispose_dimms_state(struct dimm_pid_state *state) { @@ -1588,6 +1641,127 @@ static void dispose_dimms_state(struct dimm_pid_state *state) state->monitor = NULL; } +/* + * Slots fan control loop + */ +static void do_monitor_slots(struct slots_pid_state *state) +{ + s32 temp, integral, derivative; + s64 integ_p, deriv_p, prop_p, sum; + int i, rc; + + if (--state->ticks != 0) + return; + state->ticks = SLOTS_PID_INTERVAL; + + DBG("slots:\n"); + + /* Check fan status */ + rc = get_pwm_fan(SLOTS_FAN_PWM_INDEX); + if (rc < 0) { + printk(KERN_WARNING "Error %d reading slots fan !\n", rc); + /* XXX What do we do now ? */ + } else + state->pwm = rc; + DBG(" current pwm: %d\n", state->pwm); + + /* Get some sensor readings */ + temp = le16_to_cpu(i2c_smbus_read_word_data(state->monitor, + DS1775_TEMP)) << 8; + state->last_temp = temp; + DBG(" temp: %d.%03d, target: %d.%03d\n", FIX32TOPRINT(temp), + FIX32TOPRINT(SLOTS_PID_INPUT_TARGET)); + + /* Store temperature and error in history array */ + state->cur_sample = (state->cur_sample + 1) % SLOTS_PID_HISTORY_SIZE; + state->sample_history[state->cur_sample] = temp; + state->error_history[state->cur_sample] = temp - SLOTS_PID_INPUT_TARGET; + + /* If first loop, fill the history table */ + if (state->first) { + for (i = 0; i < (SLOTS_PID_HISTORY_SIZE - 1); i++) { + state->cur_sample = (state->cur_sample + 1) % + SLOTS_PID_HISTORY_SIZE; + state->sample_history[state->cur_sample] = temp; + state->error_history[state->cur_sample] = + temp - SLOTS_PID_INPUT_TARGET; + } + state->first = 0; + } + + /* Calculate the integral term */ + sum = 0; + integral = 0; + for (i = 0; i < SLOTS_PID_HISTORY_SIZE; i++) + integral += state->error_history[i]; + integral *= SLOTS_PID_INTERVAL; + DBG(" integral: %08x\n", integral); + integ_p = ((s64)SLOTS_PID_G_r) * (s64)integral; + DBG(" integ_p: %d\n", (int)(integ_p >> 36)); + sum += integ_p; + + /* Calculate the derivative term */ + derivative = state->error_history[state->cur_sample] - + state->error_history[(state->cur_sample + SLOTS_PID_HISTORY_SIZE - 1) + % SLOTS_PID_HISTORY_SIZE]; + derivative /= SLOTS_PID_INTERVAL; + deriv_p = ((s64)SLOTS_PID_G_d) * (s64)derivative; + DBG(" deriv_p: %d\n", (int)(deriv_p >> 36)); + sum += deriv_p; + + /* Calculate the proportional term */ + prop_p = ((s64)SLOTS_PID_G_p) * (s64)(state->error_history[state->cur_sample]); + DBG(" prop_p: %d\n", (int)(prop_p >> 36)); + sum += prop_p; + + /* Scale sum */ + sum >>= 36; + + DBG(" sum: %d\n", (int)sum); + state->pwm = (s32)sum; + + state->pwm = max(state->pwm, SLOTS_PID_OUTPUT_MIN); + state->pwm = min(state->pwm, SLOTS_PID_OUTPUT_MAX); + + DBG("** DRIVES PWM: %d\n", (int)state->pwm); + set_pwm_fan(SLOTS_FAN_PWM_INDEX, state->pwm); +} + +/* + * Initialize the state structure for the slots bay fan control loop + */ +static int init_slots_state(struct slots_pid_state *state) +{ + state->ticks = 1; + state->first = 1; + state->pwm = 50; + + state->monitor = attach_i2c_chip(XSERVE_SLOTS_LM75, "slots_temp"); + if (state->monitor == NULL) + return -ENODEV; + + device_create_file(&of_dev->dev, &dev_attr_slots_temperature); + device_create_file(&of_dev->dev, &dev_attr_slots_fan_pwm); + + return 0; +} + +/* + * Dispose of the state data for the slots control loop + */ +static void dispose_slots_state(struct slots_pid_state *state) +{ + if (state->monitor == NULL) + return; + + device_remove_file(&of_dev->dev, &dev_attr_slots_temperature); + device_remove_file(&of_dev->dev, &dev_attr_slots_fan_pwm); + + detach_i2c_chip(state->monitor); + state->monitor = NULL; +} + + static int call_critical_overtemp(void) { char *argv[] = { critical_overtemp_path, NULL }; @@ -1617,14 +1791,17 @@ static int main_control_loop(void *x) goto out; } - /* Set the PCI fan once for now */ - set_pwm_fan(SLOTS_FAN_PWM_INDEX, SLOTS_FAN_DEFAULT_PWM); + /* Set the PCI fan once for now on non-RackMac */ + if (!rackmac) + set_pwm_fan(SLOTS_FAN_PWM_INDEX, SLOTS_FAN_DEFAULT_PWM); /* Initialize ADCs */ initialize_adc(&cpu_state[0]); if (cpu_state[1].monitor != NULL) initialize_adc(&cpu_state[1]); + fcu_tickle_ticks = FCU_TICKLE_TICKS; + up(&driver_lock); while (state == state_attached) { @@ -1634,6 +1811,12 @@ static int main_control_loop(void *x) down(&driver_lock); + /* Tickle the FCU just in case */ + if (--fcu_tickle_ticks < 0) { + fcu_tickle_ticks = FCU_TICKLE_TICKS; + tickle_fcu(); + } + /* First, we always calculate the new DIMMs state on an Xserve */ if (rackmac) do_monitor_dimms(&dimms_state); @@ -1654,7 +1837,9 @@ static int main_control_loop(void *x) } /* Then, the rest */ do_monitor_backside(&backside_state); - if (!rackmac) + if (rackmac) + do_monitor_slots(&slots_state); + else do_monitor_drives(&drives_state); up(&driver_lock); @@ -1696,6 +1881,7 @@ static void dispose_control_loops(void) dispose_cpu_state(&cpu_state[1]); dispose_backside_state(&backside_state); dispose_drives_state(&drives_state); + dispose_slots_state(&slots_state); dispose_dimms_state(&dimms_state); } @@ -1745,6 +1931,8 @@ static int create_control_loops(void) goto fail; if (rackmac && init_dimms_state(&dimms_state)) goto fail; + if (rackmac && init_slots_state(&slots_state)) + goto fail; if (!rackmac && init_drives_state(&drives_state)) goto fail; diff --git a/drivers/macintosh/therm_pm72.h b/drivers/macintosh/therm_pm72.h index fc7e9b7ecaf2..393cc9df94e1 100644 --- a/drivers/macintosh/therm_pm72.h +++ b/drivers/macintosh/therm_pm72.h @@ -105,6 +105,7 @@ static char * critical_overtemp_path = "/sbin/critical_overtemp"; #define DRIVES_DALLAS_ID 0x94 #define BACKSIDE_MAX_ID 0x98 #define XSERVE_DIMMS_LM87 0x25a +#define XSERVE_SLOTS_LM75 0x290 /* * Some MAX6690, DS1775, LM87 register definitions @@ -198,7 +199,7 @@ struct drives_pid_state #define SLOTS_FAN_PWM_DEFAULT_ID 2 #define SLOTS_FAN_PWM_INDEX 2 -#define SLOTS_FAN_DEFAULT_PWM 50 /* Do better here ! */ +#define SLOTS_FAN_DEFAULT_PWM 40 /* Do better here ! */ /* @@ -206,7 +207,7 @@ struct drives_pid_state */ #define DIMM_PID_G_d 0 #define DIMM_PID_G_p 0 -#define DIMM_PID_G_r 0x6553600 +#define DIMM_PID_G_r 0x06553600 #define DIMM_PID_INPUT_TARGET 3276800 #define DIMM_PID_INTERVAL 1 #define DIMM_PID_OUTPUT_MAX 14000 @@ -226,6 +227,31 @@ struct dimm_pid_state }; +/* + * PID factors for the Xserve Slots control loop + */ +#define SLOTS_PID_G_d 0 +#define SLOTS_PID_G_p 0 +#define SLOTS_PID_G_r 0x00100000 +#define SLOTS_PID_INPUT_TARGET 3200000 +#define SLOTS_PID_INTERVAL 1 +#define SLOTS_PID_OUTPUT_MAX 100 +#define SLOTS_PID_OUTPUT_MIN 20 +#define SLOTS_PID_HISTORY_SIZE 20 + +struct slots_pid_state +{ + int ticks; + struct i2c_client * monitor; + s32 sample_history[SLOTS_PID_HISTORY_SIZE]; + s32 error_history[SLOTS_PID_HISTORY_SIZE]; + int cur_sample; + s32 last_temp; + int first; + int pwm; +}; + + /* Desktops */ @@ -283,6 +309,9 @@ struct cpu_pid_state s32 pump_max; }; +/* Tickle FCU every 10 seconds */ +#define FCU_TICKLE_TICKS 10 + /* * Driver state */ |