| /* Copyright 2012 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /* NEW thermal engine module for Chrome EC. This is a completely different |
| * implementation from the original version that shipped on Link. |
| */ |
| |
| #include "chipset.h" |
| #include "common.h" |
| #include "console.h" |
| #include "fan.h" |
| #include "hooks.h" |
| #include "host_command.h" |
| #include "temp_sensor.h" |
| #include "thermal.h" |
| #include "throttle_ap.h" |
| #include "timer.h" |
| #include "util.h" |
| |
| /* Console output macros */ |
| #define CPUTS(outstr) cputs(CC_THERMAL, outstr) |
| #define CPRINTS(format, args...) cprints(CC_THERMAL, format, ## args) |
| |
| /*****************************************************************************/ |
| /* EC-specific thermal controls */ |
| |
| test_mockable_static void smi_sensor_failure_warning(void) |
| { |
| CPRINTS("can't read any temp sensors!"); |
| host_set_single_event(EC_HOST_EVENT_THERMAL); |
| } |
| |
| int thermal_fan_percent(int low, int high, int cur) |
| { |
| if (cur < low) |
| return 0; |
| if (cur > high) |
| return 100; |
| return 100 * (cur - low) / (high - low); |
| } |
| |
| /* The logic below is hard-coded for only three thresholds: WARN, HIGH, HALT. |
| * This is just a sanity check to be sure we catch any changes in thermal.h |
| */ |
| BUILD_ASSERT(EC_TEMP_THRESH_COUNT == 3); |
| |
| /* Keep track of which thresholds have triggered */ |
| static cond_t cond_hot[EC_TEMP_THRESH_COUNT]; |
| |
| static void thermal_control(void) |
| { |
| int i, j, t, rv, f; |
| int count_over[EC_TEMP_THRESH_COUNT]; |
| int count_under[EC_TEMP_THRESH_COUNT]; |
| int num_valid_limits[EC_TEMP_THRESH_COUNT]; |
| int num_sensors_read; |
| int fmax; |
| int temp_fan_configured; |
| |
| /* Get ready to count things */ |
| memset(count_over, 0, sizeof(count_over)); |
| memset(count_under, 0, sizeof(count_under)); |
| memset(num_valid_limits, 0, sizeof(num_valid_limits)); |
| num_sensors_read = 0; |
| fmax = 0; |
| temp_fan_configured = 0; |
| |
| /* go through all the sensors */ |
| for (i = 0; i < TEMP_SENSOR_COUNT; ++i) { |
| |
| /* read one */ |
| rv = temp_sensor_read(i, &t); |
| if (rv != EC_SUCCESS) |
| continue; |
| else |
| num_sensors_read++; |
| |
| /* check all the limits */ |
| for (j = 0; j < EC_TEMP_THRESH_COUNT; j++) { |
| int limit = thermal_params[i].temp_host[j]; |
| int release = thermal_params[i].temp_host_release[j]; |
| if (limit) { |
| num_valid_limits[j]++; |
| if (t > limit) { |
| count_over[j]++; |
| } else if (release) { |
| if (t < release) |
| count_under[j]++; |
| } else if (t < limit) { |
| count_under[j]++; |
| } |
| } |
| } |
| |
| /* figure out the max fan needed, too */ |
| if (thermal_params[i].temp_fan_off && |
| thermal_params[i].temp_fan_max) { |
| f = thermal_fan_percent(thermal_params[i].temp_fan_off, |
| thermal_params[i].temp_fan_max, |
| t); |
| if (f > fmax) |
| fmax = f; |
| |
| temp_fan_configured = 1; |
| } |
| } |
| |
| if (!num_sensors_read) { |
| /* |
| * Trigger a SMI event if we can't read any sensors. |
| * |
| * In theory we could do something more elaborate like forcing |
| * the system to shut down if no sensors are available after |
| * several retries. This is a very unlikely scenario - |
| * particularly on LM4-based boards, since the LM4 has its own |
| * internal temp sensor. It's most likely to occur during |
| * bringup of a new board, where we haven't debugged the I2C |
| * bus to the sensors; forcing a shutdown in that case would |
| * merely hamper board bringup. |
| * |
| * If in G3, then there is no need trigger an SMI event since |
| * the AP is off and this can be an expected state if |
| * temperature sensors are powered by a power rail that's only |
| * on if the AP is out of G3. Note this could be 'ANY_OFF' as |
| * well, but that causes the thermal unit test to fail. |
| */ |
| if (!chipset_in_state(CHIPSET_STATE_HARD_OFF)) |
| smi_sensor_failure_warning(); |
| return; |
| } |
| |
| /* See what the aggregated limits are. Any temp over the limit |
| * means it's hot, but all temps have to be under the limit to |
| * be cool again. |
| */ |
| for (j = 0; j < EC_TEMP_THRESH_COUNT; j++) { |
| if (count_over[j]) |
| cond_set_true(&cond_hot[j]); |
| else if (count_under[j] == num_valid_limits[j]) |
| cond_set_false(&cond_hot[j]); |
| } |
| |
| /* What do we do about it? (note hard-coded logic). */ |
| |
| if (cond_went_true(&cond_hot[EC_TEMP_THRESH_HALT])) { |
| CPRINTS("thermal SHUTDOWN"); |
| chipset_force_shutdown(CHIPSET_SHUTDOWN_THERMAL); |
| } else if (cond_went_false(&cond_hot[EC_TEMP_THRESH_HALT])) { |
| /* We don't reboot automatically - the user has to push |
| * the power button. It's likely that we can't even |
| * detect this sensor transition until then, but we |
| * do have to check in order to clear the cond_t. |
| */ |
| CPRINTS("thermal no longer shutdown"); |
| } |
| |
| if (cond_went_true(&cond_hot[EC_TEMP_THRESH_HIGH])) { |
| CPRINTS("thermal HIGH"); |
| throttle_ap(THROTTLE_ON, THROTTLE_HARD, THROTTLE_SRC_THERMAL); |
| } else if (cond_went_false(&cond_hot[EC_TEMP_THRESH_HIGH])) { |
| CPRINTS("thermal no longer high"); |
| throttle_ap(THROTTLE_OFF, THROTTLE_HARD, THROTTLE_SRC_THERMAL); |
| } |
| |
| if (cond_went_true(&cond_hot[EC_TEMP_THRESH_WARN])) { |
| CPRINTS("thermal WARN"); |
| throttle_ap(THROTTLE_ON, THROTTLE_SOFT, THROTTLE_SRC_THERMAL); |
| } else if (cond_went_false(&cond_hot[EC_TEMP_THRESH_WARN])) { |
| CPRINTS("thermal no longer warn"); |
| throttle_ap(THROTTLE_OFF, THROTTLE_SOFT, THROTTLE_SRC_THERMAL); |
| } |
| |
| if (temp_fan_configured) { |
| #ifdef CONFIG_FANS |
| /* TODO(crosbug.com/p/23797): For now, we just treat all fans the |
| * same. It would be better if we could assign different thermal |
| * profiles to each fan - in case one fan cools the CPU while another |
| * cools the radios or battery. |
| */ |
| for (i = 0; i < fan_get_count(); i++) |
| fan_set_percent_needed(i, fmax); |
| #endif |
| } |
| } |
| |
| /* Wait until after the sensors have been read */ |
| DECLARE_HOOK(HOOK_SECOND, thermal_control, HOOK_PRIO_TEMP_SENSOR_DONE); |
| |
| /*****************************************************************************/ |
| /* Console commands */ |
| |
| static int command_thermalget(int argc, char **argv) |
| { |
| int i; |
| |
| ccprintf("sensor warn high halt fan_off fan_max name\n"); |
| for (i = 0; i < TEMP_SENSOR_COUNT; i++) { |
| ccprintf(" %2d %3d %3d %3d %3d %3d %s\n", |
| i, |
| thermal_params[i].temp_host[EC_TEMP_THRESH_WARN], |
| thermal_params[i].temp_host[EC_TEMP_THRESH_HIGH], |
| thermal_params[i].temp_host[EC_TEMP_THRESH_HALT], |
| thermal_params[i].temp_fan_off, |
| thermal_params[i].temp_fan_max, |
| temp_sensors[i].name); |
| } |
| |
| return EC_SUCCESS; |
| } |
| DECLARE_CONSOLE_COMMAND(thermalget, command_thermalget, |
| NULL, |
| "Print thermal parameters (degrees Kelvin)"); |
| |
| |
| static int command_thermalset(int argc, char **argv) |
| { |
| unsigned int n; |
| int i, val; |
| char *e; |
| |
| if (argc < 3 || argc > 7) |
| return EC_ERROR_PARAM_COUNT; |
| |
| n = (unsigned int)strtoi(argv[1], &e, 0); |
| if (*e) |
| return EC_ERROR_PARAM1; |
| |
| for (i = 2; i < argc; i++) { |
| val = strtoi(argv[i], &e, 0); |
| if (*e) |
| return EC_ERROR_PARAM1 + i - 1; |
| if (val < 0) |
| continue; |
| switch (i) { |
| case 2: |
| thermal_params[n].temp_host[EC_TEMP_THRESH_WARN] = val; |
| break; |
| case 3: |
| thermal_params[n].temp_host[EC_TEMP_THRESH_HIGH] = val; |
| break; |
| case 4: |
| thermal_params[n].temp_host[EC_TEMP_THRESH_HALT] = val; |
| break; |
| case 5: |
| thermal_params[n].temp_fan_off = val; |
| break; |
| case 6: |
| thermal_params[n].temp_fan_max = val; |
| break; |
| } |
| } |
| |
| command_thermalget(0, 0); |
| return EC_SUCCESS; |
| } |
| DECLARE_CONSOLE_COMMAND(thermalset, command_thermalset, |
| "sensor warn [high [shutdown [fan_off [fan_max]]]]", |
| "Set thermal parameters (degrees Kelvin)." |
| " Use -1 to skip."); |
| |
| /*****************************************************************************/ |
| /* Host commands. We'll reuse the host command number, but this is version 1, |
| * not version 0. Different structs, different meanings. |
| */ |
| |
| static enum ec_status |
| thermal_command_set_threshold(struct host_cmd_handler_args *args) |
| { |
| const struct ec_params_thermal_set_threshold_v1 *p = args->params; |
| |
| if (p->sensor_num >= TEMP_SENSOR_COUNT) |
| return EC_RES_INVALID_PARAM; |
| |
| thermal_params[p->sensor_num] = p->cfg; |
| |
| return EC_RES_SUCCESS; |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_THERMAL_SET_THRESHOLD, |
| thermal_command_set_threshold, |
| EC_VER_MASK(1)); |
| |
| static enum ec_status |
| thermal_command_get_threshold(struct host_cmd_handler_args *args) |
| { |
| const struct ec_params_thermal_get_threshold_v1 *p = args->params; |
| struct ec_thermal_config *r = args->response; |
| |
| if (p->sensor_num >= TEMP_SENSOR_COUNT) |
| return EC_RES_INVALID_PARAM; |
| |
| *r = thermal_params[p->sensor_num]; |
| args->response_size = sizeof(*r); |
| return EC_RES_SUCCESS; |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_THERMAL_GET_THRESHOLD, |
| thermal_command_get_threshold, |
| EC_VER_MASK(1)); |