/** * \file * * \brief USB Device Driver for UOTGHS. Compliant with common UDD driver. * * Copyright (c) 2012-2015 Atmel Corporation. All rights reserved. * * \asf_license_start * * \page License * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The name of Atmel may not be used to endorse or promote products derived * from this software without specific prior written permission. * * 4. This software may only be redistributed and used in connection with an * Atmel microcontroller product. * * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * \asf_license_stop * */ /* * Support and FAQ: visit Atmel Support */ #ifdef ARDUINO_ARCH_SAM #include "compiler.h" #include "uotghs_device_due.h" #include "conf_usb.h" #include "sysclk.h" #include "udd.h" #include "uotghs_otg.h" #include #ifndef UDD_NO_SLEEP_MGR # include "sleep.h" # include "sleepmgr.h" #endif #if !(SAM3XA) # error The current UOTGHS Device Driver supports only SAM3X and SAM3A. #endif #ifndef UDD_USB_INT_FUN # define UDD_USB_INT_FUN UOTGHS_Handler #endif #ifndef UDD_USB_INT_LEVEL # define UDD_USB_INT_LEVEL 5 // By default USB interrupt have low priority #endif #define UDD_EP_USED(ep) (USB_DEVICE_MAX_EP >= ep) #if ( (UDD_EP_USED( 1) && Is_udd_endpoint_dma_supported( 1)) \ ||(UDD_EP_USED( 2) && Is_udd_endpoint_dma_supported( 2)) \ ||(UDD_EP_USED( 3) && Is_udd_endpoint_dma_supported( 3)) \ ||(UDD_EP_USED( 4) && Is_udd_endpoint_dma_supported( 4)) \ ||(UDD_EP_USED( 5) && Is_udd_endpoint_dma_supported( 5)) \ ||(UDD_EP_USED( 6) && Is_udd_endpoint_dma_supported( 6)) \ ||(UDD_EP_USED( 7) && Is_udd_endpoint_dma_supported( 7)) \ ||(UDD_EP_USED( 8) && Is_udd_endpoint_dma_supported( 8)) \ ||(UDD_EP_USED( 9) && Is_udd_endpoint_dma_supported( 9)) \ ||(UDD_EP_USED(10) && Is_udd_endpoint_dma_supported(10)) \ ||(UDD_EP_USED(11) && Is_udd_endpoint_dma_supported(11)) \ ||(UDD_EP_USED(12) && Is_udd_endpoint_dma_supported(12)) \ ||(UDD_EP_USED(13) && Is_udd_endpoint_dma_supported(13)) \ ||(UDD_EP_USED(14) && Is_udd_endpoint_dma_supported(14)) \ ||(UDD_EP_USED(15) && Is_udd_endpoint_dma_supported(15)) \ ) # define UDD_EP_DMA_SUPPORTED #endif #if ( (UDD_EP_USED( 1) && !Is_udd_endpoint_dma_supported( 1)) \ ||(UDD_EP_USED( 2) && !Is_udd_endpoint_dma_supported( 2)) \ ||(UDD_EP_USED( 3) && !Is_udd_endpoint_dma_supported( 3)) \ ||(UDD_EP_USED( 4) && !Is_udd_endpoint_dma_supported( 4)) \ ||(UDD_EP_USED( 5) && !Is_udd_endpoint_dma_supported( 5)) \ ||(UDD_EP_USED( 6) && !Is_udd_endpoint_dma_supported( 6)) \ ||(UDD_EP_USED( 7) && !Is_udd_endpoint_dma_supported( 7)) \ ||(UDD_EP_USED( 8) && !Is_udd_endpoint_dma_supported( 8)) \ ||(UDD_EP_USED( 9) && !Is_udd_endpoint_dma_supported( 9)) \ ||(UDD_EP_USED(10) && !Is_udd_endpoint_dma_supported(10)) \ ||(UDD_EP_USED(11) && !Is_udd_endpoint_dma_supported(11)) \ ||(UDD_EP_USED(12) && !Is_udd_endpoint_dma_supported(12)) \ ||(UDD_EP_USED(13) && !Is_udd_endpoint_dma_supported(13)) \ ||(UDD_EP_USED(14) && !Is_udd_endpoint_dma_supported(14)) \ ||(UDD_EP_USED(15) && !Is_udd_endpoint_dma_supported(15)) \ ) # define UDD_EP_FIFO_SUPPORTED #endif // for debug text //#define dbg_print printf #define dbg_print(...) /** * \ingroup udd_group * \defgroup udd_udphs_group USB On-The-Go High-Speed Port for device mode (UOTGHS) * * \section UOTGHS_CONF UOTGHS Custom configuration * The following UOTGHS driver configuration must be included in the conf_usb.h * file of the application. * * UDD_USB_INT_LEVEL
* Option to change the interrupt priority (0 to 15) by default 5 (recommended). * * UDD_USB_INT_FUN
* Option to fit interrupt function to what defined in exception table. * * UDD_ISOCHRONOUS_NB_BANK(ep)
* Feature to reduce or increase isochronous endpoints buffering (1 to 3). * Default value 2. * * UDD_BULK_NB_BANK(ep)
* Feature to reduce or increase bulk endpoints buffering (1 to 2). * Default value 2. * * UDD_INTERRUPT_NB_BANK(ep)
* Feature to reduce or increase interrupt endpoints buffering (1 to 2). * Default value 1. * * \section Callbacks management * The USB driver is fully managed by interrupt and does not request periodique * task. Thereby, the USB events use callbacks to transfer the information. * The callbacks are declared in static during compilation or in variable during * code execution. * * Static declarations defined in conf_usb.h: * - UDC_VBUS_EVENT(bool b_present)
* To signal Vbus level change * - UDC_SUSPEND_EVENT()
* Called when USB bus enter in suspend mode * - UDC_RESUME_EVENT()
* Called when USB bus is wakeup * - UDC_SOF_EVENT()
* Called for each received SOF, Note: Each 1ms in HS/FS mode only. * * Dynamic callbacks, called "endpoint job" , are registered * in udd_ep_job_t structure via the following functions: * - udd_ep_run()
* To call it when a transfer is finish * - udd_ep_wait_stall_clear()
* To call it when a endpoint halt is disabled * * \section Power mode management * The Sleep modes authorized : * - in USB IDLE state, the UOTGHS needs of USB clock and authorizes up to sleep mode WFI. * - in USB SUSPEND state, the UOTGHS no needs USB clock and authorizes up to sleep mode WAIT. * @{ */ // Check USB Device configuration #ifndef USB_DEVICE_EP_CTRL_SIZE # error USB_DEVICE_EP_CTRL_SIZE not defined #endif #ifndef USB_DEVICE_MAX_EP # error USB_DEVICE_MAX_EP not defined #endif // Note: USB_DEVICE_MAX_EP does not include control endpoint #if USB_DEVICE_MAX_EP > (UDD_MAX_PEP_NB-1) # error USB_DEVICE_MAX_EP is too high and not supported by this part #endif #define UDD_EP_ISO_NBANK_ERROR(ep) \ ( (UDD_ISOCHRONOUS_NB_BANK(ep) < 1) \ || (UDD_ISOCHRONOUS_NB_BANK(ep) > 3) ) #define UDD_EP_BULK_NBANK_ERROR(ep) \ ( (UDD_BULK_NB_BANK(ep) < 1) || (UDD_BULK_NB_BANK(ep) > 2) ) #define UDD_EP_INT_NBANK_ERROR(ep) \ ( (UDD_INTERRUPT_NB_BANK(ep) < 1) || (UDD_INTERRUPT_NB_BANK(ep) > 2) ) #define UDD_EP_ISO_NB_BANK_ERROR(ep) \ (UDD_EP_USED(ep) && UDD_EP_ISO_NBANK_ERROR(ep)) #define UDD_EP_BULK_NB_BANK_ERROR(ep) \ (UDD_EP_USED(ep) && UDD_EP_ISO_NBANK_ERROR(ep)) #define UDD_EP_INT_NB_BANK_ERROR(ep) \ (UDD_EP_USED(ep) && UDD_EP_ISO_NBANK_ERROR(ep)) #define UDD_EP_NB_BANK_ERROR(ep, type) \ (ATPASTE3(UDD_EP_, type, _NB_BANK_ERROR(ep))) #define UDD_ISO_NB_BANK_ERROR \ ( UDD_EP_NB_BANK_ERROR( 1, ISO) \ || UDD_EP_NB_BANK_ERROR( 2, ISO) \ || UDD_EP_NB_BANK_ERROR( 3, ISO) \ || UDD_EP_NB_BANK_ERROR( 4, ISO) \ || UDD_EP_NB_BANK_ERROR( 5, ISO) \ || UDD_EP_NB_BANK_ERROR( 6, ISO) \ || UDD_EP_NB_BANK_ERROR( 7, ISO) \ || UDD_EP_NB_BANK_ERROR( 8, ISO) \ || UDD_EP_NB_BANK_ERROR( 9, ISO) \ || UDD_EP_NB_BANK_ERROR(10, ISO) \ || UDD_EP_NB_BANK_ERROR(11, ISO) \ || UDD_EP_NB_BANK_ERROR(12, ISO) \ || UDD_EP_NB_BANK_ERROR(13, ISO) \ || UDD_EP_NB_BANK_ERROR(14, ISO) \ || UDD_EP_NB_BANK_ERROR(15, ISO) ) #define UDD_BULK_NB_BANK_ERROR \ ( UDD_EP_NB_BANK_ERROR( 1, BULK) \ || UDD_EP_NB_BANK_ERROR( 2, BULK) \ || UDD_EP_NB_BANK_ERROR( 3, BULK) \ || UDD_EP_NB_BANK_ERROR( 4, BULK) \ || UDD_EP_NB_BANK_ERROR( 5, BULK) \ || UDD_EP_NB_BANK_ERROR( 6, BULK) \ || UDD_EP_NB_BANK_ERROR( 7, BULK) \ || UDD_EP_NB_BANK_ERROR( 8, BULK) \ || UDD_EP_NB_BANK_ERROR( 9, BULK) \ || UDD_EP_NB_BANK_ERROR(10, BULK) \ || UDD_EP_NB_BANK_ERROR(11, BULK) \ || UDD_EP_NB_BANK_ERROR(12, BULK) \ || UDD_EP_NB_BANK_ERROR(13, BULK) \ || UDD_EP_NB_BANK_ERROR(14, BULK) \ || UDD_EP_NB_BANK_ERROR(15, BULK) ) #define UDD_INTERRUPT_NB_BANK_ERROR \ ( UDD_EP_NB_BANK_ERROR( 1, INT) \ || UDD_EP_NB_BANK_ERROR( 2, INT) \ || UDD_EP_NB_BANK_ERROR( 3, INT) \ || UDD_EP_NB_BANK_ERROR( 4, INT) \ || UDD_EP_NB_BANK_ERROR( 5, INT) \ || UDD_EP_NB_BANK_ERROR( 6, INT) \ || UDD_EP_NB_BANK_ERROR( 7, INT) \ || UDD_EP_NB_BANK_ERROR( 8, INT) \ || UDD_EP_NB_BANK_ERROR( 9, INT) \ || UDD_EP_NB_BANK_ERROR(10, INT) \ || UDD_EP_NB_BANK_ERROR(11, INT) \ || UDD_EP_NB_BANK_ERROR(12, INT) \ || UDD_EP_NB_BANK_ERROR(13, INT) \ || UDD_EP_NB_BANK_ERROR(14, INT) \ || UDD_EP_NB_BANK_ERROR(15, INT) ) #ifndef UDD_ISOCHRONOUS_NB_BANK # define UDD_ISOCHRONOUS_NB_BANK(ep) 2 #else # if UDD_ISO_NB_BANK_ERROR # error UDD_ISOCHRONOUS_NB_BANK(ep) must be define within 1 to 3. # endif #endif #ifndef UDD_BULK_NB_BANK # define UDD_BULK_NB_BANK(ep) 2 #else # if UDD_BULK_NB_BANK_ERROR # error UDD_BULK_NB_BANK must be define with 1 or 2. # endif #endif #ifndef UDD_INTERRUPT_NB_BANK # define UDD_INTERRUPT_NB_BANK(ep) 1 #else # if UDD_INTERRUPT_NB_BANK_ERROR # error UDD_INTERRUPT_NB_BANK must be define with 1 or 2. # endif #endif /** * \name Power management routine. */ //@{ #ifndef UDD_NO_SLEEP_MGR //! Definition of sleep levels #define UOTGHS_SLEEP_MODE_USB_SUSPEND SLEEPMGR_WAIT_FAST #define UOTGHS_SLEEP_MODE_USB_IDLE SLEEPMGR_SLEEP_WFI //! State of USB line static bool udd_b_idle; //! State of sleep manager static bool udd_b_sleep_initialized = false; /*! \brief Authorize or not the CPU powerdown mode * * \param b_enable true to authorize idle mode */ static void udd_sleep_mode(bool b_idle) { if (!b_idle && udd_b_idle) { dbg_print("_S "); sleepmgr_unlock_mode(UOTGHS_SLEEP_MODE_USB_IDLE); } if (b_idle && !udd_b_idle) { dbg_print("_W "); sleepmgr_lock_mode(UOTGHS_SLEEP_MODE_USB_IDLE); } udd_b_idle = b_idle; } #else static void udd_sleep_mode(bool b_idle) { b_idle = b_idle; } #endif // UDD_NO_SLEEP_MGR //@} /** * \name Control endpoint low level management routine. * * This function performs control endpoint mangement. * It handle the SETUP/DATA/HANDSHAKE phases of a control transaction. */ //@{ //! Global variable to give and record information about setup request management COMPILER_WORD_ALIGNED udd_ctrl_request_t udd_g_ctrlreq; //! Bit definitions about endpoint control state machine for udd_ep_control_state typedef enum { UDD_EPCTRL_SETUP = 0, //!< Wait a SETUP packet UDD_EPCTRL_DATA_OUT = 1, //!< Wait a OUT data packet UDD_EPCTRL_DATA_IN = 2, //!< Wait a IN data packet UDD_EPCTRL_HANDSHAKE_WAIT_IN_ZLP = 3, //!< Wait a IN ZLP packet UDD_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP = 4, //!< Wait a OUT ZLP packet UDD_EPCTRL_STALL_REQ = 5, //!< STALL enabled on IN & OUT packet } udd_ctrl_ep_state_t; //! State of the endpoint control management static udd_ctrl_ep_state_t udd_ep_control_state; //! Total number of data received/sent during data packet phase with previous payload buffers static uint16_t udd_ctrl_prev_payload_buf_cnt; //! Number of data received/sent to/from udd_g_ctrlreq.payload buffer static uint16_t udd_ctrl_payload_buf_cnt; /** * \brief Reset control endpoint * * Called after a USB line reset or when UDD is enabled */ static void udd_reset_ep_ctrl(void); /** * \brief Reset control endpoint management * * Called after a USB line reset or at the end of SETUP request (after ZLP) */ static void udd_ctrl_init(void); //! \brief Managed reception of SETUP packet on control endpoint static void udd_ctrl_setup_received(void); //! \brief Managed reception of IN packet on control endpoint static void udd_ctrl_in_sent(void); //! \brief Managed reception of OUT packet on control endpoint static void udd_ctrl_out_received(void); //! \brief Managed underflow event of IN packet on control endpoint static void udd_ctrl_underflow(void); //! \brief Managed overflow event of OUT packet on control endpoint static void udd_ctrl_overflow(void); //! \brief Managed stall event of IN/OUT packet on control endpoint static void udd_ctrl_stall_data(void); //! \brief Send a ZLP IN on control endpoint static void udd_ctrl_send_zlp_in(void); //! \brief Send a ZLP OUT on control endpoint static void udd_ctrl_send_zlp_out(void); //! \brief Call callback associated to setup request static void udd_ctrl_endofrequest(void); /** * \brief Main interrupt routine for control endpoint * * This switchs control endpoint events to correct sub function. * * \return \c 1 if an event about control endpoint is occured, otherwise \c 0. */ static bool udd_ctrl_interrupt(void); //@} /** * \name Management of bulk/interrupt/isochronous endpoints * * The UDD manages the data transfer on endpoints: * - Start data tranfer on endpoint with USB Device DMA * - Send a ZLP packet if requested * - Call callback registered to signal end of transfer * The transfer abort and stall feature are supported. */ //@{ #if (0!=USB_DEVICE_MAX_EP) //! Structure definition about job registered on an endpoint typedef struct { union { //! Callback to call at the end of transfer udd_callback_trans_t call_trans; //! Callback to call when the endpoint halt is cleared udd_callback_halt_cleared_t call_nohalt; }; //! Buffer located in internal RAM to send or fill during job uint8_t *buf; //! Size of buffer to send or fill iram_size_t buf_size; //!< Size of data transfered iram_size_t buf_cnt; //!< Size of data loaded (or prepared for DMA) last time iram_size_t buf_load; //! A job is registered on this endpoint uint8_t busy:1; //! A short packet is requested for this job on endpoint IN uint8_t b_shortpacket:1; //! A stall has been requested but not executed uint8_t stall_requested:1; } udd_ep_job_t; //! Array to register a job on bulk/interrupt/isochronous endpoint static udd_ep_job_t udd_ep_job[USB_DEVICE_MAX_EP]; //! \brief Reset all job table static void udd_ep_job_table_reset(void); //! \brief Abort all endpoint jobs on going static void udd_ep_job_table_kill(void); #ifdef UDD_EP_FIFO_SUPPORTED /** * \brief Fill banks and send them * * \param ep endpoint number of job to abort */ static void udd_ep_in_sent(udd_ep_id_t ep); /** * \brief Store received banks * * \param ep endpoint number of job to abort */ static void udd_ep_out_received(udd_ep_id_t ep); #endif /** * \brief Abort endpoint job on going * * \param ep endpoint number of job to abort */ static void udd_ep_abort_job(udd_ep_id_t ep); /** * \brief Call the callback associated to the job which is finished * * \param ptr_job job to complete * \param b_abort if true then the job has been aborted */ static void udd_ep_finish_job(udd_ep_job_t * ptr_job, bool b_abort, uint8_t ep_num); #ifdef UDD_EP_DMA_SUPPORTED /** * \brief Start the next transfer if necessary or complet the job associated. * * \param ep endpoint number without direction flag */ static void udd_ep_trans_done(udd_ep_id_t ep); #endif /** * \brief Main interrupt routine for bulk/interrupt/isochronous endpoints * * This switchs endpoint events to correct sub function. * * \return \c 1 if an event about bulk/interrupt/isochronous endpoints has occured, otherwise \c 0. */ static bool udd_ep_interrupt(void); #endif // (0!=USB_DEVICE_MAX_EP) //@} // ------------------------ //--- INTERNAL ROUTINES TO MANAGED GLOBAL EVENTS /** * \internal * \brief Function called by UOTGHS interrupt to manage USB Device interrupts * * USB Device interrupt events are splited in three parts: * - USB line events (SOF, reset, suspend, resume, wakeup) * - control endpoint events (setup reception, end of data transfer, underflow, overflow, stall) * - bulk/interrupt/isochronous endpoints events (end of data transfer) * * Note: * Here, the global interrupt mask is not clear when an USB interrupt is enabled * because this one can not be occured during the USB ISR (=during INTX is masked). * See Technical reference $3.8.3 Masking interrupt requests in peripheral modules. */ #ifdef UHD_ENABLE void udd_interrupt(void); void udd_interrupt(void) #else ISR(UDD_USB_INT_FUN) #endif { /* For fast wakeup clocks restore * In WAIT mode, clocks are switched to FASTRC. * After wakeup clocks should be restored, before that ISR should not * be served. */ if (!pmc_is_wakeup_clocks_restored() && !Is_udd_suspend()) { cpu_irq_disable(); return; } if (Is_udd_sof()) { udd_ack_sof(); if (Is_udd_full_speed_mode()) { udc_sof_notify(); } #ifdef UDC_SOF_EVENT UDC_SOF_EVENT(); #endif goto udd_interrupt_sof_end; } if (Is_udd_msof()) { udd_ack_msof(); udc_sof_notify(); goto udd_interrupt_sof_end; } dbg_print("%c ", udd_is_high_speed() ? 'H' : 'F'); if (udd_ctrl_interrupt()) { goto udd_interrupt_end; // Interrupt acked by control endpoint managed } #if (0 != USB_DEVICE_MAX_EP) if (udd_ep_interrupt()) { goto udd_interrupt_end; // Interrupt acked by bulk/interrupt/isochronous endpoint managed } #endif // USB bus reset detection if (Is_udd_reset()) { udd_ack_reset(); dbg_print("RST "); // Abort all jobs on-going #if (USB_DEVICE_MAX_EP != 0) udd_ep_job_table_kill(); #endif // Reset USB Device Stack Core udc_reset(); // Reset endpoint control udd_reset_ep_ctrl(); // Reset endpoint control management udd_ctrl_init(); goto udd_interrupt_end; } if (Is_udd_suspend_interrupt_enabled() && Is_udd_suspend()) { otg_unfreeze_clock(); // The suspend interrupt is automatic acked when a wakeup occur udd_disable_suspend_interrupt(); udd_enable_wake_up_interrupt(); otg_freeze_clock(); // Mandatory to exit of sleep mode after a wakeup event udd_sleep_mode(false); // Enter in SUSPEND mode #ifdef UDC_SUSPEND_EVENT UDC_SUSPEND_EVENT(); #endif goto udd_interrupt_end; } if (Is_udd_wake_up_interrupt_enabled() && Is_udd_wake_up()) { // Ack wakeup interrupt and enable suspend interrupt otg_unfreeze_clock(); // Check USB clock ready after suspend and eventually sleep USB clock while (!Is_otg_clock_usable()) { if (Is_udd_suspend()) { break; // In case of USB state change in HS } }; // The wakeup interrupt is automatic acked when a suspend occur udd_disable_wake_up_interrupt(); udd_enable_suspend_interrupt(); udd_sleep_mode(true); // Enter in IDLE mode #ifdef UDC_RESUME_EVENT UDC_RESUME_EVENT(); #endif goto udd_interrupt_end; } if (Is_otg_vbus_transition()) { dbg_print("VBus "); // Ack Vbus transition and send status to high level otg_unfreeze_clock(); otg_ack_vbus_transition(); otg_freeze_clock(); #ifndef USB_DEVICE_ATTACH_AUTO_DISABLE if (Is_otg_vbus_high()) { udd_attach(); } else { udd_detach(); } #endif #ifdef UDC_VBUS_EVENT UDC_VBUS_EVENT(Is_otg_vbus_high()); #endif goto udd_interrupt_end; } udd_interrupt_end: dbg_print("\n\r"); udd_interrupt_sof_end: return; } bool udd_include_vbus_monitoring(void) { return true; } void udd_enable(void) { irqflags_t flags; flags = cpu_irq_save(); #ifdef UHD_ENABLE // DUAL ROLE INITIALIZATION if (otg_dual_enable()) { // The current mode has been started by otg_dual_enable() cpu_irq_restore(flags); return; } #else // SINGLE DEVICE MODE INITIALIZATION pmc_enable_periph_clk(ID_UOTGHS); sysclk_enable_usb(); // Here, only the device mode is possible, then link UOTGHS interrupt to UDD interrupt NVIC_SetPriority((IRQn_Type) ID_UOTGHS, UDD_USB_INT_LEVEL); NVIC_EnableIRQ((IRQn_Type) ID_UOTGHS); // Always authorize asynchrone USB interrupts to exit of sleep mode // For SAM USB wake up device except BACKUP mode pmc_set_fast_startup_input(PMC_FSMR_USBAL); #endif #if (defined USB_ID_GPIO) && (defined UHD_ENABLE) // Check that the device mode is selected by ID pin if (!Is_otg_id_device()) { cpu_irq_restore(flags); return; // Device is not the current mode } #else // ID pin not used then force device mode otg_disable_id_pin(); otg_force_device_mode(); #endif // Enable USB hardware otg_enable_pad(); otg_enable(); // Set the USB speed requested by configuration file #ifdef USB_DEVICE_LOW_SPEED udd_low_speed_enable(); #else udd_low_speed_disable(); # ifdef USB_DEVICE_HS_SUPPORT udd_high_speed_enable(); # else udd_high_speed_disable(); # endif #endif // USB_DEVICE_LOW_SPEED // Check USB clock otg_unfreeze_clock(); while (!Is_otg_clock_usable()); // Reset internal variables #if (0!=USB_DEVICE_MAX_EP) udd_ep_job_table_reset(); #endif otg_ack_vbus_transition(); // Force Vbus interrupt in case of Vbus always with a high level // This is possible with a short timing between a Host mode stop/start. if (Is_otg_vbus_high()) { otg_raise_vbus_transition(); } otg_enable_vbus_interrupt(); otg_freeze_clock(); #ifndef UDD_NO_SLEEP_MGR if (!udd_b_sleep_initialized) { udd_b_sleep_initialized = true; // Initialize the sleep mode authorized for the USB suspend mode udd_b_idle = false; sleepmgr_lock_mode(UOTGHS_SLEEP_MODE_USB_SUSPEND); } else { udd_sleep_mode(false); // Enter idle mode } #endif cpu_irq_restore(flags); } void udd_disable(void) { irqflags_t flags; #ifdef UHD_ENABLE # ifdef USB_ID_GPIO if (Is_otg_id_host()) { // Freeze clock to switch mode otg_freeze_clock(); udd_detach(); otg_disable(); return; // Host mode running, ignore UDD disable } # else if (Is_otg_host_mode_forced()) { return; // Host mode running, ignore UDD disable } # endif #endif flags = cpu_irq_save(); otg_unfreeze_clock(); udd_detach(); #ifndef UDD_NO_SLEEP_MGR if (udd_b_sleep_initialized) { udd_b_sleep_initialized = false; sleepmgr_unlock_mode(UOTGHS_SLEEP_MODE_USB_SUSPEND); } #endif #ifndef UHD_ENABLE otg_disable(); otg_disable_pad(); sysclk_disable_usb(); pmc_disable_periph_clk(ID_UOTGHS); // Else the USB clock disable is done by UHC which manage USB dual role #endif cpu_irq_restore(flags); } void udd_attach(void) { irqflags_t flags; flags = cpu_irq_save(); // At startup the USB bus state is unknown, // therefore the state is considered IDLE to not miss any USB event udd_sleep_mode(true); otg_unfreeze_clock(); // This section of clock check can be improved with a chek of // USB clock source via sysclk() // Check USB clock because the source can be a PLL while (!Is_otg_clock_usable()); // Authorize attach if Vbus is present udd_attach_device(); // Enable USB line events udd_enable_reset_interrupt(); udd_enable_suspend_interrupt(); udd_enable_wake_up_interrupt(); udd_enable_sof_interrupt(); #ifdef USB_DEVICE_HS_SUPPORT udd_enable_msof_interrupt(); #endif // Reset following interupts flag udd_ack_reset(); udd_ack_sof(); udd_ack_msof(); // The first suspend interrupt must be forced // The first suspend interrupt is not detected else raise it udd_raise_suspend(); udd_ack_wake_up(); otg_freeze_clock(); cpu_irq_restore(flags); } void udd_detach(void) { otg_unfreeze_clock(); // Detach device from the bus udd_detach_device(); otg_freeze_clock(); udd_sleep_mode(false); } bool udd_is_high_speed(void) { #ifdef USB_DEVICE_HS_SUPPORT return !Is_udd_full_speed_mode(); #else return false; #endif } void udd_set_address(uint8_t address) { udd_disable_address(); udd_configure_address(address); udd_enable_address(); } uint8_t udd_getaddress(void) { return udd_get_configured_address(); } uint16_t udd_get_frame_number(void) { return udd_frame_number(); } uint16_t udd_get_micro_frame_number(void) { return udd_micro_frame_number(); } void udd_send_remotewakeup(void) { #ifndef UDD_NO_SLEEP_MGR if (!udd_b_idle) #endif { udd_sleep_mode(true); // Enter in IDLE mode otg_unfreeze_clock(); udd_initiate_remote_wake_up(); } } void udd_set_setup_payload(uint8_t *payload, uint16_t payload_size) { udd_g_ctrlreq.payload = payload; udd_g_ctrlreq.payload_size = payload_size; } #if (0 != USB_DEVICE_MAX_EP) bool udd_ep_alloc(udd_ep_id_t ep, uint8_t bmAttributes, uint16_t MaxEndpointSize) { bool b_dir_in; uint16_t ep_allocated; uint8_t nb_bank, bank, i; b_dir_in = ep & USB_EP_DIR_IN; ep = ep & USB_EP_ADDR_MASK; if (ep > USB_DEVICE_MAX_EP) { return false; } if (Is_udd_endpoint_enabled(ep)) { return false; } dbg_print("alloc(%x, %d) ", ep, MaxEndpointSize); // Bank choise switch (bmAttributes & USB_EP_TYPE_MASK) { case USB_EP_TYPE_ISOCHRONOUS: nb_bank = UDD_ISOCHRONOUS_NB_BANK(ep); break; case USB_EP_TYPE_INTERRUPT: nb_bank = UDD_INTERRUPT_NB_BANK(ep); break; case USB_EP_TYPE_BULK: nb_bank = UDD_BULK_NB_BANK(ep); break; default: Assert(false); return false; } switch (nb_bank) { case 1: bank = UOTGHS_DEVEPTCFG_EPBK_1_BANK >> UOTGHS_DEVEPTCFG_EPBK_Pos; break; case 2: bank = UOTGHS_DEVEPTCFG_EPBK_2_BANK >> UOTGHS_DEVEPTCFG_EPBK_Pos; break; case 3: bank = UOTGHS_DEVEPTCFG_EPBK_3_BANK >> UOTGHS_DEVEPTCFG_EPBK_Pos; break; default: Assert(false); return false; } // Check if endpoint size is 8,16,32,64,128,256,512 or 1023 Assert(MaxEndpointSize < 1024); Assert((MaxEndpointSize == 1023) || !(MaxEndpointSize & (MaxEndpointSize - 1))); Assert(MaxEndpointSize >= 8); // Set configuration of new endpoint udd_configure_endpoint(ep, bmAttributes, (b_dir_in ? 1 : 0), MaxEndpointSize, bank); ep_allocated = 1 << ep; // Unalloc endpoints superior for (i = USB_DEVICE_MAX_EP; i > ep; i--) { if (Is_udd_endpoint_enabled(i)) { ep_allocated |= 1 << i; udd_disable_endpoint(i); udd_unallocate_memory(i); } } // Realloc/Enable endpoints for (i = ep; i <= USB_DEVICE_MAX_EP; i++) { if (ep_allocated & (1 << i)) { udd_ep_job_t *ptr_job = &udd_ep_job[i - 1]; bool b_restart = ptr_job->busy; // Restart running job because // memory window slides up and its data is lost ptr_job->busy = false; // Re-allocate memory udd_allocate_memory(i); udd_enable_endpoint(i); if (!Is_udd_endpoint_configured(i)) { dbg_print("ErrRealloc%d ", i); if (NULL == ptr_job->call_trans) { return false; } if (Is_udd_endpoint_in(i)) { i |= USB_EP_DIR_IN; } ptr_job->call_trans(UDD_EP_TRANSFER_ABORT, ptr_job->buf_cnt, i); return false; } udd_enable_endpoint_bank_autoswitch(i); if (b_restart) { // Re-run the job remaining part # ifdef UDD_EP_FIFO_SUPPORTED if (!Is_udd_endpoint_dma_supported(i) && !Is_udd_endpoint_in(i)) { ptr_job->buf_cnt -= ptr_job->buf_load; } # else ptr_job->buf_cnt -= ptr_job->buf_load; # endif b_restart = udd_ep_run(Is_udd_endpoint_in(i) ? (i | USB_EP_DIR_IN) : i, ptr_job->b_shortpacket, &ptr_job->buf[ptr_job->buf_cnt], ptr_job->buf_size - ptr_job->buf_cnt, ptr_job->call_trans); if (!b_restart) { dbg_print("ErrReRun%d ", i); return false; } } } } return true; } void udd_ep_free(udd_ep_id_t ep) { uint8_t ep_index = ep & USB_EP_ADDR_MASK; if (USB_DEVICE_MAX_EP < ep_index) { return; } udd_disable_endpoint(ep_index); udd_unallocate_memory(ep_index); udd_ep_abort_job(ep); udd_ep_job[ep_index - 1].stall_requested = false; } bool udd_ep_is_halted(udd_ep_id_t ep) { uint8_t ep_index = ep & USB_EP_ADDR_MASK; return Is_udd_endpoint_stall_requested(ep_index); } bool udd_ep_set_halt(udd_ep_id_t ep) { uint8_t ep_index = ep & USB_EP_ADDR_MASK; udd_ep_job_t *ptr_job = &udd_ep_job[ep_index - 1]; irqflags_t flags; if (USB_DEVICE_MAX_EP < ep_index) { return false; } if (Is_udd_endpoint_stall_requested(ep_index) // Endpoint stalled || ptr_job->stall_requested) { // Endpoint stall is requested return true; // Already STALL } if (ptr_job->busy == true) { return false; // Job on going, stall impossible } flags = cpu_irq_save(); if ((ep & USB_EP_DIR_IN) && (0 != udd_nb_busy_bank(ep_index))) { // Delay the stall after the end of IN transfer on USB line ptr_job->stall_requested = true; #ifdef UDD_EP_FIFO_SUPPORTED udd_disable_in_send_interrupt(ep_index); udd_enable_endpoint_bank_autoswitch(ep_index); #endif udd_enable_bank_interrupt(ep_index); udd_enable_endpoint_interrupt(ep_index); cpu_irq_restore(flags); return true; } // Stall endpoint immediately udd_disable_endpoint_bank_autoswitch(ep_index); udd_ack_stall(ep_index); udd_enable_stall_handshake(ep_index); cpu_irq_restore(flags); return true; } bool udd_ep_clear_halt(udd_ep_id_t ep) { uint8_t ep_index = ep & USB_EP_ADDR_MASK; udd_ep_job_t *ptr_job = &udd_ep_job[ep_index - 1]; bool b_stall_cleared = false; if (USB_DEVICE_MAX_EP < ep_index) return false; if (ptr_job->stall_requested) { // Endpoint stall has been requested but not done // Remove stall request ptr_job->stall_requested = false; udd_disable_bank_interrupt(ep_index); udd_disable_endpoint_interrupt(ep_index); b_stall_cleared = true; } if (Is_udd_endpoint_stall_requested(ep_index)) { if (Is_udd_stall(ep_index)) { udd_ack_stall(ep_index); // A packet has been stalled // then reset datatoggle udd_reset_data_toggle(ep_index); } // Disable stall udd_disable_stall_handshake(ep_index); udd_enable_endpoint_bank_autoswitch(ep_index); b_stall_cleared = true; } if (b_stall_cleared) { // If a job is register on clear halt action // then execute callback if (ptr_job->busy == true) { ptr_job->busy = false; ptr_job->call_nohalt(); } } return true; } bool udd_ep_run(udd_ep_id_t ep, bool b_shortpacket, uint8_t * buf, iram_size_t buf_size, udd_callback_trans_t callback) { #ifdef UDD_EP_FIFO_SUPPORTED bool b_dir_in = Is_udd_endpoint_in(ep & USB_EP_ADDR_MASK); #endif udd_ep_job_t *ptr_job; irqflags_t flags; ep &= USB_EP_ADDR_MASK; if (USB_DEVICE_MAX_EP < ep) { return false; } // Get job about endpoint ptr_job = &udd_ep_job[ep - 1]; if ((!Is_udd_endpoint_enabled(ep)) || Is_udd_endpoint_stall_requested(ep) || ptr_job->stall_requested) { return false; // Endpoint is halted } flags = cpu_irq_save(); if (ptr_job->busy == true) { cpu_irq_restore(flags); return false; // Job already on going } ptr_job->busy = true; cpu_irq_restore(flags); // No job running. Let's setup a new one. ptr_job->buf = buf; ptr_job->buf_size = buf_size; ptr_job->buf_cnt = 0; ptr_job->buf_load = 0; ptr_job->call_trans = callback; ptr_job->b_shortpacket = b_shortpacket || (buf_size == 0); #ifdef UDD_EP_FIFO_SUPPORTED // No DMA support if (!Is_udd_endpoint_dma_supported(ep)) { dbg_print("ex%x.%c%d\n\r", ep, b_dir_in ? 'i':'o', buf_size); flags = cpu_irq_save(); udd_enable_endpoint_interrupt(ep); if (b_dir_in) { udd_disable_endpoint_bank_autoswitch(ep); udd_enable_in_send_interrupt(ep); } else { udd_disable_endpoint_bank_autoswitch(ep); udd_enable_out_received_interrupt(ep); } cpu_irq_restore(flags); return true; } #endif // UDD_EP_FIFO_SUPPORTED #ifdef UDD_EP_DMA_SUPPORTED // Request first DMA transfer dbg_print("(exDMA%x) ", ep); udd_ep_trans_done(ep); return true; #endif } void udd_ep_abort(udd_ep_id_t ep) { uint8_t ep_index = ep & USB_EP_ADDR_MASK; #ifdef UDD_EP_FIFO_SUPPORTED if (!Is_udd_endpoint_dma_supported(ep_index)) { // Disable interrupts udd_disable_endpoint_interrupt(ep_index); udd_disable_out_received_interrupt(ep_index); udd_disable_in_send_interrupt(ep_index); } else #endif { // Stop DMA transfer udd_disable_endpoint_dma_interrupt(ep_index); udd_endpoint_dma_set_control(ep_index, 0); } udd_disable_endpoint_interrupt(ep_index); // Kill IN banks if (ep & USB_EP_DIR_IN) { while(udd_nb_busy_bank(ep_index)) { udd_kill_last_in_bank(ep_index); while(Is_udd_kill_last(ep_index)); } } udd_ep_abort_job(ep); } bool udd_ep_wait_stall_clear(udd_ep_id_t ep, udd_callback_halt_cleared_t callback) { udd_ep_job_t *ptr_job; ep &= USB_EP_ADDR_MASK; if (USB_DEVICE_MAX_EP < ep) { return false; } ptr_job = &udd_ep_job[ep - 1]; if (!Is_udd_endpoint_enabled(ep)) { return false; // Endpoint not enabled } // Wait clear halt endpoint if (ptr_job->busy == true) { return false; // Job already on going } if (Is_udd_endpoint_stall_requested(ep) || ptr_job->stall_requested) { // Endpoint halted then registes the callback ptr_job->busy = true; ptr_job->call_nohalt = callback; } else { // endpoint not halted then call directly callback callback(); } return true; } #endif // (0 != USB_DEVICE_MAX_EP) #ifdef USB_DEVICE_HS_SUPPORT void udd_test_mode_j(void) { udd_enable_hs_test_mode(); udd_enable_hs_test_mode_j(); } void udd_test_mode_k(void) { udd_enable_hs_test_mode(); udd_enable_hs_test_mode_k(); } void udd_test_mode_se0_nak(void) { udd_enable_hs_test_mode(); } void udd_test_mode_packet(void) { uint8_t i; uint8_t *ptr_dest; const uint8_t *ptr_src; const uint8_t test_packet[] = { // 00000000 * 9 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 01010101 * 8 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, // 01110111 * 8 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, // 0, {111111S * 15}, 111111 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // S, 111111S, {0111111S * 7} 0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, // 00111111, {S0111111 * 9}, S0 0xFC, 0x7E, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0x7E }; // Reconfigure control endpoint to bulk IN endpoint udd_disable_endpoint(0); udd_configure_endpoint(0, USB_EP_TYPE_BULK, 1, 64, UOTGHS_DEVEPTCFG_EPBK_1_BANK); udd_allocate_memory(0); udd_enable_endpoint(0); udd_enable_hs_test_mode(); udd_enable_hs_test_mode_packet(); // Send packet on endpoint 0 ptr_dest = (uint8_t *) & udd_get_endpoint_fifo_access(0, 8); ptr_src = test_packet; for (i = 0; i < sizeof(test_packet); i++) { *ptr_dest++ = *ptr_src++; } udd_ack_fifocon(0); } #endif // USB_DEVICE_HS_SUPPORT // ------------------------ //--- INTERNAL ROUTINES TO MANAGED THE CONTROL ENDPOINT static void udd_reset_ep_ctrl(void) { irqflags_t flags; // Reset USB address to 0 udd_configure_address(0); udd_enable_address(); // Alloc and configure control endpoint udd_configure_endpoint(0, USB_EP_TYPE_CONTROL, 0, USB_DEVICE_EP_CTRL_SIZE, UOTGHS_DEVEPTCFG_EPBK_1_BANK); udd_allocate_memory(0); udd_enable_endpoint(0); flags = cpu_irq_save(); udd_enable_setup_received_interrupt(0); udd_enable_out_received_interrupt(0); udd_enable_endpoint_interrupt(0); cpu_irq_restore(flags); } static void udd_ctrl_init(void) { irqflags_t flags; flags = cpu_irq_save(); // In case of abort of IN Data Phase: // No need to abort IN transfer (rise TXINI), // because it is automatically done by hardware when a Setup packet is received. // But the interrupt must be disabled to don't generate interrupt TXINI // after SETUP reception. udd_disable_in_send_interrupt(0); cpu_irq_restore(flags); // In case of OUT ZLP event is no processed before Setup event occurs udd_ack_out_received(0); udd_g_ctrlreq.callback = NULL; udd_g_ctrlreq.over_under_run = NULL; udd_g_ctrlreq.payload_size = 0; udd_ep_control_state = UDD_EPCTRL_SETUP; } static void udd_ctrl_setup_received(void) { irqflags_t flags; uint8_t i; if (UDD_EPCTRL_SETUP != udd_ep_control_state) { // May be a hidden DATA or ZLP phase or protocol abort udd_ctrl_endofrequest(); // Reinitializes control endpoint management udd_ctrl_init(); } // Fill setup request structure if (8 != udd_byte_count(0)) { udd_ctrl_stall_data(); udd_ack_setup_received(0); return; // Error data number doesn't correspond to SETUP packet } uint8_t *ptr = (uint8_t *) & udd_get_endpoint_fifo_access(0,8); for (i = 0; i < 8; i++) { ((uint8_t*) &udd_g_ctrlreq.req)[i] = *ptr++; } // Manage LSB/MSB to fit with CPU usage udd_g_ctrlreq.req.wValue = le16_to_cpu(udd_g_ctrlreq.req.wValue); udd_g_ctrlreq.req.wIndex = le16_to_cpu(udd_g_ctrlreq.req.wIndex); udd_g_ctrlreq.req.wLength = le16_to_cpu(udd_g_ctrlreq.req.wLength); // Decode setup request if (udc_process_setup() == false) { // Setup request unknow then stall it udd_ctrl_stall_data(); udd_ack_setup_received(0); return; } udd_ack_setup_received(0); if (Udd_setup_is_in()) { // IN data phase requested udd_ctrl_prev_payload_buf_cnt = 0; udd_ctrl_payload_buf_cnt = 0; udd_ep_control_state = UDD_EPCTRL_DATA_IN; udd_ctrl_in_sent(); // Send first data transfer } else { if (0 == udd_g_ctrlreq.req.wLength) { // No data phase requested // Send IN ZLP to ACK setup request udd_ctrl_send_zlp_in(); return; } // OUT data phase requested udd_ctrl_prev_payload_buf_cnt = 0; udd_ctrl_payload_buf_cnt = 0; udd_ep_control_state = UDD_EPCTRL_DATA_OUT; // To detect a protocol error, enable nak interrupt on data IN phase udd_ack_nak_in(0); flags = cpu_irq_save(); udd_enable_nak_in_interrupt(0); cpu_irq_restore(flags); } } static void udd_ctrl_in_sent(void) { static bool b_shortpacket = false; uint16_t nb_remain; uint8_t i; uint8_t *ptr_dest, *ptr_src; irqflags_t flags; flags = cpu_irq_save(); udd_disable_in_send_interrupt(0); cpu_irq_restore(flags); if (UDD_EPCTRL_HANDSHAKE_WAIT_IN_ZLP == udd_ep_control_state) { // ZLP on IN is sent, then valid end of setup request udd_ctrl_endofrequest(); // Reinitializes control endpoint management udd_ctrl_init(); return; } Assert(udd_ep_control_state == UDD_EPCTRL_DATA_IN); nb_remain = udd_g_ctrlreq.payload_size - udd_ctrl_payload_buf_cnt; if (0 == nb_remain) { // All content of current buffer payload are sent // Update number of total data sending by previous playlaod buffer udd_ctrl_prev_payload_buf_cnt += udd_ctrl_payload_buf_cnt; if ((udd_g_ctrlreq.req.wLength == udd_ctrl_prev_payload_buf_cnt) || b_shortpacket) { // All data requested are transfered or a short packet has been sent // then it is the end of data phase. // Generate an OUT ZLP for handshake phase. udd_ctrl_send_zlp_out(); return; } // Need of new buffer because the data phase is not complete if ((!udd_g_ctrlreq.over_under_run) || (!udd_g_ctrlreq.over_under_run())) { // Underrun then send zlp on IN // Here nb_remain=0 and allows to send a IN ZLP } else { // A new payload buffer is given udd_ctrl_payload_buf_cnt = 0; nb_remain = udd_g_ctrlreq.payload_size; } } // Continue transfer and send next data if (nb_remain >= USB_DEVICE_EP_CTRL_SIZE) { nb_remain = USB_DEVICE_EP_CTRL_SIZE; b_shortpacket = false; } else { b_shortpacket = true; } // Fill buffer of endpoint control ptr_dest = (uint8_t *) & udd_get_endpoint_fifo_access(0, 8); ptr_src = udd_g_ctrlreq.payload + udd_ctrl_payload_buf_cnt; // Critical section // Only in case of DATA IN phase abort without USB Reset signal after. // The IN data don't must be written in endpoint 0 DPRAM during // a next setup reception in same endpoint 0 DPRAM. // Thereby, an OUT ZLP reception must check before IN data write // and if no OUT ZLP is received the data must be written quickly (800µs) // before an eventually ZLP OUT and SETUP reception flags = cpu_irq_save(); if (Is_udd_out_received(0)) { // IN DATA phase aborted by OUT ZLP cpu_irq_restore(flags); udd_ep_control_state = UDD_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP; return; // Exit of IN DATA phase } // Write quickly the IN data for (i = 0; i < nb_remain; i++) { *ptr_dest++ = *ptr_src++; } udd_ctrl_payload_buf_cnt += nb_remain; // Validate and send the data available in the control endpoint buffer udd_ack_in_send(0); udd_enable_in_send_interrupt(0); // In case of abort of DATA IN phase, no need to enable nak OUT interrupt // because OUT endpoint is already free and ZLP OUT accepted. cpu_irq_restore(flags); } static void udd_ctrl_out_received(void) { irqflags_t flags; uint8_t i; uint16_t nb_data; if (UDD_EPCTRL_DATA_OUT != udd_ep_control_state) { if ((UDD_EPCTRL_DATA_IN == udd_ep_control_state) || (UDD_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP == udd_ep_control_state)) { // End of SETUP request: // - Data IN Phase aborted, // - or last Data IN Phase hidden by ZLP OUT sending quiclky, // - or ZLP OUT received normaly. udd_ctrl_endofrequest(); } else { // Protocol error during SETUP request udd_ctrl_stall_data(); } // Reinitializes control endpoint management udd_ctrl_init(); return; } // Read data received during OUT phase nb_data = udd_byte_count(0); if (udd_g_ctrlreq.payload_size < (udd_ctrl_payload_buf_cnt + nb_data)) { // Payload buffer too small nb_data = udd_g_ctrlreq.payload_size - udd_ctrl_payload_buf_cnt; } uint8_t *ptr_src = (uint8_t *) & udd_get_endpoint_fifo_access(0, 8); uint8_t *ptr_dest = udd_g_ctrlreq.payload + udd_ctrl_payload_buf_cnt; for (i = 0; i < nb_data; i++) { *ptr_dest++ = *ptr_src++; } udd_ctrl_payload_buf_cnt += nb_data; if ((USB_DEVICE_EP_CTRL_SIZE != nb_data) || (udd_g_ctrlreq.req.wLength <= (udd_ctrl_prev_payload_buf_cnt + udd_ctrl_payload_buf_cnt))) { // End of reception because it is a short packet // Before send ZLP, call intermediat calback // in case of data receiv generate a stall udd_g_ctrlreq.payload_size = udd_ctrl_payload_buf_cnt; if (NULL != udd_g_ctrlreq.over_under_run) { if (!udd_g_ctrlreq.over_under_run()) { // Stall ZLP udd_ctrl_stall_data(); // Ack reception of OUT to replace NAK by a STALL udd_ack_out_received(0); return; } } // Send IN ZLP to ACK setup request udd_ack_out_received(0); udd_ctrl_send_zlp_in(); return; } if (udd_g_ctrlreq.payload_size == udd_ctrl_payload_buf_cnt) { // Overrun then request a new payload buffer if (!udd_g_ctrlreq.over_under_run) { // No callback availabled to request a new payload buffer udd_ctrl_stall_data(); // Ack reception of OUT to replace NAK by a STALL udd_ack_out_received(0); return; } if (!udd_g_ctrlreq.over_under_run()) { // No new payload buffer delivered udd_ctrl_stall_data(); // Ack reception of OUT to replace NAK by a STALL udd_ack_out_received(0); return; } // New payload buffer available // Update number of total data received udd_ctrl_prev_payload_buf_cnt += udd_ctrl_payload_buf_cnt; // Reinit reception on payload buffer udd_ctrl_payload_buf_cnt = 0; } // Free buffer of control endpoint to authorize next reception udd_ack_out_received(0); // To detect a protocol error, enable nak interrupt on data IN phase udd_ack_nak_in(0); flags = cpu_irq_save(); udd_enable_nak_in_interrupt(0); cpu_irq_restore(flags); } static void udd_ctrl_underflow(void) { if (Is_udd_out_received(0)) return; // Underflow ignored if OUT data is received if (UDD_EPCTRL_DATA_OUT == udd_ep_control_state) { // Host want to stop OUT transaction // then stop to wait OUT data phase and wait IN ZLP handshake udd_ctrl_send_zlp_in(); } else if (UDD_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP == udd_ep_control_state) { // A OUT handshake is waiting by device, // but host want extra IN data then stall extra IN data udd_enable_stall_handshake(0); } } static void udd_ctrl_overflow(void) { if (Is_udd_in_send(0)) return; // Overflow ignored if IN data is received // The case of UDD_EPCTRL_DATA_IN is not managed // because the OUT endpoint is already free and OUT ZLP accepted if (UDD_EPCTRL_HANDSHAKE_WAIT_IN_ZLP == udd_ep_control_state) { // A IN handshake is waiting by device, // but host want extra OUT data then stall extra OUT data udd_enable_stall_handshake(0); } } static void udd_ctrl_stall_data(void) { // Stall all packets on IN & OUT control endpoint udd_ep_control_state = UDD_EPCTRL_STALL_REQ; udd_enable_stall_handshake(0); } static void udd_ctrl_send_zlp_in(void) { irqflags_t flags; udd_ep_control_state = UDD_EPCTRL_HANDSHAKE_WAIT_IN_ZLP; // Validate and send empty IN packet on control endpoint flags = cpu_irq_save(); // Send ZLP on IN endpoint udd_ack_in_send(0); udd_enable_in_send_interrupt(0); // To detect a protocol error, enable nak interrupt on data OUT phase udd_ack_nak_out(0); udd_enable_nak_out_interrupt(0); cpu_irq_restore(flags); } static void udd_ctrl_send_zlp_out(void) { irqflags_t flags; udd_ep_control_state = UDD_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP; // No action is necessary to accept OUT ZLP // because the buffer of control endpoint is already free // To detect a protocol error, enable nak interrupt on data IN phase flags = cpu_irq_save(); udd_ack_nak_in(0); udd_enable_nak_in_interrupt(0); cpu_irq_restore(flags); } static void udd_ctrl_endofrequest(void) { // If a callback is registered then call it if (udd_g_ctrlreq.callback) { udd_g_ctrlreq.callback(); } } static bool udd_ctrl_interrupt(void) { if (!Is_udd_endpoint_interrupt(0)) { return false; // No interrupt events on control endpoint } dbg_print("0: "); // By default disable overflow and underflow interrupt udd_disable_nak_in_interrupt(0); udd_disable_nak_out_interrupt(0); // Search event on control endpoint if (Is_udd_setup_received(0)) { dbg_print("stup "); // SETUP packet received udd_ctrl_setup_received(); return true; } if (Is_udd_in_send(0) && Is_udd_in_send_interrupt_enabled(0)) { dbg_print("in "); // IN packet sent udd_ctrl_in_sent(); return true; } if (Is_udd_out_received(0)) { dbg_print("out "); // OUT packet received udd_ctrl_out_received(); return true; } if (Is_udd_nak_out(0)) { dbg_print("nako "); // Overflow on OUT packet udd_ack_nak_out(0); udd_ctrl_overflow(); return true; } if (Is_udd_nak_in(0)) { dbg_print("naki "); // Underflow on IN packet udd_ack_nak_in(0); udd_ctrl_underflow(); return true; } dbg_print("n%x ", UOTGHS_ARRAY(UOTGHS_DEVEPTISR[0], 0)); return false; } // ------------------------ //--- INTERNAL ROUTINES TO MANAGED THE BULK/INTERRUPT/ISOCHRONOUS ENDPOINTS #if (0 != USB_DEVICE_MAX_EP) static void udd_ep_job_table_reset(void) { uint8_t i; for (i = 0; i < USB_DEVICE_MAX_EP; i++) { udd_ep_job[i].busy = false; udd_ep_job[i].stall_requested = false; } } static void udd_ep_job_table_kill(void) { uint8_t i; // For each endpoint, kill job for (i = 0; i < USB_DEVICE_MAX_EP; i++) { udd_ep_finish_job(&udd_ep_job[i], true, i + 1); } } static void udd_ep_abort_job(udd_ep_id_t ep) { ep &= USB_EP_ADDR_MASK; // Abort job on endpoint udd_ep_finish_job(&udd_ep_job[ep - 1], true, ep); } static void udd_ep_finish_job(udd_ep_job_t * ptr_job, bool b_abort, uint8_t ep_num) { if (ptr_job->busy == false) { return; // No on-going job } dbg_print("(JobE%x:%d) ", (ptr_job-udd_ep_job)+1, b_abort); ptr_job->busy = false; if (NULL == ptr_job->call_trans) { return; // No callback linked to job } if (Is_udd_endpoint_in(ep_num)) { ep_num |= USB_EP_DIR_IN; } ptr_job->call_trans((b_abort) ? UDD_EP_TRANSFER_ABORT : UDD_EP_TRANSFER_OK, ptr_job->buf_size, ep_num); } #ifdef UDD_EP_DMA_SUPPORTED static void udd_ep_trans_done(udd_ep_id_t ep) { uint32_t udd_dma_ctrl = 0; udd_ep_job_t *ptr_job; iram_size_t next_trans; irqflags_t flags; // Get job corresponding at endpoint ptr_job = &udd_ep_job[ep - 1]; if (!ptr_job->busy) { return; // No job is running, then ignore it (system error) } if (ptr_job->buf_cnt != ptr_job->buf_size) { // Need to send or receiv other data next_trans = ptr_job->buf_size - ptr_job->buf_cnt; if (UDD_ENDPOINT_MAX_TRANS < next_trans) { // The USB hardware support a maximum // transfer size of UDD_ENDPOINT_MAX_TRANS Bytes next_trans = UDD_ENDPOINT_MAX_TRANS; // Set 0 to tranfer the maximum udd_dma_ctrl = UOTGHS_DEVDMACONTROL_BUFF_LENGTH(0); } else { udd_dma_ctrl = UOTGHS_DEVDMACONTROL_BUFF_LENGTH(next_trans); } if (Is_udd_endpoint_in(ep)) { if (0 != (next_trans % udd_get_endpoint_size(ep))) { // Enable short packet option // else the DMA transfer is accepted // and interrupt DMA valid but nothing is sent. udd_dma_ctrl |= UOTGHS_DEVDMACONTROL_END_B_EN; // No need to request another ZLP ptr_job->b_shortpacket = false; } } else { if ((USB_EP_TYPE_ISOCHRONOUS != udd_get_endpoint_type(ep)) || (next_trans <= (iram_size_t) udd_get_endpoint_size(ep))) { // Enable short packet reception udd_dma_ctrl |= UOTGHS_DEVDMACONTROL_END_TR_IT | UOTGHS_DEVDMACONTROL_END_TR_EN; } } // Start USB DMA to fill or read fifo of the selected endpoint udd_endpoint_dma_set_addr(ep, (uint32_t) & ptr_job->buf[ptr_job->buf_cnt]); udd_dma_ctrl |= UOTGHS_DEVDMACONTROL_END_BUFFIT | UOTGHS_DEVDMACONTROL_CHANN_ENB; // Disable IRQs to have a short sequence // between read of EOT_STA and DMA enable flags = cpu_irq_save(); if (!(udd_endpoint_dma_get_status(ep) & UOTGHS_DEVDMASTATUS_END_TR_ST)) { dbg_print("dmaS%x ", ep); udd_endpoint_dma_set_control(ep, udd_dma_ctrl); ptr_job->buf_cnt += next_trans; ptr_job->buf_load = next_trans; udd_enable_endpoint_dma_interrupt(ep); cpu_irq_restore(flags); return; } cpu_irq_restore(flags); // Here a ZLP has been recieved // and the DMA transfer must be not started. // It is the end of transfer ptr_job->buf_size = ptr_job->buf_cnt; } if (Is_udd_endpoint_in(ep)) { if (ptr_job->b_shortpacket) { dbg_print("zlpS%x ", ep); // Need to send a ZLP (No possible with USB DMA) // enable interrupt to wait a free bank to sent ZLP udd_ack_in_send(ep); if (Is_udd_write_enabled(ep)) { // Force interrupt in case of ep already free udd_raise_in_send(ep); } udd_enable_in_send_interrupt(ep); udd_enable_endpoint_interrupt(ep); return; } } dbg_print("dmaE "); // Call callback to signal end of transfer udd_ep_finish_job(ptr_job, false, ep); } #endif #ifdef UDD_EP_FIFO_SUPPORTED static void udd_ep_in_sent(udd_ep_id_t ep) { udd_ep_job_t *ptr_job = &udd_ep_job[ep - 1]; uint8_t *ptr_src = &ptr_job->buf[ptr_job->buf_cnt]; uint8_t *ptr_dst = (uint8_t *) & udd_get_endpoint_fifo_access(ep, 8); uint32_t pkt_size = udd_get_endpoint_size(ep); uint32_t nb_data = 0, i; uint32_t nb_remain; irqflags_t flags; // All transfer done, including ZLP, Finish Job if (ptr_job->buf_cnt >= ptr_job->buf_size && !ptr_job->b_shortpacket) { flags = cpu_irq_save(); udd_disable_in_send_interrupt(ep); udd_disable_endpoint_interrupt(ep); cpu_irq_restore(flags); ptr_job->buf_size = ptr_job->buf_cnt; // buf_size is passed to callback as XFR count udd_ep_finish_job(ptr_job, false, ep); return; } else { // ACK TXINI udd_ack_in_send(ep); // Fill FIFO ptr_dst = (uint8_t *) & udd_get_endpoint_fifo_access(ep, 8); ptr_src = &ptr_job->buf[ptr_job->buf_cnt]; nb_remain = ptr_job->buf_size - ptr_job->buf_cnt; // Fill a bank even if no data (ZLP) nb_data = min(nb_remain, pkt_size); // Modify job information ptr_job->buf_cnt += nb_data; ptr_job->buf_load = nb_data; // Copy buffer to FIFO for (i = 0; i < nb_data; i++) { *ptr_dst++ = *ptr_src++; } // Switch to next bank udd_ack_fifocon(ep); // ZLP? if (nb_data < pkt_size) { ptr_job->b_shortpacket = false; } } } static void udd_ep_out_received(udd_ep_id_t ep) { udd_ep_job_t *ptr_job = &udd_ep_job[ep - 1]; uint32_t nb_data = 0, i; uint32_t nb_remain = ptr_job->buf_size - ptr_job->buf_cnt; uint32_t pkt_size = udd_get_endpoint_size(ep); uint8_t *ptr_src = (uint8_t *) & udd_get_endpoint_fifo_access(ep, 8); uint8_t *ptr_dst = &ptr_job->buf[ptr_job->buf_cnt]; bool b_full = false, b_short = false; // Clear RX OUT udd_ack_out_received(ep); // Read byte count nb_data = udd_byte_count(ep); if (nb_data < pkt_size) { b_short = true; } //dbg_print("o%d ", ep); //dbg_print("%d ", nb_data); // Copy data if there is if (nb_data > 0) { if (nb_data >= nb_remain) { nb_data = nb_remain; b_full = true; } // Modify job information ptr_job->buf_cnt += nb_data; ptr_job->buf_load = nb_data; // Copy FIFO to buffer for (i = 0; i < nb_data; i++) { *ptr_dst++ = *ptr_src++; } } // Clear FIFO Status udd_ack_fifocon(ep); // Finish job on error or short packet if (b_full || b_short) { //dbg_print("EoO%d\n\r", ep); udd_disable_out_received_interrupt(ep); udd_disable_endpoint_interrupt(ep); ptr_job->buf_size = ptr_job->buf_cnt; // buf_size is passed to callback as XFR count udd_ep_finish_job(ptr_job, false, ep); } } #endif // #ifdef UDD_EP_FIFO_SUPPORTED static bool udd_ep_interrupt(void) { udd_ep_id_t ep; udd_ep_job_t *ptr_job; // For each endpoint different of control endpoint (0) for (ep = 1; ep <= USB_DEVICE_MAX_EP; ep++) { // Get job corresponding at endpoint ptr_job = &udd_ep_job[ep - 1]; #ifdef UDD_EP_DMA_SUPPORTED // Check DMA event if (Is_udd_endpoint_dma_interrupt_enabled(ep) && Is_udd_endpoint_dma_interrupt(ep)) { uint32_t nb_remaining; if (udd_endpoint_dma_get_status(ep) & UOTGHS_DEVDMASTATUS_CHANN_ENB) { return true; // Ignore EOT_STA interrupt } dbg_print("dma%x: ", ep); udd_disable_endpoint_dma_interrupt(ep); // Save number of data no transfered nb_remaining = (udd_endpoint_dma_get_status(ep) & UOTGHS_DEVDMASTATUS_BUFF_COUNT_Msk) >> UOTGHS_DEVDMASTATUS_BUFF_COUNT_Pos; if (nb_remaining) { // Transfer no complete (short packet or ZLP) then: // Update number of data transfered ptr_job->buf_cnt -= nb_remaining; // Set transfer complete to stop the transfer ptr_job->buf_size = ptr_job->buf_cnt; } udd_ep_trans_done(ep); return true; } #endif #ifdef UDD_EP_FIFO_SUPPORTED // Check RXRDY and TXEMPTY event for none DMA endpoints if (!Is_udd_endpoint_dma_supported(ep) && Is_udd_endpoint_interrupt_enabled(ep)) { dbg_print("ep%x: ", ep); // RXOUT: Full packet received if (Is_udd_out_received(ep) && Is_udd_out_received_interrupt_enabled(ep)) { dbg_print("Out "); udd_ep_out_received(ep); return true; } // TXIN: packet sent if (Is_udd_in_send(ep) && Is_udd_in_send_interrupt_enabled(ep)) { dbg_print("In "); udd_ep_in_sent(ep); return true; } // Errors: Abort? if (Is_udd_overflow(ep) || Is_udd_underflow(ep) || Is_udd_crc_error(ep)) { dbg_print("Err "); udd_ep_abort(ep); return true; } } #endif // UDD_EP_FIFO_SUPPORTED // Check empty bank interrupt event if (Is_udd_endpoint_interrupt_enabled(ep)) { dbg_print("bg%x: ", ep); if (Is_udd_in_send_interrupt_enabled(ep) && Is_udd_in_send(ep)) { dbg_print("I "); udd_disable_in_send_interrupt(ep); // One bank is free then send a ZLP udd_ack_in_send(ep); udd_ack_fifocon(ep); udd_ep_finish_job(ptr_job, false, ep); return true; } if (Is_udd_bank_interrupt_enabled(ep) && (0 == udd_nb_busy_bank(ep))) { dbg_print("EoT "); // End of background transfer on IN endpoint udd_disable_bank_interrupt(ep); udd_disable_endpoint_interrupt(ep); Assert(ptr_job->stall_requested); // A stall has been requested during backgound transfer ptr_job->stall_requested = false; udd_disable_endpoint_bank_autoswitch(ep); udd_enable_stall_handshake(ep); udd_reset_data_toggle(ep); return true; } } } return false; } #endif // (0 != USB_DEVICE_MAX_EP) //@} #endif // ARDUINO_ARCH_SAM