summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew McRae <amcrae@google.com>2020-06-01 13:57:45 +1000
committerCommit Bot <commit-bot@chromium.org>2020-06-23 04:53:42 +0000
commitc995f0eca43bc89f2b10bffcb2668c22e7bec233 (patch)
tree80db25181619cdd43e8171f4b939183a44b20da3
parent131f7dc96e1c794befabdd8b182c7d5a190b58cc (diff)
downloadchrome-ec-c995f0eca43bc89f2b10bffcb2668c22e7bec233.tar.gz
Puff: Implement power monitoring and control
Monitor the power usage and if necessary apply throttles to reduce power demand. go//puff-power-distro BUG=b:151252619 TEST=Reduce power margin and check that throttling occurs. BRANCH=none Signed-off-by: Andrew McRae <amcrae@google.com> Change-Id: Ib385cbf561795be0b424cf6a9a15c976e20d8b80 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2224677 Reviewed-by: Peter Marheine <pmarheine@chromium.org> Commit-Queue: Andrew McRae <amcrae@chromium.org> Tested-by: Andrew McRae <amcrae@chromium.org>
-rw-r--r--board/puff/Makefile21
-rw-r--r--board/puff/board.c343
-rw-r--r--board/puff/port-sm.c126
-rw-r--r--board/puff/validate_port_power.c40
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;
-}