/**
* \file
*
* \brief USB Device Driver for UDP. Compliant with common UDD driver.
*
* Copyright (c) 2012-2018 Microchip Technology Inc. and its subsidiaries.
*
* \asf_license_start
*
* \page License
*
* Subject to your compliance with these terms, you may use Microchip
* software and any derivatives exclusively with Microchip products.
* It is your responsibility to comply with third party license terms applicable
* to your use of third party software (including open source software) that
* may accompany Microchip software.
*
* THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES,
* WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE,
* INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY,
* AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE
* LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL
* LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE
* SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE
* POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST EXTENT
* ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY
* RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
* THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
*
* \asf_license_stop
*
*/
/*
* Support and FAQ: visit Microchip Support
*/
#include "conf_usb.h"
#include "sysclk.h"
#if SAMG55
#include "matrix.h"
#endif
#include "udd.h"
#include "udp_device.h"
#include
#ifndef UDD_NO_SLEEP_MGR
# include "sleep.h"
# include "sleepmgr.h"
#endif
#if !(SAM3S || SAM4S || SAM4E || SAMG55)
# error The current UDP Device Driver supports only SAM3S, SAM4S, SAM4E and SAMG55 devices.
#endif
#ifndef UDD_USB_INT_LEVEL
# define UDD_USB_INT_LEVEL 5 // By default USB interrupt have low priority
#endif
/**
* \ingroup udd_group
* \defgroup udd_udp_group USB Device Port Driver
*
* \section UDP_CONF UDP Custom configuration
* The following UDP 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_NO_SLEEP_MGR
* Feature to work without sleep manager module.
* Default not defined.
* Note that with this feature defined sleep manager must not be used in
* application.
*
* \section Callbacks management
* The USB driver is fully managed by interrupt and does not request periodic
* 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 UDP needs of USB clock and authorizes up to sleep mode WFI.
* - in USB SUSPEND state, the UDP 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
#if USB_DEVICE_MAX_EP > (UDD_MAX_PEP_NB-1) // USB_DEVICE_MAX_EP does not include control endpoint
# error USB_DEVICE_MAX_EP is too high and not supported by this part
#endif
#ifdef USB_DEVICE_HS_SUPPORT
# error The High speed mode is not supported on this part, please remove USB_DEVICE_HS_SUPPORT in conf_usb.h
#endif
#ifdef USB_DEVICE_LOW_SPEED
# error The Low speed mode is not supported on this part, please remove USB_DEVICE_LOW_SPEED in conf_usb.h
#endif
#ifndef UDD_USB_INT_FUN
# define UDD_USB_INT_FUN UDP_Handler
#endif
#ifndef UDC_VBUS_EVENT
# define UDC_VBUS_EVENT(present)
#endif
/**
* \name Power management routine.
*/
//@{
#ifndef UDD_NO_SLEEP_MGR
//! Definition of sleep levels
#define UDP_SLEEP_MODE_USB_SUSPEND SLEEPMGR_ACTIVE
#if SAMG55
#define UDP_SLEEP_MODE_USB_IDLE SLEEPMGR_ACTIVE
#else
#define UDP_SLEEP_MODE_USB_IDLE SLEEPMGR_SLEEP_WFI
#endif
//! State of USB line
static bool udd_b_idle;
/*! \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) {
sleepmgr_unlock_mode(UDP_SLEEP_MODE_USB_IDLE);
}
if (b_idle && !udd_b_idle) {
sleepmgr_lock_mode(UDP_SLEEP_MODE_USB_IDLE);
}
udd_b_idle = b_idle;
}
#else
static void udd_sleep_mode(bool b_idle)
{
UNUSED(b_idle);
}
#endif // UDD_NO_SLEEP_MGR
//@}
/**
* \name VBus monitor routine
*/
//@{
#if UDD_VBUS_IO
# if !defined(UDD_NO_SLEEP_MGR) && !defined(USB_VBUS_WKUP)
/* Lock to SLEEPMGR_SLEEP_WFI if VBus not connected */
static bool b_vbus_sleep_lock = false;
/**
* Lock sleep mode for VBus PIO pin change detection
*/
static void udd_vbus_monitor_sleep_mode(bool b_lock)
{
if (b_lock && !b_vbus_sleep_lock) {
b_vbus_sleep_lock = true;
sleepmgr_lock_mode(SLEEPMGR_SLEEP_WFI);
}
if (!b_lock && b_vbus_sleep_lock) {
b_vbus_sleep_lock = false;
sleepmgr_unlock_mode(SLEEPMGR_SLEEP_WFI);
}
}
# else
# define udd_vbus_monitor_sleep_mode(lock)
# endif
/**
* USB VBus pin change handler
*/
static void udd_vbus_handler(uint32_t id, uint32_t mask)
{
if (USB_VBUS_PIO_ID != id || USB_VBUS_PIO_MASK != mask) {
return;
}
/* PIO interrupt status has been cleared, just detect level */
bool b_vbus_high = Is_udd_vbus_high();
if (b_vbus_high) {
udd_ack_vbus_interrupt(true);
udd_vbus_monitor_sleep_mode(false);
udd_attach();
} else {
udd_ack_vbus_interrupt(false);
udd_vbus_monitor_sleep_mode(true);
udd_detach();
}
UDC_VBUS_EVENT(b_vbus_high);
}
#endif
//@}
/**
* \name Control endpoint low level management routine.
*
* This function performs control endpoint management.
* 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
} 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_nb_trans;
//! Number of data received/sent to/from udd_g_ctrlreq.payload buffer
static uint16_t udd_ctrl_payload_nb_trans;
/**
* \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 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 switches control endpoint events to correct sub function.
*
* \return \c 1 if an event about control endpoint is occurred, 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 transfer 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;
//! Total number of data transfered on endpoint
iram_size_t buf_cnt;
//! Maximum packet size for the endpoint
uint32_t size:10;
//! Current reception bank or current number of filled transmit banks
uint32_t bank:2;
//! A job is registered on this endpoint
uint32_t busy:1;
//! A stall is requested for this job on endpoint IN
uint32_t b_stall_requested:1;
//! A short packet is requested for this job on endpoint IN
uint32_t b_shortpacket:1;
//! Registered buffer on this endpoint reach its end
uint32_t b_buf_end:1;
} udd_ep_job_t;
//! Job status to finish the job
#define UDD_EP_TRANSFER_BUFFER_END 0x2
//! 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);
/**
* \brief Fill banks and send them
*
* \param ep endpoint number without direction flag
* \param b_tx true if data is ready to send
*
* \return true if data buffer is in transmitting
*/
static bool udd_ep_in_sent(udd_ep_id_t ep, bool b_tx);
/**
* \brief Store received banks
*
* \param ep endpoint number without direction flag
*/
static void udd_ep_out_received(udd_ep_id_t ep);
/**
* \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 status job status
*/
static void udd_ep_finish_job(udd_ep_job_t * ptr_job, int status,
uint8_t ep_num);
/**
* \brief Ack OUT received event.
*
* This acks the right bank when multiple banks are used.
*
* \param ep endpoint number without direction flag
*/
static void udd_ep_ack_out_received(udd_ep_id_t ep);
/**
* \brief Fill transmit data into FIFO
*
* \param ep endpoint number without direction flag
*
* \return true if a short packet has been written
*/
static bool udd_ep_write_fifo(udd_ep_id_t ep);
/**
* \brief Main interrupt routine for bulk/interrupt/isochronous endpoints
*
* This switches endpoint events to correct sub function.
*
* \return \c 1 if an event about bulk/interrupt/isochronous endpoints has occurred, 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 UDP interrupt to manage USB Device interrupts
*
* USB Device interrupt events are split 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)
*
*/
ISR(UDD_USB_INT_FUN)
{
#ifndef UDD_NO_SLEEP_MGR
/* 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;
}
#endif
/* The UDP peripheral clock in the Power Management Controller (PMC)
must be enabled before any read/write operations to the UDP registers
including the UDP_TXVC register. */
udd_enable_periph_ck();
if (Is_udd_sof_interrupt_enabled() && Is_udd_sof()) {
udd_ack_sof();
udc_sof_notify();
#ifdef UDC_SOF_EVENT
UDC_SOF_EVENT();
#endif
goto udd_interrupt_sof_end;
}
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
if ((Is_udd_wake_up_interrupt_enabled() && Is_udd_wake_up()) ||
(Is_udd_resume_interrupt_enabled() && Is_udd_resume()) ||
(Is_udd_ext_resume_interrupt_enabled() && Is_udd_ext_resume())) {
// Ack wakeup interrupt and enable suspend interrupt
udd_ack_wakeups();
// Do resume operations
udd_disable_wakeups();
udd_sleep_mode(true); // Enter in IDLE mode
#ifdef UDC_RESUME_EVENT
UDC_RESUME_EVENT();
#endif
udd_ack_suspend();
udd_enable_suspend_interrupt();
udd_enable_sof_interrupt();
goto udd_interrupt_end;
}
if (Is_udd_suspend_interrupt_enabled() && Is_udd_suspend()) {
// Ack suspend interrupt and enable resume interrupt
udd_ack_suspend();
udd_disable_suspend_interrupt();
udd_enable_wake_up_interrupt();
udd_enable_resume_interrupt();
udd_enable_ext_resume_interrupt();
udd_disable_periph_ck();
udd_sleep_mode(false); // Enter in SUSPEND mode
#ifdef UDC_SUSPEND_EVENT
UDC_SUSPEND_EVENT();
#endif
goto udd_interrupt_end;
}
if (Is_udd_reset()) {
// USB bus reset detection
udd_ack_reset();
// Abort all jobs on-going
#if (0 != USB_DEVICE_MAX_EP)
udd_ep_job_table_kill();
#endif
// Reset USB Device Stack Core
udc_reset();
// Reset device state
udd_disable_address_state();
udd_disable_configured_state();
// Reset endpoint control
udd_reset_ep_ctrl();
// Reset endpoint control management
udd_ctrl_init();
// After a USB reset, the suspend and SOF interrupt masks has been reseted
// Thus, re-enable these
udd_enable_suspend_interrupt();
udd_enable_sof_interrupt();
goto udd_interrupt_end;
}
udd_interrupt_end:
udd_interrupt_sof_end:
return;
}
bool udd_include_vbus_monitoring(void)
{
#if UDD_VBUS_IO
return true;
#else
return false;
#endif
}
void udd_enable(void)
{
irqflags_t flags;
flags = cpu_irq_save();
#if SAMG55
matrix_set_usb_device();
#endif
// Enable USB hardware
udd_enable_periph_ck();
sysclk_enable_usb();
// Cortex, uses NVIC, no need to register IRQ handler
NVIC_SetPriority((IRQn_Type) ID_UDP, UDD_USB_INT_LEVEL);
NVIC_EnableIRQ((IRQn_Type) ID_UDP);
// Reset internal variables
#if (0!=USB_DEVICE_MAX_EP)
udd_ep_job_table_reset();
#endif
// Always authorize asynchronous USB interrupts to exit of sleep mode
pmc_set_fast_startup_input(PMC_FSMR_USBAL);
#ifndef UDD_NO_SLEEP_MGR
// Initialize the sleep mode authorized for the USB suspend mode
udd_b_idle = false;
sleepmgr_lock_mode(UDP_SLEEP_MODE_USB_SUSPEND);
#endif
#if UDD_VBUS_IO
/* Initialize VBus monitor */
udd_vbus_init(udd_vbus_handler);
udd_vbus_monitor_sleep_mode(true);
/* Force Vbus interrupt when Vbus is always high
* This is possible due to a short timing between a Host mode stop/start.
*/
if (Is_udd_vbus_high()) {
udd_vbus_handler(USB_VBUS_PIO_ID, USB_VBUS_PIO_MASK);
}
#else
# ifndef USB_DEVICE_ATTACH_AUTO_DISABLE
udd_attach();
# endif
#endif
cpu_irq_restore(flags);
}
void udd_disable(void)
{
irqflags_t flags;
flags = cpu_irq_save();
udd_detach();
#ifndef UDD_NO_SLEEP_MGR
sleepmgr_unlock_mode(UDP_SLEEP_MODE_USB_SUSPEND);
#endif
# if UDD_VBUS_IO
udd_vbus_monitor_sleep_mode(false);
# 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);
// Enable peripheral clock and USB clock
udd_enable_periph_ck();
// Authorize attach if VBus is present
udd_enable_transceiver();
udd_attach_device();
// Enable USB line events
udd_enable_suspend_interrupt();
udd_enable_wake_up_interrupt();
udd_enable_resume_interrupt();
udd_enable_ext_resume_interrupt();
udd_enable_sof_interrupt();
cpu_irq_restore(flags);
}
void udd_detach(void)
{
// Disable transceiver
udd_disable_transceiver();
// Detach device from the bus
udd_detach_device();
udd_sleep_mode(false);
}
bool udd_is_high_speed(void)
{
return false;
}
void udd_set_address(uint8_t address)
{
udd_disable_address_state();
udd_disable_address();
if (address) {
udd_configure_address(address);
udd_enable_address();
udd_enable_address_state();
}
}
uint8_t udd_getaddress(void)
{
if (Is_udd_address_state_enabled())
return udd_get_configured_address();
return 0;
}
uint16_t udd_get_frame_number(void)
{
return udd_frame_number();
}
uint16_t udd_get_micro_frame_number(void)
{
return 0;
}
void udd_send_remotewakeup(void)
{
#ifndef UDD_NO_SLEEP_MGR
if (!udd_b_idle)
#endif
{
udd_sleep_mode(true); // Enter in IDLE mode
udd_enable_periph_ck();
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)
{
udd_ep_job_t *ptr_job;
bool b_dir_in;
bool b_iso;
b_dir_in = ep & USB_EP_DIR_IN;
b_iso = (bmAttributes&USB_EP_TYPE_MASK) == USB_EP_TYPE_ISOCHRONOUS;
ep = ep & USB_EP_ADDR_MASK;
if (ep > USB_DEVICE_MAX_EP) {
return false;
}
if (Is_udd_endpoint_enabled(ep)) {
return false;
}
// Check parameters
if (b_iso && (!udd_is_endpoint_support_iso(ep))) {
return false;
}
if (MaxEndpointSize > udd_get_endpoint_size_max(ep)) {
return false;
}
ptr_job = &udd_ep_job[ep - 1];
// Set endpoint size
ptr_job->size = MaxEndpointSize;
ptr_job->b_buf_end = false;
ptr_job->b_stall_requested = false;
if (b_dir_in) {
// No data buffered in FIFO
ptr_job->bank = 0;
}
// Reset FIFOs
udd_reset_endpoint(ep);
// Set configuration of new endpoint
udd_configure_endpoint(ep,
(b_dir_in ? ((bmAttributes&USB_EP_TYPE_MASK) | 0x4) : (bmAttributes&USB_EP_TYPE_MASK)),
0);
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_ep_abort_job(ep);
}
bool udd_ep_is_halted(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];
if (USB_DEVICE_MAX_EP < ep_index) {
return false;
}
return ptr_job->b_stall_requested ||
Is_udd_endpoint_stall_pending(ep & USB_EP_ADDR_MASK);
}
bool udd_ep_set_halt(udd_ep_id_t ep)
{
bool b_dir_in = ep & USB_EP_DIR_IN;
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;
}
flags = cpu_irq_save();
if (b_dir_in && (Is_udd_transmit_ready(ep_index)
|| ptr_job->bank > 1)) {
// Halt until banks sent
ptr_job->b_stall_requested = true;
udd_enable_endpoint_interrupt(ep_index);
cpu_irq_restore(flags);
return true;
} else {
// Stall endpoint
udd_enable_stall_handshake(ep_index);
udd_enable_endpoint_interrupt(ep_index);
cpu_irq_restore(flags);
}
return true;
}
bool udd_ep_clear_halt(udd_ep_id_t ep)
{
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];
ptr_job->b_stall_requested = false;
if (Is_udd_endpoint_stall_requested(ep)) {
// Remove stall
udd_disable_stall_handshake(ep);
// Reset FIFO and data toggle (after stall cleared)
udd_reset_endpoint(ep);
// Clear stall status
udd_ack_stall(ep);
// 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)
{
udd_ep_job_t *ptr_job;
irqflags_t flags;
bool b_dir_in = ep & USB_EP_DIR_IN;
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))
|| ptr_job->b_stall_requested
|| Is_udd_endpoint_stall_requested(ep)) {
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->call_trans = callback;
ptr_job->b_shortpacket = b_shortpacket || (buf_size == 0);
ptr_job->b_buf_end = false;
flags = cpu_irq_save();
udd_enable_endpoint_interrupt(ep);
// Request first transfer
if (b_dir_in) {
if (Is_udd_in_pending(ep)) {
// Append more data (handled in interrupt service)
} else {
// Start new, try to fill 1~2 banks before handling status
if (udd_ep_in_sent(ep, true)) {
// Over one bank
udd_ep_in_sent(ep, false);
} else {
// Less than one bank
}
}
} else {
// Waiting for OUT received interrupt
}
cpu_irq_restore(flags);
return true;
}
void udd_ep_abort(udd_ep_id_t ep)
{
bool b_dir_in = ep & USB_EP_DIR_IN;
irqflags_t flags;
ep &= USB_EP_ADDR_MASK;
if (USB_DEVICE_MAX_EP < ep)
return;
// Disable interrupts
flags = cpu_irq_save();
udd_disable_endpoint_interrupt(ep);
cpu_irq_restore(flags);
// Clear pending statuses
if (b_dir_in) {
// Kill banks
if (Is_udd_transmit_ready(ep)) {
udd_kill_data_in_fifo(ep,
udd_get_endpoint_bank_max_nbr(ep)>1);
}
udd_ack_in_sent(ep);
// Reset number of buffered banks
udd_ep_job[ep - 1].bank = 0;
} else {
// Clear all pending banks statuses
while(Is_udd_any_bank_received(ep)) {
udd_ep_ack_out_received(ep);
}
}
// Reset FIFO and data toggle
udd_reset_endpoint(ep);
// Abort job
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->b_stall_requested) {
// Endpoint halted then registers 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)
//--------------------------------------------------------
//--- INTERNAL ROUTINES TO MANAGED THE CONTROL ENDPOINT
static void udd_reset_ep_ctrl(void)
{
irqflags_t flags;
// Reset USB address to 0
udd_enable_address();
udd_configure_address(0);
// Alloc and configure control endpoint in OUT direction
udd_configure_endpoint(0, USB_EP_TYPE_CONTROL, 0);
udd_enable_endpoint(0);
flags = cpu_irq_save();
udd_enable_endpoint_interrupt(0);
cpu_irq_restore(flags);
}
static void udd_ctrl_init(void)
{
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)
{
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_ack_setup_received(0);
udd_ctrl_stall_data();
return; // Error data number doesn't correspond to SETUP packet
}
for (i = 0; i < 8; i++) {
((uint8_t *) & udd_g_ctrlreq.req)[i] =
udd_endpoint_fifo_read(0);
}
// 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 unknown then stall it
udd_ack_setup_received(0);
udd_ctrl_stall_data();
return;
}
if (Udd_setup_is_in()) {
// Set DIR
udd_set_endpoint_direction_in(0);
udd_ack_setup_received(0);
// IN data phase requested
udd_ctrl_prev_payload_nb_trans = 0;
udd_ctrl_payload_nb_trans = 0;
udd_ep_control_state = UDD_EPCTRL_DATA_IN;
udd_ctrl_in_sent(); // Send first data transfer
} else {
udd_ack_setup_received(0);
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_nb_trans = 0;
udd_ctrl_payload_nb_trans = 0;
udd_ep_control_state = UDD_EPCTRL_DATA_OUT;
}
}
static void udd_ctrl_in_sent(void)
{
static bool b_shortpacket = false;
uint16_t nb_remain;
uint8_t i;
uint8_t *ptr_src;
irqflags_t flags;
if (UDD_EPCTRL_HANDSHAKE_WAIT_IN_ZLP == udd_ep_control_state) {
// Ack
udd_ack_in_sent(0);
// 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_nb_trans;
if (0 == nb_remain) {
// All content of current buffer payload are sent
// Update number of total data sending by previous payload buffer
udd_ctrl_prev_payload_nb_trans += udd_ctrl_payload_nb_trans;
if ((udd_g_ctrlreq.req.wLength == udd_ctrl_prev_payload_nb_trans)
|| 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();
udd_ack_in_sent(0);
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_nb_trans = 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_src = udd_g_ctrlreq.payload + udd_ctrl_payload_nb_trans;
//** 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 (800us)
// before an eventually ZLP OUT and SETUP reception
flags = cpu_irq_save();
if (Is_udd_bank0_received(0)) {
// IN DATA phase aborted by OUT ZLP
cpu_irq_restore(flags);
udd_ep_control_state = UDD_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP;
udd_ack_in_sent(0);
return; // Exit of IN DATA phase
}
// Write quickly the IN data
for (i = 0; i < nb_remain; i++) {
udd_endpoint_fifo_write(0, *ptr_src++);
}
udd_ctrl_payload_nb_trans += nb_remain;
// Validate and send the data available in the control endpoint buffer
udd_set_transmit_ready(0);
udd_ack_in_sent(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)
{
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 quickly,
// - or ZLP OUT received normally.
udd_ctrl_endofrequest();
} else {
// Protocol error during SETUP request
udd_ctrl_stall_data();
}
udd_ack_bank0_received(0);
// 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_nb_trans + nb_data)) {
// Payload buffer too small
nb_data = udd_g_ctrlreq.payload_size -
udd_ctrl_payload_nb_trans;
}
uint8_t *ptr_dest = udd_g_ctrlreq.payload + udd_ctrl_payload_nb_trans;
for (i = 0; i < nb_data; i++) {
*ptr_dest++ = udd_endpoint_fifo_read(0);
}
udd_ctrl_payload_nb_trans += nb_data;
if ((USB_DEVICE_EP_CTRL_SIZE != nb_data)
|| (udd_g_ctrlreq.req.wLength <=
(udd_ctrl_prev_payload_nb_trans +
udd_ctrl_payload_nb_trans))) {
// End of reception because it is a short packet
// Before send ZLP, call intermediate callback
// in case of data receive generate a stall
udd_g_ctrlreq.payload_size = udd_ctrl_payload_nb_trans;
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_bank0_received(0);
return;
}
}
// Send IN ZLP to ACK setup request
udd_ack_bank0_received(0);
udd_ctrl_send_zlp_in();
return;
}
if (udd_g_ctrlreq.payload_size == udd_ctrl_payload_nb_trans) {
// Overrun then request a new payload buffer
if (!udd_g_ctrlreq.over_under_run) {
// No callback available to request a new payload buffer
udd_ctrl_stall_data();
// Ack reception of OUT to replace NAK by a STALL
udd_ack_bank0_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_bank0_received(0);
return;
}
// New payload buffer available
// Update number of total data received
udd_ctrl_prev_payload_nb_trans += udd_ctrl_payload_nb_trans;
// Reinit reception on payload buffer
udd_ctrl_payload_nb_trans = 0;
}
// Free buffer of control endpoint to authorize next reception
udd_ack_bank0_received(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)
{
udd_ep_control_state = UDD_EPCTRL_HANDSHAKE_WAIT_IN_ZLP;
// Validate and send empty IN packet on control endpoint
// Send ZLP on IN endpoint
udd_set_transmit_ready(0);
}
static void udd_ctrl_send_zlp_out(void)
{
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
}
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
// Search event on control endpoint
if (Is_udd_setup_received(0)) {
// SETUP packet received
udd_ctrl_setup_received();
return true;
}
if (Is_udd_in_sent(0)) {
// IN packet sent
udd_ctrl_in_sent();
return true;
}
if (Is_udd_bank0_received(0)) {
// OUT packet received
udd_ctrl_out_received();
return true;
}
if (Is_udd_stall(0)) {
// STALLed
udd_ack_stall(0);
return true;
}
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].bank = 0;
udd_ep_job[i].busy = false;
udd_ep_job[i].b_stall_requested = false;
udd_ep_job[i].b_shortpacket = false;
udd_ep_job[i].b_buf_end = 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], UDD_EP_TRANSFER_ABORT, 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], UDD_EP_TRANSFER_ABORT, ep);
}
static void udd_ep_finish_job(udd_ep_job_t * ptr_job, int status,
uint8_t ep_num)
{
if (ptr_job->busy == false) {
return; // No on-going job
}
ptr_job->busy = false;
if (NULL == ptr_job->call_trans) {
return; // No callback linked to job
}
if (Is_udd_endpoint_type_in(ep_num)) {
ep_num |= USB_EP_DIR_IN;
}
ptr_job->call_trans((status == UDD_EP_TRANSFER_ABORT) ?
UDD_EP_TRANSFER_ABORT : UDD_EP_TRANSFER_OK, ptr_job->buf_size, ep_num);
}
static void udd_ep_ack_out_received(udd_ep_id_t ep)
{
bool bank0_received, bank1_received;
udd_ep_job_t *ptr_job = &udd_ep_job[ep - 1];
bank0_received = Is_udd_bank0_received(ep);
bank1_received = Is_udd_bank1_received(ep);
if (bank0_received && bank1_received) {
// The only way is to use ptr_job->bank
} else if (bank0_received) {
// Must be bank0
ptr_job->bank = 0;
} else {
// Must be bank1
ptr_job->bank = 1;
}
if (ptr_job->bank == 0) {
udd_ack_bank0_received(ep);
if (udd_get_endpoint_bank_max_nbr(ep) > 1) {
ptr_job->bank = 1;
}
} else {
udd_ack_bank1_received(ep);
ptr_job->bank = 0;
}
}
static bool udd_ep_write_fifo(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];
uint32_t nb_remain = ptr_job->buf_size - ptr_job->buf_cnt;
uint32_t pkt_size = ptr_job->size;
bool is_short_pkt = false;
// Packet size
if (nb_remain < pkt_size) {
pkt_size = nb_remain;
is_short_pkt = true;
}
// Modify job information
ptr_job->buf_cnt += pkt_size;
// Speed block data transfer to FIFO (DPRAM)
for (; pkt_size >= 8; pkt_size -= 8) {
udd_endpoint_fifo_write(ep, *ptr_src++);
udd_endpoint_fifo_write(ep, *ptr_src++);
udd_endpoint_fifo_write(ep, *ptr_src++);
udd_endpoint_fifo_write(ep, *ptr_src++);
udd_endpoint_fifo_write(ep, *ptr_src++);
udd_endpoint_fifo_write(ep, *ptr_src++);
udd_endpoint_fifo_write(ep, *ptr_src++);
udd_endpoint_fifo_write(ep, *ptr_src++);
}
// Normal speed data transfer to FIFO (DPRAM)
for (; pkt_size; pkt_size--) {
udd_endpoint_fifo_write(ep, *ptr_src++);
}
// Add to buffered banks
ptr_job->bank++;
return is_short_pkt;
}
static bool udd_ep_in_sent(udd_ep_id_t ep, bool b_tx)
{
bool b_shortpacket;
udd_ep_job_t *ptr_job = &udd_ep_job[ep - 1];
// All banks are full
if (ptr_job->bank >= udd_get_endpoint_bank_max_nbr(ep)) {
return true; // Data pending
}
// No more data in buffer
if (ptr_job->buf_cnt >= ptr_job->buf_size && !ptr_job->b_shortpacket) {
return false;
}
// Fill FIFO
b_shortpacket = udd_ep_write_fifo(ep);
// Data is ready to send
if (b_tx) {
udd_set_transmit_ready(ep);
}
// Short PKT? no need to send it again.
if (b_shortpacket) {
ptr_job->b_shortpacket = false;
}
// All transfer done, including ZLP, Finish Job
if ((ptr_job->buf_cnt >= ptr_job->buf_size)
&& (!ptr_job->b_shortpacket)) {
ptr_job->b_buf_end = true;
return false;
}
return true; // Pending
}
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 = ptr_job->size;
uint8_t *ptr_dst = &ptr_job->buf[ptr_job->buf_cnt];
bool b_full = false, b_short;
// Read byte count
nb_data = udd_byte_count(ep);
b_short = (nb_data < pkt_size);
// 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;
// Copy FIFO (DPRAM) to buffer
for (i = 0; i < nb_data; i++) {
*ptr_dst++ = udd_endpoint_fifo_read(ep);
}
}
// Clear FIFO Status
udd_ep_ack_out_received(ep);
// Finish job on error or short packet
if ((b_full || b_short) &&
!Is_udd_endpoint_stall_requested(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, UDD_EP_TRANSFER_OK, ep);
}
}
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++) {
// Check RXRDY and TXEMPTY event for none DMA endpoints
if (!Is_udd_endpoint_interrupt_enabled(ep)) {
continue;
}
// Get job corresponding at endpoint
ptr_job = &udd_ep_job[ep - 1];
// RXOUT: Full packet received
if (Is_udd_any_bank_received(ep)) {
udd_ep_out_received(ep);
return true;
}
// TXIN: packet sent
if (Is_udd_in_sent(ep)) {
ptr_job->bank--;
// Stall when all banks free
if (ptr_job->b_stall_requested) {
if (ptr_job->bank) {
// Send remaining
udd_set_transmit_ready(ep);
udd_ack_in_sent(ep);
} else {
// Ack last packet
udd_ack_in_sent(ep);
// Enable stall
udd_enable_stall_handshake(ep);
// Halt executed
ptr_job->b_stall_requested = false;
}
return true;
}
// Finish Job when buffer end
if (ptr_job->b_buf_end) {
ptr_job->b_buf_end = false;
ptr_job->buf_size = ptr_job->buf_cnt; // buf_size is passed to callback as XFR count
udd_disable_endpoint_interrupt(ep);
udd_ep_finish_job(ptr_job, UDD_EP_TRANSFER_OK, ep);
}
if (ptr_job->buf_cnt >= ptr_job->buf_size &&
!ptr_job->b_shortpacket &&
ptr_job->bank == 0) {
// All transfer done, including ZLP
irqflags_t flags = cpu_irq_save();
udd_disable_endpoint_interrupt(ep);
cpu_irq_restore(flags);
// Ack last packet
udd_ack_in_sent(ep);
return true;
} else if (udd_get_endpoint_bank_max_nbr(ep) > 1
&& ptr_job->bank > 0) {
// Already banks buffered, transmit while loading
udd_set_transmit_ready(ep);
udd_ack_in_sent(ep);
udd_ep_in_sent(ep, false);
} else if (udd_get_endpoint_bank_max_nbr(ep) > 1) {
// Still bank free, load and transmit
if (!udd_ep_in_sent(ep, true)) {
ptr_job->b_buf_end = false;
ptr_job->buf_size = ptr_job->buf_cnt; // buf_size is passed to callback as XFR count
udd_disable_endpoint_interrupt(ep);
udd_ep_finish_job(ptr_job, UDD_EP_TRANSFER_OK, ep);
}
udd_ack_in_sent(ep);
udd_ep_in_sent(ep, false);
} else {
// Single bank transfer, ack when ready
udd_ep_in_sent(ep, true);
udd_ack_in_sent(ep);
}
return true;
}
// Stall sent/CRC error
if (Is_udd_stall(ep)) {
udd_ack_stall(ep);
if (udd_get_endpoint_type(ep) == UDP_CSR_EPTYPE_ISO_OUT ||
udd_get_endpoint_type(ep) == UDP_CSR_EPTYPE_ISO_IN) {
}
return true;
}
}
return false;
}
#endif // (0!=USB_DEVICE_MAX_EP)
//@}