/* * Copyright (c) 2017 Christer Weinigel. * Copyright (c) 2017, I-SENSE group of ICCS * * SPDX-License-Identifier: Apache-2.0 */ /** * @file * @brief USB device controller shim driver for STM32 devices * * This driver uses the STM32 Cube low level drivers to talk to the USB * device controller on the STM32 family of devices using the * STM32Cube HAL layer. */ #include #include #include #include #include #include #include #include #include #include #include #include "stm32_hsem.h" #define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL #include LOG_MODULE_REGISTER(usb_dc_stm32); #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otgfs) && DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs) #error "Only one interface should be enabled at a time, OTG FS or OTG HS" #endif #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs) #define DT_DRV_COMPAT st_stm32_otghs #define USB_IRQ_NAME otghs #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otgfs) #define DT_DRV_COMPAT st_stm32_otgfs #define USB_IRQ_NAME otgfs #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32_usb) #define DT_DRV_COMPAT st_stm32_usb #define USB_IRQ_NAME usb #if DT_INST_PROP(0, enable_pin_remap) #define USB_ENABLE_PIN_REMAP DT_INST_PROP(0, enable_pin_remap) #warning "Property deprecated in favor of property 'remap-pa11-pa12' from 'st-stm32-pinctrl'" #endif #endif #define USB_BASE_ADDRESS DT_INST_REG_ADDR(0) #define USB_IRQ DT_INST_IRQ_BY_NAME(0, USB_IRQ_NAME, irq) #define USB_IRQ_PRI DT_INST_IRQ_BY_NAME(0, USB_IRQ_NAME, priority) #define USB_NUM_BIDIR_ENDPOINTS DT_INST_PROP(0, num_bidir_endpoints) #define USB_RAM_SIZE DT_INST_PROP(0, ram_size) #define USB_CLOCK_BITS DT_INST_CLOCKS_CELL(0, bits) #define USB_CLOCK_BUS DT_INST_CLOCKS_CELL(0, bus) #if DT_INST_NODE_HAS_PROP(0, maximum_speed) #define USB_MAXIMUM_SPEED DT_INST_PROP(0, maximum_speed) #endif PINCTRL_DT_INST_DEFINE(0); static const struct pinctrl_dev_config *usb_pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0); #define USB_OTG_HS_EMB_PHY (DT_HAS_COMPAT_STATUS_OKAY(st_stm32_usbphyc) && \ DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs)) /* * USB, USB_OTG_FS and USB_DRD_FS are defined in STM32Cube HAL and allows to * distinguish between two kind of USB DC. STM32 F0, F3, L0 and G4 series * support USB device controller. STM32 F4 and F7 series support USB_OTG_FS * device controller. STM32 F1 and L4 series support either USB or USB_OTG_FS * device controller.STM32 G0 series supports USB_DRD_FS device controller. * * WARNING: Don't mix USB defined in STM32Cube HAL and CONFIG_USB_* from Zephyr * Kconfig system. */ #if defined(USB) || defined(USB_DRD_FS) #define EP0_MPS 64U #define EP_MPS 64U /* * USB BTABLE is stored in the PMA. The size of BTABLE is 4 bytes * per endpoint. * */ #define USB_BTABLE_SIZE (8 * USB_NUM_BIDIR_ENDPOINTS) #else /* USB_OTG_FS */ /* * STM32L4 series USB LL API doesn't provide HIGH and HIGH_IN_FULL speed * defines. */ #if defined(CONFIG_SOC_SERIES_STM32L4X) #define USB_OTG_SPEED_HIGH 0U #define USB_OTG_SPEED_HIGH_IN_FULL 1U #endif /* CONFIG_SOC_SERIES_STM32L4X */ #define EP0_MPS USB_OTG_MAX_EP0_SIZE #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs) #define EP_MPS USB_OTG_HS_MAX_PACKET_SIZE #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otgfs) || DT_HAS_COMPAT_STATUS_OKAY(st_stm32_usb) #define EP_MPS USB_OTG_FS_MAX_PACKET_SIZE #endif /* We need one RX FIFO and n TX-IN FIFOs */ #define FIFO_NUM (1 + USB_NUM_BIDIR_ENDPOINTS) /* 4-byte words FIFO */ #define FIFO_WORDS (USB_RAM_SIZE / 4) /* Allocate FIFO memory evenly between the FIFOs */ #define FIFO_EP_WORDS (FIFO_WORDS / FIFO_NUM) #endif /* USB */ /* Size of a USB SETUP packet */ #define SETUP_SIZE 8 /* Helper macros to make it easier to work with endpoint numbers */ #define EP0_IDX 0 #define EP0_IN (EP0_IDX | USB_EP_DIR_IN) #define EP0_OUT (EP0_IDX | USB_EP_DIR_OUT) /* Endpoint state */ struct usb_dc_stm32_ep_state { uint16_t ep_mps; /** Endpoint max packet size */ uint16_t ep_pma_buf_len; /** Previously allocated buffer size */ uint8_t ep_type; /** Endpoint type (STM32 HAL enum) */ uint8_t ep_stalled; /** Endpoint stall flag */ usb_dc_ep_callback cb; /** Endpoint callback function */ uint32_t read_count; /** Number of bytes in read buffer */ uint32_t read_offset; /** Current offset in read buffer */ struct k_sem write_sem; /** Write boolean semaphore */ }; /* Driver state */ struct usb_dc_stm32_state { PCD_HandleTypeDef pcd; /* Storage for the HAL_PCD api */ usb_dc_status_callback status_cb; /* Status callback */ struct usb_dc_stm32_ep_state out_ep_state[USB_NUM_BIDIR_ENDPOINTS]; struct usb_dc_stm32_ep_state in_ep_state[USB_NUM_BIDIR_ENDPOINTS]; uint8_t ep_buf[USB_NUM_BIDIR_ENDPOINTS][EP_MPS]; #if defined(USB) || defined(USB_DRD_FS) uint32_t pma_offset; #endif /* USB */ }; static struct usb_dc_stm32_state usb_dc_stm32_state; /* Internal functions */ static struct usb_dc_stm32_ep_state *usb_dc_stm32_get_ep_state(uint8_t ep) { struct usb_dc_stm32_ep_state *ep_state_base; if (USB_EP_GET_IDX(ep) >= USB_NUM_BIDIR_ENDPOINTS) { return NULL; } if (USB_EP_DIR_IS_OUT(ep)) { ep_state_base = usb_dc_stm32_state.out_ep_state; } else { ep_state_base = usb_dc_stm32_state.in_ep_state; } return ep_state_base + USB_EP_GET_IDX(ep); } static void usb_dc_stm32_isr(const void *arg) { HAL_PCD_IRQHandler(&usb_dc_stm32_state.pcd); } #ifdef CONFIG_USB_DEVICE_SOF void HAL_PCD_SOFCallback(PCD_HandleTypeDef *hpcd) { usb_dc_stm32_state.status_cb(USB_DC_SOF, NULL); } #endif static int usb_dc_stm32_clock_enable(void) { const struct device *clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); struct stm32_pclken pclken = { .bus = USB_CLOCK_BUS, .enr = USB_CLOCK_BITS, }; /* * Some SoCs in STM32F0/L0/L4 series disable USB clock by * default. We force USB clock source to MSI or PLL clock for this * SoCs. However, if these parts have an HSI48 clock, use * that instead. Example reference manual RM0360 for * STM32F030x4/x6/x8/xC and STM32F070x6/xB. */ #if defined(RCC_HSI48_SUPPORT) || \ defined(CONFIG_SOC_SERIES_STM32WBX) || \ defined(CONFIG_SOC_SERIES_STM32H7X) || \ defined(CONFIG_SOC_SERIES_STM32L5X) || \ defined(CONFIG_SOC_SERIES_STM32U5X) /* * In STM32L0 series, HSI48 requires VREFINT and its buffer * with 48 MHz RC to be enabled. * See ENREF_HSI48 in reference manual RM0367 section10.2.3: * "Reference control and status register (SYSCFG_CFGR3)" */ #ifdef CONFIG_SOC_SERIES_STM32L0X if (LL_APB2_GRP1_IsEnabledClock(LL_APB2_GRP1_PERIPH_SYSCFG)) { LL_SYSCFG_VREFINT_EnableHSI48(); } else { LOG_ERR("System Configuration Controller clock is " "disabled. Unable to enable VREFINT which " "is required by HSI48."); } #endif /* CONFIG_SOC_SERIES_STM32L0X */ z_stm32_hsem_lock(CFG_HW_CLK48_CONFIG_SEMID, HSEM_LOCK_DEFAULT_RETRY); LL_RCC_HSI48_Enable(); while (!LL_RCC_HSI48_IsReady()) { /* Wait for HSI48 to become ready */ } LL_RCC_SetUSBClockSource(LL_RCC_USB_CLKSOURCE_HSI48); #ifdef CONFIG_SOC_SERIES_STM32U5X /* VDDUSB independent USB supply (PWR clock is on) */ LL_PWR_EnableVDDUSB(); #endif /* CONFIG_SOC_SERIES_STM32U5X */ #if !defined(CONFIG_SOC_SERIES_STM32WBX) /* Specially for STM32WB, don't unlock the HSEM to prevent M0 core * to disable HSI48 clock used for RNG. */ z_stm32_hsem_unlock(CFG_HW_CLK48_CONFIG_SEMID); #endif /* CONFIG_SOC_SERIES_STM32WBX */ #elif defined(LL_RCC_USB_CLKSOURCE_NONE) /* When MSI is configured in PLL mode with a 32.768 kHz clock source, * the MSI frequency can be automatically trimmed by hardware to reach * better than ±0.25% accuracy. In this mode the MSI can feed the USB * device. For now, we only use MSI for USB if not already used as * system clock source. */ #if STM32_MSI_PLL_MODE && !STM32_SYSCLK_SRC_MSI LL_RCC_MSI_Enable(); while (!LL_RCC_MSI_IsReady()) { /* Wait for MSI to become ready */ } /* Force 48 MHz mode */ LL_RCC_MSI_EnableRangeSelection(); LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_11); LL_RCC_SetUSBClockSource(LL_RCC_USB_CLKSOURCE_MSI); #else if (LL_RCC_PLL_IsReady()) { LL_RCC_SetUSBClockSource(LL_RCC_USB_CLKSOURCE_PLL); } else { LOG_ERR("Unable to set USB clock source to PLL."); } #endif /* STM32_MSI_PLL_MODE && !STM32_SYSCLK_SRC_MSI */ #elif defined(RCC_CFGR_OTGFSPRE) /* On STM32F105 and STM32F107 parts the USB OTGFSCLK is derived from * PLL1, and must result in a 48 MHz clock... the options to achieve * this are as below, controlled by the RCC_CFGR_OTGFSPRE bit. * - PLLCLK * 2 / 2 i.e: PLLCLK == 48 MHz * - PLLCLK * 2 / 3 i.e: PLLCLK == 72 MHz * * this requires that the system is running from PLLCLK */ if (LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_PLL) { switch (sys_clock_hw_cycles_per_sec()) { case 48000000U: LL_RCC_SetUSBClockSource(LL_RCC_USB_CLKSOURCE_PLL_DIV_2); break; case 72000000U: LL_RCC_SetUSBClockSource(LL_RCC_USB_CLKSOURCE_PLL_DIV_3); break; default: LOG_ERR("Unable to set USB clock source (incompatible PLLCLK rate)"); return -EIO; } } else { LOG_ERR("Unable to set USB clock source (not using PLL1)"); return -EIO; } #endif /* RCC_HSI48_SUPPORT / LL_RCC_USB_CLKSOURCE_NONE / RCC_CFGR_OTGFSPRE */ if (clock_control_on(clk, (clock_control_subsys_t *)&pclken) != 0) { LOG_ERR("Unable to enable USB clock"); return -EIO; } #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs) #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_usbphyc) LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_OTGHSULPI); LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_OTGPHYC); #else /* Disable ULPI interface (for external high-speed PHY) clock */ LL_AHB1_GRP1_DisableClock(LL_AHB1_GRP1_PERIPH_OTGHSULPI); LL_AHB1_GRP1_DisableClockLowPower(LL_AHB1_GRP1_PERIPH_OTGHSULPI); #endif #endif return 0; } #if defined(USB_OTG_FS) || defined(USB_OTG_HS) static uint32_t usb_dc_stm32_get_maximum_speed(void) { /* * If max-speed is not passed via DT, set it to USB controller's * maximum hardware capability. */ #if USB_OTG_HS_EMB_PHY uint32_t speed = USB_OTG_SPEED_HIGH; #else uint32_t speed = USB_OTG_SPEED_FULL; #endif #ifdef USB_MAXIMUM_SPEED if (!strncmp(USB_MAXIMUM_SPEED, "high-speed", 10)) { speed = USB_OTG_SPEED_HIGH; } else if (!strncmp(USB_MAXIMUM_SPEED, "full-speed", 10)) { #if defined(CONFIG_SOC_SERIES_STM32H7X) || defined(USB_OTG_HS_EMB_PHY) speed = USB_OTG_SPEED_HIGH_IN_FULL; #else speed = USB_OTG_SPEED_FULL; #endif } else { LOG_DBG("Unsupported maximum speed defined in device tree. " "USB controller will default to its maximum HW " "capability"); } #endif return speed; } #endif /* USB_OTG_FS || USB_OTG_HS */ static int usb_dc_stm32_init(void) { HAL_StatusTypeDef status; unsigned int i; #if defined(USB) || defined(USB_DRD_FS) #ifdef USB usb_dc_stm32_state.pcd.Instance = USB; #else usb_dc_stm32_state.pcd.Instance = USB_DRD_FS; #endif usb_dc_stm32_state.pcd.Init.speed = PCD_SPEED_FULL; usb_dc_stm32_state.pcd.Init.dev_endpoints = USB_NUM_BIDIR_ENDPOINTS; usb_dc_stm32_state.pcd.Init.phy_itface = PCD_PHY_EMBEDDED; usb_dc_stm32_state.pcd.Init.ep0_mps = PCD_EP0MPS_64; usb_dc_stm32_state.pcd.Init.low_power_enable = 0; #else /* USB_OTG_FS || USB_OTG_HS */ #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs) usb_dc_stm32_state.pcd.Instance = USB_OTG_HS; #else usb_dc_stm32_state.pcd.Instance = USB_OTG_FS; #endif usb_dc_stm32_state.pcd.Init.dev_endpoints = USB_NUM_BIDIR_ENDPOINTS; usb_dc_stm32_state.pcd.Init.speed = usb_dc_stm32_get_maximum_speed(); #if USB_OTG_HS_EMB_PHY usb_dc_stm32_state.pcd.Init.phy_itface = USB_OTG_HS_EMBEDDED_PHY; #else usb_dc_stm32_state.pcd.Init.phy_itface = PCD_PHY_EMBEDDED; #endif usb_dc_stm32_state.pcd.Init.ep0_mps = USB_OTG_MAX_EP0_SIZE; usb_dc_stm32_state.pcd.Init.vbus_sensing_enable = DISABLE; #ifndef CONFIG_SOC_SERIES_STM32F1X usb_dc_stm32_state.pcd.Init.dma_enable = DISABLE; #endif #endif /* USB */ #ifdef CONFIG_USB_DEVICE_SOF usb_dc_stm32_state.pcd.Init.Sof_enable = 1; #endif /* CONFIG_USB_DEVICE_SOF */ #if defined(CONFIG_SOC_SERIES_STM32H7X) /* Currently assuming FS mode. Need to disable the ULPI clock on USB2 and * enable the FS clock. Need to make this dependent on HS or FS config. */ LL_AHB1_GRP1_DisableClock(LL_AHB1_GRP1_PERIPH_USB2OTGHSULPI); LL_AHB1_GRP1_DisableClockSleep(LL_AHB1_GRP1_PERIPH_USB2OTGHSULPI); LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_USB2OTGHS); LL_AHB1_GRP1_EnableClockSleep(LL_AHB1_GRP1_PERIPH_USB2OTGHS); LL_PWR_EnableUSBVoltageDetector(); /* Per AN2606: USBREGEN not supported when running in FS mode. */ LL_PWR_DisableUSBReg(); while (!LL_PWR_IsActiveFlag_USB()) { LOG_INF("PWR not active yet"); k_sleep(K_MSEC(100)); } #endif LOG_DBG("Pinctrl signals configuration"); status = pinctrl_apply_state(usb_pcfg, PINCTRL_STATE_DEFAULT); if (status < 0) { LOG_ERR("USB pinctrl setup failed (%d)", status); return status; } LOG_DBG("HAL_PCD_Init"); status = HAL_PCD_Init(&usb_dc_stm32_state.pcd); if (status != HAL_OK) { LOG_ERR("PCD_Init failed, %d", (int)status); return -EIO; } LOG_DBG("HAL_PCD_Start"); status = HAL_PCD_Start(&usb_dc_stm32_state.pcd); if (status != HAL_OK) { LOG_ERR("PCD_Start failed, %d", (int)status); return -EIO; } usb_dc_stm32_state.out_ep_state[EP0_IDX].ep_mps = EP0_MPS; usb_dc_stm32_state.out_ep_state[EP0_IDX].ep_type = EP_TYPE_CTRL; usb_dc_stm32_state.in_ep_state[EP0_IDX].ep_mps = EP0_MPS; usb_dc_stm32_state.in_ep_state[EP0_IDX].ep_type = EP_TYPE_CTRL; #if defined(USB) || defined(USB_DRD_FS) /* Start PMA configuration for the endpoints after the BTABLE. */ usb_dc_stm32_state.pma_offset = USB_BTABLE_SIZE; for (i = 0U; i < USB_NUM_BIDIR_ENDPOINTS; i++) { k_sem_init(&usb_dc_stm32_state.in_ep_state[i].write_sem, 1, 1); } #else /* USB_OTG_FS */ /* TODO: make this dynamic (depending usage) */ HAL_PCDEx_SetRxFiFo(&usb_dc_stm32_state.pcd, FIFO_EP_WORDS); for (i = 0U; i < USB_NUM_BIDIR_ENDPOINTS; i++) { HAL_PCDEx_SetTxFiFo(&usb_dc_stm32_state.pcd, i, FIFO_EP_WORDS); k_sem_init(&usb_dc_stm32_state.in_ep_state[i].write_sem, 1, 1); } #endif /* USB */ IRQ_CONNECT(USB_IRQ, USB_IRQ_PRI, usb_dc_stm32_isr, 0, 0); irq_enable(USB_IRQ); return 0; } /* Zephyr USB device controller API implementation */ int usb_dc_attach(void) { int ret; LOG_DBG(""); #ifdef SYSCFG_CFGR1_USB_IT_RMP /* * STM32F302/F303: USB IRQ collides with CAN_1 IRQ (§14.1.3, RM0316) * Remap IRQ by default to enable use of both IPs simultaneoulsy * This should be done before calling any HAL function */ if (LL_APB2_GRP1_IsEnabledClock(LL_APB2_GRP1_PERIPH_SYSCFG)) { LL_SYSCFG_EnableRemapIT_USB(); } else { LOG_ERR("System Configuration Controller clock is " "disabled. Unable to enable IRQ remapping."); } #endif /* * For STM32F0 series SoCs on QFN28 and TSSOP20 packages enable PIN * pair PA11/12 mapped instead of PA9/10 (e.g. stm32f070x6) */ #if USB_ENABLE_PIN_REMAP == 1 if (LL_APB1_GRP2_IsEnabledClock(LL_APB1_GRP2_PERIPH_SYSCFG)) { LL_SYSCFG_EnablePinRemap(); } else { LOG_ERR("System Configuration Controller clock is " "disabled. Unable to enable pin remapping."); } #endif ret = usb_dc_stm32_clock_enable(); if (ret) { return ret; } ret = usb_dc_stm32_init(); if (ret) { return ret; } /* * Required for at least STM32L4 devices as they electrically * isolate USB features from VDDUSB. It must be enabled before * USB can function. Refer to section 5.1.3 in DM00083560 or * DM00310109. */ #ifdef PWR_CR2_USV #if defined(LL_APB1_GRP1_PERIPH_PWR) if (LL_APB1_GRP1_IsEnabledClock(LL_APB1_GRP1_PERIPH_PWR)) { LL_PWR_EnableVddUSB(); } else { LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR); LL_PWR_EnableVddUSB(); LL_APB1_GRP1_DisableClock(LL_APB1_GRP1_PERIPH_PWR); } #else LL_PWR_EnableVddUSB(); #endif /* defined(LL_APB1_GRP1_PERIPH_PWR) */ #endif /* PWR_CR2_USV */ return 0; } int usb_dc_ep_set_callback(const uint8_t ep, const usb_dc_ep_callback cb) { struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); LOG_DBG("ep 0x%02x", ep); if (!ep_state) { return -EINVAL; } ep_state->cb = cb; return 0; } void usb_dc_set_status_callback(const usb_dc_status_callback cb) { LOG_DBG(""); usb_dc_stm32_state.status_cb = cb; } int usb_dc_set_address(const uint8_t addr) { HAL_StatusTypeDef status; LOG_DBG("addr %u (0x%02x)", addr, addr); status = HAL_PCD_SetAddress(&usb_dc_stm32_state.pcd, addr); if (status != HAL_OK) { LOG_ERR("HAL_PCD_SetAddress failed(0x%02x), %d", addr, (int)status); return -EIO; } return 0; } int usb_dc_ep_start_read(uint8_t ep, uint8_t *data, uint32_t max_data_len) { HAL_StatusTypeDef status; LOG_DBG("ep 0x%02x, len %u", ep, max_data_len); /* we flush EP0_IN by doing a 0 length receive on it */ if (!USB_EP_DIR_IS_OUT(ep) && (ep != EP0_IN || max_data_len)) { LOG_ERR("invalid ep 0x%02x", ep); return -EINVAL; } if (max_data_len > EP_MPS) { max_data_len = EP_MPS; } status = HAL_PCD_EP_Receive(&usb_dc_stm32_state.pcd, ep, usb_dc_stm32_state.ep_buf[USB_EP_GET_IDX(ep)], max_data_len); if (status != HAL_OK) { LOG_ERR("HAL_PCD_EP_Receive failed(0x%02x), %d", ep, (int)status); return -EIO; } return 0; } int usb_dc_ep_get_read_count(uint8_t ep, uint32_t *read_bytes) { if (!USB_EP_DIR_IS_OUT(ep) || !read_bytes) { LOG_ERR("invalid ep 0x%02x", ep); return -EINVAL; } *read_bytes = HAL_PCD_EP_GetRxCount(&usb_dc_stm32_state.pcd, ep); return 0; } int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data * const cfg) { uint8_t ep_idx = USB_EP_GET_IDX(cfg->ep_addr); LOG_DBG("ep %x, mps %d, type %d", cfg->ep_addr, cfg->ep_mps, cfg->ep_type); if ((cfg->ep_type == USB_DC_EP_CONTROL) && ep_idx) { LOG_ERR("invalid endpoint configuration"); return -1; } if (ep_idx > (USB_NUM_BIDIR_ENDPOINTS - 1)) { LOG_ERR("endpoint index/address out of range"); return -1; } return 0; } int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data * const ep_cfg) { uint8_t ep = ep_cfg->ep_addr; struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); if (!ep_state) { return -EINVAL; } LOG_DBG("ep 0x%02x, previous ep_mps %u, ep_mps %u, ep_type %u", ep_cfg->ep_addr, ep_state->ep_mps, ep_cfg->ep_mps, ep_cfg->ep_type); #if defined(USB) || defined(USB_DRD_FS) if (ep_cfg->ep_mps > ep_state->ep_pma_buf_len) { if (USB_RAM_SIZE <= (usb_dc_stm32_state.pma_offset + ep_cfg->ep_mps)) { return -EINVAL; } HAL_PCDEx_PMAConfig(&usb_dc_stm32_state.pcd, ep, PCD_SNG_BUF, usb_dc_stm32_state.pma_offset); ep_state->ep_pma_buf_len = ep_cfg->ep_mps; usb_dc_stm32_state.pma_offset += ep_cfg->ep_mps; } #endif ep_state->ep_mps = ep_cfg->ep_mps; switch (ep_cfg->ep_type) { case USB_DC_EP_CONTROL: ep_state->ep_type = EP_TYPE_CTRL; break; case USB_DC_EP_ISOCHRONOUS: ep_state->ep_type = EP_TYPE_ISOC; break; case USB_DC_EP_BULK: ep_state->ep_type = EP_TYPE_BULK; break; case USB_DC_EP_INTERRUPT: ep_state->ep_type = EP_TYPE_INTR; break; default: return -EINVAL; } return 0; } int usb_dc_ep_set_stall(const uint8_t ep) { struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); HAL_StatusTypeDef status; LOG_DBG("ep 0x%02x", ep); if (!ep_state) { return -EINVAL; } status = HAL_PCD_EP_SetStall(&usb_dc_stm32_state.pcd, ep); if (status != HAL_OK) { LOG_ERR("HAL_PCD_EP_SetStall failed(0x%02x), %d", ep, (int)status); return -EIO; } ep_state->ep_stalled = 1U; return 0; } int usb_dc_ep_clear_stall(const uint8_t ep) { struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); HAL_StatusTypeDef status; LOG_DBG("ep 0x%02x", ep); if (!ep_state) { return -EINVAL; } status = HAL_PCD_EP_ClrStall(&usb_dc_stm32_state.pcd, ep); if (status != HAL_OK) { LOG_ERR("HAL_PCD_EP_ClrStall failed(0x%02x), %d", ep, (int)status); return -EIO; } ep_state->ep_stalled = 0U; ep_state->read_count = 0U; return 0; } int usb_dc_ep_is_stalled(const uint8_t ep, uint8_t *const stalled) { struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); LOG_DBG("ep 0x%02x", ep); if (!ep_state || !stalled) { return -EINVAL; } *stalled = ep_state->ep_stalled; return 0; } int usb_dc_ep_enable(const uint8_t ep) { struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); HAL_StatusTypeDef status; LOG_DBG("ep 0x%02x", ep); if (!ep_state) { return -EINVAL; } LOG_DBG("HAL_PCD_EP_Open(0x%02x, %u, %u)", ep, ep_state->ep_mps, ep_state->ep_type); status = HAL_PCD_EP_Open(&usb_dc_stm32_state.pcd, ep, ep_state->ep_mps, ep_state->ep_type); if (status != HAL_OK) { LOG_ERR("HAL_PCD_EP_Open failed(0x%02x), %d", ep, (int)status); return -EIO; } if (USB_EP_DIR_IS_OUT(ep) && ep != EP0_OUT) { return usb_dc_ep_start_read(ep, usb_dc_stm32_state.ep_buf[USB_EP_GET_IDX(ep)], EP_MPS); } return 0; } int usb_dc_ep_disable(const uint8_t ep) { struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); HAL_StatusTypeDef status; LOG_DBG("ep 0x%02x", ep); if (!ep_state) { return -EINVAL; } status = HAL_PCD_EP_Close(&usb_dc_stm32_state.pcd, ep); if (status != HAL_OK) { LOG_ERR("HAL_PCD_EP_Close failed(0x%02x), %d", ep, (int)status); return -EIO; } return 0; } int usb_dc_ep_write(const uint8_t ep, const uint8_t *const data, const uint32_t data_len, uint32_t * const ret_bytes) { struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); HAL_StatusTypeDef status; uint32_t len = data_len; int ret = 0; LOG_DBG("ep 0x%02x, len %u", ep, data_len); if (!ep_state || !USB_EP_DIR_IS_IN(ep)) { LOG_ERR("invalid ep 0x%02x", ep); return -EINVAL; } ret = k_sem_take(&ep_state->write_sem, K_NO_WAIT); if (ret) { LOG_ERR("Unable to get write lock (%d)", ret); return -EAGAIN; } if (!k_is_in_isr()) { irq_disable(USB_IRQ); } if (ep == EP0_IN && len > USB_MAX_CTRL_MPS) { len = USB_MAX_CTRL_MPS; } status = HAL_PCD_EP_Transmit(&usb_dc_stm32_state.pcd, ep, (void *)data, len); if (status != HAL_OK) { LOG_ERR("HAL_PCD_EP_Transmit failed(0x%02x), %d", ep, (int)status); k_sem_give(&ep_state->write_sem); ret = -EIO; } if (!ret && ep == EP0_IN && len > 0) { /* Wait for an empty package as from the host. * This also flushes the TX FIFO to the host. */ usb_dc_ep_start_read(ep, NULL, 0); } if (!k_is_in_isr()) { irq_enable(USB_IRQ); } if (!ret && ret_bytes) { *ret_bytes = len; } return ret; } int usb_dc_ep_read_wait(uint8_t ep, uint8_t *data, uint32_t max_data_len, uint32_t *read_bytes) { struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); uint32_t read_count; if (!ep_state) { LOG_ERR("Invalid Endpoint %x", ep); return -EINVAL; } read_count = ep_state->read_count; LOG_DBG("ep 0x%02x, %u bytes, %u+%u, %p", ep, max_data_len, ep_state->read_offset, read_count, data); if (!USB_EP_DIR_IS_OUT(ep)) { /* check if OUT ep */ LOG_ERR("Wrong endpoint direction: 0x%02x", ep); return -EINVAL; } /* When both buffer and max data to read are zero, just ignore reading * and return available data in buffer. Otherwise, return data * previously stored in the buffer. */ if (data) { read_count = MIN(read_count, max_data_len); memcpy(data, usb_dc_stm32_state.ep_buf[USB_EP_GET_IDX(ep)] + ep_state->read_offset, read_count); ep_state->read_count -= read_count; ep_state->read_offset += read_count; } else if (max_data_len) { LOG_ERR("Wrong arguments"); } if (read_bytes) { *read_bytes = read_count; } return 0; } int usb_dc_ep_read_continue(uint8_t ep) { struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); if (!ep_state || !USB_EP_DIR_IS_OUT(ep)) { /* Check if OUT ep */ LOG_ERR("Not valid endpoint: %02x", ep); return -EINVAL; } /* If no more data in the buffer, start a new read transaction. * DataOutStageCallback will called on transaction complete. */ if (!ep_state->read_count) { usb_dc_ep_start_read(ep, usb_dc_stm32_state.ep_buf[USB_EP_GET_IDX(ep)], EP_MPS); } return 0; } int usb_dc_ep_read(const uint8_t ep, uint8_t *const data, const uint32_t max_data_len, uint32_t * const read_bytes) { if (usb_dc_ep_read_wait(ep, data, max_data_len, read_bytes) != 0) { return -EINVAL; } if (usb_dc_ep_read_continue(ep) != 0) { return -EINVAL; } return 0; } int usb_dc_ep_halt(const uint8_t ep) { return usb_dc_ep_set_stall(ep); } int usb_dc_ep_flush(const uint8_t ep) { struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); if (!ep_state) { return -EINVAL; } LOG_ERR("Not implemented"); return 0; } int usb_dc_ep_mps(const uint8_t ep) { struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); if (!ep_state) { return -EINVAL; } return ep_state->ep_mps; } int usb_dc_detach(void) { LOG_ERR("Not implemented"); #ifdef CONFIG_SOC_SERIES_STM32WBX /* Specially for STM32WB, unlock the HSEM when USB is no more used. */ z_stm32_hsem_unlock(CFG_HW_CLK48_CONFIG_SEMID); /* * TODO: AN5289 notes a process of locking Sem0, with possible waits * via interrupts before switching off CLK48, but lacking any actual * examples of that, that remains to be implemented. See * https://github.com/zephyrproject-rtos/zephyr/pull/25850 */ #endif /* CONFIG_SOC_SERIES_STM32WBX */ return 0; } int usb_dc_reset(void) { LOG_ERR("Not implemented"); return 0; } /* Callbacks from the STM32 Cube HAL code */ void HAL_PCD_ResetCallback(PCD_HandleTypeDef *hpcd) { int i; LOG_DBG(""); HAL_PCD_EP_Open(&usb_dc_stm32_state.pcd, EP0_IN, EP0_MPS, EP_TYPE_CTRL); HAL_PCD_EP_Open(&usb_dc_stm32_state.pcd, EP0_OUT, EP0_MPS, EP_TYPE_CTRL); /* The DataInCallback will never be called at this point for any pending * transactions. Reset the IN semaphores to prevent perpetual locked state. * */ for (i = 0; i < USB_NUM_BIDIR_ENDPOINTS; i++) { k_sem_give(&usb_dc_stm32_state.in_ep_state[i].write_sem); } if (usb_dc_stm32_state.status_cb) { usb_dc_stm32_state.status_cb(USB_DC_RESET, NULL); } } void HAL_PCD_ConnectCallback(PCD_HandleTypeDef *hpcd) { LOG_DBG(""); if (usb_dc_stm32_state.status_cb) { usb_dc_stm32_state.status_cb(USB_DC_CONNECTED, NULL); } } void HAL_PCD_DisconnectCallback(PCD_HandleTypeDef *hpcd) { LOG_DBG(""); if (usb_dc_stm32_state.status_cb) { usb_dc_stm32_state.status_cb(USB_DC_DISCONNECTED, NULL); } } void HAL_PCD_SuspendCallback(PCD_HandleTypeDef *hpcd) { LOG_DBG(""); if (usb_dc_stm32_state.status_cb) { usb_dc_stm32_state.status_cb(USB_DC_SUSPEND, NULL); } } void HAL_PCD_ResumeCallback(PCD_HandleTypeDef *hpcd) { LOG_DBG(""); if (usb_dc_stm32_state.status_cb) { usb_dc_stm32_state.status_cb(USB_DC_RESUME, NULL); } } void HAL_PCD_SetupStageCallback(PCD_HandleTypeDef *hpcd) { struct usb_setup_packet *setup = (void *)usb_dc_stm32_state.pcd.Setup; struct usb_dc_stm32_ep_state *ep_state; LOG_DBG(""); ep_state = usb_dc_stm32_get_ep_state(EP0_OUT); /* can't fail for ep0 */ __ASSERT(ep_state, "No corresponding ep_state for EP0"); ep_state->read_count = SETUP_SIZE; ep_state->read_offset = 0U; memcpy(&usb_dc_stm32_state.ep_buf[EP0_IDX], usb_dc_stm32_state.pcd.Setup, ep_state->read_count); if (ep_state->cb) { ep_state->cb(EP0_OUT, USB_DC_EP_SETUP); if (!(setup->wLength == 0U) && usb_reqtype_is_to_device(setup)) { usb_dc_ep_start_read(EP0_OUT, usb_dc_stm32_state.ep_buf[EP0_IDX], setup->wLength); } } } void HAL_PCD_DataOutStageCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum) { uint8_t ep_idx = USB_EP_GET_IDX(epnum); uint8_t ep = ep_idx | USB_EP_DIR_OUT; struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); LOG_DBG("epnum 0x%02x, rx_count %u", epnum, HAL_PCD_EP_GetRxCount(&usb_dc_stm32_state.pcd, epnum)); /* Transaction complete, data is now stored in the buffer and ready * for the upper stack (usb_dc_ep_read to retrieve). */ usb_dc_ep_get_read_count(ep, &ep_state->read_count); ep_state->read_offset = 0U; if (ep_state->cb) { ep_state->cb(ep, USB_DC_EP_DATA_OUT); } } void HAL_PCD_DataInStageCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum) { uint8_t ep_idx = USB_EP_GET_IDX(epnum); uint8_t ep = ep_idx | USB_EP_DIR_IN; struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); LOG_DBG("epnum 0x%02x", epnum); __ASSERT(ep_state, "No corresponding ep_state for ep"); k_sem_give(&ep_state->write_sem); if (ep_state->cb) { ep_state->cb(ep, USB_DC_EP_DATA_IN); } } #if (defined(USB) || defined(USB_DRD_FS)) && DT_INST_NODE_HAS_PROP(0, disconnect_gpios) void HAL_PCDEx_SetConnectionState(PCD_HandleTypeDef *hpcd, uint8_t state) { const struct device *usb_disconnect; usb_disconnect = device_get_binding( DT_GPIO_LABEL(DT_INST(0, st_stm32_usb), disconnect_gpios)); gpio_pin_configure(usb_disconnect, DT_GPIO_PIN(DT_INST(0, st_stm32_usb), disconnect_gpios), DT_GPIO_FLAGS(DT_INST(0, st_stm32_usb), disconnect_gpios) | (state ? GPIO_OUTPUT_ACTIVE : GPIO_OUTPUT_INACTIVE)); } #endif /* USB && DT_INST_NODE_HAS_PROP(0, disconnect_gpios) */