diff options
-rw-r--r-- | common/build.mk | 1 | ||||
-rw-r--r-- | common/charge_manager.c | 19 | ||||
-rw-r--r-- | common/dps.c | 639 | ||||
-rw-r--r-- | common/usb_pd_dual_role.c | 12 | ||||
-rw-r--r-- | common/usbc/usb_pe_drp_sm.c | 10 | ||||
-rw-r--r-- | include/config.h | 9 | ||||
-rw-r--r-- | include/dps.h | 71 | ||||
-rw-r--r-- | util/config_allowed.txt | 1 | ||||
-rw-r--r-- | zephyr/Kconfig.usbc | 11 | ||||
-rw-r--r-- | zephyr/shim/include/config_chip.h | 5 |
10 files changed, 773 insertions, 5 deletions
diff --git a/common/build.mk b/common/build.mk index 48a247d58a..867dcf465f 100644 --- a/common/build.mk +++ b/common/build.mk @@ -174,6 +174,7 @@ common-$(CONFIG_USB_PD_CONSOLE_CMD)+=usb_pd_console_cmd.o endif common-$(CONFIG_USB_PD_ALT_MODE_DFP)+=usb_pd_alt_mode_dfp.o common-$(CONFIG_USB_PD_ALT_MODE_UFP)+=usb_pd_alt_mode_ufp.o +common-$(CONFIG_USB_PD_DPS)+=dps.o common-$(CONFIG_USB_PD_LOGGING)+=event_log.o pd_log.o common-$(CONFIG_USB_PD_TCPC)+=usb_pd_tcpc.o common-$(CONFIG_USB_UPDATE)+=usb_update.o update_fw.o diff --git a/common/charge_manager.c b/common/charge_manager.c index 28fe109f1d..862bb28725 100644 --- a/common/charge_manager.c +++ b/common/charge_manager.c @@ -11,6 +11,7 @@ #include "charge_state_v2.h" #include "charger.h" #include "console.h" +#include "dps.h" #include "extpower.h" #include "gpio.h" #include "hooks.h" @@ -624,11 +625,13 @@ static void charge_manager_get_best_charge_port(int *new_port, /* Skip port selection on OVERRIDE_DONT_CHARGE. */ if (override_port != OVERRIDE_DONT_CHARGE) { + /* * Charge supplier selection logic: - * 1. Prefer higher priority supply. - * 2. Prefer higher power over lower in case priority is tied. - * 3. Prefer current charge port over new port in case (1) + * 1. Prefer DPS charge port. + * 2. Prefer higher priority supply. + * 3. Prefer higher power over lower in case priority is tied. + * 4. Prefer current charge port over new port in case (1) * and (2) are tied. * available_charge can be changed at any time by other tasks, * so make no assumptions about its consistency. @@ -670,8 +673,16 @@ static void charge_manager_get_best_charge_port(int *new_port, candidate_port_power = POWER(available_charge[i][j]); + /* Select DPS port if provided. */ + if (IS_ENABLED(CONFIG_USB_PD_DPS) && + override_port == OVERRIDE_OFF && + i == CHARGE_SUPPLIER_PD && + j == dps_get_charge_port()) { + supplier = i; + port = j; + break; /* Select if no supplier chosen yet. */ - if (supplier == CHARGE_SUPPLIER_NONE || + } else if (supplier == CHARGE_SUPPLIER_NONE || /* ..or if supplier priority is higher. */ supplier_priority[i] < supplier_priority[supplier] || diff --git a/common/dps.c b/common/dps.c new file mode 100644 index 0000000000..235f4d4e08 --- /dev/null +++ b/common/dps.c @@ -0,0 +1,639 @@ +/* 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."); diff --git a/common/usb_pd_dual_role.c b/common/usb_pd_dual_role.c index ae8429262d..3981353101 100644 --- a/common/usb_pd_dual_role.c +++ b/common/usb_pd_dual_role.c @@ -7,6 +7,7 @@ #include "charge_manager.h" #include "charge_state.h" +#include "dps.h" #include "system.h" #include "usb_common.h" #include "usb_pd.h" @@ -21,6 +22,7 @@ */ static unsigned int max_request_mv = PD_MAX_VOLTAGE_MV; +/* TODO(b:169532537): deprecate CONFIG_USB_PD_PREFER_MV */ STATIC_IF_NOT(CONFIG_USB_PD_PREFER_MV) struct pd_pref_config_t __maybe_unused pd_pref_config; @@ -236,6 +238,10 @@ void pd_build_request(int32_t vpd_vdo, uint32_t *rdo, uint32_t *ma, else max_request_allowed = 1; + if (IS_ENABLED(CONFIG_USB_PD_DPS) && dps_is_enabled()) + max_request_mv = + MIN(max_request_mv, dps_get_dynamic_voltage()); + /* * If currently charging on a different port, or we are not allowed to * request the max voltage, then select vSafe5V @@ -342,11 +348,15 @@ void pd_process_source_cap(int port, int cnt, uint32_t *src_caps) if (IS_ENABLED(CONFIG_CHARGE_MANAGER)) { uint32_t ma, mv, pdo, unused; + uint32_t max_mv = pd_get_max_voltage(); + + if (IS_ENABLED(CONFIG_USB_PD_DPS) && dps_is_enabled()) + max_mv = MIN(max_mv, dps_get_dynamic_voltage()); /* Get max power info that we could request */ pd_find_pdo_index(pd_get_src_cap_cnt(port), pd_get_src_caps(port), - pd_get_max_voltage(), &pdo); + max_mv, &pdo); pd_extract_pdo_power(pdo, &ma, &mv, &unused); /* Set max. limit, but apply 500mA ceiling */ diff --git a/common/usbc/usb_pe_drp_sm.c b/common/usbc/usb_pe_drp_sm.c index f3faa1eabb..bf134c5449 100644 --- a/common/usbc/usb_pe_drp_sm.c +++ b/common/usbc/usb_pe_drp_sm.c @@ -10,6 +10,7 @@ #include "charge_state.h" #include "common.h" #include "console.h" +#include "dps.h" #include "driver/tcpm/tcpm.h" #include "ec_commands.h" #include "hooks.h" @@ -3159,6 +3160,11 @@ static void pe_snk_evaluate_capability_entry(int port) /* Device Policy Response Received */ set_state_pe(port, PE_SNK_SELECT_CAPABILITY); + +#ifdef HAS_TASK_DPS + /* Wake DPS task to evaluate the SrcCaps */ + task_wake(TASK_ID_DPS); +#endif } /** @@ -3388,6 +3394,10 @@ static void pe_snk_transition_sink_exit(int port) CEIL_REQUESTOR_PD, pe[port].curr_limit); pd_timer_disable(port, PE_TIMER_PS_TRANSITION); + + if (IS_ENABLED(CONFIG_USB_PD_DPS)) + if (charge_manager_get_active_charge_port() == port) + dps_update_stabilized_time(port); } diff --git a/include/config.h b/include/config.h index 1366d86662..f157047273 100644 --- a/include/config.h +++ b/include/config.h @@ -4115,6 +4115,15 @@ #undef CONFIG_USB_PD_TCPMV2 /* + * Enable dynamic PDO selection. + * + * DPS picks a power efficient voltage regarding to the battery configuration + * and the system loading. It monitors PIn (Power In), so VBUS/IBUS ADC + * should be supported on the platform. + */ +#undef CONFIG_USB_PD_DPS + +/* * Device Types for TCPMv2. * * Exactly one must be defined when CONFIG_USB_PD_TCPMV2 is defined. diff --git a/include/dps.h b/include/dps.h new file mode 100644 index 0000000000..151c6b3f09 --- /dev/null +++ b/include/dps.h @@ -0,0 +1,71 @@ +/* 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. + */ + +#ifndef __CROS_EC_DPS__H +#define __CROS_EC_DPS__H + +#include <stdbool.h> + +#include "common.h" + +/* Dynamic PDO Selection config. */ +struct dps_config_t { + /* (0, 100) coeff for transition to a lower power PDO*/ + uint32_t k_less_pwr; + /* (0, 100) coeff for transition to a higher power PDO*/ + uint32_t k_more_pwr; + /* Number for how many the same consecutive sample to transist */ + uint32_t k_sample; + /* Number for moving average window for the power and the current. */ + uint32_t k_window; + /* Power stabilized time after a new contract in us */ + uint32_t t_stable; + /* Next power evaluation time interval in us */ + uint32_t t_check; + /* + * If the current voltage is more efficient than the previous voltage + * + * @param curr_mv: current PDO voltage + * @param prev_mv: previous PDO voltage + * @param batt_mv: battery desired voltage + * @param batt_mw: current battery power + * @param input_mw: current adapter input power + * @return true is curr_mv is more efficient otherwise false + */ + bool (*is_more_efficient)(int curr_mv, int prev_mv, int batt_mv, + int batt_mw, int input_mw); +}; + +/* + * Get voltage in the current system load + * + * @return a voltage(mV) that the adapter supports to charge at the given port. + */ +int dps_get_dynamic_voltage(void); + +/* + * Get DPS charge port + * + * @return the DPS charge port, or CHARGE_PORT_NONE if unavailable. + */ +int dps_get_charge_port(void); + +/* + * Check if DPS is enabled. + * + * @return true if enabled, false otherwise. + */ +bool dps_is_enabled(void); + +/* + * Update DPS stablized timeout + * + * This is called at the exit of PE_SNK_TRANSITION_SINK + * + * @param port: the port for timer reset. + */ +void dps_update_stabilized_time(int port); + +#endif /* __CROS_EC_DPS__H */ diff --git a/util/config_allowed.txt b/util/config_allowed.txt index ffdadb450b..2542d2389c 100644 --- a/util/config_allowed.txt +++ b/util/config_allowed.txt @@ -1016,6 +1016,7 @@ CONFIG_USB_PD_COMM_DISABLED CONFIG_USB_PD_COMM_LOCKED CONFIG_USB_PD_CUSTOM_PDO CONFIG_USB_PD_DEBUG_DR +CONFIG_USB_PD_DPS CONFIG_USB_PD_DYNAMIC_SRC_CAP CONFIG_USB_PD_EXTENDED_MESSAGES CONFIG_USB_PD_FLASH diff --git a/zephyr/Kconfig.usbc b/zephyr/Kconfig.usbc index e40eba1140..b687c4a5cc 100644 --- a/zephyr/Kconfig.usbc +++ b/zephyr/Kconfig.usbc @@ -421,6 +421,17 @@ endchoice # Trigger implementation endif # PLATFORM_EC_USB_PD_FRS +config PLATFORM_EC_USB_PD_DPS + bool "Board can support Dynamic PDO Selection" + depends on PLATFORM_EC_BATTERY + default n + help + Enable this if the board needs dynamic PDO selection. + DPS picks a power efficient PDO regarding to the underlying battery + configuration and the system loading. + Default configuration can be overrided by `dps_config` to adapt + to each board's need. + config PLATFORM_EC_USB_PD_DUAL_ROLE_AUTO_TOGGLE bool "Board can use TCPC-controlled DRP toggle" depends on PLATFORM_EC_USB_PD_DUAL_ROLE diff --git a/zephyr/shim/include/config_chip.h b/zephyr/shim/include/config_chip.h index e4ad0ab28e..2c7ac5dfe3 100644 --- a/zephyr/shim/include/config_chip.h +++ b/zephyr/shim/include/config_chip.h @@ -1024,6 +1024,11 @@ #define CONFIG_USB_PD_ALT_MODE_UFP #endif +#undef CONFIG_USB_PD_DPS +#ifdef CONFIG_PLATFORM_EC_USB_PD_DPS +#define CONFIG_USB_PD_DPS +#endif + #undef CONFIG_DP_REDRIVER_TDP142 #ifdef CONFIG_PLATFORM_EC_DP_REDRIVER_TDP142 #define CONFIG_DP_REDRIVER_TDP142 |