/* Copyright 2017 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 "atomic.h" #include "charge_manager.h" #include "common.h" #include "console.h" #include "gpio.h" #include "hooks.h" #include "host_command.h" #include "i2c.h" #include "registers.h" #include "system.h" #include "task.h" #include "tcpm.h" #include "timer.h" #include "util.h" #include "usb_mux.h" #include "usb_pd.h" #include "usb_pd_config.h" #include "usb_pd_tcpm.h" #define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) #define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) #define DUT_PDO_FIXED_FLAGS (PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP |\ PDO_FIXED_COMM_CAP) #define CHG_PDO_FIXED_FLAGS (PDO_FIXED_DATA_SWAP) #define VBUS_UNCHANGED(curr, pend, new) (curr == new && pend == new) /* * Dynamic PDO that reflects capabilities present on the CHG port. Allow for * multiple entries so that we can offer greater than 5V charging. The 1st * entry will be fixed 5V, but its current value may change based on the CHG * port vbus info. Subsequent entries are used for when offering vbus greater * than 5V. */ static const uint16_t pd_src_voltages_mv[] = { 5000, 9000, 12000, 15000, 20000, }; static uint32_t pd_src_chg_pdo[ARRAY_SIZE(pd_src_voltages_mv)]; static uint8_t chg_pdo_cnt; static const uint32_t pd_src_host_pdo[] = { PDO_FIXED(5000, 500, DUT_PDO_FIXED_FLAGS), }; const uint32_t pd_snk_pdo[] = { PDO_FIXED(5000, 500, CHG_PDO_FIXED_FLAGS), PDO_BATT(4750, 21000, 15000), PDO_VAR(4750, 21000, 3000), }; const int pd_snk_pdo_cnt = ARRAY_SIZE(pd_snk_pdo); struct vbus_prop { int mv; int ma; }; static struct vbus_prop vbus[CONFIG_USB_PD_PORT_COUNT]; static int active_charge_port = CHARGE_PORT_NONE; static enum charge_supplier active_charge_supplier; static uint8_t vbus_rp = TYPEC_RP_RESERVED; static int disable_dts_mode; /* Voltage thresholds for no connect in DTS mode */ static int pd_src_vnc_dts[TYPEC_RP_RESERVED][2] = { {PD_SRC_3_0_VNC_MV, PD_SRC_1_5_VNC_MV}, {PD_SRC_1_5_VNC_MV, PD_SRC_DEF_VNC_MV}, {PD_SRC_3_0_VNC_MV, PD_SRC_DEF_VNC_MV}, }; /* Voltage thresholds for Ra attach in DTS mode */ static int pd_src_rd_threshold_dts[TYPEC_RP_RESERVED][2] = { {PD_SRC_3_0_RD_THRESH_MV, PD_SRC_1_5_RD_THRESH_MV}, {PD_SRC_1_5_RD_THRESH_MV, PD_SRC_DEF_RD_THRESH_MV}, {PD_SRC_3_0_RD_THRESH_MV, PD_SRC_DEF_RD_THRESH_MV}, }; /* Voltage thresholds for no connect in normal SRC mode */ static int pd_src_vnc[TYPEC_RP_RESERVED] = { PD_SRC_DEF_VNC_MV, PD_SRC_1_5_VNC_MV, PD_SRC_3_0_VNC_MV, }; /* Voltage thresholds for Ra attach in normal SRC mode */ static int pd_src_rd_threshold[TYPEC_RP_RESERVED] = { PD_SRC_DEF_RD_THRESH_MV, PD_SRC_1_5_RD_THRESH_MV, PD_SRC_3_0_RD_THRESH_MV, }; /* * Set the USB PD max voltage to value appropriate for the board version. * The red/blue versions of servo_v4 have an ESD between VBUS and CC1/CC2 * that has a breakdown voltage of 11V. */ #define MAX_MV_RED_BLUE 9000 static uint32_t max_supported_voltage(void) { return board_get_version() >= BOARD_VERSION_BLACK ? PD_MAX_VOLTAGE_MV : MAX_MV_RED_BLUE; } static int charge_port_is_active(void) { return active_charge_port == CHG && vbus[CHG].mv > 0; } static void board_manage_dut_port(void) { int rp; /* * This function is called by the CHG port whenever there has been a * change in its vbus voltage or current. That change may necessitate * that the DUT port present a different Rp value or renogiate its PD * contract if it is connected. */ /* Assume the default value of Rp */ rp = TYPEC_RP_USB; if (vbus[CHG].mv == PD_MIN_MV && charge_port_is_active()) { /* Only advertise higher current via Rp if vbus == 5V */ if (vbus[CHG].ma >= 3000) /* CHG port is connected and DUt can advertise 3A */ rp = TYPEC_RP_3A0; else if (vbus[CHG].ma >= 1500) rp = TYPEC_RP_1A5; } /* Check if Rp setting needs to change from current value */ if (vbus_rp != rp) /* Present new Rp value */ tcpm_select_rp_value(DUT, rp); /* * Update PD contract to reflect new available CHG * voltage/current values. */ pd_update_contract(DUT); } static void update_ports(void) { int pdo_index, src_index, snk_index, i; uint32_t pdo, max_ma, max_mv; /* * CHG Vbus has changed states, update PDO that reflects CHG port * state */ if (!charge_port_is_active()) { /* CHG Vbus has dropped, so always source DUT Vbus from host */ chg_pdo_cnt = 0; } else { /* Advertise the 'best' PDOs at various discrete voltages */ if (active_charge_supplier == CHARGE_SUPPLIER_PD) { src_index = 0; snk_index = -1; for (i = 0; i < ARRAY_SIZE(pd_src_voltages_mv); ++i) { /* Adhere to board voltage limits */ if (pd_src_voltages_mv[i] > max_supported_voltage()) break; /* Find the 'best' PDO <= voltage */ pdo_index = pd_find_pdo_index( CHG, pd_src_voltages_mv[i], &pdo); /* Don't duplicate PDOs */ if (pdo_index == snk_index) continue; /* Skip battery / variable PDOs */ if ((pdo & PDO_TYPE_MASK) != PDO_TYPE_FIXED) continue; snk_index = pdo_index; pd_extract_pdo_power(pdo, &max_ma, &max_mv); pd_src_chg_pdo[src_index++] = PDO_FIXED_VOLT(max_mv) | PDO_FIXED_CURR(max_ma) | DUT_PDO_FIXED_FLAGS | PDO_FIXED_EXTERNAL; } chg_pdo_cnt = src_index; } else { /* 5V PDO */ pd_src_chg_pdo[0] = PDO_FIXED_VOLT(PD_MIN_MV) | PDO_FIXED_CURR(vbus[CHG].ma) | DUT_PDO_FIXED_FLAGS | PDO_FIXED_EXTERNAL; chg_pdo_cnt = 1; } } /* Call DUT port manager to update Rp and possible PD contract */ board_manage_dut_port(); /* * Supply VBUS from the CHG port if available. This may glitch VBUS * on the DUT during switchover. */ gpio_set_level(GPIO_HOST_OR_CHG_CTL, chg_pdo_cnt > 0); } int board_set_active_charge_port(int charge_port) { if (charge_port == DUT) return -1; active_charge_port = charge_port; update_ports(); if (!charge_port_is_active()) /* Don't negotiate > 5V, except in lockstep with DUT */ pd_set_external_voltage_limit(CHG, PD_MIN_MV); return 0; } void board_set_charge_limit(int port, int supplier, int charge_ma, int max_ma, int charge_mv) { if (port != CHG) return; active_charge_supplier = supplier; /* Update the voltage/current values for CHG port */ vbus[CHG].ma = charge_ma; vbus[CHG].mv = charge_mv; update_ports(); } int pd_tcpc_cc_nc(int port, int cc_volt, int cc_sel) { int rp_index; int nc; /* Can never be called from CHG port as it's sink only */ if (port == CHG) return 0; rp_index = vbus_rp; /* * If rp_index > 2, then always return not connected. This case should * only happen when all Rp GPIO controls are tri-stated. */ if (rp_index >= TYPEC_RP_RESERVED) return 1; /* Select the correct voltage threshold for current Rp and DTS mode */ if (disable_dts_mode) nc = cc_volt >= pd_src_vnc[rp_index]; else nc = cc_volt >= pd_src_vnc_dts[rp_index][cc_sel]; return nc; } int pd_tcpc_cc_ra(int port, int cc_volt, int cc_sel) { int rp_index; int ra; /* Can never be called from CHG port as it's sink only */ if (port == CHG) return 0; rp_index = vbus_rp; /* * If rp_index > 2, then can't be Ra. This case should * only happen when all Rp GPIO controls are tri-stated. */ if (rp_index >= TYPEC_RP_RESERVED) return 0; /* Select the correct voltage threshold for current Rp and DTS mode */ if (disable_dts_mode) ra = cc_volt < pd_src_rd_threshold[rp_index]; else ra = cc_volt < pd_src_rd_threshold_dts[rp_index][cc_sel]; return ra; } static int board_set_rp(int rp) { if (disable_dts_mode) { /* * DTS mode is disabled, so only present the requested Rp value * on CC1 and leave all Rp/Rd resistors on CC2 disconnected. */ switch (rp) { case TYPEC_RP_USB: gpio_set_flags(GPIO_USB_DUT_CC1_RPUSB, GPIO_OUT_HIGH); break; case TYPEC_RP_1A5: gpio_set_flags(GPIO_USB_DUT_CC1_RP1A5, GPIO_OUT_HIGH); break; case TYPEC_RP_3A0: gpio_set_flags(GPIO_USB_DUT_CC1_RP3A0, GPIO_OUT_HIGH); break; case TYPEC_RP_RESERVED: /* * This case can be used to force a detach event since * all values are set to inputs above. Nothing else to * set. */ break; default: return EC_ERROR_INVAL; } } else { /* DTS mode is enabled. The rp parameter is used to select the * Type C current limit to advertise. The combinations of Rp on * each CC line is shown in the table below. * * CC values for Debug sources (DTS) * * Source type Mode of Operation CC1 CC2 * --------------------------------------------- * DTS Default USB Power Rp3A0 Rp1A5 * DTS USB-C @ 1.5 A Rp1A5 RpUSB * DTS USB-C @ 3 A Rp3A0 RpUSB */ switch (rp) { case TYPEC_RP_USB: gpio_set_flags(GPIO_USB_DUT_CC1_RP3A0, GPIO_OUT_HIGH); gpio_set_flags(GPIO_USB_DUT_CC2_RP1A5, GPIO_OUT_HIGH); break; case TYPEC_RP_1A5: gpio_set_flags(GPIO_USB_DUT_CC1_RP1A5, GPIO_OUT_HIGH); gpio_set_flags(GPIO_USB_DUT_CC2_RPUSB, GPIO_OUT_HIGH); break; case TYPEC_RP_3A0: gpio_set_flags(GPIO_USB_DUT_CC1_RP3A0, GPIO_OUT_HIGH); gpio_set_flags(GPIO_USB_DUT_CC2_RPUSB, GPIO_OUT_HIGH); break; case TYPEC_RP_RESERVED: /* * This case can be used to force a detach event since * all values are set to inputs above. Nothing else to * set. */ break; default: return EC_ERROR_INVAL; } } /* Save new Rp value for DUT port */ vbus_rp = rp; return EC_SUCCESS; } int pd_set_rp_rd(int port, int cc_pull, int rp_value) { int rv = EC_SUCCESS; /* By default disconnect all Rp/Rd resistors from both CC lines */ /* Set Rd for CC1/CC2 to High-Z. */ gpio_set_flags(GPIO_USB_DUT_CC1_RD, GPIO_INPUT); gpio_set_flags(GPIO_USB_DUT_CC2_RD, GPIO_INPUT); /* Set Rp for CC1/CC2 to High-Z. */ gpio_set_flags(GPIO_USB_DUT_CC1_RP3A0, GPIO_INPUT); gpio_set_flags(GPIO_USB_DUT_CC2_RP3A0, GPIO_INPUT); gpio_set_flags(GPIO_USB_DUT_CC1_RP1A5, GPIO_INPUT); gpio_set_flags(GPIO_USB_DUT_CC2_RP1A5, GPIO_INPUT); gpio_set_flags(GPIO_USB_DUT_CC1_RPUSB, GPIO_INPUT); gpio_set_flags(GPIO_USB_DUT_CC2_RPUSB, GPIO_INPUT); /* Set TX Hi-Z */ gpio_set_flags(GPIO_USB_DUT_CC1_TX_DATA, GPIO_INPUT); gpio_set_flags(GPIO_USB_DUT_CC2_TX_DATA, GPIO_INPUT); if (cc_pull == TYPEC_CC_RP) { rv = board_set_rp(rp_value); } else if (cc_pull == TYPEC_CC_RD) { /* * The DUT port uses a captive cable. If can present Rd on both * CC1 and CC2. If DTS mode is enabled, then present Rd on both * CC lines. However, if DTS mode is disabled only present Rd on * CC1. */ gpio_set_flags(GPIO_USB_DUT_CC1_RD, GPIO_OUT_LOW); if (!disable_dts_mode) gpio_set_flags(GPIO_USB_DUT_CC2_RD, GPIO_OUT_LOW); } return rv; } int board_select_rp_value(int port, int rp) { return pd_set_rp_rd(port, TYPEC_CC_RP, rp); } int charge_manager_get_source_pdo(const uint32_t **src_pdo, const int port) { int pdo_cnt; /* * If CHG is providing VBUS, then advertise what's available on the CHG * port, otherwise used the fixed value that matches host capabilities. */ if (charge_port_is_active()) { *src_pdo = pd_src_chg_pdo; pdo_cnt = chg_pdo_cnt; } else { *src_pdo = pd_src_host_pdo; pdo_cnt = ARRAY_SIZE(pd_src_host_pdo); } return pdo_cnt; } int pd_is_valid_input_voltage(int mv) { /* Any voltage less than the max is allowed */ return 1; } void pd_transition_voltage(int idx) { timestamp_t deadline; uint32_t ma, mv; pd_extract_pdo_power(pd_src_chg_pdo[idx - 1], &ma, &mv); /* Is this a transition to a new voltage? */ if (charge_port_is_active() && vbus[CHG].mv != mv) { /* * Alter voltage limit on charge port, this should cause * the port to select the desired PDO. */ pd_set_external_voltage_limit(CHG, mv); /* Wait for CHG transition */ deadline.val = get_time().val + PD_T_PS_TRANSITION; CPRINTS("Waiting for CHG port transition"); while (charge_port_is_active() && vbus[CHG].mv != mv && get_time().val < deadline.val) msleep(10); if (vbus[CHG].mv != mv) { CPRINTS("Missed CHG transition, resetting DUT"); pd_power_supply_reset(DUT); return; } CPRINTS("CHG transitioned"); } vbus[DUT].mv = vbus[CHG].mv; vbus[DUT].ma = vbus[CHG].ma; } int pd_set_power_supply_ready(int port) { /* Port 0 can never provide vbus. */ if (port == CHG) return EC_ERROR_INVAL; /* Enable VBUS */ gpio_set_level(GPIO_DUT_CHG_EN, 1); if (charge_port_is_active()) { if (vbus[CHG].mv != PD_MIN_MV) CPRINTS("ERROR, CHG port voltage %d != PD_MIN_MV", vbus[CHG].mv); vbus[DUT].mv = vbus[CHG].mv; vbus[DUT].ma = vbus[CHG].mv; } else { /* Host vbus is always 5V/500mA */ vbus[DUT].mv = PD_MIN_MV; vbus[DUT].ma = 500; } /* Enable CCD, if debuggable TS attached */ if (pd_ts_dts_plugged(DUT)) ccd_enable(1); return EC_SUCCESS; /* we are ready */ } void pd_power_supply_reset(int port) { /* Port 0 can never provide vbus. */ if (port == CHG) return; ccd_enable(0); /* Disable VBUS */ gpio_set_level(GPIO_DUT_CHG_EN, 0); /* Host vbus is always 5V/500mA */ vbus[DUT].mv = 0; vbus[DUT].ma = 0; /* DUT is lost, back to 5V limit on CHG */ pd_set_external_voltage_limit(CHG, PD_MIN_MV); } int pd_snk_is_vbus_provided(int port) { return gpio_get_level(port ? GPIO_USB_DET_PP_DUT : GPIO_USB_DET_PP_CHG); } int pd_board_checks(void) { return EC_SUCCESS; } int pd_check_power_swap(int port) { /* * When only host VBUS is available, then servo_v4 is not setting * PDO_FIXED_EXTERNAL in the src_pdo sent to the DUT. When this bit is * not set, the DUT will always attempt to swap its power role to * SRC. Let servo_v4 have more control over its power role by always * rejecting power swap requests from the DUT. */ return 0; } int pd_check_data_swap(int port, int data_role) { /* Servo can allow data role swaps */ return 1; } void pd_execute_data_swap(int port, int data_role) { /* Should we do something here? */ } void pd_check_pr_role(int port, int pr_role, int flags) { /* * TODO(crosbug.com/p/60792): CHG port can't do a power swap as it's SNK * only. DUT port should be able to support a power role swap, but VBUS * will need to be present. For now, don't allow swaps on either port. */ } void pd_check_dr_role(int port, int dr_role, int flags) { if (port == CHG) return; /* If DFP, try to switch to UFP */ if ((flags & PD_FLAGS_PARTNER_DR_DATA) && dr_role == PD_ROLE_DFP && !disable_dts_mode) pd_request_data_swap(port); } /* ----------------- Vendor Defined Messages ------------------ */ const struct svdm_response svdm_rsp = { .identity = NULL, .svids = NULL, .modes = NULL, }; int pd_custom_vdm(int port, int cnt, uint32_t *payload, uint32_t **rpayload) { int cmd = PD_VDO_CMD(payload[0]); /* make sure we have some payload */ if (cnt == 0) return 0; switch (cmd) { case VDO_CMD_VERSION: /* guarantee last byte of payload is null character */ *(payload + cnt - 1) = 0; CPRINTF("ver: %s\n", (char *)(payload+1)); break; case VDO_CMD_CURRENT: CPRINTF("Current: %dmA\n", payload[1]); break; } return 0; } const struct svdm_amode_fx supported_modes[] = {}; const int supported_modes_cnt = ARRAY_SIZE(supported_modes); static int command_dts(int argc, char **argv) { int disable_dts_new; int val; if (argc < 2) { /* Get current CCD status */ ccprintf("dts mode: %s\n", disable_dts_mode ? "off" : "on"); return EC_SUCCESS; } if (!parse_bool(argv[1], &val)) return EC_ERROR_PARAM2; disable_dts_new = val ^ 1; if (disable_dts_new != disable_dts_mode) { /* Force detach */ pd_power_supply_reset(DUT); /* Always set to 0 here so both CC lines are changed */ disable_dts_mode = 0; /* Remove Rp/Rd on both CC lines */ board_select_rp_value(DUT, TYPEC_RP_RESERVED); /* Accept new disable_dts value */ disable_dts_mode = disable_dts_new; /* Some time for DUT to detach */ msleep(100); /* Present RP_USB on CC1 and CC2 based on disable_dts_mode */ board_select_rp_value(DUT, TYPEC_RP_USB); ccprintf("dts mode: %s\n", disable_dts_mode ? "off" : "on"); } return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(dts, command_dts, "off|on", "Servo_v4 DTS mode on/off");