/* Copyright 2013 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ /* Common chipset throttling code for Chrome EC */ #include "builtin/assert.h" #include "chipset.h" #include "common.h" #include "console.h" #include "dptf.h" #include "gpio.h" #include "hooks.h" #include "host_command.h" #include "task.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) /* * When C10 deasserts, PROCHOT may also change state when the corresponding * power rail is turned back on. Recheck PROCHOT directly from the C10 exit * using a shorter debounce than the PROCHOT interrupt. */ #define C10_IN_DEBOUNCE_US (10 * MSEC) /*****************************************************************************/ /* This enforces the virtual OR of all throttling sources. */ K_MUTEX_DEFINE(throttle_mutex); static uint32_t throttle_request[NUM_THROTTLE_TYPES]; static int debounced_prochot_in; static const struct prochot_cfg *prochot_cfg; void throttle_ap(enum throttle_level level, enum throttle_type type, enum throttle_sources source) { uint32_t tmpval, bitmask; mutex_lock(&throttle_mutex); bitmask = BIT(source); switch (level) { case THROTTLE_ON: throttle_request[type] |= bitmask; break; case THROTTLE_OFF: throttle_request[type] &= ~bitmask; break; } tmpval = throttle_request[type]; /* save for printing */ switch (type) { case THROTTLE_SOFT: #ifdef HAS_TASK_HOSTCMD host_throttle_cpu(tmpval); #endif break; case THROTTLE_HARD: #ifdef CONFIG_CHIPSET_CAN_THROTTLE chipset_throttle_cpu(tmpval); #endif break; case NUM_THROTTLE_TYPES: /* Make the compiler shut up. Don't use 'default', because * we still want to catch any new types. */ break; } mutex_unlock(&throttle_mutex); /* print outside the mutex */ CPRINTS("set AP throttling type %d to %s (0x%08x)", type, tmpval ? "on" : "off", tmpval); } void throttle_ap_config_prochot(const struct prochot_cfg *cfg) { prochot_cfg = cfg; if (IS_ENABLED(CONFIG_THROTTLE_AP_SINGLE_PIN)) { gpio_set_flags(prochot_cfg->gpio_prochot_in, GPIO_INPUT); } } __maybe_unused static bool prochot_is_gated_by_c10(int prochot_in) { #ifdef CONFIG_CPU_PROCHOT_GATE_ON_C10 int c10_in = gpio_get_level(prochot_cfg->gpio_c10_in); if (!prochot_cfg->c10_active_high) c10_in = !c10_in; if (c10_in && prochot_in) { return true; } #endif return false; } static void prochot_input_deferred(void) { int prochot_in; /* * Validate board called throttle_ap_config_prochot(). */ ASSERT(prochot_cfg); prochot_in = gpio_get_level(prochot_cfg->gpio_prochot_in); if (IS_ENABLED(CONFIG_CPU_PROCHOT_ACTIVE_LOW)) prochot_in = !prochot_in; if (prochot_in == debounced_prochot_in) return; /* * b/173180788 Confirmed from Intel internal that SLP_S3# asserts low * about 10us before PROCHOT# asserts low, which means that * the CPU is already in reset and therefore the PROCHOT# * asserting low is normal behavior and not a concern * for PROCHOT# event. Ignore all PROCHOT changes while the AP is off */ if (chipset_in_state(CHIPSET_STATE_ANY_OFF | CHIPSET_STATE_ANY_SUSPEND)) return; /* * b/185810479 When the AP enters C10, the PROCHOT signal may not be * valid. Refer to the CONFIG_CPU_PROCHOT_GATE_ON_C10 documentation * for details. */ if (prochot_is_gated_by_c10(prochot_in)) return; debounced_prochot_in = prochot_in; if (debounced_prochot_in) { CPRINTS("External PROCHOT assertion detected"); #if defined(CONFIG_FANS) && !defined(CONFIG_THROTTLE_AP_NO_FAN) dptf_set_fan_duty_target(100); #endif } else { CPRINTS("External PROCHOT condition cleared"); #if defined(CONFIG_FANS) && !defined(CONFIG_THROTTLE_AP_NO_FAN) /* Revert to automatic control of the fan */ dptf_set_fan_duty_target(-1); #endif } if (prochot_cfg->callback) prochot_cfg->callback(debounced_prochot_in, prochot_cfg->callback_data); } DECLARE_DEFERRED(prochot_input_deferred); void throttle_ap_prochot_input_interrupt(enum gpio_signal signal) { /* * Trigger deferred notification of PROCHOT change so we can ignore * any pulses that are too short. */ hook_call_deferred(&prochot_input_deferred_data, PROCHOT_IN_DEBOUNCE_US); } #ifdef CONFIG_CPU_PROCHOT_GATE_ON_C10 void throttle_ap_c10_input_interrupt(enum gpio_signal signal) { /* * This interrupt is configured to fire only when the AP exits C10 * and de-asserts the C10 signal. Recheck the PROCHOT signal in case * another PROCHOT source is active when the AP exits C10. */ hook_call_deferred(&prochot_input_deferred_data, C10_IN_DEBOUNCE_US); } #endif /*****************************************************************************/ /* Console commands */ #ifdef CONFIG_CMD_APTHROTTLE static int command_apthrottle(int argc, const char **argv) { int i; uint32_t tmpval; for (i = 0; i < NUM_THROTTLE_TYPES; i++) { mutex_lock(&throttle_mutex); tmpval = throttle_request[i]; mutex_unlock(&throttle_mutex); ccprintf("AP throttling type %d is %s (0x%08x)\n", i, tmpval ? "on" : "off", tmpval); } return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(apthrottle, command_apthrottle, NULL, "Display the AP throttling state"); #endif