From 2e0de3c50f0106264303d2b8b55ec6253d9eda55 Mon Sep 17 00:00:00 2001 From: Vincent Palatin Date: Wed, 14 May 2014 17:39:30 -0700 Subject: zinger: add voltage discharge When the power supply voltage is transitioning to a lower value, use the discharging FET to ensure that the voltage is acceptable before re-enabling the output. Note: when discharging, we must disable the fast OCP ADC interrupt, but that is ok because we still have the slow OCP check in board_checks(). BRANCH=none BUG=chrome-os-partner:28332 TEST=on Zinger, transition from 20V to 5V using Firefly buttons and observe that we no longer have an over-voltage event. Also, verified that fast OCP triggering still works after a discharge. Change-Id: Ie327645e74819aebd1260f5ce16b2ba46a674a7b Signed-off-by: Vincent Palatin Signed-off-by: Alec Berg Reviewed-on: https://chromium-review.googlesource.com/201577 Reviewed-by: Todd Broch --- board/zinger/hardware.c | 2 + board/zinger/usb_pd_policy.c | 111 +++++++++++++++++++++++++++++++++---------- 2 files changed, 89 insertions(+), 24 deletions(-) diff --git a/board/zinger/hardware.c b/board/zinger/hardware.c index fd9b86ff15..fc0612eeb9 100644 --- a/board/zinger/hardware.c +++ b/board/zinger/hardware.c @@ -244,6 +244,8 @@ int adc_disable_watchdog(void) STM32_ADC_CFGR1 = 1 << 12; /* Disable interrupt */ STM32_ADC_IER = 0; + /* Clear flags */ + STM32_ADC_ISR = 0x8e; return EC_SUCCESS; } diff --git a/board/zinger/usb_pd_policy.c b/board/zinger/usb_pd_policy.c index e4aa2d1f98..a4fcc38fe1 100644 --- a/board/zinger/usb_pd_policy.c +++ b/board/zinger/usb_pd_policy.c @@ -61,6 +61,7 @@ enum faults { FAULT_OCP, /* Over-Current Protection */ FAULT_FAST_OCP, /* Over-Current Protection for interrupt context */ FAULT_OVP, /* Under or Over-Voltage Protection */ + FAULT_DISCHARGE, /* Discharge was ineffective */ }; /* current fault condition */ @@ -84,6 +85,8 @@ static timestamp_t fault_deadline; #define VBUS_MA(ma) ((ma)*ADC_SCALE*R_SENSE/1000*CURR_GAIN/VDDA_MV) /* convert raw ADC value to mA */ #define ADC_TO_CURR_MA(vbus) ((vbus)*1000/(ADC_SCALE*R_SENSE)*VDDA_MV/CURR_GAIN) +/* convert raw ADC value to mV */ +#define ADC_TO_VOLT_MV(vbus) ((vbus)*VOLT_DIV*VDDA_MV/ADC_SCALE) /* Max current : 20% over 3A = 3.6A */ #define MAX_CURRENT VBUS_MA(3600) @@ -99,9 +102,41 @@ static timestamp_t fault_deadline; /* Over-voltage recovery threshold is 1.1x Vnom */ #define OVP_REC_MV(mv) VBUS_MV((mv) * 11 / 10) -/* Time to allow for voltage down stepping */ -/* TODO reduce this time if possible when voltage discharging is implemented */ -#define VOLTAGE_DOWN_STEP_TIME (500*MSEC) +/* Maximum discharging delay */ +#define DISCHARGE_TIMEOUT (90*MSEC) +/* Voltage overshoot below the OVP threshold for discharging to avoid OVP */ +#define DISCHARGE_OVERSHOOT_MV VBUS_MV(200) + +/* ----- output voltage discharging ----- */ + +/* expiration date of the discharge */ +static timestamp_t discharge_deadline; + +static inline void discharge_enable(void) +{ + STM32_GPIO_BSRR(GPIO_F) = GPIO_SET(1); +} + +static inline void discharge_disable(void) +{ + STM32_GPIO_BSRR(GPIO_F) = GPIO_RESET(1); + adc_disable_watchdog(); +} + +static inline int discharge_is_enabled(void) +{ + /* GPF1 = enable discharge FET */ + return STM32_GPIO_IDR(GPIO_F) & 2; +} + +static void discharge_voltage(int target_volt) +{ + discharge_enable(); + discharge_deadline.val = get_time().val + DISCHARGE_TIMEOUT; + /* Monitor VBUS voltage */ + target_volt -= DISCHARGE_OVERSHOOT_MV; + adc_enable_watchdog(ADC_CH_V_SENSE, 0xFFF, target_volt); +} /* ----------------------- USB Power delivery policy ---------------------- */ @@ -131,10 +166,6 @@ static const struct { static int volt_idx; static int last_volt_idx; -/* flag and timestamp for down-stepping the voltage */ -static int down_step; -static uint64_t down_step_done_time; - /* output current measurement */ int vbus_amp; @@ -146,9 +177,8 @@ int pd_request_voltage(uint32_t rdo) uint32_t pdo; uint32_t pdo_ma; - - /* fault condition not cleared : reject transitions */ - if (fault != FAULT_OK) + /* fault condition or output disabled: reject transitions */ + if (fault != FAULT_OK || !output_is_enabled()) return EC_ERROR_INVAL; if (!idx || idx > pd_src_pdo_cnt) @@ -167,9 +197,18 @@ int pd_request_voltage(uint32_t rdo) ((rdo >> 10) & 0x3ff) * 10, (rdo & 0x3ff) * 10); if (idx - 1 < volt_idx) { /* down voltage transition */ - down_step = 1; - down_step_done_time = get_time().val + VOLTAGE_DOWN_STEP_TIME; - /* TODO discharge on down voltage transitions ? */ + /* Stop OCP monitoring */ + adc_disable_watchdog(); + + discharge_voltage(voltages[idx - 1].ovp); + } else if (idx - 1 > volt_idx) { /* up voltage transition */ + if (discharge_is_enabled()) { + /* Make sure discharging is disabled */ + discharge_disable(); + /* Enable over-current monitoring */ + adc_enable_watchdog(ADC_CH_A_SENSE, + MAX_CURRENT_FAST, 0); + } } last_volt_idx = volt_idx; volt_idx = idx - 1; @@ -181,7 +220,7 @@ int pd_request_voltage(uint32_t rdo) int pd_set_power_supply_ready(int port) { /* fault condition not cleared : do not turn on power */ - if (fault != FAULT_OK) + if ((fault != FAULT_OK) || discharge_is_enabled()) return EC_ERROR_INVAL; output_enable(); @@ -193,14 +232,19 @@ int pd_set_power_supply_ready(int port) void pd_power_supply_reset(int port) { + int need_discharge = (volt_idx > 1) || discharge_is_enabled(); + output_disable(); - /* TODO discharge ? */ volt_idx = 0; set_output_voltage(VO_5V); /* TODO transition delay */ /* Stop OCP monitoring to save power */ adc_disable_watchdog(); + + /* discharge voltage to 5V ? */ + if (need_discharge) + discharge_voltage(voltages[0].ovp); } int pd_board_checks(void) @@ -244,22 +288,33 @@ int pd_board_checks(void) * Set the voltage index to use for checking OVP. During a down step * transition, use the previous voltage index to check for OVP. */ - if (down_step && get_time().val >= down_step_done_time) - down_step = 0; - ovp_idx = down_step ? last_volt_idx : volt_idx; + ovp_idx = discharge_is_enabled() ? last_volt_idx : volt_idx; if ((output_is_enabled() && (vbus_volt > voltages[ovp_idx].ovp)) || (fault && (vbus_volt > voltages[ovp_idx].ovp_rec))) { if (!fault) debug_printf("OverVoltage : %d mV\n", - vbus_volt * VDDA_MV * VOLT_DIV / ADC_SCALE); - /* TODO(crosbug.com/p/28331) discharge */ + ADC_TO_VOLT_MV(vbus_volt)); fault = FAULT_OVP; /* no timeout */ fault_deadline.val = get_time().val; return EC_ERROR_INVAL; } + /* the discharge did not work properly */ + if (discharge_is_enabled() && + (get_time().val > discharge_deadline.val)) { + /* stop it */ + discharge_disable(); + /* enable over-current monitoring */ + adc_enable_watchdog(ADC_CH_A_SENSE, MAX_CURRENT_FAST, 0); + debug_printf("Discharge failure : %d mV\n", + ADC_TO_VOLT_MV(vbus_volt)); + fault = FAULT_DISCHARGE; + /* reset it after 1 second */ + fault_deadline.val = get_time().val + OCP_TIMEOUT; + } + /* everything is good *and* the error condition has expired */ if ((fault != FAULT_OK) && (get_time().val > fault_deadline.val)) { fault = FAULT_OK; @@ -277,12 +332,20 @@ int pd_board_checks(void) void pd_adc_interrupt(void) { - /* cut the power output */ - pd_power_supply_reset(0); /* Clear flags */ STM32_ADC_ISR = 0x8e; - /* record a special fault, the normal check will record the timeout */ - fault = FAULT_FAST_OCP; + + if (discharge_is_enabled()) { /* discharge completed */ + discharge_disable(); + /* enable over-current monitoring */ + adc_enable_watchdog(ADC_CH_A_SENSE, MAX_CURRENT_FAST, 0); + } else {/* Over-current detection */ + /* cut the power output */ + pd_power_supply_reset(0); + /* record a special fault */ + fault = FAULT_FAST_OCP; + /* pd_board_checks() will record the timeout later */ + } } DECLARE_IRQ(STM32_IRQ_ADC_COMP, pd_adc_interrupt, 1); -- cgit v1.2.1