/* * The MIT License (MIT) * * Copyright 2021 Bridgetek Pte Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * This file is part of the TinyUSB stack. */ /* * Contains code adapted from Bridgetek Pte Ltd via license terms stated * in https://brtchip.com/BRTSourceCodeLicenseAgreement */ #include "tusb_option.h" #if CFG_TUD_ENABLED && \ (CFG_TUSB_MCU == OPT_MCU_FT90X || CFG_TUSB_MCU == OPT_MCU_FT93X) #include #include #include #include "board.h" #include "bsp/board.h" #define USBD_USE_STREAMS #include "device/dcd.h" //--------------------------------------------------------------------+ // MACRO TYPEDEF CONSTANT ENUM DECLARATION //--------------------------------------------------------------------+ // Board code will determine the state of VBUS from USB host. extern int8_t board_ft90x_vbus(void); // Static array to store an incoming SETUP request for processing by tinyusb. static uint8_t _ft90x_setup_packet[8]; struct ft90x_xfer_state { volatile uint8_t valid; // Transfer is pending and total_size, remain_size, and buff_ptr are valid. volatile int16_t total_size; // Total transfer size in bytes for this transfer. volatile int16_t remain_size; // Total remaining in transfer. volatile uint8_t *buff_ptr; // Pointer to buffer to transmit from or receive to. uint8_t type; // Endpoint type. Of type USBD_ENDPOINT_TYPE from endpoint descriptor. uint8_t dir; // Endpoint direction. TUSB_DIR_OUT or TUSB_DIR_IN. For control endpoint this is the current direction. uint16_t buff_size; // Actual size of buffer RAM used by endpoint. uint16_t size; // Max packet size for endpoint from endpoint descriptor. }; // Endpoint description array for each endpoint. static struct ft90x_xfer_state ep_xfer[USBD_MAX_ENDPOINT_COUNT]; // USB speed. static tusb_speed_t _speed; // Interrupt handlers. void _ft90x_usbd_ISR(void); // Interrupt handler for USB device. void ft90x_usbd_pm_ISR(void); // Interrupt handler for USB device for power management (called by board). // Internal functions forward declarations. static uint16_t _ft90x_edpt_xfer_out(uint8_t ep_number, uint8_t *buffer, uint16_t xfer_bytes); static uint16_t _ft90x_edpt_xfer_in(uint8_t ep_number, uint8_t *buffer, uint16_t xfer_bytes); static void _ft90x_reset_edpts(void); static inline void _ft90x_phy_enable(bool en); static void _ft90x_usb_speed(void); static void _dcd_ft90x_attach(void); static void _dcd_ft90x_detach(void) __attribute__((unused)); static uint16_t _ft90x_dusb_in(uint8_t ep_number, const uint8_t *buffer, uint16_t length); static uint16_t _ft90x_dusb_out(uint8_t ep_number, uint8_t *buffer, uint16_t length); // Internal functions. // Manage an OUT transfer from the host. // This can be up-to the maximum packet size of the endpoint. // Continuation of a transfer beyond the maximum packet size is performed // by the interrupt handler. static uint16_t _ft90x_edpt_xfer_out(uint8_t ep_number, uint8_t *buffer, uint16_t xfer_bytes) { //Note: this is called from only the interrupt handler when an OUT transfer is called. uint16_t ep_size = ep_xfer[ep_number].size; (void)ep_size; if (xfer_bytes > ep_size) { xfer_bytes = ep_size; } // Wait until the endpoint has finished - it should be complete! //while (!(USBD_EP_SR_REG(ep_number) & MASK_USBD_EPxSR_OPRDY)) //; // Send the first packet of max packet size xfer_bytes = _ft90x_dusb_out(ep_number, (uint8_t *)buffer, xfer_bytes); if (ep_number == USBD_EP_0) { // Set flags to indicate data ready. USBD_EP_SR_REG(USBD_EP_0) = (MASK_USBD_EP0SR_OPRDY); } else { USBD_EP_SR_REG(ep_number) = (MASK_USBD_EPxSR_OPRDY); } return xfer_bytes; } // Manage an IN transfer to the host. // This can be up-to the maximum packet size of the endpoint. // Continuation of a transfer beyond the maximum packet size is performed // by the interrupt handler. static uint16_t _ft90x_edpt_xfer_in(uint8_t ep_number, uint8_t *buffer, uint16_t xfer_bytes) { //Note: this may be called from the interrupt handler or from normal code. uint8_t end = 0; uint16_t ep_size = ep_xfer[ep_number].size; (void)ep_size; if ((xfer_bytes == 0) || (xfer_bytes < ep_size)) { end = 1; } else { xfer_bytes = ep_size; } if (ep_number == USBD_EP_0) { // An IN direction SETUP can be interrupted by an OUT packet. // This will result in a STALL generated by the silicon. while (USBD_EP_SR_REG(USBD_EP_0) & MASK_USBD_EP0SR_STALL) { // Clear the STALL and finish the transaction. USBD_EP_SR_REG(USBD_EP_0) = (MASK_USBD_EP0SR_STALL); } } else { uint8_t sr_reg; // If there is data to transmit then wait until the IN buffer // for the endpoint is empty. do { sr_reg = USBD_EP_SR_REG(ep_number); } while (sr_reg & MASK_USBD_EPxSR_INPRDY); } xfer_bytes = _ft90x_dusb_in(ep_number, (uint8_t *)buffer, xfer_bytes); if (ep_number == USBD_EP_0) { if (end) { // Set flags to indicate data ready and transfer complete. USBD_EP_SR_REG(USBD_EP_0) = MASK_USBD_EP0SR_INPRDY | MASK_USBD_EP0SR_DATAEND; } else { // Set flags to indicate data ready. USBD_EP_SR_REG(USBD_EP_0) = (MASK_USBD_EP0SR_INPRDY); } } else { // Set flags to indicate data ready. USBD_EP_SR_REG(ep_number) = (MASK_USBD_EPxSR_INPRDY); } return xfer_bytes; } // Reset all non-control endpoints to a default state. // Control endpoint is always enabled and ready. All others disabled. static void _ft90x_reset_edpts(void) { // Disable all endpoints and remove configuration values. for (int i = 1; i < USBD_MAX_ENDPOINT_COUNT; i++) { // Clear settings. tu_memclr(&ep_xfer[i], sizeof(struct ft90x_xfer_state)); // Disable hardware. USBD_EP_CR_REG(i) = 0; } // Enable interrupts from USB device control. USBD_REG(cmie) = MASK_USBD_CMIE_ALL; } // Enable or disable the USB PHY. static inline void _ft90x_phy_enable(bool en) { if (en) SYS->PMCFG_L |= MASK_SYS_PMCFG_DEV_PHY_EN; else SYS->PMCFG_L &= ~MASK_SYS_PMCFG_DEV_PHY_EN; } // Safely connect to the USB. static void _dcd_ft90x_attach(void) { uint8_t reg; CRITICAL_SECTION_BEGIN // Disable device responses. USBD_REG(faddr) = 0; // Reset USB Device. SYS->MSC0CFG = SYS->MSC0CFG | MASK_SYS_MSC0CFG_DEV_RESET_ALL; // Disable device connect/disconnect/host reset detection. SYS->PMCFG_H = MASK_SYS_PMCFG_DEV_DIS_DEV; SYS->PMCFG_H = MASK_SYS_PMCFG_DEV_CONN_DEV; SYS->PMCFG_L = SYS->PMCFG_L & (~MASK_SYS_PMCFG_DEV_DETECT_EN); // Enable Chip USB device clock/PM configuration. sys_enable(sys_device_usb_device); CRITICAL_SECTION_END; // Wait a short time to get started. delayms(1); CRITICAL_SECTION_BEGIN // Turn off the device enable bit. #if BOARD_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED USBD_REG(fctrl) = 0; #else // BOARD_TUD_MAX_SPEED == OPT_MODE_FULL_SPEED //Set the full speed only bit if required. USBD_REG(fctrl) = MASK_USBD_FCTRL_MODE_FS_ONLY; #endif // BOARD_TUD_MAX_SPEED // Clear first reset and suspend interrupts. do { reg = USBD_REG(cmif); USBD_REG(cmif) = reg; } while (reg); // Clear any endpoint interrupts. reg = USBD_REG(epif); USBD_REG(epif) = reg; // Disable all interrupts from USB device control before attaching interrupt. USBD_REG(cmie) = 0; CRITICAL_SECTION_END; // Enable device connect/disconnect/host reset detection. // Set device detect and remote wakeup enable interrupt enables. SYS->PMCFG_L = SYS->PMCFG_L | MASK_SYS_PMCFG_DEV_DETECT_EN; #if defined(__FT930__) // Setup VBUS detect SYS->MSC0CFG = SYS->MSC0CFG | MASK_SYS_MSC0CFG_USB_VBUS_EN; #endif } // Gracefully disconnect from the USB. static void _dcd_ft90x_detach(void) { // Disable device connect/disconnect/host reset detection. SYS->PMCFG_L = SYS->PMCFG_L & (~MASK_SYS_PMCFG_DEV_DETECT_EN); #if defined(__FT930__) // Disable VBUS detection. SYS->MSC0CFG = SYS->MSC0CFG & (~MASK_SYS_MSC0CFG_USB_VBUS_EN); #endif CRITICAL_SECTION_BEGIN // Disable interrupts from USB. USBD_REG(epie) = 0; USBD_REG(cmie) = 0; // Turn off the device enable bit. USBD_REG(fctrl) = 0; CRITICAL_SECTION_END; delayms(1); // Disable USB PHY dcd_disconnect(BOARD_TUD_RHPORT); delayms(1); // Disable Chip USB device clock/PM configuration. sys_disable(sys_device_usb_device); // Reset USB Device... Needed for Back voltage D+ to be <400mV SYS->MSC0CFG = SYS->MSC0CFG | MASK_SYS_MSC0CFG_DEV_RESET_ALL; delayms(1); // Set device detect and remote wakeup enable interrupt enables. SYS->PMCFG_L = SYS->PMCFG_L | MASK_SYS_PMCFG_DEV_DETECT_EN; #if defined(__FT930__) // Setup VBUS detect SYS->MSC0CFG = SYS->MSC0CFG | MASK_SYS_MSC0CFG_USB_VBUS_EN; #endif } // Determine the speed of the USB to which we are connected. // Set the speed of the PHY accordingly. // High speed can be disabled through CFG_TUSB_RHPORT0_MODE or CFG_TUD_MAX_SPEED settings. static void _ft90x_usb_speed(void) { uint8_t fctrl_val; // If USB device function is already enabled then disable it. if (USBD_REG(fctrl) & MASK_USBD_FCTRL_USB_DEV_EN) { USBD_REG(fctrl) = (USBD_REG(fctrl) & (~MASK_USBD_FCTRL_USB_DEV_EN)); delayus(200); } #if BOARD_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED /* Detect high or full speed */ fctrl_val = MASK_USBD_FCTRL_USB_DEV_EN; #if defined(__FT900__) if (!sys_check_ft900_revB())//if 90x series is rev C { fctrl_val |= MASK_USBD_FCTRL_IMP_PERF; } #endif USBD_REG(fctrl) = fctrl_val; #if defined(__FT930__) delayus(200); _speed = (SYS->MSC0CFG & MASK_SYS_MSC0CFG_HIGH_SPED_MODE) ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL; #else /* __FT930__ */ /* Detection by SOF */ while (!(USBD_REG(cmif) & MASK_USBD_CMIF_SOFIRQ)); USBD_REG(cmif) = MASK_USBD_CMIF_SOFIRQ; delayus(125 + 5); _speed = (USBD_REG(cmif) & MASK_USBD_CMIF_SOFIRQ) ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL; dcd_event_bus_reset(BOARD_TUD_RHPORT, _speed, true); #endif /* !__FT930__ */ #else // BOARD_TUD_MAX_SPEED == OPT_MODE_FULL_SPEED /* User force set to full speed */ _speed = TUSB_SPEED_FULL; fctrl_val = MASK_USBD_FCTRL_USB_DEV_EN | MASK_USBD_FCTRL_MODE_FS_ONLY; #if defined(__FT900__) if (!sys_check_ft900_revB())//if 90x series is rev C { fctrl_val |= MASK_USBD_FCTRL_IMP_PERF; } #endif USBD_REG(fctrl) = fctrl_val; dcd_event_bus_reset(BOARD_TUD_RHPORT, _speed, true); return; #endif // BOARD_TUD_MAX_SPEED } // Send a buffer to the USB IN FIFO. // When the macro USBD_USE_STREAMS is defined this will stream a buffer of data // to the FIFO using the most efficient MCU streamout combination. // If streaming is disabled then it will send each byte of the buffer in turn // to the FIFO. The is no reason to not stream. // The total number of bytes sent to the FIFO is returned. static uint16_t _ft90x_dusb_in(uint8_t ep_number, const uint8_t *buffer, uint16_t length) { uint16_t bytes_read = 0; uint16_t buff_size = length; #ifdef USBD_USE_STREAMS volatile uint8_t *data_reg; data_reg = (volatile uint8_t *)&(USBD->ep[ep_number].epxfifo); if (buff_size) { if (((uint32_t)buffer) % 4 == 0) { uint16_t aligned = buff_size & (~3); uint16_t left = buff_size & 3; if (aligned) { __asm__ volatile("streamout.l %0,%1,%2" : : "r"(data_reg), "r"(buffer), "r"(aligned)); buffer += aligned; } if (left) { __asm__ volatile("streamout.b %0,%1,%2" : : "r"(data_reg), "r"(buffer), "r"(left)); } } else { __asm__ volatile("streamout.b %0,%1,%2" : : "r"(data_reg), "r"(buffer), "r"(buff_size)); } bytes_read = buff_size; } #else // USBD_USE_STREAMS bytes_read = buff_size; while (buff_size--) { USBD_EP_FIFO_REG(ep_number) = *buffer++; }; #endif // USBD_USE_STREAMS return bytes_read; } // Receive a buffer from the USB OUT FIFO. // When the macro USBD_USE_STREAMS is defined this will stream from the FIFO // to a buffer of data using the most efficient MCU streamin combination. // If streaming is disabled then it will receive each byte from the FIFO in turn // to the buffer. The is no reason to not stream. // The total number of bytes received from the FIFO is returned. static uint16_t _ft90x_dusb_out(uint8_t ep_number, uint8_t *buffer, uint16_t length) { #ifdef USBD_USE_STREAMS volatile uint8_t *data_reg; #endif // USBD_USE_STREAMS uint16_t bytes_read = 0; uint16_t buff_size = length; if (length > 0) { if (ep_number == USBD_EP_0) { buff_size = USBD_EP_CNT_REG(USBD_EP_0); } else { if (USBD_EP_SR_REG(ep_number) & (MASK_USBD_EPxSR_OPRDY)) { buff_size = USBD_EP_CNT_REG(ep_number); } } } // Only read as many bytes as we have space for. if (buff_size > length) buff_size = length; #ifdef USBD_USE_STREAMS data_reg = (volatile uint8_t *)&(USBD->ep[ep_number].epxfifo); if (buff_size) { if ((uint32_t)buffer % 4 == 0) { uint16_t aligned = buff_size & (~3); uint16_t left = buff_size & 3; if (aligned) { __asm__ volatile("streamin.l %0,%1,%2" : : "r"(buffer), "r"(data_reg), "r"(aligned)); buffer += aligned; } if (left) { __asm__ volatile("streamin.b %0,%1,%2" : : "r"(buffer), "r"(data_reg), "r"(left)); } } else { __asm__ volatile("streamin.b %0,%1,%2" : : "r"(buffer), "r"(data_reg), "r"(buff_size)); } bytes_read = buff_size; } #else // USBD_USE_STREAMS bytes_read = buff_size; while (buff_size--) { *buffer++ = USBD_EP_FIFO_REG(ep_number); } #endif // USBD_USE_STREAMS return bytes_read; } /*------------------------------------------------------------------*/ /* Device API *------------------------------------------------------------------*/ // Initialize controller to device mode void dcd_init(uint8_t rhport) { TU_LOG2("FT90x initialisation\r\n"); _dcd_ft90x_attach(); interrupt_attach(interrupt_usb_device, (int8_t)interrupt_usb_device, _ft90x_usbd_ISR); dcd_connect(rhport); } // Enable device interrupt void dcd_int_enable(uint8_t rhport) { (void)rhport; TU_LOG3("FT90x int enable\r\n"); // Peripheral devices interrupt enable. interrupt_enable_globally(); } // Disable device interrupt void dcd_int_disable(uint8_t rhport) { (void)rhport; TU_LOG3("FT90x int disable\r\n"); // Peripheral devices interrupt disable. interrupt_disable_globally(); } // Receive Set Address request, mcu port must also include status IN response void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { (void)rhport; (void)dev_addr; // Respond with status. There is no checking that the address is in range. dcd_edpt_xfer(rhport, tu_edpt_addr(USBD_EP_0, TUSB_DIR_IN), NULL, 0); // Set the update bit for the address register. dev_addr |= 0x80; // Modify the address register within a critical section. CRITICAL_SECTION_BEGIN { USBD_REG(faddr) = dev_addr; } CRITICAL_SECTION_END; } // Invoked when a control transfer's status stage is complete. // May help DCD to prepare for next control transfer, this API is optional. #if 0 // never called void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request) { (void) rhport; if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD ) { if (request->bRequest == TUSB_REQ_SET_ADDRESS) { } else if (request->bRequest == TUSB_REQ_SET_CONFIGURATION) { } } } #endif // 0 // Wake up host void dcd_remote_wakeup(uint8_t rhport) { (void)rhport; SYS->MSC0CFG = SYS->MSC0CFG | MASK_SYS_MSC0CFG_DEV_RMWAKEUP; // At least 2 ms of delay needed for RESUME Data K state. delayms(2); SYS->MSC0CFG &= ~MASK_SYS_MSC0CFG_DEV_RMWAKEUP; // Enable USB PHY and determine current bus speed. dcd_connect(rhport); } // Connect by enabling internal pull-up resistor on D+/D- void dcd_connect(uint8_t rhport) { (void)rhport; TU_LOG2("FT90x connect\r\n"); CRITICAL_SECTION_BEGIN // Is device connected? if (board_ft90x_vbus()) { // Clear/disable address register. USBD_REG(faddr) = 0; _ft90x_phy_enable(true); // Determine bus speed and signal speed to tusb. _ft90x_usb_speed(); } // Setup the control endpoint only. #if CFG_TUD_ENDPOINT0_SIZE == 64 USBD_EP_CR_REG(USBD_EP_0) = (USBD_EP0_MAX_SIZE_64 << BIT_USBD_EP0_MAX_SIZE); #elif CFG_TUD_ENDPOINT0_SIZE == 32 USBD_EP_CR_REG(USBD_EP_0) = (USBD_EP0_MAX_SIZE_32 << BIT_USBD_EP0_MAX_SIZE); #elif CFG_TUD_ENDPOINT0_SIZE == 16 USBD_EP_CR_REG(USBD_EP_0) = (USBD_EP0_MAX_SIZE_16 << BIT_USBD_EP0_MAX_SIZE); #elif CFG_TUD_ENDPOINT0_SIZE == 8 USBD_EP_CR_REG(USBD_EP_0) = (USBD_EP0_MAX_SIZE_8 << BIT_USBD_EP0_MAX_SIZE); #else #error "CFG_TUD_ENDPOINT0_SIZE must be defined with a value of 8, 16, 32 or 64." #endif CRITICAL_SECTION_END; // Configure the control endpoint. ep_xfer[USBD_EP_0].size = CFG_TUD_ENDPOINT0_SIZE; ep_xfer[USBD_EP_0].type = TUSB_XFER_CONTROL; // Enable interrupts on EP0. USBD_REG(epie) = (MASK_USBD_EPIE_EP0IE); // Restore default endpoint state. _ft90x_reset_edpts(); } // Disconnect by disabling internal pull-up resistor on D+/D- void dcd_disconnect(uint8_t rhport) { (void)rhport; TU_LOG2("FT90x disconnect\r\n"); // Disable the USB PHY. _ft90x_phy_enable(false); } void dcd_sof_enable(uint8_t rhport, bool en) { (void) rhport; (void) en; // TODO implement later } //--------------------------------------------------------------------+ // Endpoint API //--------------------------------------------------------------------+ // Configure endpoint's registers according to descriptor bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const *ep_desc) { (void)rhport; uint8_t const ep_number = tu_edpt_number(ep_desc->bEndpointAddress); uint8_t const ep_dir = tu_edpt_dir(ep_desc->bEndpointAddress); uint8_t const ep_type = ep_desc->bmAttributes.xfer; uint16_t const ep_size = tu_edpt_packet_size(ep_desc); // Mask size per packet, bits 10..0. uint16_t ep_buff_size; uint8_t ep_reg_size = USBD_EP_MAX_SIZE_8; uint8_t ep_reg_data = 0; int16_t total_ram; TU_LOG2("FT90x endpoint open %d %c\r\n", ep_number, ep_dir?'I':'O'); // Check that the requested endpoint number is allowable. if (ep_number >= USBD_MAX_ENDPOINT_COUNT) { TU_LOG1("FT90x endpoint not valid: requested %d max %d\r\n", ep_number, USBD_MAX_ENDPOINT_COUNT); return false; } // Calculate the physical size of the endpoint as a power of 2. This may be more than // the requested size. while (ep_size > (8 * (1 << ep_reg_size))) { ep_reg_size++; } if (ep_reg_size > USBD_EP_MAX_SIZE_1024) { TU_LOG1("FT90x endpoint size not valid: requested %d max 1024\r\n", ep_size); return false; } // Calculate actual amount of buffer RAM used by this endpoint. This may be more than the // requested size. ep_buff_size = 8 << ep_reg_size; if (ep_number > 0) { // Set EP cmd parameters... ep_reg_data |= (ep_reg_size << BIT_USBD_EP_MAX_SIZE); if (ep_xfer[ep_number].type != USBD_EP_TYPE_DISABLED) { // This could be because an endpoint has been assigned with the same number. // On FT90x, IN and OUT endpoints may not have the same number. e.g. There // cannot been an 0x81 and 0x01 endpoint. TU_LOG1("FT90x endpoint %d already assigned\r\n", ep_number); return false; } // Check that there is enough buffer RAM to allocate to this new endpoint. // Available buffer RAM depends on the device revision. // The IN and OUT buffer RAM should be the same size. if (ep_dir == USBD_DIR_IN) total_ram = USBD_RAMTOTAL_IN; else total_ram = USBD_RAMTOTAL_OUT; // Work out how much has been allocated to existing endpoints. // The total RAM allocated should always be a positive number as this // algorithm should not let it go below zero. for (int i = 1; i < USBD_MAX_ENDPOINT_COUNT; i++) { if (ep_xfer[i].type != USBD_EP_TYPE_DISABLED) { if (ep_xfer[i].dir == ep_dir) { total_ram -= ep_xfer[i].buff_size; } } } // The control endpoint is taken into account as well. total_ram -= ep_xfer[0].buff_size; // Make sure we have enough space. The corner case is having zero bytes // free which means that total_ram must be signed as zero bytes free is // allowable. if (total_ram < ep_buff_size) { TU_LOG1("FT90x insufficient buffer RAM for endpoint %d\r\n", ep_number); return false; } // Set the type of this endpoint in the control register. if (ep_type == USBD_EP_BULK) ep_reg_data |= (USBD_EP_DIS_BULK << BIT_USBD_EP_CONTROL_DIS); else if (ep_type == USBD_EP_INT) ep_reg_data |= (USBD_EP_DIS_INT << BIT_USBD_EP_CONTROL_DIS); else if (ep_type == USBD_EP_ISOC) ep_reg_data |= (USBD_EP_DIS_ISO << BIT_USBD_EP_CONTROL_DIS); // Set the direction of this endpoint in the control register. if (ep_dir == USBD_DIR_IN) ep_reg_data |= MASK_USBD_EPxCR_DIR; // Do not perform double buffering. //if ( != USBD_DB_OFF) //ep_reg_data |= MASK_USBD_EPxCR_DB; // Set the control endpoint for this endpoint. USBD_EP_CR_REG(ep_number) = ep_reg_data; TU_LOG2("FT90x endpoint setting %x\r\n", ep_reg_data); } else { // Set the control register for endpoint zero. USBD_EP_CR_REG(USBD_EP_0) = (ep_reg_size << BIT_USBD_EP0_MAX_SIZE); } // Store the endpoint characteristics for later reference. ep_xfer[ep_number].dir = ep_dir; ep_xfer[ep_number].type = ep_type; ep_xfer[ep_number].size = ep_size; ep_xfer[ep_number].buff_size = ep_buff_size; CRITICAL_SECTION_BEGIN // Clear register transaction continuation and signalling state. ep_xfer[ep_number].valid = 0; ep_xfer[ep_number].buff_ptr = NULL; ep_xfer[ep_number].total_size = 0; ep_xfer[ep_number].remain_size = 0; CRITICAL_SECTION_END return true; } // Close all endpoints. void dcd_edpt_close_all(uint8_t rhport) { (void)rhport; // Reset the endpoint configurations. _ft90x_reset_edpts(); } // Submit a transfer, When complete dcd_event_xfer_complete() is invoked to notify the stack bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes) { (void)rhport; uint8_t ep_number = tu_edpt_number(ep_addr); uint8_t dir = tu_edpt_dir(ep_addr); uint16_t xfer_bytes; bool status = false; // We will attempt to transfer the buffer. If it is less than or equal to the endpoint // maximum packet size then the whole buffer will be transferred. If it is larger then // the interrupt handler will transfer the remainder. // ep_xfer is used to tell the interrupt handler what to do. // ep_xfer can be used at interrupt level to continue transfers. CRITICAL_SECTION_BEGIN // Transfer currently in progress. if (ep_xfer[ep_number].valid == 0) { status = true; ep_xfer[ep_number].total_size = total_bytes; ep_xfer[ep_number].remain_size = total_bytes; ep_xfer[ep_number].buff_ptr = buffer; ep_xfer[ep_number].valid = 1; if (ep_number == USBD_EP_0) { ep_xfer[USBD_EP_0].dir = dir; } else { // Enable the interrupt for this endpoint allowing the interrupt handler to report // continue the transfer and signal completion. USBD_REG(epie) = USBD_REG(epie) | (1 << ep_number); } if (dir == TUSB_DIR_IN) { // For IN transfers send the first packet as a starter. Interrupt handler to complete // this if it is larger than one packet. xfer_bytes = _ft90x_edpt_xfer_in(ep_number, buffer, total_bytes); ep_xfer[ep_number].buff_ptr += xfer_bytes; ep_xfer[ep_number].remain_size -= xfer_bytes; } } CRITICAL_SECTION_END return status; } // Submit a transfer where is managed by FIFO, When complete dcd_event_xfer_complete() is invoked to notify the stack - optional, however, must be listed in usbd.c bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t *ff, uint16_t total_bytes) { (void)rhport; (void)ep_addr; (void)ff; (void)total_bytes; bool status = false; return status; } // Stall endpoint (non-control endpoint) void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { uint8_t ep_number = tu_edpt_number(ep_addr); (void)rhport; CRITICAL_SECTION_BEGIN if (ep_number == USBD_EP_0) { USBD_EP_CR_REG(USBD_EP_0) = USBD_EP_CR_REG(USBD_EP_0) | MASK_USBD_EP0CR_SDSTL; } else { USBD_EP_CR_REG(ep_number) = USBD_EP_CR_REG(ep_number) | MASK_USBD_EPxCR_SDSTL; USBD_EP_SR_REG(ep_number) = MASK_USBD_EPxSR_CLR_TOGGLE | MASK_USBD_EPxSR_FIFO_FLUSH; } CRITICAL_SECTION_END } // Clear stall (non-control endpoint), data toggle is also reset to DATA0 void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { uint8_t ep_number = tu_edpt_number(ep_addr); (void)rhport; if (ep_number > USBD_EP_0) { CRITICAL_SECTION_BEGIN USBD_EP_CR_REG(ep_number) = USBD_EP_CR_REG(ep_number) & (~MASK_USBD_EPxCR_SDSTL); USBD_EP_SR_REG(ep_number) = MASK_USBD_EPxSR_CLR_TOGGLE; // Allow transfers to restart. ep_xfer[ep_number].valid = 0; ep_xfer[ep_number].remain_size = 0; CRITICAL_SECTION_END } } // Interrupt handling. void _ft90x_usbd_ISR(void) { tud_int_handler(BOARD_TUD_RHPORT); // Resolves to dcd_int_handler(). } void dcd_int_handler(uint8_t rhport) { (void)rhport; // Read the Common Interrupt Flag Register. uint8_t cmif = USBD_REG(cmif); // Read the Endpoint Interrupt Flag Register. #if defined(__FT930__) // This is 16 bits on FT93x. uint16_t epif = USBD_REG(epif); #else // This is 8 bits on FT90x. uint8_t epif = USBD_REG(epif); #endif if (cmif & MASK_USBD_CMIF_ALL) { // Clear all CMIF bits. USBD_REG(cmif) = MASK_USBD_CMIF_ALL; if (cmif & MASK_USBD_CMIF_PHYIRQ) //Handle PHY interrupt { } if (cmif & MASK_USBD_CMIF_PIDIRQ) //Handle PIDIRQ interrupt { } if (cmif & MASK_USBD_CMIF_CRC16IRQ) //Handle CRC16IRQ interrupt { } if (cmif & MASK_USBD_CMIF_CRC5IRQ) //Handle CRC5 interrupt { } if (cmif & MASK_USBD_CMIF_RSTIRQ) //Handle Reset interrupt { // Reset endpoints to default state. _ft90x_reset_edpts(); dcd_event_bus_reset(BOARD_TUD_RHPORT, _speed, true); } if (cmif & MASK_USBD_CMIF_SUSIRQ) //Handle Suspend interrupt { dcd_event_bus_signal(BOARD_TUD_RHPORT, DCD_EVENT_SUSPEND, true); } if (cmif & MASK_USBD_CMIF_RESIRQ) //Handle Resume interrupt { dcd_event_bus_signal(BOARD_TUD_RHPORT, DCD_EVENT_RESUME, true); } if (cmif & MASK_USBD_CMIF_SOFIRQ) //Handle SOF interrupt { dcd_event_bus_signal(BOARD_TUD_RHPORT, DCD_EVENT_SOF, true); } } // Handle endpoint interrupts. if (epif) { uint16_t xfer_bytes; // Check for EP0 interrupts pending. if (epif & MASK_USBD_EPIF_EP0IRQ) { // Clear interrupt register. USBD_REG(epif) = MASK_USBD_EPIF_EP0IRQ; // Test for an incoming SETUP request on the control endpoint. if (USBD_EP_SR_REG(USBD_EP_0) & MASK_USBD_EP0SR_SETUP) { // If protocol STALL, End the STALL signalling. if (USBD_EP_CR_REG(USBD_EP_0) & MASK_USBD_EP0CR_SDSTL) { // STALL end. USBD_EP_CR_REG(USBD_EP_0) = USBD_EP_CR_REG(USBD_EP_0) & (~MASK_USBD_EP0CR_SDSTL); // Clear STALL send. USBD_EP_SR_REG(USBD_EP_0) = MASK_USBD_EP0SR_STALL; } // Host has sent a SETUP packet. Receive this into the setup packet store. _ft90x_dusb_out(USBD_EP_0, (uint8_t *)_ft90x_setup_packet, sizeof(USB_device_request)); // Send the packet to tinyusb. dcd_event_setup_received(BOARD_TUD_RHPORT, _ft90x_setup_packet, true); // Clear the interrupt that signals a SETUP packet is received. USBD_EP_SR_REG(USBD_EP_0) = (MASK_USBD_EP0SR_SETUP); // Allow new transfers on the control endpoint. ep_xfer[USBD_EP_0].valid = 0; return; } else { // Check for a complete or partially complete transfers on EP0. if (ep_xfer[USBD_EP_0].valid) { xfer_bytes = (uint16_t)ep_xfer[USBD_EP_0].total_size; // Transfer incoming data from an OUT packet to the buffer supplied. if (ep_xfer[USBD_EP_0].dir == TUSB_DIR_OUT) { xfer_bytes = _ft90x_edpt_xfer_out(USBD_EP_0, (uint8_t *)ep_xfer[USBD_EP_0].buff_ptr, xfer_bytes); } // Now signal completion of data packet. dcd_event_xfer_complete(BOARD_TUD_RHPORT, (ep_xfer[USBD_EP_0].dir ? TUSB_DIR_IN_MASK : 0), xfer_bytes, XFER_RESULT_SUCCESS, true); // Allow new transfers on the control endpoint. ep_xfer[USBD_EP_0].valid = 0; } } } else // !(epif & MASK_USBD_EPIF_EP0IRQ) { // Mask out currently disabled endpoints. epif &= USBD_REG(epie); // Handle complete and partially complete transfers for each endpoint. for (uint8_t ep_number = 1; ep_number < USBD_MAX_ENDPOINT_COUNT; ep_number++) { if ((epif & MASK_USBD_EPIF_IRQ(ep_number)) == 0) { // No pending interrupt for this endpoint. continue; } if (ep_xfer[ep_number].valid) { xfer_bytes = 0; uint8_t ep_dirmask = (ep_xfer[ep_number].dir ? TUSB_DIR_IN_MASK : 0); // Clear interrupt register for this endpoint. USBD_REG(epif) = MASK_USBD_EPIF_IRQ(ep_number); // Start or continue an OUT transfer. if (ep_xfer[ep_number].dir == TUSB_DIR_OUT) { xfer_bytes = _ft90x_edpt_xfer_out(ep_number, (uint8_t *)ep_xfer[ep_number].buff_ptr, (uint16_t)ep_xfer[ep_number].remain_size); ep_xfer[ep_number].buff_ptr += xfer_bytes; ep_xfer[ep_number].remain_size -= xfer_bytes; } // continue an IN transfer else // if (ep_xfer[ep_number].dir == TUSB_DIR_IN) { if (ep_xfer[ep_number].remain_size > 0) { xfer_bytes = _ft90x_edpt_xfer_in(ep_number, (uint8_t *)ep_xfer[ep_number].buff_ptr, (uint16_t)ep_xfer[ep_number].remain_size); ep_xfer[ep_number].buff_ptr += xfer_bytes; ep_xfer[ep_number].remain_size -= xfer_bytes; } } // When the transfer is complete... if (ep_xfer[ep_number].remain_size == 0) { // Signal tinyUSB. dcd_event_xfer_complete(BOARD_TUD_RHPORT, ep_number | ep_dirmask, ep_xfer[ep_number].total_size, XFER_RESULT_SUCCESS, true); // Allow new transfers on this endpoint. ep_xfer[ep_number].valid = 0; // Disable the interrupt for this endpoint now it is complete. USBD_REG(epie) = USBD_REG(epie) & (~(1 << ep_number)); } } } } } } // Power management interrupt handler. // This handles USB device related power management interrupts only. void ft90x_usbd_pm_ISR(void) { uint16_t pmcfg = SYS->PMCFG_H; // Main interrupt handler is responible for if (pmcfg & MASK_SYS_PMCFG_DEV_CONN_DEV) { // Signal connection interrupt SYS->PMCFG_H = MASK_SYS_PMCFG_PM_GPIO_IRQ_PEND; dcd_event_bus_signal(BOARD_TUD_RHPORT, DCD_EVENT_RESUME, true); } if (pmcfg & MASK_SYS_PMCFG_DEV_DIS_DEV) { // Signal disconnection interrupt SYS->PMCFG_H = MASK_SYS_PMCFG_PM_GPIO_IRQ_PEND; dcd_event_bus_signal(BOARD_TUD_RHPORT, DCD_EVENT_UNPLUGGED, true); } if (pmcfg & MASK_SYS_PMCFG_HOST_RST_DEV) { // Signal Host Reset interrupt SYS->PMCFG_H = MASK_SYS_PMCFG_PM_GPIO_IRQ_PEND; dcd_event_bus_signal(BOARD_TUD_RHPORT, DCD_EVENT_BUS_RESET, true); } if (pmcfg & MASK_SYS_PMCFG_HOST_RESUME_DEV) { // Signal Host Resume interrupt SYS->PMCFG_H = MASK_SYS_PMCFG_PM_GPIO_IRQ_PEND; if (!(SYS->MSC0CFG & MASK_SYS_MSC0CFG_DEV_RMWAKEUP)) { // If we are driving K-state on Device USB port; // We must maintain the 1ms requirement before resuming the phy dcd_event_bus_signal(BOARD_TUD_RHPORT, DCD_EVENT_RESUME, true); } } } #endif