diff --git a/drivers/usb_c/tcpc/ucpd_stm32.c b/drivers/usb_c/tcpc/ucpd_stm32.c index 4c037421b48..1469861add5 100644 --- a/drivers/usb_c/tcpc/ucpd_stm32.c +++ b/drivers/usb_c/tcpc/ucpd_stm32.c @@ -156,10 +156,15 @@ static uint32_t ucpd_get_cc_enable_mask(const struct device *dev) const struct tcpc_config *const config = dev->config; uint32_t mask = UCPD_CR_CCENABLE_Msk; + /* + * When VCONN is enabled, it is supplied on the CC line that's + * not being used for Power Delivery messages. + */ if (data->ucpd_vconn_enable) { uint32_t cr = LL_UCPD_ReadReg(config->ucpd_port, CR); int pol = (cr & UCPD_CR_PHYCCSEL); + /* Dissable CC line that's used for VCONN */ mask &= ~BIT(UCPD_CR_CCENABLE_Pos + !pol); } @@ -281,12 +286,52 @@ static int ucpd_set_vconn(const struct device *dev, bool enable) update_stm32g0x_cc_line(config->ucpd_port); #endif + /* Get CC line that VCONN is active on */ + data->ucpd_vconn_cc = (cr & UCPD_CR_CCENABLE_0) ? + TC_POLARITY_CC2 : TC_POLARITY_CC1; + /* Call user supplied callback to set vconn */ - ret = data->vconn_cb(dev, enable); + ret = data->vconn_cb(dev, data->ucpd_vconn_cc, enable); return ret; } +/** + * @brief Discharge VCONN + * + * @retval 0 on success + * @retval -EIO on failure + * @retval -ENOTSUP if not supported + */ +static int ucpd_vconn_discharge(const struct device *dev, bool enable) +{ + struct tcpc_data *data = dev->data; + const struct tcpc_config *const config = dev->config; + + /* VCONN should not be discharged while it's enabled */ + if (data->ucpd_vconn_enable) { + return -EIO; + } + + if (data->vconn_discharge_cb) { + /* Use DPM supplied VCONN Discharge */ + return data->vconn_discharge_cb(dev, data->ucpd_vconn_cc, enable); + } + + /* Use TCPC VCONN Discharge */ + if (enable) { + LL_UCPD_VconnDischargeEnable(config->ucpd_port); + } else { + LL_UCPD_VconnDischargeDisable(config->ucpd_port); + } + +#ifdef CONFIG_SOC_SERIES_STM32G0X + update_stm32g0x_cc_line(config->ucpd_port); +#endif + + return 0; +} + /** * @brief Sets the value of the CC pull up resistor used when operating as a Source * @@ -1236,6 +1281,20 @@ static void ucpd_set_vconn_cb(const struct device *dev, data->vconn_cb = vconn_cb; } +/** + * @brief Sets a callback that can discharge VCONN if the TCPC is + * unable to or the system is configured in a way that does not use + * the VCONN discharge capabilities of the TCPC + * + */ +static void ucpd_set_vconn_discharge_cb(const struct device *dev, + tcpc_vconn_discharge_cb_t cb) +{ + struct tcpc_data *data = dev->data; + + data->vconn_discharge_cb = cb; +} + /** * @brief UCPD interrupt init */ @@ -1344,7 +1403,9 @@ static const struct tcpc_driver_api driver_api = { .set_cc = ucpd_set_cc, .set_roles = ucpd_set_roles, .set_vconn_cb = ucpd_set_vconn_cb, + .set_vconn_discharge_cb = ucpd_set_vconn_discharge_cb, .set_vconn = ucpd_set_vconn, + .vconn_discharge = ucpd_vconn_discharge, .set_cc_polarity = ucpd_cc_set_polarity, .dump_std_reg = ucpd_dump_std_reg, .set_bist_test_mode = ucpd_set_bist_test_mode, diff --git a/drivers/usb_c/tcpc/ucpd_stm32_priv.h b/drivers/usb_c/tcpc/ucpd_stm32_priv.h index ce0a19471a7..c1c8c8633ed 100644 --- a/drivers/usb_c/tcpc/ucpd_stm32_priv.h +++ b/drivers/usb_c/tcpc/ucpd_stm32_priv.h @@ -267,6 +267,8 @@ struct tcpc_config { struct tcpc_data { /* VCONN callback function */ tcpc_vconn_control_cb_t vconn_cb; + /* VCONN Discharge callback function */ + tcpc_vconn_discharge_cb_t vconn_discharge_cb; /* Alert information */ struct alert_info alert_info; @@ -309,6 +311,8 @@ struct tcpc_data { struct msg_header_info msg_header; /* Track VCONN on/off state */ bool ucpd_vconn_enable; + /* Track CC line that VCONN was active on */ + enum tc_cc_polarity ucpd_vconn_cc; /* Timer for amount of time to wait for receiving a GoodCRC */ struct k_timer goodcrc_rx_timer; diff --git a/include/zephyr/drivers/usb_c/usbc_tcpc.h b/include/zephyr/drivers/usb_c/usbc_tcpc.h index f96effc1327..74245501618 100644 --- a/include/zephyr/drivers/usb_c/usbc_tcpc.h +++ b/include/zephyr/drivers/usb_c/usbc_tcpc.h @@ -116,7 +116,10 @@ struct tcpc_chip_info { }; }; -typedef int (*tcpc_vconn_control_cb_t)(const struct device *dev, bool enable); +typedef int (*tcpc_vconn_control_cb_t)(const struct device *dev, + enum tc_cc_polarity pol, bool enable); +typedef int (*tcpc_vconn_discharge_cb_t)(const struct device *dev, + enum tc_cc_polarity pol, bool enable); typedef void (*tcpc_alert_handler_cb_t)(const struct device *dev, void *data, enum tcpc_alert alert); @@ -127,7 +130,9 @@ __subsystem struct tcpc_driver_api { int (*select_rp_value)(const struct device *dev, enum tc_rp_value rp); int (*get_rp_value)(const struct device *dev, enum tc_rp_value *rp); int (*set_cc)(const struct device *dev, enum tc_cc_pull pull); + void (*set_vconn_discharge_cb)(const struct device *dev, tcpc_vconn_discharge_cb_t cb); void (*set_vconn_cb)(const struct device *dev, tcpc_vconn_control_cb_t vconn_cb); + int (*vconn_discharge)(const struct device *dev, bool enable); int (*set_vconn)(const struct device *dev, bool enable); int (*set_roles)(const struct device *dev, enum tc_power_role power_role, enum tc_data_role data_role); @@ -350,6 +355,53 @@ static inline void tcpc_set_vconn_cb(const struct device *dev, return api->set_vconn_cb(dev, vconn_cb); } +/** + * @brief Sets a callback that can enable or discharge VCONN if the TCPC is + * unable to or the system is configured in a way that does not use + * the VCONN control capabilities of the TCPC + * + * The callback is called in the tcpc_vconn_discharge function if cb isn't NULL + * + * @param dev Runtime device structure + * @param cb pointer to the callback function that discharges vconn + */ +static inline void tcpc_set_vconn_discharge_cb(const struct device *dev, + tcpc_vconn_discharge_cb_t cb) +{ + const struct tcpc_driver_api *api = + (const struct tcpc_driver_api *)dev->api; + + __ASSERT(api->set_vconn_discharge_cb != NULL, + "Callback pointer should not be NULL"); + + return api->set_vconn_discharge_cb(dev, cb); +} + +/** + * @brief Discharges VCONN + * + * This function uses the TCPC to discharge VCONN if possible or calls the + * callback function set by tcpc_set_vconn_cb + * + * @param dev Runtime device structure + * @param enable VCONN discharge is enabled when true, it's disabled + * + * @retval 0 on success + * @retval -EIO on failure + * @retval -ENOSYS if not implemented + */ +static inline int tcpc_vconn_discharge(const struct device *dev, bool enable) +{ + const struct tcpc_driver_api *api = + (const struct tcpc_driver_api *)dev->api; + + if (api->vconn_discharge == NULL) { + return -ENOSYS; + } + + return api->vconn_discharge(dev, enable); +} + /** * @brief Enables or disables VCONN * diff --git a/include/zephyr/usb_c/usbc.h b/include/zephyr/usb_c/usbc.h index 17a1e2d36e5..59aae4d9928 100644 --- a/include/zephyr/usb_c/usbc.h +++ b/include/zephyr/usb_c/usbc.h @@ -123,6 +123,8 @@ enum usbc_policy_check_t { CHECK_DATA_ROLE_SWAP_TO_UFP, /** Check if Sink is at default level */ CHECK_SNK_AT_DEFAULT_LEVEL, + /** Check if should control VCONN */ + CHECK_VCONN_CONTROL, }; /** @@ -213,6 +215,15 @@ void *usbc_get_dpm_data(const struct device *dev); void usbc_set_vconn_control_cb(const struct device *dev, const tcpc_vconn_control_cb_t cb); +/** + * @brief Set the callback used to discharge VCONN + * + * @param dev Runtime device structure + * @param cb VCONN discharge callback + */ +void usbc_set_vconn_discharge_cb(const struct device *dev, + const tcpc_vconn_discharge_cb_t cb); + /** * @brief Set the callback used to check a policy *