blob: 033577249fde5943ed08a918d8bdcafe2b5c6bc8 [file] [log] [blame]
/* 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.
*/
/*
* Contains common USB functions shared between the old (i.e. usb_pd_protocol)
* and the new (i.e. usb_sm_*) USB-C PD stacks.
*/
#include "atomic.h"
#include "charge_state.h"
#include "common.h"
#include "hooks.h"
#include "task.h"
#include "usb_common.h"
#include "usb_pd.h"
#include "usb_pd_tcpm.h"
#include "usbc_ppc.h"
#include "util.h"
#ifdef CONFIG_COMMON_RUNTIME
#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args)
#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args)
#endif
int usb_get_battery_soc(void)
{
#if defined(CONFIG_CHARGER)
return charge_get_percent();
#elif defined(CONFIG_BATTERY)
return board_get_battery_soc();
#else
return 0;
#endif
}
/*
* CC values for regular sources and Debug sources (aka DTS)
*
* Source type Mode of Operation CC1 CC2
* ---------------------------------------------
* Regular Default USB Power RpUSB Open
* Regular USB-C @ 1.5 A Rp1A5 Open
* Regular USB-C @ 3 A Rp3A0 Open
* DTS Default USB Power Rp3A0 Rp1A5
* DTS USB-C @ 1.5 A Rp1A5 RpUSB
* DTS USB-C @ 3 A Rp3A0 RpUSB
*/
typec_current_t usb_get_typec_current_limit(enum pd_cc_polarity_type polarity,
enum tcpc_cc_voltage_status cc1, enum tcpc_cc_voltage_status cc2)
{
typec_current_t charge = 0;
enum tcpc_cc_voltage_status cc = polarity ? cc2 : cc1;
enum tcpc_cc_voltage_status cc_alt = polarity ? cc1 : cc2;
switch (cc) {
case TYPEC_CC_VOLT_RP_3_0:
if (!cc_is_rp(cc_alt) || cc_alt == TYPEC_CC_VOLT_RP_DEF)
charge = 3000;
else if (cc_alt == TYPEC_CC_VOLT_RP_1_5)
charge = 500;
break;
case TYPEC_CC_VOLT_RP_1_5:
charge = 1500;
break;
case TYPEC_CC_VOLT_RP_DEF:
charge = 500;
break;
default:
break;
}
if (IS_ENABLED(CONFIG_USBC_DISABLE_CHARGE_FROM_RP_DEF) && charge == 500)
charge = 0;
if (cc_is_rp(cc_alt))
charge |= TYPEC_CURRENT_DTS_MASK;
return charge;
}
enum pd_cc_polarity_type get_snk_polarity(enum tcpc_cc_voltage_status cc1,
enum tcpc_cc_voltage_status cc2)
{
/* The following assumes:
*
* TYPEC_CC_VOLT_RP_3_0 > TYPEC_CC_VOLT_RP_1_5
* TYPEC_CC_VOLT_RP_1_5 > TYPEC_CC_VOLT_RP_DEF
* TYPEC_CC_VOLT_RP_DEF > TYPEC_CC_VOLT_OPEN
*/
return cc2 > cc1;
}
enum pd_cc_states pd_get_cc_state(
enum tcpc_cc_voltage_status cc1, enum tcpc_cc_voltage_status cc2)
{
/* Port partner is a SNK */
if (cc_is_snk_dbg_acc(cc1, cc2))
return PD_CC_UFP_DEBUG_ACC;
if (cc_is_at_least_one_rd(cc1, cc2))
return PD_CC_UFP_ATTACHED;
if (cc_is_audio_acc(cc1, cc2))
return PD_CC_UFP_AUDIO_ACC;
/* Port partner is a SRC */
if (cc_is_rp(cc1) && cc_is_rp(cc2))
return PD_CC_DFP_DEBUG_ACC;
if (cc_is_rp(cc1) || cc_is_rp(cc2))
return PD_CC_DFP_ATTACHED;
/*
* 1) Both lines are Vopen or
* 2) Only an e-marked cabled without a partner on the other side
*/
return PD_CC_NONE;
}
/*
* Zinger implements a board specific usb policy that does not define
* PD_MAX_VOLTAGE_MV and PD_OPERATING_POWER_MW. And in turn, does not
* use the following functions.
*/
#if defined(PD_MAX_VOLTAGE_MV) && defined(PD_OPERATING_POWER_MW)
int pd_find_pdo_index(uint32_t src_cap_cnt, const uint32_t * const src_caps,
int max_mv, uint32_t *selected_pdo)
{
int i, uw, mv;
int ret = 0;
int cur_uw = 0;
int prefer_cur;
int __attribute__((unused)) cur_mv = 0;
/* max voltage is always limited by this boards max request */
max_mv = MIN(max_mv, PD_MAX_VOLTAGE_MV);
/* Get max power that is under our max voltage input */
for (i = 0; i < src_cap_cnt; i++) {
/* its an unsupported Augmented PDO (PD3.0) */
if ((src_caps[i] & PDO_TYPE_MASK) == PDO_TYPE_AUGMENTED)
continue;
mv = ((src_caps[i] >> 10) & 0x3FF) * 50;
/* Skip invalid voltage */
if (!mv)
continue;
/* Skip any voltage not supported by this board */
if (!pd_is_valid_input_voltage(mv))
continue;
if ((src_caps[i] & PDO_TYPE_MASK) == PDO_TYPE_BATTERY) {
uw = 250000 * (src_caps[i] & 0x3FF);
} else {
int ma = (src_caps[i] & 0x3FF) * 10;
ma = MIN(ma, PD_MAX_CURRENT_MA);
uw = ma * mv;
}
if (mv > max_mv)
continue;
uw = MIN(uw, PD_MAX_POWER_MW * 1000);
prefer_cur = 0;
/* Apply special rules in case of 'tie' */
if (IS_ENABLED(PD_PREFER_LOW_VOLTAGE)) {
if (uw == cur_uw && mv < cur_mv)
prefer_cur = 1;
} else if (IS_ENABLED(PD_PREFER_HIGH_VOLTAGE)) {
if (uw == cur_uw && mv > cur_mv)
prefer_cur = 1;
}
/* Prefer higher power, except for tiebreaker */
if (uw > cur_uw || prefer_cur) {
ret = i;
cur_uw = uw;
cur_mv = mv;
}
}
if (selected_pdo)
*selected_pdo = src_caps[ret];
return ret;
}
void pd_extract_pdo_power(uint32_t pdo, uint32_t *ma, uint32_t *mv)
{
int max_ma, uw;
*mv = ((pdo >> 10) & 0x3FF) * 50;
if (*mv == 0) {
*ma = 0;
return;
}
if ((pdo & PDO_TYPE_MASK) == PDO_TYPE_BATTERY) {
uw = 250000 * (pdo & 0x3FF);
max_ma = 1000 * MIN(1000 * uw, PD_MAX_POWER_MW) / *mv;
} else {
max_ma = 10 * (pdo & 0x3FF);
max_ma = MIN(max_ma, PD_MAX_POWER_MW * 1000 / *mv);
}
*ma = MIN(max_ma, PD_MAX_CURRENT_MA);
}
void pd_build_request(uint32_t src_cap_cnt, const uint32_t * const src_caps,
int32_t vpd_vdo, uint32_t *rdo, uint32_t *ma,
uint32_t *mv, enum pd_request_type req_type,
uint32_t max_request_mv)
{
uint32_t pdo;
int pdo_index, flags = 0;
int uw;
int max_or_min_ma;
int max_or_min_mw;
int max_vbus;
int vpd_vbus_dcr;
int vpd_gnd_dcr;
if (req_type == PD_REQUEST_VSAFE5V) {
/* src cap 0 should be vSafe5V */
pdo_index = 0;
pdo = src_caps[0];
} else {
/* find pdo index for max voltage we can request */
pdo_index = pd_find_pdo_index(src_cap_cnt, src_caps,
max_request_mv, &pdo);
}
pd_extract_pdo_power(pdo, ma, mv);
/*
* Adjust VBUS current if CTVPD device was detected.
*/
if (vpd_vdo > 0) {
max_vbus = VPD_VDO_MAX_VBUS(vpd_vdo);
vpd_vbus_dcr = VPD_VDO_VBUS_IMP(vpd_vdo) << 1;
vpd_gnd_dcr = VPD_VDO_GND_IMP(vpd_vdo);
/*
* Valid max_vbus values:
* 00b - 20000 mV
* 01b - 30000 mV
* 10b - 40000 mV
* 11b - 50000 mV
*/
max_vbus = 20000 + max_vbus * 10000;
if (*mv > max_vbus)
*mv = max_vbus;
/*
* 5000 mA cable: 150 = 750000 / 50000
* 3000 mA cable: 250 = 750000 / 30000
*/
if (*ma > 3000)
*ma = 750000 / (150 + vpd_vbus_dcr + vpd_gnd_dcr);
else
*ma = 750000 / (250 + vpd_vbus_dcr + vpd_gnd_dcr);
}
uw = *ma * *mv;
/* Mismatch bit set if less power offered than the operating power */
if (uw < (1000 * PD_OPERATING_POWER_MW))
flags |= RDO_CAP_MISMATCH;
#ifdef CONFIG_USB_PD_GIVE_BACK
/* Tell source we are give back capable. */
flags |= RDO_GIVE_BACK;
/*
* BATTERY PDO: Inform the source that the sink will reduce
* power to this minimum level on receipt of a GotoMin Request.
*/
max_or_min_mw = PD_MIN_POWER_MW;
/*
* FIXED or VARIABLE PDO: Inform the source that the sink will
* reduce current to this minimum level on receipt of a GotoMin
* Request.
*/
max_or_min_ma = PD_MIN_CURRENT_MA;
#else
/*
* Can't give back, so set maximum current and power to
* operating level.
*/
max_or_min_ma = *ma;
max_or_min_mw = uw / 1000;
#endif
if ((pdo & PDO_TYPE_MASK) == PDO_TYPE_BATTERY) {
int mw = uw / 1000;
*rdo = RDO_BATT(pdo_index + 1, mw, max_or_min_mw, flags);
} else {
*rdo = RDO_FIXED(pdo_index + 1, *ma, max_or_min_ma, flags);
}
}
#endif
#ifdef CONFIG_USB_PD_ALT_MODE_DFP
void notify_sysjump_ready(volatile const task_id_t * const sysjump_task_waiting)
{
/*
* If event was set from pd_prepare_sysjump, wake the
* task waiting on us to complete.
*/
if (*sysjump_task_waiting != TASK_ID_INVALID)
task_set_event(*sysjump_task_waiting,
TASK_EVENT_SYSJUMP_READY, 0);
}
#endif
__attribute__((weak)) uint8_t board_get_usb_pd_port_count(void)
{
return CONFIG_USB_PD_PORT_MAX_COUNT;
}
enum pd_drp_next_states drp_auto_toggle_next_state(
uint64_t *drp_sink_time,
enum pd_power_role power_role,
enum pd_dual_role_states drp_state,
enum tcpc_cc_voltage_status cc1,
enum tcpc_cc_voltage_status cc2)
{
/* Set to appropriate port state */
if (cc_is_open(cc1, cc2)) {
/*
* If nothing is attached then use drp_state to determine next
* state. If DRP auto toggle is still on, then remain in the
* DRP_AUTO_TOGGLE state. Otherwise, stop dual role toggling
* and go to a disconnected state.
*/
switch (drp_state) {
case PD_DRP_TOGGLE_OFF:
return DRP_TC_DEFAULT;
case PD_DRP_FREEZE:
if (power_role == PD_ROLE_SINK)
return DRP_TC_UNATTACHED_SNK;
else
return DRP_TC_UNATTACHED_SRC;
case PD_DRP_FORCE_SINK:
return DRP_TC_UNATTACHED_SNK;
case PD_DRP_FORCE_SOURCE:
return DRP_TC_UNATTACHED_SRC;
case PD_DRP_TOGGLE_ON:
default:
return DRP_TC_DRP_AUTO_TOGGLE;
}
} else if ((cc_is_rp(cc1) || cc_is_rp(cc2)) &&
drp_state != PD_DRP_FORCE_SOURCE) {
/* SNK allowed unless ForceSRC */
return DRP_TC_UNATTACHED_SNK;
} else if (cc_is_at_least_one_rd(cc1, cc2) ||
cc_is_audio_acc(cc1, cc2)) {
/*
* SRC allowed unless ForceSNK or Toggle Off
*
* Ideally we wouldn't use auto-toggle when drp_state is
* TOGGLE_OFF/FORCE_SINK, but for some TCPCs, auto-toggle can't
* be prevented in low power mode. Try being a sink in case the
* connected device is dual-role (this ensures reliable charging
* from a hub, b/72007056). 100 ms is enough time for a
* dual-role partner to switch from sink to source. If the
* connected device is sink-only, then we will attempt
* TC_UNATTACHED_SNK twice (due to debounce time), then return
* to low power mode (and stay there). After 200 ms, reset
* ready for a new connection.
*/
if (drp_state == PD_DRP_TOGGLE_OFF ||
drp_state == PD_DRP_FORCE_SINK) {
if (get_time().val > *drp_sink_time + 200*MSEC)
*drp_sink_time = get_time().val;
if (get_time().val < *drp_sink_time + 100*MSEC)
return DRP_TC_UNATTACHED_SNK;
else
return DRP_TC_DRP_AUTO_TOGGLE;
} else {
return DRP_TC_UNATTACHED_SRC;
}
} else {
/* Anything else, keep toggling */
return DRP_TC_DRP_AUTO_TOGGLE;
}
}
#ifdef CONFIG_USBC_PPC
static void pd_send_hard_reset(int port)
{
task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_SEND_HARD_RESET, 0);
}
static uint32_t port_oc_reset_req;
static void re_enable_ports(void)
{
uint32_t ports = atomic_read_clear(&port_oc_reset_req);
while (ports) {
int port = __fls(ports);
ports &= ~BIT(port);
/*
* Let the board know that the overcurrent is
* over since we're going to attempt re-enabling
* the port.
*/
board_overcurrent_event(port, 0);
pd_send_hard_reset(port);
/*
* TODO(b/117854867): PD3.0 to send an alert message
* indicating OCP after explicit contract.
*/
}
}
DECLARE_DEFERRED(re_enable_ports);
void pd_handle_overcurrent(int port)
{
/* Keep track of the overcurrent events. */
CPRINTS("C%d: overcurrent!", port);
if (IS_ENABLED(CONFIG_USB_PD_LOGGING))
pd_log_event(PD_EVENT_PS_FAULT, PD_LOG_PORT_SIZE(port, 0),
PS_FAULT_OCP, NULL);
ppc_add_oc_event(port);
/* Let the board specific code know about the OC event. */
board_overcurrent_event(port, 1);
/* Wait 1s before trying to re-enable the port. */
atomic_or(&port_oc_reset_req, BIT(port));
hook_call_deferred(&re_enable_ports_data, SECOND);
}
void pd_handle_cc_overvoltage(int port)
{
pd_send_hard_reset(port);
}
#endif /* CONFIG_USBC_PPC */