summaryrefslogtreecommitdiff
path: root/baseboard/honeybuns/usbc_support.c
diff options
context:
space:
mode:
Diffstat (limited to 'baseboard/honeybuns/usbc_support.c')
-rw-r--r--baseboard/honeybuns/usbc_support.c340
1 files changed, 299 insertions, 41 deletions
diff --git a/baseboard/honeybuns/usbc_support.c b/baseboard/honeybuns/usbc_support.c
index 1ca7be6fe3..80915048ab 100644
--- a/baseboard/honeybuns/usbc_support.c
+++ b/baseboard/honeybuns/usbc_support.c
@@ -23,46 +23,24 @@
#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_SYSTEM, format, ## args)
-static void baseboard_ucpd_apply_rd(int port)
-{
- uint32_t cfgr1_reg;
- uint32_t moder_reg;
- uint32_t cr;
-
- /* Ensure that clock to UCPD is enabled */
- STM32_RCC_APB1ENR2 |= STM32_RCC_APB1ENR2_UPCD1EN;
-
- /* Make sure CC1/CC2 pins PB4/PB6 are set for analog mode */
- moder_reg = STM32_GPIO_MODER(GPIO_B);
- moder_reg |= 0x3300;
- STM32_GPIO_MODER(GPIO_B) = moder_reg;
- /*
- * CFGR1 must be written when UCPD peripheral is disabled. Note that
- * disabling ucpd causes the peripheral to quit any ongoing activity and
- * sets all ucpd registers back their default values.
- */
+enum usbc_states {
+ UNATTACHED_SNK,
+ ATTACH_WAIT_SNK,
+ ATTACHED_SNK,
+};
- cfgr1_reg = STM32_UCPD_CFGR1_PSC_CLK_VAL(UCPD_PSC_DIV - 1) |
- STM32_UCPD_CFGR1_TRANSWIN_VAL(UCPD_TRANSWIN_CNT - 1) |
- STM32_UCPD_CFGR1_IFRGAP_VAL(UCPD_IFRGAP_CNT - 1) |
- STM32_UCPD_CFGR1_HBITCLKD_VAL(UCPD_HBIT_DIV - 1);
- STM32_UCPD_CFGR1(port) = cfgr1_reg;
+/* Variables used to manage the simple usbc state machine */
+static int usbc_port;
+static int usbc_state;
+static int usbc_vbus;
+static enum tcpc_cc_voltage_status cc1_v;
+static enum tcpc_cc_voltage_status cc2_v;
- /* Enable ucpd */
- STM32_UCPD_CFGR1(port) |= STM32_UCPD_CFGR1_UCPDEN;
-
- /* Apply Rd to both CC lines */
- cr = STM32_UCPD_CR(port);
- cr |= STM32_UCPD_CR_ANAMODE | STM32_UCPD_CR_CCENABLE_MASK;
- STM32_UCPD_CR(port) = cr;
-
- /*
- * After exiting reset, stm32gx will have dead battery mode enabled by
- * default which connects Rd to CC1/CC2. This should be disabled when EC
- * is powered up.
- */
- STM32_PWR_CR3 |= STM32_PWR_CR3_UCPD1_DBDIS;
-}
+__maybe_unused static __const_data const char * const usbc_state_names[] = {
+ [UNATTACHED_SNK] = "Unattached.SNK",
+ [ATTACH_WAIT_SNK] = "AttachWait.SNK",
+ [ATTACHED_SNK] = "Attached.SNK",
+};
static int read_reg(uint8_t port, int reg, int *regval)
{
@@ -125,6 +103,183 @@ static int baseboard_ppc_enable_sink_path(int port)
return EC_SUCCESS;
}
+static void baseboard_ucpd_apply_rd(int port)
+{
+ uint32_t cfgr1_reg;
+ uint32_t moder_reg;
+ uint32_t cr;
+
+ /* Ensure that clock to UCPD is enabled */
+ STM32_RCC_APB1ENR2 |= STM32_RCC_APB1ENR2_UPCD1EN;
+
+ /* Make sure CC1/CC2 pins PB4/PB6 are set for analog mode */
+ moder_reg = STM32_GPIO_MODER(GPIO_B);
+ moder_reg |= 0x3300;
+ STM32_GPIO_MODER(GPIO_B) = moder_reg;
+ /*
+ * CFGR1 must be written when UCPD peripheral is disabled. Note that
+ * disabling ucpd causes the peripheral to quit any ongoing activity and
+ * sets all ucpd registers back their default values.
+ */
+
+ cfgr1_reg = STM32_UCPD_CFGR1_PSC_CLK_VAL(UCPD_PSC_DIV - 1) |
+ STM32_UCPD_CFGR1_TRANSWIN_VAL(UCPD_TRANSWIN_CNT - 1) |
+ STM32_UCPD_CFGR1_IFRGAP_VAL(UCPD_IFRGAP_CNT - 1) |
+ STM32_UCPD_CFGR1_HBITCLKD_VAL(UCPD_HBIT_DIV - 1);
+ STM32_UCPD_CFGR1(port) = cfgr1_reg;
+
+ /* Enable ucpd */
+ STM32_UCPD_CFGR1(port) |= STM32_UCPD_CFGR1_UCPDEN;
+
+ /* Apply Rd to both CC lines */
+ cr = STM32_UCPD_CR(port);
+ cr |= STM32_UCPD_CR_ANAMODE | STM32_UCPD_CR_CCENABLE_MASK;
+ STM32_UCPD_CR(port) = cr;
+
+ /*
+ * After exiting reset, stm32gx will have dead battery mode enabled by
+ * default which connects Rd to CC1/CC2. This should be disabled when EC
+ * is powered up.
+ */
+ STM32_PWR_CR3 |= STM32_PWR_CR3_UCPD1_DBDIS;
+}
+
+
+static void baseboard_ucpd_get_cc(int port, enum tcpc_cc_voltage_status *cc1,
+ enum tcpc_cc_voltage_status *cc2)
+{
+ int vstate_cc1;
+ int vstate_cc2;
+ int anamode;
+ uint32_t sr;
+
+ /*
+ * cc_voltage_status is determined from vstate_cc bit field in the
+ * status register. The meaning of the value vstate_cc depends on
+ * current value of ANAMODE (src/snk).
+ *
+ * vstate_cc maps directly to cc_state from tcpci spec when ANAMODE = 1,
+ * but needs to be modified slightly for case ANAMODE = 0.
+ *
+ * If presenting Rp (source), then need to to a circular shift of
+ * vstate_ccx value:
+ * vstate_cc | cc_state
+ * ------------------
+ * 0 -> 1
+ * 1 -> 2
+ * 2 -> 0
+ */
+
+ /* Get vstate_ccx values and power role */
+ sr = STM32_UCPD_SR(port);
+ /* Get Rp or Rd active */
+ anamode = !!(STM32_UCPD_CR(port) & STM32_UCPD_CR_ANAMODE);
+ vstate_cc1 = (sr & STM32_UCPD_SR_VSTATE_CC1_MASK) >>
+ STM32_UCPD_SR_VSTATE_CC1_SHIFT;
+ vstate_cc2 = (sr & STM32_UCPD_SR_VSTATE_CC2_MASK) >>
+ STM32_UCPD_SR_VSTATE_CC2_SHIFT;
+
+ /* Do circular shift if port == source */
+ if (anamode) {
+ if (vstate_cc1 != STM32_UCPD_SR_VSTATE_RA)
+ vstate_cc1 += 4;
+ if (vstate_cc2 != STM32_UCPD_SR_VSTATE_RA)
+ vstate_cc2 += 4;
+ } else {
+ if (vstate_cc1 != STM32_UCPD_SR_VSTATE_OPEN)
+ vstate_cc1 = (vstate_cc1 + 1) % 3;
+ if (vstate_cc2 != STM32_UCPD_SR_VSTATE_OPEN)
+ vstate_cc2 = (vstate_cc2 + 1) % 3;
+ }
+
+ *cc1 = vstate_cc1;
+ *cc2 = vstate_cc2;
+}
+
+static int baseboard_rp_is_present(enum tcpc_cc_voltage_status cc1,
+ enum tcpc_cc_voltage_status cc2)
+{
+ return (cc1 >= TYPEC_CC_VOLT_RP_DEF || cc2 >= TYPEC_CC_VOLT_RP_DEF);
+}
+
+static void baseboard_usbc_check_connect(void);
+DECLARE_DEFERRED(baseboard_usbc_check_connect);
+
+static void baseboard_usbc_check_connect(void)
+{
+ enum tcpc_cc_voltage_status cc1;
+ enum tcpc_cc_voltage_status cc2;
+ int ppc_reg;
+ enum usbc_states enter_state = usbc_state;
+
+ /*
+ * In RO, the only usbc related requirement is to enable the stm32g4
+ * USB-EP to be enumerated by the host attached to C0. To prevent D+
+ * being pulled high prior to VBUS presence, the EC uses GPIO_BPWR_DET
+ * to signal the USB hub that VBUS is present. Therefore, we need a
+ * simple usbc state machine to detect an attach (Rp and VBUS) event so
+ * this GPIO signal is properly controlled in RO.
+ *
+ * Note that RO only runs until the RWSIG timer expires and jumps to RW,
+ * and in RW, the full usb-pd stack is initialized and run.
+ */
+
+ /* Get current CC voltage levels */
+ baseboard_ucpd_get_cc(usbc_port, &cc1, &cc2);
+ /* Update VBUS state */
+ if (!read_reg(usbc_port, SN5S330_INT_STATUS_REG3, &ppc_reg))
+ usbc_vbus = ppc_reg & SN5S330_VBUS_GOOD;
+
+ switch (usbc_state) {
+ case UNATTACHED_SNK:
+ /*
+ * Require either CC1 or CC2 to have a valid Rp CC voltage level
+ * to advance to ATTACH_WAIT_SNK.
+ */
+ if (baseboard_rp_is_present(cc1, cc2))
+ usbc_state = ATTACH_WAIT_SNK;
+ break;
+ case ATTACH_WAIT_SNK:
+ /*
+ * This state handles debounce by ensuring the CC voltages are
+ * the same between two state machine iterations. If this
+ * condition is met, and VBUS is present, then advance to
+ * ATTACHED_SNK and set GPIO_BPWR_DET.
+ *
+ * If Rp voltage is no longer detected, then return to
+ * UNATTACHED_SNK.
+ */
+ if (usbc_vbus && cc1 == cc1_v && cc2 == cc2_v) {
+ usbc_state = ATTACHED_SNK;
+ gpio_set_level(GPIO_BPWR_DET, 1);
+ } else if (!baseboard_rp_is_present(cc1, cc2)) {
+ usbc_state = UNATTACHED_SNK;
+ }
+ break;
+ case ATTACHED_SNK:
+ /*
+ * In this state, only checking for VBUS going away to indicate
+ * a detach event and inform the USB hub via GPIO_BPWR_DET.
+ */
+ if (!usbc_vbus) {
+ usbc_state = UNATTACHED_SNK;
+ gpio_set_level(GPIO_BPWR_DET, 0);
+ }
+ break;
+ }
+
+ /* Save CC voltage for debounce check */
+ cc1_v = cc1;
+ cc2_v = cc2;
+
+ if (enter_state != usbc_state)
+ CPRINTS("%s: cc1 = %d, cc2 = %d vbus = %d",
+ usbc_state_names[usbc_state], cc1, cc2, usbc_vbus);
+
+ hook_call_deferred(&baseboard_usbc_check_connect_data,
+ PD_T_TRY_CC_DEBOUNCE);
+}
+
int baseboard_usbc_init(int port)
{
int rv;
@@ -133,11 +288,21 @@ int baseboard_usbc_init(int port)
baseboard_ucpd_apply_rd(port);
/* Initialize ppc to enable sink path */
rv = baseboard_ppc_enable_sink_path(port);
+ if (rv)
+ CPRINTS("ppc init failed!");
+ /* Save host port value */
+ usbc_port = port;
+ /* Start RO usbc attach state machine */
+ gpio_set_level(GPIO_BPWR_DET, 0);
+ /* Start simple usbc state machine */
+ baseboard_usbc_check_connect();
return rv;
}
#if defined(GPIO_USBC_UF_ATTACHED_SRC) && defined(SECTION_IS_RW)
+static int ppc_ocp_count;
+
static void baseboard_usb3_manage_vbus(void)
{
int level = gpio_get_level(GPIO_USBC_UF_ATTACHED_SRC);
@@ -149,6 +314,21 @@ static void baseboard_usb3_manage_vbus(void)
*/
ppc_vbus_source_enable(USB_PD_PORT_USB3, level);
CPRINTS("C2: State = %s", level ? "Attached.SRC " : "Unattached.SRC");
+
+ /* Reset OCP event counter for detach */
+ if (!level) {
+ ppc_ocp_count = 0;
+
+#ifdef GPIO_USB_HUB_OCP_NOTIFY
+ /*
+ * In the case of an OCP event on this port, the usb hub should be
+ * notified via a GPIO signal. Following, an OCP, the attached.src state
+ * for the usb3 only port is checked again. If it's attached, then make
+ * sure the OCP notify signal is reset.
+ */
+ gpio_set_level(GPIO_USB_HUB_OCP_NOTIFY, 1);
+#endif
+ }
}
DECLARE_DEFERRED(baseboard_usb3_manage_vbus);
@@ -157,6 +337,21 @@ void baseboard_usb3_check_state(void)
hook_call_deferred(&baseboard_usb3_manage_vbus_data, 0);
}
+void baseboard_usbc_usb3_enable_interrupts(int enable)
+{
+ if (enable) {
+ /* Enable VBUS control interrupt for C2 */
+ gpio_enable_interrupt(GPIO_USBC_UF_ATTACHED_SRC);
+ /* Enable PPC interrupt */
+ gpio_enable_interrupt(GPIO_USBC_UF_PPC_INT_ODL);
+ } else {
+ /* Disable VBUS control interrupt for C2 */
+ gpio_disable_interrupt(GPIO_USBC_UF_ATTACHED_SRC);
+ /* Disable PPC interrupt */
+ gpio_disable_interrupt(GPIO_USBC_UF_PPC_INT_ODL);
+ }
+}
+
int baseboard_config_usbc_usb3_ppc(void)
{
int rv;
@@ -171,14 +366,77 @@ int baseboard_config_usbc_usb3_ppc(void)
/* Need to set current limit to 3A to match advertised value */
ppc_set_vbus_source_current_limit(USB_PD_PORT_USB3, TYPEC_RP_3A0);
+ /* Reset OCP event counter */
+ ppc_ocp_count = 0;
/* Check state at init time */
baseboard_usb3_manage_vbus();
- /* Enable VBUS control interrupt for C2 */
- gpio_enable_interrupt(GPIO_USBC_UF_ATTACHED_SRC);
+ /* Enable attached.src and PPC interrupts */
+ baseboard_usbc_usb3_enable_interrupts(1);
return EC_SUCCESS;
}
-#endif
+
+static void baseboard_usbc_usb3_handle_interrupt(void)
+{
+ int port = USB_PD_PORT_USB3;
+
+ /*
+ * SN5S330's /INT pin is level, so process interrupts until it
+ * deasserts if the chip has a dedicated interrupt pin.
+ */
+ while (gpio_get_level(GPIO_USBC_UF_PPC_INT_ODL) == 0) {
+ int rise = 0;
+ int fall = 0;
+
+ read_reg(port, SN5S330_INT_TRIP_RISE_REG1, &rise);
+ read_reg(port, SN5S330_INT_TRIP_FALL_REG1, &fall);
+
+ /* Notify the system about the overcurrent event. */
+ if (rise & SN5S330_ILIM_PP1_MASK) {
+ CPRINTS("usb3_ppc: VBUS OC!");
+ gpio_set_level(GPIO_USB_HUB_OCP_NOTIFY, 0);
+ if (++ppc_ocp_count < 5)
+ hook_call_deferred(&baseboard_usb3_manage_vbus_data,
+ USB_HUB_OCP_RESET_MSEC);
+ else
+ CPRINTS("usb3_ppc: VBUS OC limit reached!");
+ }
+
+ /* Clear the interrupt sources. */
+ write_reg(port, SN5S330_INT_TRIP_RISE_REG1, rise);
+ write_reg(port, SN5S330_INT_TRIP_FALL_REG1, fall);
+
+ read_reg(port, SN5S330_INT_TRIP_RISE_REG2, &rise);
+ read_reg(port, SN5S330_INT_TRIP_FALL_REG2, &fall);
+
+ /*
+ * VCONN may be latched off due to an overcurrent. Indicate
+ * when the VCONN overcurrent happens.
+ */
+ if (rise & SN5S330_VCONN_ILIM)
+ CPRINTS("usb3_ppc: VCONN OC!");
+
+ /*
+ * CC overvoltage event. There is not action to take here, but
+ * log the event.
+ */
+ if (rise & SN5S330_CC1_CON || rise & SN5S330_CC2_CON)
+ CPRINTS("usb3_ppc: CC OV!");
+
+ /* Clear the interrupt sources. */
+ write_reg(port, SN5S330_INT_TRIP_RISE_REG2, rise);
+ write_reg(port, SN5S330_INT_TRIP_FALL_REG2, fall);
+
+ }
+}
+DECLARE_DEFERRED(baseboard_usbc_usb3_handle_interrupt);
+
+void baseboard_usbc_usb3_irq(void)
+{
+ hook_call_deferred(&baseboard_usbc_usb3_handle_interrupt_data, 0);
+}
+
+#endif /* defined(GPIO_USBC_UF_ATTACHED_SRC) && defined(SECTION_IS_RW) */