diff options
-rw-r--r-- | board/puff/Makefile | 21 | ||||
-rw-r--r-- | board/puff/board.c | 343 | ||||
-rw-r--r-- | board/puff/port-sm.c | 126 | ||||
-rw-r--r-- | board/puff/validate_port_power.c | 40 |
4 files changed, 296 insertions, 234 deletions
diff --git a/board/puff/Makefile b/board/puff/Makefile deleted file mode 100644 index bc7435ed75..0000000000 --- a/board/puff/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2019 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. -CLANG:=clang-6.0 -KLEE:=klee -KTEST_TOOL:=ktest-tool - -.PHONY: report -report: klee-last - $(KTEST_TOOL) \ - $(patsubst %.assert.err,%.ktest,$(wildcard klee-last/test*.assert.err)) - -klee-last: validate_port_power.bc - $(KLEE) --emit-all-errors $^ - -validate_port_power.bc: validate_port_power.c port-sm.c - $(CLANG) -emit-llvm -c -g -O0 -Xclang -disable-O0-optnone -o $@ $< - -.PHONY: clean -clean: - rm -rf validate_port_power.bc klee-last klee-out-* diff --git a/board/puff/board.c b/board/puff/board.c index 54b5175c90..97967d6efd 100644 --- a/board/puff/board.c +++ b/board/puff/board.c @@ -49,6 +49,9 @@ #define CPRINTS(format, args...) cprints(CC_USBCHARGE, format, ## args) #define CPRINTF(format, args...) cprintf(CC_USBCHARGE, format, ## args) +static void power_monitor(void); +DECLARE_DEFERRED(power_monitor); + static void ppc_interrupt(enum gpio_signal signal) { if (signal == GPIO_USB_C0_TCPPC_INT_ODL) @@ -96,59 +99,72 @@ void board_set_charge_limit(int port, int supplier, int charge_ma, led_alert(insufficient_power); } -#include "port-sm.c" +static uint8_t usbc_overcurrent; +static int32_t base_5v_power; -static bool usbc_overcurrent; /* - * Update USB port power limits based on current state. - * - * Port power consumption is assumed to be negligible if not overcurrent, - * and we have two knobs: the front port power limit, and the USB-C power limit. - * Either one of the front ports may run at high power (one at a time) or we - * can limit both to low power. On USB-C we can similarly limit the port power, - * but there's only one port. + * Power usage for each port as measured or estimated. + * Units are milliwatts (5v x ma current) */ -void update_port_limits(void) -{ - struct port_states state = { - .bitfield = (!gpio_get_level(GPIO_USB_A0_OC_ODL) - << PORTMASK_FRONT_A0) | - (!gpio_get_level(GPIO_USB_A1_OC_ODL) - << PORTMASK_FRONT_A1) | - (!gpio_get_level(GPIO_USB_A2_OC_ODL) - << PORTMASK_REAR_A0) | - (!gpio_get_level(GPIO_USB_A3_OC_ODL) - << PORTMASK_REAR_A1) | - (!gpio_get_level(GPIO_USB_A4_OC_ODL) - << PORTMASK_REAR_A2) | - (!gpio_get_level(GPIO_HDMI_CONN0_OC_ODL) - << PORTMASK_HDMI0) | - (!gpio_get_level(GPIO_HDMI_CONN1_OC_ODL) - << PORTMASK_HDMI1) | - ((ppc_is_sourcing_vbus(0) && usbc_overcurrent) - << PORTMASK_TYPEC), - .front_a_limited = gpio_get_level(GPIO_USB_A_LOW_PWR_OD), - /* Assume high-power; there's no way to poll this. */ - /* - * TODO(b/143190102) add a way to poll port power limit so we - * can detect when it can be increased again if the port is - * already active. - */ - .c_low_power = 0, - }; +#define PWR_BASE_LOAD (5*1335) +#define PWR_FRONT_HIGH (5*1603) +#define PWR_FRONT_LOW (5*963) +#define PWR_REAR (5*1075) +#define PWR_HDMI (5*562) +#define PWR_C_HIGH (5*3740) +#define PWR_C_LOW (5*2090) +#define PWR_MAX (5*10000) - update_port_state(&state); - - ppc_set_vbus_source_current_limit(0, - state.c_low_power ? TYPEC_RP_1A5 : TYPEC_RP_3A0); - /* Output high limits power */ - gpio_set_level(GPIO_USB_A_LOW_PWR_OD, state.front_a_limited); +/* + * Update the 5V power usage, assuming no throttling, + * and invoke the power monitoring. + */ +static void update_5v_usage(void) +{ + int front_ports = 0; + /* + * Recalculate the 5V load, assuming no throttling. + */ + base_5v_power = PWR_BASE_LOAD; + if (!gpio_get_level(GPIO_USB_A0_OC_ODL)) { + front_ports++; + base_5v_power += PWR_FRONT_LOW; + } + if (!gpio_get_level(GPIO_USB_A1_OC_ODL)) { + front_ports++; + base_5v_power += PWR_FRONT_LOW; + } + /* + * Only 1 front port can run higher power at a time. + */ + if (front_ports > 0) + base_5v_power += PWR_FRONT_HIGH - PWR_FRONT_LOW; + if (!gpio_get_level(GPIO_USB_A2_OC_ODL)) + base_5v_power += PWR_REAR; + if (!gpio_get_level(GPIO_USB_A3_OC_ODL)) + base_5v_power += PWR_REAR; + if (!gpio_get_level(GPIO_USB_A4_OC_ODL)) + base_5v_power += PWR_REAR; + if (!gpio_get_level(GPIO_HDMI_CONN0_OC_ODL)) + base_5v_power += PWR_HDMI; + if (!gpio_get_level(GPIO_HDMI_CONN1_OC_ODL)) + base_5v_power += PWR_HDMI; + if (usbc_overcurrent) + base_5v_power += PWR_C_HIGH; + /* + * Invoke the power handler immediately. + */ + hook_call_deferred(&power_monitor_data, 0); } -DECLARE_DEFERRED(update_port_limits); +DECLARE_DEFERRED(update_5v_usage); +/* + * Start power monitoring after ADCs have been initialised. + */ +DECLARE_HOOK(HOOK_INIT, update_5v_usage, HOOK_PRIO_INIT_ADC + 1); static void port_ocp_interrupt(enum gpio_signal signal) { - hook_call_deferred(&update_port_limits_data, 0); + hook_call_deferred(&update_5v_usage_data, 0); } /******************************************************************************/ @@ -309,7 +325,7 @@ const struct adc_t adc_channels[] = { [ADC_PPVAR_IMON] = { /* 500 mV/A */ .name = "PPVAR_IMON", .input_ch = NPCX_ADC_CH9, - .factor_mul = ADC_MAX_VOLT, + .factor_mul = ADC_MAX_VOLT * 2, /* Milliamps */ .factor_div = ADC_READ_MAX + 1, }, [ADC_TEMP_SENSOR_1] = { @@ -468,7 +484,6 @@ static void board_init(void) */ cpu_set_interrupt_priority(NPCX_IRQ_WKINTC_0, 2); - update_port_limits(); gpio_enable_interrupt(GPIO_BJ_ADP_PRESENT_L); /* Always claim AC is online, because we don't have a battery. */ @@ -617,6 +632,7 @@ void board_overcurrent_event(int port, int is_overcurrented) if ((port < 0) || (port >= CONFIG_USB_PD_PORT_MAX_COUNT)) return; usbc_overcurrent = is_overcurrented; + update_5v_usage(); } int extpower_is_present(void) @@ -649,3 +665,236 @@ unsigned int ec_config_get_bj_power(void) bj = 0; return bj; } + +/* + * Power monitoring and management. + * + * The overall goal is to gracefully manage the power demand so that + * the power budgets are met without letting the system fall into + * power deficit (perhaps causing a brownout). + * + * There are 2 power budgets that need to be managed: + * - overall system power as measured on the main power supply rail. + * - 5V power delivered to the USB and HDMI ports. + * + * The actual system power demand is calculated from the VBUS voltage and + * the input current (read from a shunt), averaged over 5 readings. + * The power budget limit is from the charge manager. + * + * The 5V power cannot be read directly. Instead, we rely on overcurrent + * inputs from the USB and HDMI ports to indicate that the port is in use + * (and drawing maximum power). + * + * There are 3 throttles that can be applied (in priority order): + * + * - Type A BC1.2 front port restriction (3W) + * - Type C PD (throttle to 1.5A if sourcing) + * - Turn on PROCHOT, which immediately throttles the CPU. + * + * The first 2 throttles affect both the system power and the 5V rails. + * The third is a last resort to force an immediate CPU throttle to + * reduce the overall power use. + * + * The strategy is to determine what the state of the throttles should be, + * and to then turn throttles off or on as needed to match this. + * + * This function runs on demand, or every 2 ms when the CPU is up, + * and continually monitors the power usage, applying the + * throttles when necessary. + * + * All measurements are in milliwatts. + */ +#define THROT_TYPE_A BIT(0) +#define THROT_TYPE_C BIT(1) +#define THROT_PROCHOT BIT(2) + +/* + * Power gain if front USB A ports are limited. + */ +#define POWER_GAIN_TYPE_A 3200 +/* + * Power gain if Type C port is limited. + */ +#define POWER_GAIN_TYPE_C 8800 +/* + * Power is averaged over 10 ms, with a reading every 2 ms. + */ +#define POWER_DELAY_MS 2 +#define POWER_READINGS (10/POWER_DELAY_MS) + +static void power_monitor(void) +{ + static uint32_t current_state; + static uint32_t history[POWER_READINGS]; + static uint8_t index; + int32_t delay; + uint32_t new_state = 0, diff; + int32_t headroom_5v = PWR_MAX - base_5v_power; + + /* + * If CPU is off or suspended, no need to throttle + * or restrict power. + */ + if (chipset_in_state(CHIPSET_STATE_ANY_OFF | + CHIPSET_STATE_SUSPEND)) { + /* + * Slow down monitoring, assume no throttling required. + */ + delay = 20 * MSEC; + /* + * Clear the first entry of the power table so that + * it is re-initilalised when the CPU starts. + */ + history[0] = 0; + } else { + int32_t charger_mw; + + delay = POWER_DELAY_MS * MSEC; + /* + * Get current charger limit (in mw). + * If not configured yet, skip. + */ + charger_mw = charge_manager_get_power_limit_uw() / 1000; + if (charger_mw != 0) { + int32_t gap, total, max, power; + int i; + + /* + * Read power usage. + */ + power = (adc_read_channel(ADC_VBUS) * + adc_read_channel(ADC_PPVAR_IMON)) / + 1000; + /* Init power table */ + if (history[0] == 0) { + for (i = 0; i < POWER_READINGS; i++) + history[i] = power; + } + /* + * Update the power readings and + * calculate the average and max. + */ + history[index] = power; + index = (index + 1) % POWER_READINGS; + total = 0; + max = history[0]; + for (i = 0; i < POWER_READINGS; i++) { + total += history[i]; + if (history[i] > max) + max = history[i]; + } + /* + * For Type-C power supplies, there is + * less tolerance for exceeding the rating, + * so use the max power that has been measured + * over the measuring period. + * For barrel-jack supplies, the rating can be + * exceeded briefly, so use the average. + */ + if (charge_manager_get_supplier() == + CHARGE_SUPPLIER_PD) + power = max; + else + power = total / POWER_READINGS; + /* + * Calculate gap, and if negative, power + * demand is exceeding configured power budget, so + * throttling is required to reduce the demand. + */ + gap = charger_mw - power; + /* + * Limiting type-A power. + */ + if (gap <= 0) { + new_state |= THROT_TYPE_A; + headroom_5v += PWR_FRONT_HIGH - PWR_FRONT_LOW; + if (!(current_state & THROT_TYPE_A)) + gap += POWER_GAIN_TYPE_A; + } + /* + * If the type-C port is sourcing power, + * check whether it should be throttled. + */ + if (ppc_is_sourcing_vbus(0) && gap <= 0) { + new_state |= THROT_TYPE_C; + headroom_5v += PWR_C_HIGH - PWR_C_LOW; + if (!(current_state & THROT_TYPE_C)) + gap += POWER_GAIN_TYPE_C; + } + /* + * As a last resort, turn on PROCHOT to + * throttle the CPU. + */ + if (gap <= 0) + new_state |= THROT_PROCHOT; + } + } + /* + * Check the 5v power usage and if necessary, + * adjust the throttles in priority order. + * + * Either throttle may have already been activated by + * the overall power control. + * + * We rely on the overcurrent detection to inform us + * if the port is in use. + * + * - If type C not already throttled: + * * If not overcurrent, prefer to limit type C [1]. + * * If in overcurrentuse: + * - limit type A first [2] + * - If necessary, limit type C [3]. + * - If type A not throttled, if necessary limit it [2]. + */ + if (headroom_5v < 0) { + /* + * Check whether type C is not throttled, + * and is not overcurrent. + */ + if (!((new_state & THROT_TYPE_C) || usbc_overcurrent)) { + /* + * [1] Type C not in overcurrent, throttle it. + */ + headroom_5v += PWR_C_HIGH - PWR_C_LOW; + new_state |= THROT_TYPE_C; + } + /* + * [2] If type A not already throttled, and power still + * needed, limit type A. + */ + if (!(new_state & THROT_TYPE_A) && headroom_5v < 0) { + headroom_5v += PWR_FRONT_HIGH - PWR_FRONT_LOW; + new_state |= THROT_TYPE_A; + } + /* + * [3] If still under-budget, limit type C. + * No need to check if it is already throttled or not. + */ + if (headroom_5v < 0) + new_state |= THROT_TYPE_C; + } + /* + * Turn the throttles on or off if they have changed. + */ + diff = new_state ^ current_state; + current_state = new_state; + if (diff & THROT_PROCHOT) { + int prochot = (new_state & THROT_PROCHOT) ? 0 : 1; + + gpio_set_level(GPIO_EC_PROCHOT_ODL, prochot); + } + if (diff & THROT_TYPE_C) { + enum tcpc_rp_value rp = (new_state & THROT_TYPE_C) + ? TYPEC_RP_1A5 : TYPEC_RP_3A0; + + ppc_set_vbus_source_current_limit(0, rp); + tcpm_select_rp_value(0, rp); + pd_update_contract(0); + } + if (diff & THROT_TYPE_A) { + int typea_bc = (new_state & THROT_TYPE_A) ? 1 : 0; + + gpio_set_level(GPIO_USB_A_LOW_PWR_OD, typea_bc); + } + hook_call_deferred(&power_monitor_data, delay); +} diff --git a/board/puff/port-sm.c b/board/puff/port-sm.c deleted file mode 100644 index d549b5754b..0000000000 --- a/board/puff/port-sm.c +++ /dev/null @@ -1,126 +0,0 @@ -/* Copyright 2019 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. - */ - -struct port_states { - /* PORT_*n masks correspond to bits in this field */ - uint8_t bitfield; - /* If 1, type C is RP_1A5 (otherwise assumed to be RP_3A0) */ - uint8_t c_low_power; - /* If 1, front ports are current-limited */ - uint8_t front_a_limited; -}; - -#define PORTMASK_FRONT_A0 0 -#define PORTMASK_FRONT_A1 1 -#define PORTMASK_REAR_A0 2 -#define PORTMASK_REAR_A1 3 -#define PORTMASK_REAR_A2 4 -#define PORTMASK_HDMI0 5 -#define PORTMASK_HDMI1 6 -#define PORTMASK_TYPEC 7 - -#define PORT_FRONT_A0 (1 << PORTMASK_FRONT_A0) -#define PORT_FRONT_A1 (1 << PORTMASK_FRONT_A1) -#define PORT_REAR_A0 (1 << PORTMASK_REAR_A0) -#define PORT_REAR_A1 (1 << PORTMASK_REAR_A1) -#define PORT_REAR_A2 (1 << PORTMASK_REAR_A2) -#define PORT_HDMI0 (1 << PORTMASK_HDMI0) -#define PORT_HDMI1 (1 << PORTMASK_HDMI1) -#define PORT_TYPEC (1 << PORTMASK_TYPEC) -#define PORT_ENABLED(id) (!!(states->bitfield & (PORT_##id))) - -#define PWR_FRONT_HIGH 1603 -#define PWR_FRONT_LOW 963 -#define PWR_REAR 1075 -#define PWR_HDMI 562 -#define PWR_C_HIGH 3740 -#define PWR_C_LOW 2090 - -/* - * Calculate the amount of power (in mA) available on the 5V rail. - * - * If negative, the system is at risk of browning out. - */ -int compute_headroom(const struct port_states *states) -{ - int headroom = 10000 - 1335; /* Capacity less base load */ - - headroom -= PWR_HDMI * (PORT_ENABLED(HDMI0) + PORT_ENABLED(HDMI1)); - headroom -= PWR_REAR * (PORT_ENABLED(REAR_A0) + PORT_ENABLED(REAR_A1) + - PORT_ENABLED(REAR_A2)); - - switch (PORT_ENABLED(FRONT_A0) + PORT_ENABLED(FRONT_A1)) { - case 2: - headroom -= PWR_FRONT_LOW + (states->front_a_limited ? - PWR_FRONT_LOW : - PWR_FRONT_HIGH); - break; - case 1: - headroom -= states->front_a_limited ? PWR_FRONT_LOW : - PWR_FRONT_HIGH; - break; - default: - break; - } - - if (PORT_ENABLED(TYPEC)) - headroom -= states->c_low_power ? PWR_C_LOW : PWR_C_HIGH; - - return headroom; -} - -/* - * Update states to stay within the 5V rail power budget. - * - * Only the current limits (c_low_power and front_a_limited) are effective. - * - * The goal here is to ensure that any single state change from what we set - * (specifically, something being plugged into a port) does not exceed the 5V - * power budget. - */ -void update_port_state(struct port_states *states) -{ - int headroom = compute_headroom(states); - - if (!PORT_ENABLED(TYPEC)) { - /* - * USB-C not in use, prefer to adjust it. We may still need - * to limit front port power. - * - * We want to run the front type-A ports at high power, and they - * may be limited so we need to account for the extra power - * we may be allowing the front ports to draw. - */ - if (headroom > - (PWR_C_HIGH + (PWR_FRONT_HIGH - PWR_FRONT_LOW))) { - states->front_a_limited = 0; - states->c_low_power = 0; - } else { - states->front_a_limited = - headroom < - (PWR_C_LOW + (PWR_FRONT_HIGH - PWR_FRONT_LOW)); - states->c_low_power = 1; - } - } else { - /* - * USB-C is in use, prefer to drop front port limits. - * Pessimistically Assume C is currently in low-power mode. - */ - if (headroom > (PWR_C_HIGH - PWR_C_LOW + PWR_FRONT_HIGH)) { - /* Can still go full power */ - states->front_a_limited = 0; - states->c_low_power = 0; - } else if (headroom > - (PWR_C_HIGH - PWR_C_LOW + PWR_FRONT_LOW)) { - /* Reducing front allows C to go to full power */ - states->front_a_limited = 1; - states->c_low_power = 0; - } else { - /* Must reduce both */ - states->front_a_limited = 1; - states->c_low_power = 1; - } - } -} diff --git a/board/puff/validate_port_power.c b/board/puff/validate_port_power.c deleted file mode 100644 index 7afc870fc1..0000000000 --- a/board/puff/validate_port_power.c +++ /dev/null @@ -1,40 +0,0 @@ -/* Copyright 2019 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. - */ - -#include <klee/klee.h> - -#include "port-sm.c" - -int main(void) -{ - uint8_t bitfield = klee_range(0, 256, "port_bitmask"); - uint8_t c_low_power = klee_range(0, 2, "c_low_power"); - uint8_t front_a_limited = klee_range(0, 2, "front_a_limited"); - struct port_states states = { - .bitfield = bitfield, - .c_low_power = c_low_power, - .front_a_limited = front_a_limited - }; - /* - * Assume illegal states with no headroom cannot be reached in the first - * place. - */ - klee_assume(compute_headroom(&states) >= 0); - - update_port_state(&states); - - /* - * Plug something into an unused port and ensure we still have - * non-negative headroom. - */ - uint8_t enable_port = (uint8_t)klee_range(0, 8, "enable_port"); - uint8_t enable_mask = 1 << enable_port; - - klee_assume((states.bitfield & enable_mask) == 0); - states.bitfield |= enable_mask; - klee_assert(compute_headroom(&states) >= 0); - - return 0; -} |