diff options
Diffstat (limited to 'common/dps.c')
-rw-r--r-- | common/dps.c | 639 |
1 files changed, 0 insertions, 639 deletions
diff --git a/common/dps.c b/common/dps.c deleted file mode 100644 index 235f4d4e08..0000000000 --- a/common/dps.c +++ /dev/null @@ -1,639 +0,0 @@ -/* Copyright 2021 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. - * - * Dynamic PDO Selection. - */ - -#include <stdint.h> - -#include "adc.h" -#include "dps.h" -#include "atomic.h" -#include "battery.h" -#include "console.h" -#include "charger.h" -#include "charge_manager.h" -#include "charge_state.h" -#include "charge_state_v2.h" -#include "math_util.h" -#include "task.h" -#include "timer.h" -#include "usb_common.h" -#include "usb_pd.h" -#include "util.h" -#include "usb_pe_sm.h" - - -#define K_MORE_PWR 96 -#define K_LESS_PWR 93 -#define K_SAMPLE 1 -#define K_WINDOW 3 -#define T_REQUEST_STABLE_TIME (10 * SECOND) -#define T_NEXT_CHECK_TIME (5 * SECOND) - -#define DPS_FLAG_DISABLED BIT(0) -#define DPS_FLAG_NO_SRCCAP BIT(1) -#define DPS_FLAG_WAITING BIT(2) -#define DPS_FLAG_SAMPLED BIT(3) -#define DPS_FLAG_NEED_MORE_PWR BIT(4) - -#define DPS_FLAG_STOP_EVENTS (DPS_FLAG_DISABLED | \ - DPS_FLAG_NO_SRCCAP) -#define DPS_FLAG_ALL GENMASK(31, 0) - -#define MAX_MOVING_AVG_WINDOW 5 - -BUILD_ASSERT(K_MORE_PWR > K_LESS_PWR && 100 >= K_MORE_PWR && 100 >= K_LESS_PWR); - -/* lock for updating timeout value */ -static mutex_t dps_lock; -static timestamp_t timeout; -static bool is_enabled = true; -static int debug_level; -static bool fake_enabled; -static int fake_mv, fake_ma; -static int dynamic_mv; -static int dps_port = CHARGE_PORT_NONE; -static uint32_t flag; - -#define CPRINTF(format, args...) cprintf(CC_USBPD, "DPS " format, ##args) -#define CPRINTS(format, args...) cprints(CC_USBPD, "DPS " format, ##args) - -__overridable struct dps_config_t dps_config = { - .k_less_pwr = K_LESS_PWR, - .k_more_pwr = K_MORE_PWR, - .k_sample = K_SAMPLE, - .k_window = K_WINDOW, - .t_stable = T_REQUEST_STABLE_TIME, - .t_check = T_NEXT_CHECK_TIME, - .is_more_efficient = NULL, -}; - -int dps_get_dynamic_voltage(void) -{ - return dynamic_mv; -} - -int dps_get_charge_port(void) -{ - return dps_port; -} - -bool dps_is_enabled(void) -{ - return is_enabled; -} - -static void dps_enable(bool en) -{ - bool prev_en = is_enabled; - - is_enabled = en; - - if (is_enabled && !prev_en) - task_wake(TASK_ID_DPS); -} - -static void update_timeout(int us) -{ - timestamp_t new_timeout; - - new_timeout.val = get_time().val + us; - - mutex_lock(&dps_lock); - if (new_timeout.val > timeout.val) - timeout = new_timeout; - mutex_unlock(&dps_lock); -} - -/* - * DPS reset. - */ -static void dps_reset(void) -{ - dynamic_mv = PD_MAX_VOLTAGE_MV; - dps_port = CHARGE_PORT_NONE; -} - -/* - * DPS initialization. - */ -static void dps_init(void) -{ - dps_reset(); - - if (dps_config.k_window > MAX_MOVING_AVG_WINDOW) { - dps_config.k_window = MAX_MOVING_AVG_WINDOW; - CPRINTS("ERR:WIN"); - } - - if (dps_config.k_less_pwr > 100 || - dps_config.k_more_pwr > 100 || - dps_config.k_more_pwr <= dps_config.k_less_pwr) { - dps_config.k_less_pwr = K_LESS_PWR; - dps_config.k_more_pwr = K_MORE_PWR; - CPRINTS("ERR:COEF"); - } -} - -static bool is_near_limit(int val, int limit) -{ - return val >= (limit * dps_config.k_more_pwr / 100); -} - -bool is_more_efficient(int curr_mv, int prev_mv, int batt_mv, int batt_mw, - int input_mw) -{ - if (dps_config.is_more_efficient) - return dps_config.is_more_efficient(curr_mv, prev_mv, batt_mv, - batt_mw, input_mw); - - return ABS(curr_mv - batt_mv) < ABS(prev_mv - batt_mv); -} - -/* - * Get the input power of the active port. - * - * input_power = vbus * input_current - * - * @param vbus: VBUS in mV - * @param input_curr: input current in mA - * - * @return input_power of the result of vbus * input_curr in mW - */ -static int get_desired_input_power(int *vbus, int *input_current) -{ - int active_port; - int charger_id; - enum ec_error_list rv; - - active_port = charge_manager_get_active_charge_port(); - - if (active_port == CHARGE_PORT_NONE) - return 0; - - charger_id = charge_get_active_chg_chip(); - - if (fake_enabled) { - *vbus = fake_mv; - *input_current = fake_ma; - return fake_mv * fake_ma / 1000; - } - - rv = charger_get_input_current(charger_id, input_current); - if (rv) - return 0; - - *vbus = charge_manager_get_vbus_voltage(active_port); - - return (*vbus) * (*input_current) / 1000; -} - -/* - * Get the most efficient PDO voltage for the battery of the charging port - * - * | W\Batt | 1S(3.7V) | 2S(7.4V) | 3S(11.1V) | 4S(14.8V) | - * -------------------------------------------------------- - * | 0-15W | 5V | 9V | 12V | 15V | - * | 15-27W | 9V | 9V | 12V | 15V | - * | 27-36W | 12V | 12V | 12V | 15V | - * | 36-45W | 15V | 15V | 15V | 15V | - * | 45-60W | 20V | 20V | 20V | 20V | - * - * - * @return 0 if error occurs, else battery efficient voltage in mV - */ -int get_efficient_voltage(void) -{ - int eff_mv = 0; - int batt_mv; - int batt_pwr; - int input_pwr, vbus, input_curr; - const struct batt_params *batt = charger_current_battery_params(); - - input_pwr = get_desired_input_power(&vbus, &input_curr); - - if (!input_pwr) - return 0; - - if (battery_design_voltage(&batt_mv)) - return 0; - - batt_pwr = batt->current * batt->voltage / 1000; - - for (int i = 0; i < board_get_usb_pd_port_count(); ++i) { - const int cnt = pd_get_src_cap_cnt(i); - const uint32_t *src_caps = pd_get_src_caps(i); - - for (int j = 0; j < cnt; ++j) { - int ma, mv, unused; - - pd_extract_pdo_power(src_caps[j], &ma, &mv, &unused); - /* - * If the eff_mv is not picked, or we have more - * efficient voltage (less voltage diff) - */ - if (eff_mv == 0 || - is_more_efficient(mv, eff_mv, batt_mv, batt_pwr, - input_pwr)) - eff_mv = mv; - } - } - - return eff_mv; -} - -struct pdo_candidate { - int port; - int mv; - int mw; -}; - -#define UPDATE_CANDIDATE(new_port, new_mv, new_mw) \ - do { \ - cand->port = new_port; \ - cand->mv = new_mv; \ - cand->mw = new_mw; \ - } while (0) - -#define CLEAR_AND_RETURN() \ - do { \ - moving_avg_count = 0; \ - return false; \ - } while (0) - -/* - * Evaluate the system power if a new PD power request is needed. - * - * @param struct pdo_candidate: The candidate PDO. (Return value) - * @return true if a new power request, or false otherwise. - */ -static bool has_new_power_request(struct pdo_candidate *cand) -{ - int vbus, input_curr, input_pwr; - int input_pwr_avg = 0, input_curr_avg = 0; - int batt_pwr, batt_mv; - int max_mv = pd_get_max_voltage(); - int req_pwr, req_ma, req_mv; - int input_curr_limit; - int active_port = charge_manager_get_active_charge_port(); - int charger_id; - static int input_pwrs[MAX_MOVING_AVG_WINDOW]; - static int input_currs[MAX_MOVING_AVG_WINDOW]; - static int prev_active_port = CHARGE_PORT_NONE; - static int prev_req_mv; - static int moving_avg_count; - const struct batt_params *batt = charger_current_battery_params(); - - /* set a default value in case it early returns. */ - UPDATE_CANDIDATE(CHARGE_PORT_NONE, INT32_MAX, 0); - - if (active_port == CHARGE_PORT_NONE) - CLEAR_AND_RETURN(); - - req_mv = pd_get_requested_voltage(active_port); - req_ma = pd_get_requested_current(active_port); - - if (!req_mv) - CLEAR_AND_RETURN(); - - if (battery_design_voltage(&batt_mv)) - CLEAR_AND_RETURN(); - - /* if last sample is not the same as the current one, reset counting. */ - if (prev_req_mv != req_mv || prev_active_port != active_port) - moving_avg_count = 0; - prev_active_port = active_port; - prev_req_mv = req_mv; - - req_pwr = req_mv * req_ma / 1000; - batt_pwr = batt->current * batt->voltage / 1000; - input_pwr = get_desired_input_power(&vbus, &input_curr); - - if (!input_pwr) - CLEAR_AND_RETURN(); - - /* record moving average */ - input_pwrs[moving_avg_count % dps_config.k_window] = input_pwr; - input_currs[moving_avg_count % dps_config.k_window] = input_curr; - if (++moving_avg_count < dps_config.k_window) - return false; - - for (int i = 0; i < dps_config.k_window; i++) { - input_curr_avg += input_currs[i]; - input_pwr_avg += input_pwrs[i]; - } - input_curr_avg /= dps_config.k_window; - input_pwr_avg /= dps_config.k_window; - - charger_id = charge_get_active_chg_chip(); - - if (!charger_get_input_current_limit(charger_id, &input_curr_limit)) - /* set as last requested mA if we're unable to get the limit. */ - input_curr_limit = req_ma; - - /* - * input power might be insufficient, force it to negotiate a more - * powerful PDO. - */ - if (is_near_limit(input_pwr_avg, req_pwr) || - is_near_limit(input_curr_avg, MIN(req_ma, input_curr_limit))) { - flag |= DPS_FLAG_NEED_MORE_PWR; - if (!fake_enabled) - input_pwr_avg = req_pwr + 1; - } else { - flag &= ~DPS_FLAG_NEED_MORE_PWR; - } - - if (debug_level) - CPRINTS("C%d 0x%x last (%dmW %dmV) input (%dmW %dmV %dmA) " - "avg (%dmW, %dmA)", - active_port, flag, req_pwr, req_mv, input_pwr, vbus, - input_curr, input_pwr_avg, input_curr_avg); - - for (int i = 0; i < board_get_usb_pd_port_count(); ++i) { - const uint32_t * const src_caps = pd_get_src_caps(i); - - for (int j = 0; j < pd_get_src_cap_cnt(i); ++j) { - int ma, mv, unused; - int mw; - bool efficient; - - /* TODO(b:169532537): support augmented PDO. */ - if ((src_caps[j] & PDO_TYPE_MASK) != PDO_TYPE_FIXED) - continue; - - pd_extract_pdo_power(src_caps[j], &ma, &mv, &unused); - - if (mv > max_mv) - continue; - - mw = ma * mv / 1000; - efficient = is_more_efficient(mv, cand->mv, batt_mv, - batt_pwr, input_pwr_avg); - - if (flag & DPS_FLAG_NEED_MORE_PWR) { - /* the insufficient case.*/ - if (input_pwr_avg > cand->mw && - (mw > cand->mw || - (mw == cand->mw && efficient))) { - UPDATE_CANDIDATE(i, mv, mw); - } else if (input_pwr_avg <= mw && efficient) { - UPDATE_CANDIDATE(i, mv, mw); - } - } else { - int adjust_pwr = - mw * dps_config.k_less_pwr / 100; - int adjust_cand_mw = - cand->mw * dps_config.k_less_pwr / 100; - - /* Pick if we don't have a candidate yet. */ - if (!cand->mw) { - UPDATE_CANDIDATE(i, mv, mw); - /* - * if the candidate is insufficient, and - * we get one provides more. - */ - } else if ((adjust_cand_mw < input_pwr_avg && - cand->mw < mw) || - /* - * if the candidate is sufficient, - * and we pick a more efficient one. - */ - (adjust_cand_mw >= input_pwr_avg && - adjust_pwr >= input_pwr_avg && - efficient)) { - UPDATE_CANDIDATE(i, mv, mw); - } - } - - - /* - * if the candidate is the same as the current one, pick - * the one at active charge port. - */ - if (mw == cand->mw && mv == cand->mv && - i == active_port) - UPDATE_CANDIDATE(i, mv, mw); - } - } - - if (!cand->mv) - CPRINTS("ERR:CNDMV"); - - return (cand->mv != req_mv); -} - -static bool has_srccap(void) -{ - for (int i = 0; i < board_get_usb_pd_port_count(); ++i) { - if (pd_is_connected(i) && - pd_get_power_role(i) == PD_ROLE_SINK && - pd_get_src_cap_cnt(i) > 0) - return true; - } - return false; -} - -void dps_update_stabilized_time(int port) -{ - update_timeout(dps_config.t_stable); -} - -void dps_task(void *u) -{ - struct pdo_candidate last_cand = {CHARGE_PORT_NONE, 0, 0}; - int sample_count = 0; - - dps_init(); - update_timeout(dps_config.t_check); - - while (1) { - struct pdo_candidate curr_cand = {CHARGE_PORT_NONE, 0, 0}; - timestamp_t now; - - now = get_time(); - if (flag & DPS_FLAG_STOP_EVENTS) { - dps_reset(); - task_wait_event(-1); - /* clear flags after wake up. */ - flag = 0; - update_timeout(dps_config.t_check); - continue; - } else if (now.val < timeout.val) { - flag |= DPS_FLAG_WAITING; - task_wait_event(timeout.val - now.val); - flag &= ~DPS_FLAG_WAITING; - } - - if (!is_enabled) { - flag |= DPS_FLAG_DISABLED; - continue; - } - - if (!has_srccap()) { - flag |= DPS_FLAG_NO_SRCCAP; - continue; - } - - if (!has_new_power_request(&curr_cand)) { - sample_count = 0; - flag &= ~DPS_FLAG_SAMPLED; - } else { - if (last_cand.port == curr_cand.port && - last_cand.mv == curr_cand.mv && - last_cand.mw == curr_cand.mw) - sample_count++; - else - sample_count = 1; - flag |= DPS_FLAG_SAMPLED; - } - - if (sample_count == dps_config.k_sample) { - dynamic_mv = curr_cand.mv; - dps_port = curr_cand.port; - pd_dpm_request(dps_port, - DPM_REQUEST_NEW_POWER_LEVEL); - sample_count = 0; - flag &= ~(DPS_FLAG_SAMPLED | DPS_FLAG_NEED_MORE_PWR); - } - - last_cand.port = curr_cand.port; - last_cand.mv = curr_cand.mv; - last_cand.mw = curr_cand.mw; - - update_timeout(dps_config.t_check); - } -} - -static int command_dps(int argc, char **argv) -{ - int port = charge_manager_get_active_charge_port(); - int input_pwr, vbus, input_curr; - int holder; - - if (argc == 1) { - uint32_t last_ma = 0, last_mv = 0; - int batt_mv; - - ccprintf("flag=0x%x k_more=%d k_less=%d k_sample=%d k_win=%d\n", - flag, dps_config.k_more_pwr, dps_config.k_less_pwr, - dps_config.k_sample, dps_config.k_window); - ccprintf("t_stable=%d t_check=%d\n", - dps_config.t_stable / SECOND, - dps_config.t_check / SECOND); - if (!is_enabled) { - ccprintf("DPS Disabled\n"); - return EC_SUCCESS; - } - - if (port == CHARGE_PORT_NONE) { - ccprintf("No charger attached\n"); - return EC_SUCCESS; - } - - battery_design_voltage(&batt_mv); - input_pwr = get_desired_input_power(&vbus, &input_curr); - if (!(flag & DPS_FLAG_NO_SRCCAP)) { - last_mv = pd_get_requested_voltage(port); - last_ma = pd_get_requested_current(port); - } - ccprintf("C%d DPS Enabled\n" - "Requested: %dmV/%dmA\n" - "Measured: %dmV/%dmA/%dmW\n" - "Efficient: %dmV\n" - "Batt: %dmv\n" - "PDMaxMV: %dmV\n", - port, last_mv, last_ma, - vbus, input_curr, input_pwr, - get_efficient_voltage(), - batt_mv, - pd_get_max_voltage()); - return EC_SUCCESS; - } - - if (!strcasecmp(argv[1], "en")) { - dps_enable(true); - return EC_SUCCESS; - } else if (!strcasecmp(argv[1], "dis")) { - dps_enable(false); - return EC_SUCCESS; - } else if (!strcasecmp(argv[1], "fakepwr")) { - if (argc == 2) { - ccprintf("%sabled %dmV/%dmA\n", - fake_enabled ? "en" : "dis", fake_mv, fake_ma); - return EC_SUCCESS; - } - - if (!strcasecmp(argv[2], "dis")) { - fake_enabled = false; - return EC_SUCCESS; - } - - if (argc < 4) - return EC_ERROR_PARAM_COUNT; - - holder = atoi(argv[2]); - if (holder <= 0) - return EC_ERROR_PARAM2; - fake_mv = holder; - - holder = atoi(argv[3]); - if (holder <= 0) - return EC_ERROR_PARAM3; - fake_ma = holder; - - fake_enabled = true; - return EC_SUCCESS; - } - - if (argc != 3) - return EC_ERROR_PARAM2; - - if (!strcasecmp(argv[1], "debug")) { - debug_level = atoi(argv[2]); - } else if (!strcasecmp(argv[1], "setkmore")) { - holder = atoi(argv[2]); - if (holder > 100 || holder <= 0 || - holder < dps_config.k_less_pwr) - return EC_ERROR_PARAM2; - dps_config.k_more_pwr = holder; - } else if (!strcasecmp(argv[1], "setkless")) { - holder = atoi(argv[2]); - if (holder > 100 || holder <= 0 || - holder > dps_config.k_more_pwr) - return EC_ERROR_PARAM2; - dps_config.k_less_pwr = holder; - } else if (!strcasecmp(argv[1], "setksample")) { - holder = atoi(argv[2]); - if (holder <= 0) - return EC_ERROR_PARAM2; - dps_config.k_sample = holder; - } else if (!strcasecmp(argv[1], "setkwin")) { - holder = atoi(argv[2]); - if (holder <= 0 || holder > MAX_MOVING_AVG_WINDOW) - return EC_ERROR_PARAM2; - dps_config.k_window = holder; - } else if (!strcasecmp(argv[1], "settcheck")) { - holder = atoi(argv[2]); - if (holder <= 0) - return EC_ERROR_PARAM2; - dps_config.t_check = holder * SECOND; - } else if (!strcasecmp(argv[1], "settstable")) { - holder = atoi(argv[2]); - if (holder <= 0) - return EC_ERROR_PARAM2; - dps_config.t_stable = holder * SECOND; - } else { - return EC_ERROR_PARAM1; - } - - return EC_SUCCESS; -} -DECLARE_CONSOLE_COMMAND(dps, command_dps, - "en|dis|debug <int>\n" - "\t\t set(kmore|kless|ksample|kwindow) <int>\n" - "\t\t set(tstable|tcheck) <int>\n" - "\t\t fakepwr [dis|<mV> <mA>]", - "Print/set Dynamic PDO Selection state."); |