diff --git a/boards/nordic/nrf54lm20dk/nrf54lm20_a_b_cpuapp_common.dtsi b/boards/nordic/nrf54lm20dk/nrf54lm20_a_b_cpuapp_common.dtsi index fc4275f44bc12..79f10b66435ab 100644 --- a/boards/nordic/nrf54lm20dk/nrf54lm20_a_b_cpuapp_common.dtsi +++ b/boards/nordic/nrf54lm20dk/nrf54lm20_a_b_cpuapp_common.dtsi @@ -19,6 +19,7 @@ zephyr,flash-controller = &rram_controller; zephyr,flash = &cpuapp_rram; zephyr,ieee802154 = &ieee802154; + zephyr,usb-bc12 = &usbhs_bc12; }; aliases { @@ -114,6 +115,10 @@ zephyr_udc0: &usbhs { status = "okay"; }; +&usbhs_bc12 { + status = "okay"; +}; + &spi00 { status = "okay"; cs-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>; diff --git a/drivers/usb/bc12/CMakeLists.txt b/drivers/usb/bc12/CMakeLists.txt index 31b9c3212a296..33a684e4ea1c1 100644 --- a/drivers/usb/bc12/CMakeLists.txt +++ b/drivers/usb/bc12/CMakeLists.txt @@ -7,3 +7,4 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_USB_BC12_PI3USB9201 bc12_pi3usb9201.c) zephyr_library_sources_ifdef(CONFIG_EMUL_BC12_PI3USB9201 emul_bc12_pi3usb9201.c) zephyr_include_directories_ifdef(CONFIG_EMUL_BC12_PI3USB9201 .) +zephyr_library_sources_ifdef(CONFIG_USB_BC12_NRF_USBHS bc12_nrf_usbhs.c) diff --git a/drivers/usb/bc12/Kconfig b/drivers/usb/bc12/Kconfig index 65f75966aced6..c5b890a2f97fa 100644 --- a/drivers/usb/bc12/Kconfig +++ b/drivers/usb/bc12/Kconfig @@ -13,5 +13,6 @@ module-str = usb_bc12 source "subsys/logging/Kconfig.template.log_config" source "drivers/usb/bc12/Kconfig.pi3usb9201" +source "drivers/usb/bc12/Kconfig.nrf54l" endif # USB_BC12 diff --git a/drivers/usb/bc12/Kconfig.nrf54l b/drivers/usb/bc12/Kconfig.nrf54l new file mode 100644 index 0000000000000..0addd9cde0ba6 --- /dev/null +++ b/drivers/usb/bc12/Kconfig.nrf54l @@ -0,0 +1,11 @@ +# Copyright Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config USB_BC12_NRF_USBHS + bool "Nordic USBHS PD Charging-Type Detector" + default y + depends on DT_HAS_NORDIC_NRF_USBHS_BC12_ENABLED + select NRF_USBHS_WRAPPER + select REGULATOR + help + Nordic portable device mode USB Charging-Type Detector. diff --git a/drivers/usb/bc12/bc12_nrf_usbhs.c b/drivers/usb/bc12/bc12_nrf_usbhs.c new file mode 100644 index 0000000000000..eb170f28f98bd --- /dev/null +++ b/drivers/usb/bc12/bc12_nrf_usbhs.c @@ -0,0 +1,395 @@ +/* + * SPDX-FileCopyrightText: Copyright Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(bc12_usbhs, CONFIG_USB_BC12_LOG_LEVEL); + +struct usbhs_bc_config { + NRF_USBHS_Type *base; + const struct device *parent; +}; + +enum usbhs_bc_state { + USBHS_BC_WAIT_FOR_VBUS, + USBHS_BC_DCD, + USBHS_BC_PRIMARY_DETECTION, + USBHS_BC_START_SECONDARY_DETECTION, + USBHS_BC_SECONDARY_DETECTION, + USBHS_BC_FINISHED, + USBHS_BC_DISCONNECTED, +}; + +struct usbhs_bc_data { + struct bc12_partner_state partner_state; + struct k_timer timer; + bc12_callback_t result_cb; + void *result_cb_data; + bool vbus_present; + enum usbhs_bc_state state; +}; + +#define TDCD_TIMEOUT 300 +#define TVDPSRC_ON 40 +#define TVDMSRC_ON 40 +#define TCP_VDM_EN 200 +#define TVDPSRC_DIS 20 +#define TVDMSRC_DIS 20 + +static void usbhs_bc_set_detection_result(const struct device *dev, + const enum bc12_type type) +{ + struct usbhs_bc_data *const data = dev->data; + + switch (type) { + case BC12_TYPE_DCP: + LOG_DBG("DCP"); + break; + case BC12_TYPE_CDP: + LOG_DBG("CDP"); + break; + case BC12_TYPE_SDP: + LOG_DBG("SDP"); + break; + case BC12_TYPE_UNKNOWN: + LOG_DBG("UNKNOWN"); + break; + case BC12_TYPE_PROPRIETARY: + LOG_DBG("PROPRIETARY"); + break; + case BC12_TYPE_NONE: + LOG_DBG("NONE"); + break; + default: + LOG_WRN("Unhandled BC12 type"); + } + + data->partner_state.type = type; + if (type == BC12_TYPE_DCP || type == BC12_TYPE_CDP) { + data->partner_state.current_ua = BC12_CHARGER_MAX_CURR_UA; + data->partner_state.voltage_uv = BC12_CHARGER_VOLTAGE_UV; + } else if (type == BC12_TYPE_NONE) { + data->partner_state.current_ua = 0; + data->partner_state.voltage_uv = 0; + } else { + data->partner_state.current_ua = BC12_CHARGER_MIN_CURR_UA; + data->partner_state.voltage_uv = BC12_CHARGER_VOLTAGE_UV; + } + + if (data->state == USBHS_BC_FINISHED && data->result_cb != NULL) { + data->result_cb(dev, &data->partner_state, data->result_cb_data); + } +} + +static void usbhs_bc_change_state(const struct device *dev, + const enum usbhs_bc_state state, const int ms) +{ + struct usbhs_bc_data *const data = dev->data; + + data->state = state; + k_timer_start(&data->timer, K_MSEC(ms), K_NO_WAIT); +} + +static void usbhs_bc_finish_state(const struct device *dev) +{ + struct usbhs_bc_data *const data = dev->data; + + data->state = USBHS_BC_FINISHED; +} + +static void usbhs_bc_start_dcd(const struct device *dev) +{ + usbhs_bc_change_state(dev, USBHS_BC_DCD, TDCD_TIMEOUT); +} + +static void usbhs_bc_start_primary_detection(const struct device *dev) +{ + const struct usbhs_bc_config *const config = dev->config; + NRF_USBHS_Type *const wrapper = config->base; + + wrapper->PHY.OVERRIDEVALUES = BIT(USBHS_PHY_OVERRIDEVALUES_OPMODE0_Pos) | + BIT(USBHS_PHY_OVERRIDEVALUES_XCVRSEL0_Pos); + wrapper->PHY.INPUTOVERRIDE = USBHS_PHY_INPUTOVERRIDE_OPMODE0_Msk | + USBHS_PHY_INPUTOVERRIDE_XCVRSEL0_Msk | + USBHS_PHY_INPUTOVERRIDE_DPPULLDOWN_Msk | + USBHS_PHY_INPUTOVERRIDE_DMPULLDOWN_Msk | + USBHS_PHY_INPUTOVERRIDE_SUSPENDM0_Msk; + + /* + * Set BATTCHRG. + * Enable Attach/Connect detection and data source voltage. + * The voltage is sourced onto DP and sunk from DM. + */ + wrapper->PHY.BATTCHRG = USBHS_PHY_BATTCHRG_VDATENB0_Msk | + USBHS_PHY_BATTCHRG_VDATSRCENB0_Msk; + + /* Add 1 ms extra to account for V_DP_SRC startup */ + usbhs_bc_change_state(dev, USBHS_BC_PRIMARY_DETECTION, TVDPSRC_ON + 1); +} + +static void usbhs_bc_start_secondary_detection(const struct device *dev) +{ + const struct usbhs_bc_config *const config = dev->config; + NRF_USBHS_Type *const wrapper = config->base; + + /* + * Set BATTCHRG. + * The voltage is sourced onto DM and sunk from DP. + * Enable Attach/Connect detection and data source voltage. + */ + wrapper->PHY.BATTCHRG = USBHS_PHY_BATTCHRG_CHRGSEL0_Msk; + wrapper->PHY.BATTCHRG = USBHS_PHY_BATTCHRG_CHRGSEL0_Msk | + USBHS_PHY_BATTCHRG_VDATENB0_Msk | + USBHS_PHY_BATTCHRG_VDATSRCENB0_Msk; + + /* Add 1 ms extra to account for V_DM_SRC startup */ + usbhs_bc_change_state(dev, USBHS_BC_SECONDARY_DETECTION, TVDMSRC_ON + 1); +} + +static void usbhs_bc_check_secondary_detection(const struct device *dev) +{ + const struct usbhs_bc_config *const config = dev->config; + NRF_USBHS_Type *const wrapper = config->base; + enum bc12_type type; + uint32_t status; + + status = wrapper->PHY.BATTCHRGSTATUS; + /* Disable VDM_SRC, IDP_SINK and VDAT_REF comparator */ + wrapper->PHY.BATTCHRG = 0; + + LOG_DBG("Status secondary detection 0x%08x", status); + if (status & USBHS_PHY_BATTCHRGSTATUS_CHGDET_Msk) { + /* + * Enable VDP_SRC onto DP and keep it enabled until DWC2 driver + * is ready and enables D+ pull-up. + */ + wrapper->PHY.BATTCHRG = USBHS_PHY_BATTCHRG_VDATSRCENB0_Msk; + type = BC12_TYPE_DCP; + } else { + type = BC12_TYPE_CDP; + } + + usbhs_bc_finish_state(dev); + usbhs_bc_set_detection_result(dev, type); + nrf_usbhs_wrapper_bc12_finished(config->parent); +} + +static void usbhs_bc_check_primary_detection(const struct device *dev) +{ + const struct usbhs_bc_config *const config = dev->config; + NRF_USBHS_Type *const wrapper = config->base; + bool need_secondary_detection = false; + enum bc12_type type; + uint32_t status; + + status = wrapper->PHY.BATTCHRGSTATUS; + LOG_DBG("Status primary detection 0x%08x", status); + status &= USBHS_PHY_BATTCHRGSTATUS_CHGDET_Msk | USBHS_PHY_BATTCHRGSTATUS_FSVMINUS_Msk; + + switch (status) { + case 0: + type = BC12_TYPE_SDP; + break; + case USBHS_PHY_BATTCHRGSTATUS_CHGDET_Msk: + /* Either CDP or DCP, to be determined in Secondary Detection */ + need_secondary_detection = true; + break; + case USBHS_PHY_BATTCHRGSTATUS_CHGDET_Msk | USBHS_PHY_BATTCHRGSTATUS_FSVMINUS_Msk: + /* + * Either prioprietary charger or PS/2 port. BC1.2 does not have + * any method to differentiate between the two. + */ + type = BC12_TYPE_PROPRIETARY; + break; + default: + type = BC12_TYPE_UNKNOWN; + break; + } + + /* Disable VDP_SRC */ + wrapper->PHY.BATTCHRG = 0; + + if (need_secondary_detection) { + usbhs_bc_change_state(dev, USBHS_BC_START_SECONDARY_DETECTION, TVDPSRC_DIS); + } else { + /* Battery Charging finished */ + usbhs_bc_finish_state(dev); + usbhs_bc_set_detection_result(dev, type); + nrf_usbhs_wrapper_bc12_finished(config->parent); + } +} + +static void usbhs_bc_timer_expiry(struct k_timer *timer) +{ + const struct device *dev = k_timer_user_data_get(timer); + struct usbhs_bc_data *const data = dev->data; + + switch (data->state) { + case USBHS_BC_DCD: + usbhs_bc_start_primary_detection(dev); + break; + case USBHS_BC_PRIMARY_DETECTION: + usbhs_bc_check_primary_detection(dev); + break; + case USBHS_BC_START_SECONDARY_DETECTION: + usbhs_bc_start_secondary_detection(dev); + break; + case USBHS_BC_SECONDARY_DETECTION: + usbhs_bc_check_secondary_detection(dev); + break; + case USBHS_BC_DISCONNECTED: + /* TCP_VDM_EN elapsed, restart charger detection */ + if (data->vbus_present) { + /* VBUS present - proceed to DCD */ + usbhs_bc_start_dcd(dev); + } else { + /* Allow DCD as soon as VBUS gets detected */ + data->state = USBHS_BC_WAIT_FOR_VBUS; + } + break; + default: + LOG_ERR("Unhandled state %u", data->state); + break; + } +} + +static void usbhs_bc_vbus_detected(const struct device *dev) +{ + const struct usbhs_bc_config *const config = dev->config; + struct usbhs_bc_data *const data = dev->data; + + nrf_usbhs_wrapper_enable_phy(config->parent, true); + + if (data->state == USBHS_BC_WAIT_FOR_VBUS) { + usbhs_bc_start_dcd(dev); + } else { + __ASSERT_NO_MSG(data->state == USBHS_BC_DISCONNECTED); + /* Detection process will start after TCP_VDM_EN elapses */ + } +} + +static void usbhs_bc_vbus_removed(const struct device *dev) +{ + const struct usbhs_bc_config *const config = dev->config; + + nrf_usbhs_wrapper_disable(config->parent); + usbhs_bc_set_detection_result(dev, BC12_TYPE_NONE); + /* + * The PD is required to wait for at least TCP_VDM_EN between + * disconnect event and restarting the detection process. + */ + usbhs_bc_change_state(dev, USBHS_BC_DISCONNECTED, TCP_VDM_EN); +} + +static void vregusb_event_cb(const struct device *dev, + const struct regulator_event *const evt, + const void *const user_data) +{ + const struct device *const bc_dev = user_data; + struct usbhs_bc_data *const data = bc_dev->data; + + if (evt->type == REGULATOR_VOLTAGE_DETECTED) { + LOG_DBG("VBUS detected"); + data->vbus_present = true; + usbhs_bc_vbus_detected(bc_dev); + } + + if (evt->type == REGULATOR_VOLTAGE_REMOVED) { + LOG_DBG("VBUS removed"); + data->vbus_present = false; + usbhs_bc_vbus_removed(bc_dev); + } +} + +static int usbhs_bc_set_role(const struct device *dev, const enum bc12_role role) +{ + const struct usbhs_bc_config *const config = dev->config; + struct usbhs_bc_data *const data = dev->data; + int err; + + if (role == BC12_DISCONNECTED) { + data->partner_state.type = BC12_TYPE_NONE; + data->partner_state.bc12_role = role; + err = nrf_usbhs_wrapper_vreg_disable(config->parent); + if (err) { + LOG_ERR("Failed to disable regulator"); + } + + return err; + } + + if (role == BC12_PORTABLE_DEVICE) { + if (data->result_cb == NULL) { + LOG_ERR("Detection result callback is not set"); + return -EIO; + } + + data->partner_state.type = BC12_TYPE_NONE; + data->partner_state.bc12_role = role; + err = nrf_usbhs_wrapper_vreg_enable(config->parent); + if (err) { + LOG_ERR("Failed to enable regulator %s error %d", + config->parent->name, err); + } + + return err; + } + + return -ENOTSUP; +} + +static int usbhs_bc_set_result_cb(const struct device *dev, + const bc12_callback_t cb, void *const user_data) +{ + struct usbhs_bc_data *const data = dev->data; + + data->result_cb = cb; + data->result_cb_data = user_data; + + return 0; +} + +static int usbhs_bc_init(const struct device *dev) +{ + const struct usbhs_bc_config *const config = dev->config; + struct usbhs_bc_data *const data = dev->data; + + usbhs_bc_set_role(dev, BC12_DISCONNECTED); + k_timer_init(&data->timer, usbhs_bc_timer_expiry, NULL); + k_timer_user_data_set(&data->timer, (void *)dev); + + nrf_usbhs_wrapper_set_bc12_cb(config->parent, vregusb_event_cb, dev); + + return 0; +} + +static DEVICE_API(bc12, usbhs_bc_driver_api) = { + .set_role = usbhs_bc_set_role, + .set_result_cb = usbhs_bc_set_result_cb, +}; + +#define DT_DRV_COMPAT nordic_nrf_usbhs_bc12 + +#define BC12_NRF54L_DEFINE(n) \ + static const struct usbhs_bc_config bc_config_##n = { \ + .base = (void *)(DT_REG_ADDR(DT_INST_PARENT(n))), \ + .parent = DEVICE_DT_GET(DT_INST_PARENT(n)), \ + }; \ + \ + static struct usbhs_bc_data usbhs_bc_data_##n; \ + \ + DEVICE_DT_INST_DEFINE(n, usbhs_bc_init, NULL, \ + &usbhs_bc_data_##n, &bc_config_##n, \ + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ + &usbhs_bc_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(BC12_NRF54L_DEFINE) diff --git a/drivers/usb/common/CMakeLists.txt b/drivers/usb/common/CMakeLists.txt index 339498c982bb4..595e7a6b9ab9b 100644 --- a/drivers/usb/common/CMakeLists.txt +++ b/drivers/usb/common/CMakeLists.txt @@ -3,3 +3,4 @@ add_subdirectory(buf) add_subdirectory_ifdef(CONFIG_HAS_NRFX nrf_usbd_common) add_subdirectory_ifdef(CONFIG_SOC_FAMILY_STM32 stm32) +add_subdirectory_ifdef(CONFIG_NRF_USBHS_WRAPPER nordic) diff --git a/drivers/usb/common/Kconfig b/drivers/usb/common/Kconfig index d80a784c7e483..5a5f0731314f4 100644 --- a/drivers/usb/common/Kconfig +++ b/drivers/usb/common/Kconfig @@ -5,5 +5,6 @@ menu "USB common" rsource "nrf_usbd_common/Kconfig" rsource "stm32/Kconfig" +rsource "nordic/Kconfig" endmenu diff --git a/drivers/usb/common/nordic/CMakeLists.txt b/drivers/usb/common/nordic/CMakeLists.txt new file mode 100644 index 0000000000000..8b566f850db7f --- /dev/null +++ b/drivers/usb/common/nordic/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: Copyright The Zephyr Project Contributors +# SPDX-License-Identifier: Apache-2.0 + +if(CONFIG_NRF_USBHS_WRAPPER) + zephyr_library() + zephyr_include_directories(.) + zephyr_library_sources(nrf_usbhs_wrapper.c) +endif() diff --git a/drivers/usb/common/nordic/Kconfig b/drivers/usb/common/nordic/Kconfig new file mode 100644 index 0000000000000..c954a6a6ea7b4 --- /dev/null +++ b/drivers/usb/common/nordic/Kconfig @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: Copyright The Zephyr Project Contributors +# SPDX-License-Identifier: Apache-2.0 + +config NRF_USBHS_WRAPPER + bool "Nordic USBHS wrapper" + default y + depends on DT_HAS_NORDIC_NRF_USBHS_WRAPPER_ENABLED + depends on REGULATOR + help + Driver for Nordic USBHS wrapper. diff --git a/drivers/usb/common/nordic/nrf_usbhs_wrapper.c b/drivers/usb/common/nordic/nrf_usbhs_wrapper.c new file mode 100644 index 0000000000000..448144fa9d416 --- /dev/null +++ b/drivers/usb/common/nordic/nrf_usbhs_wrapper.c @@ -0,0 +1,288 @@ +/* + * SPDX-FileCopyrightText: Copyright Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(usbhs, LOG_LEVEL_ERR); + +struct usbhs_wrapper_config { + NRF_USBHS_Type *base; + const struct device *vregusb_dev; +}; + +struct nrf_usbhs_wrapper_data { + const struct device *dev; + struct k_event events; + regulator_callback_t bc12_cb; + const void *bc12_cb_data; + regulator_callback_t phy_cb; + const void *phy_cb_data; + atomic_t refcount; + struct k_spinlock lock; +}; + +static void vregusb_event_cb(const struct device *vreg_dev, + const struct regulator_event *const evt, + const void *const user_data) +{ + const struct device *const dev = user_data; + const struct usbhs_wrapper_config *const config = dev->config; + struct nrf_usbhs_wrapper_data *const data = dev->data; + + if (evt->type == REGULATOR_VOLTAGE_DETECTED) { + LOG_DBG("VBUS detected"); + k_event_post(&data->events, NRF_USBHS_VREG_READY); + if (!IS_ENABLED(CONFIG_USB_BC12_NRF_USBHS)) { + /* + * If the USB BC12 driver is not enabled, post the + * event immediately. + */ + LOG_INF("USB BC12 driver disabled, post PHY ready"); + k_event_post(&data->events, NRF_USBHS_PHY_READY); + } + } + + if (evt->type == REGULATOR_VOLTAGE_REMOVED) { + LOG_DBG("VBUS removed"); + k_event_set_masked(&data->events, 0, UINT32_MAX); + } + + if (data->bc12_cb != NULL) { + data->bc12_cb(config->vregusb_dev, evt, data->bc12_cb_data); + } + + if (data->phy_cb != NULL) { + data->phy_cb(config->vregusb_dev, evt, data->phy_cb_data); + } + +} + +void nrf_usbhs_wrapper_start(const struct device *dev) +{ + const struct usbhs_wrapper_config *const config = dev->config; + NRF_USBHS_Type *wrapper = config->base; + + wrapper->TASKS_START = 1UL; +} + +void nrf_usbhs_wrapper_stop(const struct device *dev) +{ + const struct usbhs_wrapper_config *const config = dev->config; + NRF_USBHS_Type *wrapper = config->base; + + wrapper->TASKS_STOP = 1UL; +} + +static void wrapper_enable_phy_internal(const struct device *dev, const bool suspended) +{ + const struct usbhs_wrapper_config *const config = dev->config; + NRF_USBHS_Type *wrapper = config->base; + uint32_t inputoverride; + + /* Power up peripheral */ + wrapper->ENABLE = USBHS_ENABLE_CORE_Msk; + + /* Set role to Device, force D+ pull-up off by overriding VBUS valid signal */ + if (suspended) { + /* Additionally suspend PHY */ + inputoverride = USBHS_PHY_INPUTOVERRIDE_ID_Msk | + USBHS_PHY_INPUTOVERRIDE_VBUSVALID_Msk | + USBHS_PHY_INPUTOVERRIDE_SUSPENDM0_Msk; + } else { + inputoverride = USBHS_PHY_INPUTOVERRIDE_ID_Msk | + USBHS_PHY_INPUTOVERRIDE_VBUSVALID_Msk; + } + + wrapper->PHY.OVERRIDEVALUES = USBHS_PHY_OVERRIDEVALUES_ID_Msk; + wrapper->PHY.INPUTOVERRIDE = inputoverride; + + /* Release PHY power-on reset */ + wrapper->ENABLE = USBHS_ENABLE_PHY_Msk | USBHS_ENABLE_CORE_Msk; +} + +static void wrapper_enable_and_start_internal(const struct device *dev) +{ + const struct usbhs_wrapper_config *const config = dev->config; + NRF_USBHS_Type *wrapper = config->base; + + if (IS_ENABLED(CONFIG_USB_BC12_NRF_USBHS)) { + /* Clear suspend bit set by the BC12 driver */ + wrapper->PHY.INPUTOVERRIDE &= ~USBHS_PHY_INPUTOVERRIDE_SUSPENDM0_Msk; + } else { + wrapper_enable_phy_internal(dev, false); + } + + /* Wait for PHY clock to start */ + k_busy_wait(45); + + /* Release DWC2 reset */ + nrf_usbhs_wrapper_start(dev); + + /* Wait for clock to start to avoid hang on too early register read */ + k_busy_wait(1); + + /* DWC2 opmode is now guaranteed to be Non-Driving, allow D+ pull-up to + * become active once driver clears DCTL SftDiscon bit. + */ + wrapper->PHY.INPUTOVERRIDE = USBHS_PHY_INPUTOVERRIDE_ID_Msk; + + if (IS_ENABLED(CONFIG_USB_BC12_NRF_USBHS)) { + wrapper->PHY.OVERRIDEVALUES = USBHS_PHY_OVERRIDEVALUES_ID_Msk; + wrapper->PHY.BATTCHRG &= ~USBHS_PHY_BATTCHRG_VDATSRCENB0_Msk; + } +} + +void nrf_usbhs_wrapper_enable_phy(const struct device *dev, const bool suspended) +{ + struct nrf_usbhs_wrapper_data *const data = dev->data; + + wrapper_enable_phy_internal(dev, suspended); + atomic_inc(&data->refcount); + LOG_INF("Enable %s, refcount %lu", + "USB peripheral", atomic_get(&data->refcount)); +} + +void nrf_usbhs_wrapper_enable_udc(const struct device *dev) +{ + struct nrf_usbhs_wrapper_data *const data = dev->data; + k_spinlock_key_t key; + + key = k_spin_lock(&data->lock); + wrapper_enable_and_start_internal(dev); + k_spin_unlock(&data->lock, key); + atomic_inc(&data->refcount); + LOG_INF("Enable and start %s, refcount %lu", + "USB peripheral", atomic_get(&data->refcount)); +} + +void nrf_usbhs_wrapper_bc12_finished(const struct device *dev) +{ + struct nrf_usbhs_wrapper_data *const data = dev->data; + + LOG_INF("BC12 detection finished, refcount %lu", atomic_get(&data->refcount)); + + k_event_post(&data->events, NRF_USBHS_PHY_READY); + if (atomic_get(&data->refcount) > 1) { + LOG_INF("Re-enable and start %s", "USB peripheral"); + /* + * The UDC controller enables the PHY and controller + * unconditionally and wants to keep it enabled. Otherwise, the + * PHY will remain suspended, and the controller will be + * disabled after reconnecting and finished BC12 driver + * detection. + */ + wrapper_enable_and_start_internal(dev); + } +} + +void nrf_usbhs_wrapper_disable(const struct device *dev) +{ + const struct usbhs_wrapper_config *const config = dev->config; + struct nrf_usbhs_wrapper_data *const data = dev->data; + NRF_USBHS_Type *wrapper = config->base; + atomic_val_t prev_refcount; + k_spinlock_key_t key; + + prev_refcount = atomic_dec(&data->refcount); + if (prev_refcount != 1) { + LOG_INF("Keep PHY and %s enabled, previous refcount %lu", + "USB peripheral", prev_refcount); + return; + } + + key = k_spin_lock(&data->lock); + /* Set ID to Device and forcefully disable D+ pull-up */ + wrapper->PHY.OVERRIDEVALUES = USBHS_PHY_OVERRIDEVALUES_ID_Msk; + wrapper->PHY.INPUTOVERRIDE = USBHS_PHY_INPUTOVERRIDE_ID_Msk | + USBHS_PHY_INPUTOVERRIDE_VBUSVALID_Msk; + + wrapper->ENABLE = 0UL; + k_spin_unlock(&data->lock, key); + LOG_INF("Disable %s, refcount %lu", + "USB peripheral", atomic_get(&data->refcount)); +} + +struct k_event *nrf_usbhs_wrapper_get_events_ptr(const struct device *dev) +{ + struct nrf_usbhs_wrapper_data *const data = dev->data; + + return &data->events; +} + +void nrf_usbhs_wrapper_set_bc12_cb(const struct device *dev, + regulator_callback_t cb, + const void *const user_data) +{ + struct nrf_usbhs_wrapper_data *const data = dev->data; + + data->bc12_cb = cb; + data->bc12_cb_data = user_data; +} + +void nrf_usbhs_wrapper_set_udc_cb(const struct device *dev, + regulator_callback_t cb, + const void *const user_data) +{ + struct nrf_usbhs_wrapper_data *const data = dev->data; + + data->phy_cb = cb; + data->phy_cb_data = user_data; +} + +int nrf_usbhs_wrapper_vreg_enable(const struct device *dev) +{ + const struct usbhs_wrapper_config *const config = dev->config; + + return regulator_enable(config->vregusb_dev); +} + +int nrf_usbhs_wrapper_vreg_disable(const struct device *dev) +{ + const struct usbhs_wrapper_config *const config = dev->config; + + return regulator_disable(config->vregusb_dev); +} + +static int usbhs_wrapper_init(const struct device *dev) +{ + const struct usbhs_wrapper_config *const config = dev->config; + struct nrf_usbhs_wrapper_data *const data = dev->data; + int err; + + k_event_init(&data->events); + atomic_set(&data->refcount, 0); + + err = regulator_set_callback(config->vregusb_dev, vregusb_event_cb, dev); + if (err) { + LOG_ERR("Failed to set regulator callback"); + return err; + } + + return 0; +} + +#define DT_DRV_COMPAT nordic_nrf_usbhs_wrapper + +#define NRF_USBHS_WRAPPER_DEFINE(n) \ + static const struct usbhs_wrapper_config usbhs_wrapper_config_##n = { \ + .base = (void *)(DT_INST_REG_ADDR(n)), \ + .vregusb_dev = DEVICE_DT_GET(DT_INST_PHANDLE(n, regulator)), \ + }; \ + \ + static struct nrf_usbhs_wrapper_data usbhs_wrapper_data_##n; \ + \ + DEVICE_DT_INST_DEFINE(n, usbhs_wrapper_init, NULL, \ + &usbhs_wrapper_data_##n, &usbhs_wrapper_config_##n, \ + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ + NULL); + +DT_INST_FOREACH_STATUS_OKAY(NRF_USBHS_WRAPPER_DEFINE) diff --git a/drivers/usb/common/nordic/nrf_usbhs_wrapper.h b/drivers/usb/common/nordic/nrf_usbhs_wrapper.h new file mode 100644 index 0000000000000..fb04b79a90d25 --- /dev/null +++ b/drivers/usb/common/nordic/nrf_usbhs_wrapper.h @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: Copyright Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_USB_COMMON_NORDIC_NRF_USBHS_WRAPPER_H_ +#define ZEPHYR_DRIVERS_USB_COMMON_NORDIC_NRF_USBHS_WRAPPER_H_ + +#include + +#define NRF_USBHS_VREG_READY BIT(0) +#define NRF_USBHS_PHY_READY BIT(1) + +/* + * Nordic USBHS wrapper is a set of registers used by both USB BC12 driver and + * vendor specific part in UDC driver. The wrapper can be considered to be MFD + * device. Both drivers depends on the VBUS regulator driver and require some + * synchronisation when accessing wrapper register. + * + * The device argument in all the functions below is the wrapper device, what + * is always a parent of the BC12 and UDC drivers. + */ + +/* + * Set the event handler callback for the BC12 device driver. The callback will + * be called in the regulator driver ISR context, and must not be blocking. + */ +void nrf_usbhs_wrapper_set_bc12_cb(const struct device *dev, + regulator_callback_t cb, + const void *const user_data); + +/* + * Set the event handler callback for the UDC device driver. The callback will + * be called in the regulator driver ISR context, and must not be blocking. + */ +void nrf_usbhs_wrapper_set_udc_cb(const struct device *dev, + regulator_callback_t cb, + const void *const user_data); + +/* + * Enable VBUS regulator. Can be called from both drivers. Regulator has own + * reference counting. + */ +int nrf_usbhs_wrapper_vreg_enable(const struct device *dev); + +/* + * Disable VBUS regulator. Can be called from both drivers. Regulator has own + * reference counting. + */ +int nrf_usbhs_wrapper_vreg_disable(const struct device *dev); + +/* + * Post the event that the BC12 driver is finished, and PHY may be used by the + * UDC driver. Set the PHY to the default state, and if necessary, enable the + * controller after the BC12 detection process is finished. + */ +void nrf_usbhs_wrapper_bc12_finished(const struct device *dev); + +/* + * Disable the PHY and controller. This function can be called from UDC and + * BC12 drivers and has own reference counter. + */ +void nrf_usbhs_wrapper_disable(const struct device *dev); + +/* + * Enable PHY and controller. This function should be called from the UDC + * driver. It increments internal referece counter. + */ +void nrf_usbhs_wrapper_enable_udc(const struct device *dev); + +/* + * Enable PHY. This function should be called from the BC12 driver. It + * increments internal referece counter. + */ +void nrf_usbhs_wrapper_enable_phy(const struct device *dev, const bool suspended); + +/* Start USB peripheral */ +void nrf_usbhs_wrapper_stop(const struct device *dev); + +/* Stop USB peripheral */ +void nrf_usbhs_wrapper_start(const struct device *dev); + +/* + * Get the pointer to the event struct where NRF_USBHS_VREG_READY and + * NRF_USBHS_PHY_READY are posted. NRF_USBHS_PHY_READY signals that PHY can be + * used by the UDC driver. + */ +struct k_event *nrf_usbhs_wrapper_get_events_ptr(const struct device *dev); + +#endif /* ZEPHYR_DRIVERS_USB_COMMON_NORDIC_NRF_USBHS_WRAPPER_H_ */ diff --git a/drivers/usb/udc/Kconfig.dwc2 b/drivers/usb/udc/Kconfig.dwc2 index 86e67c9f893f4..ddf07275b1e8c 100644 --- a/drivers/usb/udc/Kconfig.dwc2 +++ b/drivers/usb/udc/Kconfig.dwc2 @@ -10,6 +10,7 @@ config UDC_DWC2 select NRFS_VBUS_DETECTOR_SERVICE_ENABLED if NRFS_HAS_VBUS_DETECTOR_SERVICE select EVENTS select REGULATOR if SOC_SERIES_NRF54L || SOC_SERIES_NRF92X + select NRF_USBHS_WRAPPER if SOC_SERIES_NRF54L || SOC_SERIES_NRF92X help DWC2 USB device controller driver. diff --git a/drivers/usb/udc/udc_dwc2_vendor_quirks.h b/drivers/usb/udc/udc_dwc2_vendor_quirks.h index 2236172709154..93580a709d1c1 100644 --- a/drivers/usb/udc/udc_dwc2_vendor_quirks.h +++ b/drivers/usb/udc/udc_dwc2_vendor_quirks.h @@ -326,13 +326,12 @@ DT_INST_FOREACH_STATUS_OKAY(QUIRK_NRF_USBHS_DEFINE) #if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_usbhs_nrf54l) -#define USBHS_DT_WRAPPER_REG_ADDR(n) UINT_TO_POINTER(DT_REG_ADDR(DT_INST_PARENT(n))) - #include #include #include #include #include +#include /* * On USBHS, we cannot access the DWC2 register until VBUS is detected and @@ -341,11 +340,7 @@ DT_INST_FOREACH_STATUS_OKAY(QUIRK_NRF_USBHS_DEFINE) * until a valid VBUS signal is detected or until the * CONFIG_UDC_DWC2_USBHS_VBUS_READY_TIMEOUT timeout expires. */ -static K_EVENT_DEFINE(usbhs_events); -#define USBHS_VBUS_READY BIT(0) -const static struct device *const vregusb_dev = - DEVICE_DT_GET(DT_PHANDLE(DT_INST_PARENT(0), regulator)); static struct onoff_manager *pclk24m_mgr; static struct onoff_client pclk24m_cli; @@ -357,28 +352,23 @@ static void vregusb_event_cb(const struct device *dev, const struct device *const udc_dev = user_data; if (evt->type == REGULATOR_VOLTAGE_DETECTED) { - k_event_post(&usbhs_events, USBHS_VBUS_READY); udc_submit_event(udc_dev, UDC_EVT_VBUS_READY, 0); } if (evt->type == REGULATOR_VOLTAGE_REMOVED) { - k_event_set_masked(&usbhs_events, 0, USBHS_VBUS_READY); udc_submit_event(udc_dev, UDC_EVT_VBUS_REMOVED, 0); } } static inline int usbhs_init_vreg_and_clock(const struct device *dev) { + const struct device *parent = DEVICE_DT_GET(DT_INST_PARENT(0)); LOG_MODULE_DECLARE(udc_dwc2, CONFIG_UDC_DRIVER_LOG_LEVEL); int err; - err = regulator_set_callback(vregusb_dev, vregusb_event_cb, dev); - if (err) { - LOG_ERR("Failed to set regulator callback"); - return err; - } + nrf_usbhs_wrapper_set_udc_cb(parent, vregusb_event_cb, dev); - err = regulator_enable(vregusb_dev); + err = nrf_usbhs_wrapper_vreg_enable(parent); if (err) { LOG_ERR("Failed to enable regulator"); return err; @@ -391,18 +381,20 @@ static inline int usbhs_init_vreg_and_clock(const struct device *dev) static inline int usbhs_enable_core(const struct device *dev) { + const struct device *parent = DEVICE_DT_GET(DT_INST_PARENT(0)); + struct k_event *events = nrf_usbhs_wrapper_get_events_ptr(parent); LOG_MODULE_DECLARE(udc_dwc2, CONFIG_UDC_DRIVER_LOG_LEVEL); - NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0); k_timeout_t timeout = K_FOREVER; int err; + if (CONFIG_UDC_DWC2_USBHS_VBUS_READY_TIMEOUT) { timeout = K_MSEC(CONFIG_UDC_DWC2_USBHS_VBUS_READY_TIMEOUT); } - if (!k_event_wait(&usbhs_events, USBHS_VBUS_READY, false, K_NO_WAIT)) { + if (!k_event_wait(events, NRF_USBHS_PHY_READY, false, K_NO_WAIT)) { LOG_WRN("VBUS is not ready, block udc_enable()"); - if (!k_event_wait(&usbhs_events, USBHS_VBUS_READY, false, timeout)) { + if (!k_event_wait(events, NRF_USBHS_PHY_READY, false, timeout)) { return -ETIMEDOUT; } } @@ -415,45 +407,18 @@ static inline int usbhs_enable_core(const struct device *dev) return err; } - /* Power up peripheral */ - wrapper->ENABLE = USBHS_ENABLE_CORE_Msk; - - /* Set ID to Device and force D+ pull-up off for now */ - wrapper->PHY.OVERRIDEVALUES = (1 << 31); - wrapper->PHY.INPUTOVERRIDE = (1 << 31) | USBHS_PHY_INPUTOVERRIDE_VBUSVALID_Msk; - - /* Release PHY power-on reset */ - wrapper->ENABLE = USBHS_ENABLE_PHY_Msk | USBHS_ENABLE_CORE_Msk; - - /* Wait for PHY clock to start */ - k_busy_wait(45); - - /* Release DWC2 reset */ - wrapper->TASKS_START = 1UL; - - /* Wait for clock to start to avoid hang on too early register read */ - k_busy_wait(1); - - /* DWC2 opmode is now guaranteed to be Non-Driving, allow D+ pull-up to - * become active once driver clears DCTL SftDiscon bit. - */ - wrapper->PHY.INPUTOVERRIDE = (1 << 31); + nrf_usbhs_wrapper_enable_udc(parent); return 0; } static inline int usbhs_disable_core(const struct device *dev) { + const struct device *parent = DEVICE_DT_GET(DT_INST_PARENT(0)); LOG_MODULE_DECLARE(udc_dwc2, CONFIG_UDC_DRIVER_LOG_LEVEL); - NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0); int err; - /* Set ID to Device and forcefully disable D+ pull-up */ - wrapper->PHY.OVERRIDEVALUES = (1 << 31); - wrapper->PHY.INPUTOVERRIDE = (1 << 31) | USBHS_PHY_INPUTOVERRIDE_VBUSVALID_Msk; - - wrapper->ENABLE = 0UL; - + nrf_usbhs_wrapper_disable(parent); /* Release PCLK24M using clock control driver */ err = onoff_cancel_or_release(pclk24m_mgr, &pclk24m_cli); if (err < 0) { @@ -466,9 +431,10 @@ static inline int usbhs_disable_core(const struct device *dev) static inline int usbhs_disable_vreg(const struct device *dev) { + const struct device *parent = DEVICE_DT_GET(DT_INST_PARENT(0)); ARG_UNUSED(dev); - return regulator_disable(vregusb_dev); + return nrf_usbhs_wrapper_vreg_disable(parent); } static inline int usbhs_init_caps(const struct device *dev) @@ -483,31 +449,37 @@ static inline int usbhs_init_caps(const struct device *dev) static inline int usbhs_is_phy_clk_off(const struct device *dev) { - return !k_event_test(&usbhs_events, USBHS_VBUS_READY); + const struct device *parent = DEVICE_DT_GET(DT_INST_PARENT(0)); + struct k_event *events = nrf_usbhs_wrapper_get_events_ptr(parent); + bool clk_on; + + clk_on = k_event_test(events, NRF_USBHS_PHY_READY); + + return !clk_on; } static inline int usbhs_post_hibernation_entry(const struct device *dev) { + const struct device *parent = DEVICE_DT_GET(DT_INST_PARENT(0)); const struct udc_dwc2_config *const config = dev->config; struct usb_dwc2_reg *const base = config->base; - NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0); sys_set_bits((mem_addr_t)&base->pcgcctl, USB_DWC2_PCGCCTL_GATEHCLK); - wrapper->TASKS_STOP = 1; + nrf_usbhs_wrapper_stop(parent); return 0; } static inline int usbhs_pre_hibernation_exit(const struct device *dev) { + const struct device *parent = DEVICE_DT_GET(DT_INST_PARENT(0)); const struct udc_dwc2_config *const config = dev->config; struct usb_dwc2_reg *const base = config->base; - NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0); sys_clear_bits((mem_addr_t)&base->pcgcctl, USB_DWC2_PCGCCTL_GATEHCLK); - wrapper->TASKS_START = 1; + nrf_usbhs_wrapper_start(parent); return 0; } diff --git a/dts/bindings/usb/nordic,nrf-usbhs-bc12.yaml b/dts/bindings/usb/nordic,nrf-usbhs-bc12.yaml new file mode 100644 index 0000000000000..63399a16b19cf --- /dev/null +++ b/dts/bindings/usb/nordic,nrf-usbhs-bc12.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +description: Nordic USB PD Charging-Type Detector + +compatible: "nordic,nrf-usbhs-bc12" + +include: [usb-bc12.yaml] diff --git a/dts/vendor/nordic/nrf54lm20_a_b.dtsi b/dts/vendor/nordic/nrf54lm20_a_b.dtsi index d5a8245a50183..12e0d3c388055 100644 --- a/dts/vendor/nordic/nrf54lm20_a_b.dtsi +++ b/dts/vendor/nordic/nrf54lm20_a_b.dtsi @@ -253,6 +253,12 @@ ghwcfg4 = <0x3e10aa60>; status = "disabled"; }; + + usbhs_bc12: usbhs_bc12 { + compatible = "nordic,nrf-usbhs-bc12"; + charging-mode = "BC12_TYPE_NONE"; + status = "disabled"; + }; }; dppic10: dppic@82000 { diff --git a/samples/subsys/usb/hid-mouse/sample.yaml b/samples/subsys/usb/hid-mouse/sample.yaml index 0a9e1af37f0f8..95ba33ee11f97 100644 --- a/samples/subsys/usb/hid-mouse/sample.yaml +++ b/samples/subsys/usb/hid-mouse/sample.yaml @@ -18,3 +18,12 @@ tests: - mimxrt1060_evk/mimxrt1062/qspi - max32690evkit/max32690/m4 tags: usb + sample.usb_device_next.hid-mouse-bc12: + filter: dt_chosen_enabled("zephyr,usb-bc12") + depends_on: + - usbd + integration_platforms: + - nrf54lm20dk/nrf54lm20b/cpuapp + extra_configs: + - CONFIG_USB_BC12=y + tags: usb diff --git a/samples/subsys/usb/hid-mouse/src/main.c b/samples/subsys/usb/hid-mouse/src/main.c index 967591b2a7119..7ca304b456641 100644 --- a/samples/subsys/usb/hid-mouse/src/main.c +++ b/samples/subsys/usb/hid-mouse/src/main.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -21,6 +22,7 @@ #include LOG_MODULE_REGISTER(main, LOG_LEVEL_INF); +static const struct device *bc12_dev = DEVICE_DT_GET_OR_NULL(DT_CHOSEN(zephyr_usb_bc12)); static const struct gpio_dt_spec led0 = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios); static const uint8_t hid_report_desc[] = HID_MOUSE_REPORT_DESC(2); @@ -101,6 +103,40 @@ struct hid_device_ops mouse_ops = { .get_report = mouse_get_report, }; +static const char *bc12type_to_str(const enum bc12_type type) +{ + switch (type) { + case BC12_TYPE_NONE: + return "NONE"; + case BC12_TYPE_SDP: + return "SDP"; + case BC12_TYPE_DCP: + return "DCP"; + case BC12_TYPE_CDP: + return "CDP"; + case BC12_TYPE_PROPRIETARY: + return "PROPRIETARY"; + case BC12_TYPE_UNKNOWN: + return "UNKNOWN"; + default: + return "ERROR"; + } +} + +static void bc12_result_cb(const struct device *dev, + struct bc12_partner_state *const state, + void *const user_data) +{ + if (state->bc12_role != BC12_PORTABLE_DEVICE) { + LOG_ERR("Unexpected BC role"); + return; + } + + LOG_INF("New BC state %s (%d uV %d uA)", + bc12type_to_str(state->type), state->voltage_uv, state->current_ua); + +} + int main(void) { struct usbd_context *sample_usbd; @@ -138,6 +174,20 @@ int main(void) return -ENODEV; } + if (IS_ENABLED(CONFIG_USB_BC12)) { + if (!device_is_ready(bc12_dev)) { + LOG_ERR("USB BC1.2 device %s is not ready", bc12_dev->name); + return -EIO; + } + + bc12_set_result_cb(bc12_dev, &bc12_result_cb, sample_usbd); + ret = bc12_set_role(bc12_dev, BC12_PORTABLE_DEVICE); + if (ret != 0) { + LOG_ERR("Failed to set BC12 role"); + return -EIO; + } + } + ret = usbd_enable(sample_usbd); if (ret != 0) { LOG_ERR("Failed to enable device support");