Labrador/AVR_Code/USB_BULK_TEST/src/ASF/xmega/drivers/usb/usb_device.c

1461 lines
39 KiB
C

/**
* \file
*
* \brief USB Device driver
* Compliance with common driver UDD
*
* Copyright (c) 2011-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 <a href="http://www.atmel.com/design-support/">Atmel Support</a>
*/
#include "conf_usb.h"
// Read Modify Write opcode is implemented after IAR AVR 5.51
#ifdef __ICCAVR__
# if (__VER__ <= 551 || (__VER__ <= 611 && XMEGA_A1U) )
# undef USB_WORKAROUND_DO_NOT_USE_RMW
# define USB_WORKAROUND_DO_NOT_USE_RMW
# endif
#endif
#include "sysclk.h"
#include "udd.h"
#include "usb_device.h"
#include <string.h>
#ifndef UDD_NO_SLEEP_MGR
#include "sleepmgr.h"
#endif
#ifndef UDD_USB_INT_LEVEL
// By default USB interrupt have low priority
# define UDD_USB_INT_LEVEL USB_INTLVL_MED_gc
#endif
#ifdef USB_DEVICE_HS_SUPPORT
#error This product does not support high speed mode, please remove define USB_DEVICE_HS_SUPPORT in conf_usb.h
#endif
////////////////////////////////////////////////////
// USBB Device low-level driver (UDD)
////////////////////////////////////////////////////
/**
* \ingroup udd_group
* \defgroup udd_xmega_usb_group Xmega USB Device Driver
*
* \section USBB_CONF USBB Custom configuration
* The following USBB driver configuration must be included in the conf_usb.h
* file of the application.
*
* UDD_USB_INT_LEVEL<br>
* Option to change the interrupt priority (USB_INTLVL_x_gc)
* by default USB_INTLVL_LO_gc (recommended).
*
* \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)<br>
* To signal Vbus level change
* - UDC_SUSPEND_EVENT()<br>
* Called when USB bus enter in suspend mode
* - UDC_RESUME_EVENT()<br>
* Called when USB bus is wakeup
* - UDC_SOF_EVENT()<br>
* 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()<br>
* To call it when a transfer is finish
* - udd_ep_wait_stall_clear()<br>
* To call it when a endpoint halt is disabled
*
* \section Power mode management
* The Sleep modes authorized :
* - in USB IDLE state, the USB needs of USB clock and authorizes up to IDLE mode
* - in USB SUSPEND state, the USB no needs USB clock but requests a minimum
* clock restart timing. Thus, it authorizes up to POWER_DOWN or STANDBY mode.
*
* The USB_SLEEP_MODE_USB_IDLE equals SLEEPMGR_IDLE.
*
* The USB_SLEEP_MODE_USB_SUSPEND depends on USB clock startup timing:
* | Clock Startup | Sleep mode authorized |
* | >10ms | SLEEPMGR_STDBY |
* | <=10ms | SLEEPMGR_PDOWN |
*
* @{
*/
// 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
/**
* \name Power management routine.
*/
//@{
#ifndef UDD_NO_SLEEP_MGR
//! Definition of sleep levels
#if ((defined USB_DEVICE_HS_SUPPORT) && (USBCLK_STARTUP_TIMEOUT>3000)) \
|| ((!defined USB_DEVICE_HS_SUPPORT) && (USBCLK_STARTUP_TIMEOUT>10000))
# define USBC_SLEEP_MODE_USB_SUSPEND SLEEPMGR_IDLE
#else
# define USBC_SLEEP_MODE_USB_SUSPEND SLEEPMGR_PDOWN
#endif
#define USBC_SLEEP_MODE_USB_IDLE SLEEPMGR_IDLE
//! State of USB line
static bool udd_b_idle;
/*! \brief Authorize or not the CPU powerdown mode
*
* \param b_enable true to authorize powerdown mode
*/
static void udd_sleep_mode(bool b_idle)
{
if (!b_idle && udd_b_idle) {
sleepmgr_unlock_mode(USBC_SLEEP_MODE_USB_IDLE);
}
if (b_idle && !udd_b_idle) {
sleepmgr_lock_mode(USBC_SLEEP_MODE_USB_IDLE);
}
udd_b_idle = b_idle;
}
#else
static void udd_sleep_mode(bool b_idle) {
}
#endif // UDD_NO_SLEEP_MGR
//@}
/**
* \brief USB SRAM data about fifo, endpoint descriptor table and frame number
*
* The content of the USB SRAM can be:
* - modified by USB hardware by interface to signal endpoint status.
* Thereby, it is read by software.
* - modified by USB software to control endpoint.
* Thereby, it is read by hardware.
* This data section is volatile and the specific opcode read/modify/write must be used.
*
* @{
*/
struct udd_sram_data {
#if XMEGA_A1U
# if (0!=((USB_DEVICE_MAX_EP+1)%4))
uint8_t padding_align[16 - ((USB_DEVICE_MAX_EP + 1) *
sizeof(uint32_t)) % 16];
# endif
#endif
uint32_t fifo[USB_DEVICE_MAX_EP + 1];
USB_EP_t ep_ctrl[2 * (USB_DEVICE_MAX_EP + 1)];
uint16_t frame_number;
};
#if XMEGA_A1U
COMPILER_ALIGNED(16)
#else
COMPILER_ALIGNED(4) //! Caution seems GCC does not handle 2 alignment properly
#endif
static volatile struct udd_sram_data udd_sram;
#define UDD_EP_t USB_EP_t volatile
// @}
/**
* \name initialization of endpoint
*/
//@{
/**
* \brief Configures and enables an endpoint
*
* \param ep Endpoint number including direction (USB_EP_DIR_IN/USB_EP_DIR_OUT).
* \param bmAttributes Attribute of endpoint declared in descriptor.
* \param MaxEndpointSize Endpoint size maximum
*/
static void udd_ep_init(udd_ep_id_t ep, uint8_t bmAttributes,
uint16_t MaxEndpointSize);
/**
* \brief Returns a pointer on endpoint control SRAM corresponding at endpoint number
*
* \param ep Endpoint number including direction (USB_EP_DIR_IN/USB_EP_DIR_OUT).
*
* \return endpoint descriptor index
*/
static UDD_EP_t *udd_ep_get_ctrl(udd_ep_id_t ep);
//@}
/**
* \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
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_nb_trans;
//! Number of data received/sent to/from udd_g_ctrlreq.payload buffer
static uint16_t udd_ctrl_payload_nb_trans;
/**
* \brief Buffer to store the data received on control endpoint (SETUP/OUT endpoint 0)
*
* Used to avoid a RAM buffer overflow in case of the payload buffer
* is smaller than control endpoint size
*/
static uint8_t udd_ctrl_buffer[USB_DEVICE_EP_CTRL_SIZE];
/**
* \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
//! It is used to detect a DATA phase stopped by the host via a ZLP request.
//! This is mandatory for chapter 8 compliance
static void udd_ctrl_underflow(void);
//! \brief Managed overflow event of OUT packet on control endpoint
//! It is used to detect a DATA phase stopped by the host via a ZLP request.
//! This is mandatory for chapter 8 compliance
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 Sub interrupt routine to manage error on control endpoint
*
* \return \c 1 if an error about control endpoint is occurred, otherwise \c 0.
*/
static bool udd_ctrl_interrupt_error(void);
/**
* \brief Sub interrupt routine to manage a SETUP transfer complete on control endpoint
*
* \return \c 1 if an SETUP transfer complete about control endpoint is occurred,
* otherwise \c 0.
*/
static bool udd_ctrl_interrupt_tc_setup(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 {
//! 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;
//! The cache buffer is currently used on endpoint OUT
uint8_t b_use_out_cache_buffer:1;
//! 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 nb_trans;
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;
};
} 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 * 2];
/**
* \brief Buffer to store the data received on bulk/interrupt endpoints
*
* Used to avoid a RAM buffer overflow in case of the user buffer
* is smaller than endpoint size
*
* \warning The isochronous endpoint is not protected by this system
* and the user must always use a buffer corresponding at endpoint size
*/
#ifdef USB_DEVICE_LOW_SPEED
static uint8_t udd_ep_out_cache_buffer[USB_DEVICE_MAX_EP][8];
#else
static uint8_t udd_ep_out_cache_buffer[USB_DEVICE_MAX_EP][64];
#endif
/**
* \brief Checks endpoint number
*
* \param ep endpoint number
*/
bool udd_ep_is_valid(udd_ep_id_t ep);
/**
* \brief Manages transfer complete on bulk/interrupt/isochronous endpoints
*
* \param ep endpoint number to manage
*/
static void udd_ep_trans_complet(udd_ep_id_t ep);
/**
* \brief Returns the size of endpoint
*
* \return the size of current selected endpoint
*/
static uint16_t udd_ep_get_size(UDD_EP_t * ep_ctrl);
/**
* \brief Returns a pointer on endpoint job corresponding at endpoint number
*
* \param ep Endpoint number including direction (USB_EP_DIR_IN/USB_EP_DIR_OUT).
*/
static udd_ep_job_t *udd_ep_get_job(udd_ep_id_t ep);
#endif // (0!=USB_DEVICE_MAX_EP)
//@}
void udd_enable(void)
{
uint8_t i;
irqflags_t flags;
// Sanity check Silicon revision
#if AVR8_PART_IS_DEFINED(ATxmega128A1U)
// The part ATxmega128A1U Rev. J is not supported, please use new silicon revision.
Assert(!(MCU_REVID < 0x0A));
#endif
#ifdef CONFIG_OSC_AUTOCAL_RC32MHZ_REF_OSC
# if CONFIG_OSC_AUTOCAL_RC32MHZ_REF_OSC == OSC_ID_USBSOF
// RC oscillator calibration via USB Start Of Frame is not available
// in low speed mode.
// Thus, the calibration is disabled
// when USB interface start in low speed mode
DFLLRC32M.CTRL = 0;
# endif
#endif
#ifdef USB_DEVICE_LOW_SPEED
// The USB hardware need of 6MHz in low speed mode
sysclk_enable_usb(6);
udd_set_low_speed();
#else
// The USB hardware need of 48MHz in full speed mode
sysclk_enable_usb(48);
udd_set_full_speed();
#endif
// The XMEGA_A1U does not support the RC calibration through Keepalive (Low speed).
#if (!defined USB_DEVICE_LOW_SPEED) || (!XMEGA_A1U)
# ifdef CONFIG_OSC_AUTOCAL_RC32MHZ_REF_OSC
# if CONFIG_OSC_AUTOCAL_RC32MHZ_REF_OSC == OSC_ID_USBSOF
// The SOF calibration can be enabled
DFLLRC32M.CTRL = DFLL_ENABLE_bm;
# endif
# endif
#endif
flags = cpu_irq_save();
// Reset endpoints table
for (i = 0; i < ((USB_DEVICE_MAX_EP + 1) * 2); i++) {
udd_sram.ep_ctrl[i].CTRL = 0;
}
#if (0!=USB_DEVICE_MAX_EP)
// Reset internal variables
for (i = 0; i < (USB_DEVICE_MAX_EP * 2); i++) {
udd_ep_job[i].busy = false;
}
#endif
//** Enable USB hardware
usb_pad_init();
udd_set_nb_max_ep(USB_DEVICE_MAX_EP);
udd_enable_interface();
udd_enable_store_frame_number();
#if XMEGA_A1U
Assert(((uint16_t)(&udd_sram) & 0x0F) == 0); /* check align on 16bit */
#else
Assert(((uint16_t)(&udd_sram) & 0x01) == 0); /* check align on WORD */
#endif
udd_set_ep_table_addr(udd_sram.ep_ctrl);
// Enable TC fifo management
udd_enable_fifo();
udd_reset_fifo();
// Enable Interrupt USB Device
udd_enable_interrupt(UDD_USB_INT_LEVEL);
#ifndef UDD_NO_SLEEP_MGR
// Initialize the sleep mode authorized for the USB suspend mode
udd_b_idle = false;
sleepmgr_lock_mode(USBC_SLEEP_MODE_USB_SUSPEND);
#endif
#ifndef USB_DEVICE_ATTACH_AUTO_DISABLE
udd_attach();
#endif
cpu_irq_restore(flags);
}
void udd_disable(void)
{
irqflags_t flags;
flags = cpu_irq_save();
udd_detach_device();
// Disable interface
USB_CTRLA = 0;
USB_CTRLB = 0;
sysclk_disable_usb();
udd_sleep_mode(false);
#ifndef UDD_NO_SLEEP_MGR
sleepmgr_unlock_mode(USBC_SLEEP_MODE_USB_SUSPEND);
#endif
cpu_irq_restore(flags);
}
bool udd_include_vbus_monitoring(void)
{
return false; // No Vbus monitoring
}
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);
udd_ack_suspend_event();
udd_ack_resume_event();
udd_attach_device();
// Enable main USB interrupts
udd_enable_tc_interrupt();
udd_enable_busevt_interrupt();
udd_enable_setup_interrupt();
udd_enable_start_of_frame_interrupt();
cpu_irq_restore(flags);
}
void udd_detach(void)
{
// Detach device from the bus
udd_detach_device();
}
bool udd_is_high_speed(void)
{
return false;
}
void udd_set_address(uint8_t address)
{
udd_set_device_address(address);
}
uint8_t udd_getaddress(void)
{
return udd_get_device_address();
}
uint16_t udd_get_frame_number(void)
{
return udd_sram.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_send_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_t *ep_ctrl;
Assert(udd_ep_is_valid(ep));
ep_ctrl = udd_ep_get_ctrl(ep);
if (udd_endpoint_is_enable(ep_ctrl)) {
return false; // Already allocated
}
udd_ep_init(ep, bmAttributes, MaxEndpointSize);
/*
// Do not use multipacket mode with isochronous 1023 bytes endpoint
if (udd_endpoint_get_type(ep_ctrl)==USB_EP_TYPE_ISOCHRONOUS_gc
&& (udd_endpoint_get_size_field(ep_ctrl)
==USB_EP_BUFSIZE_1023_gc)) {
return true;
}*/
udd_endpoint_set_multipacket(ep_ctrl);
return true;
}
void udd_ep_free(udd_ep_id_t ep)
{
UDD_EP_t *ep_ctrl;
Assert(udd_ep_is_valid(ep));
udd_ep_abort(ep);
ep_ctrl = udd_ep_get_ctrl(ep);
udd_endpoint_disable(ep_ctrl);
}
bool udd_ep_is_halted(udd_ep_id_t ep)
{
UDD_EP_t *ep_ctrl;
Assert(udd_ep_is_valid(ep));
ep_ctrl = udd_ep_get_ctrl(ep);
return (udd_endpoint_is_stall(ep_ctrl));
}
bool udd_ep_set_halt(udd_ep_id_t ep)
{
UDD_EP_t *ep_ctrl;
Assert(udd_ep_is_valid(ep));
ep_ctrl = udd_ep_get_ctrl(ep);
udd_endpoint_enable_stall(ep_ctrl);
udd_endpoint_clear_dtgl(ep_ctrl);
udd_ep_abort(ep);
return true;
}
bool udd_ep_clear_halt(udd_ep_id_t ep)
{
udd_ep_job_t *ptr_job;
UDD_EP_t *ep_ctrl;
Assert(udd_ep_is_valid(ep));
ep_ctrl = udd_ep_get_ctrl(ep);
if (!udd_endpoint_is_stall(ep_ctrl)) {
return true; // No stall on going
}
udd_endpoint_disable_stall(ep_ctrl);
// If a job is register on clear halt action
// then execute callback
ptr_job = udd_ep_get_job(ep);
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;
UDD_EP_t *ep_ctrl;
Assert(udd_ep_is_valid(ep));
// Get control & job about this endpoint
ptr_job = udd_ep_get_job(ep);
ep_ctrl = udd_ep_get_ctrl(ep);
if (!udd_endpoint_is_enable(ep_ctrl)) {
return false; // Endpoint not allocated
}
if (udd_endpoint_get_type(ep_ctrl)!=USB_EP_TYPE_ISOCHRONOUS_gc
&& udd_endpoint_is_stall(ep_ctrl)) {
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);
// Update Job information
ptr_job->buf = buf;
ptr_job->buf_size = buf_size;
ptr_job->nb_trans = 0;
ptr_job->call_trans = callback;
// Need to enable shortpacket to send a ZLP (buf_size==0)
ptr_job->b_shortpacket = b_shortpacket || (buf_size==0);
ptr_job->b_use_out_cache_buffer = false;
// Initialize value to simulate a empty transfer
if (USB_EP_DIR_IN == (ep & USB_EP_DIR_IN)) {
udd_endpoint_in_reset_nb_sent(ep_ctrl);
}
else
{
if ((USB_EP_TYPE_ISOCHRONOUS_gc == udd_endpoint_get_type(ep_ctrl))
&& (0 != (buf_size % udd_ep_get_size(ep_ctrl)))) {
// The user must use a buffer size modulo endpoint size
ptr_job->busy = false;
return false;
}
udd_endpoint_out_reset_nb_received(ep_ctrl);
udd_endpoint_out_set_nbbyte(ep_ctrl, 0);
}
// Request next transfer
udd_ep_trans_complet(ep);
return true;
}
void udd_ep_abort(udd_ep_id_t ep)
{
UDD_EP_t *ep_ctrl;
udd_ep_job_t *ptr_job;
Assert(udd_ep_is_valid(ep));
ep_ctrl = udd_ep_get_ctrl(ep);
ptr_job = udd_ep_get_job(ep);
// Stop transfer
udd_endpoint_set_NACK0(ep_ctrl);
if (ptr_job->busy == false) {
return; // No job on going
}
ptr_job->busy = false;
if (NULL != ptr_job->call_trans) {
ptr_job->call_trans(UDD_EP_TRANSFER_ABORT,
(ep & USB_EP_DIR_IN) ?
udd_endpoint_in_nb_sent(ep_ctrl)
: udd_endpoint_out_nb_receiv(ep_ctrl),
ep);
}
}
bool udd_ep_wait_stall_clear(udd_ep_id_t ep,
udd_callback_halt_cleared_t callback)
{
udd_ep_job_t *ptr_job;
UDD_EP_t *ep_ctrl;
Assert(udd_ep_is_valid(ep));
ep_ctrl = udd_ep_get_ctrl(ep);
ptr_job = udd_ep_get_job(ep);
if (udd_endpoint_is_stall(ep_ctrl)) {
// Wait clear halt endpoint
if (ptr_job->busy == true) {
return false; // Job already on going
}
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 GLOBAL EVENTS
/**
* \internal
* \brief Function called by USB bus event interrupt
*
* USB bus event interrupt includes :
* - USB line events SOF, reset, suspend, resume, wakeup
* - endpoint control errors underflow, overflow, stall
*/
ISR(USB_BUSEVENT_vect)
{
if (udd_is_start_of_frame_event()) {
udd_ack_start_of_frame_event();
udc_sof_notify();
#ifdef UDC_SOF_EVENT
UDC_SOF_EVENT();
#endif
goto udd_interrupt_bus_event_end;
}
if (udd_ctrl_interrupt_error()) {
goto udd_interrupt_bus_event_end;
}
if (udd_is_reset_event()) {
udd_ack_reset_event();
#if (0!=USB_DEVICE_MAX_EP)
// Abort all endpoint jobs on going
uint8_t i;
for (i = 1; i < USB_DEVICE_MAX_EP; i++) {
udd_ep_abort(i);
udd_ep_abort(i | USB_EP_DIR_IN);
}
#endif
udc_reset();
// Reset USB address to 0
udd_set_device_address(0);
// Alloc and configure control endpoint
udd_ep_init(0, USB_EP_TYPE_CONTROL, USB_DEVICE_EP_CTRL_SIZE);
udd_ep_init(0 | USB_EP_DIR_IN, USB_EP_TYPE_CONTROL,
USB_DEVICE_EP_CTRL_SIZE);
udd_control_out_set_buf(&udd_ctrl_buffer);
// Reset endpoint control management
udd_ctrl_init();
goto udd_interrupt_bus_event_end;
}
if (udd_is_suspend_event()) {
udd_ack_suspend_event();
udd_sleep_mode(false); // Enter in SUSPEND mode
#ifdef UDC_SUSPEND_EVENT
UDC_SUSPEND_EVENT();
#endif
goto udd_interrupt_bus_event_end;
}
if (udd_is_resume_event()) {
udd_ack_resume_event();
udd_sleep_mode(true); // Enter in power reduction mode
#ifdef UDC_RESUME_EVENT
UDC_RESUME_EVENT();
#endif
goto udd_interrupt_bus_event_end;
}
udd_interrupt_bus_event_end:
return;
}
/**
* \internal
* \brief Function called by USB transfer complete interrupt
*
* USB transfer complete interrupt includes events about endpoint transfer on all endpoints.
*/
ISR(USB_TRNCOMPL_vect)
{
#if (0!=USB_DEVICE_MAX_EP)
uint8_t ep_index;
uint8_t i_fifo;
uint16_t ad;
uint16_t *p_ad;
int8_t rp;
UDD_EP_t *ep_ctrl;
udd_ep_id_t ep;
#endif
if (!udd_is_tc_event()) {
// If no other transfer complete
// then check reception of SETUP packet on control endpoint
if (udd_ctrl_interrupt_tc_setup()) {
// Interrupt acked by control endpoint managed
goto udd_interrupt_tc_end;
}
Assert(false);
}
// Check IN/OUT transfer complete on all endpoints
udd_ack_tc_event();
#if (0!=USB_DEVICE_MAX_EP)
//** Decode TC FIFO
// Compute ep addr
rp = udd_get_fifo_rp();
i_fifo = 2 * (1 + ~rp);
ad = ((uint16_t) udd_sram.ep_ctrl) - i_fifo;
p_ad = (uint16_t *) ad;
// Compute ep
ep_index = (((uint16_t) * p_ad - ((uint16_t) udd_sram.ep_ctrl)) >> 3);
ep = (ep_index / 2) + ((ep_index & 1) ? USB_EP_DIR_IN : 0);
Assert(USB_DEVICE_MAX_EP >= (ep & USB_EP_ADDR_MASK));
// Ack IT TC of endpoint
ep_ctrl = udd_ep_get_ctrl(ep);
if (!udd_endpoint_transfer_complete(ep_ctrl)) {
return; // Error, TC is generated by Multipacket transfer
}
udd_endpoint_ack_transfer_complete(ep_ctrl);
// Check status on control endpoint
if (ep == 0) {
udd_ctrl_out_received();
goto udd_interrupt_tc_end; // Interrupt acked by control endpoint managed
}
if (ep == (0 | USB_EP_DIR_IN)) {
udd_ctrl_in_sent();
goto udd_interrupt_tc_end; // Interrupt acked by control endpoint managed
}
Assert(udd_ep_is_valid(ep));
// Manage end of transfer on endpoint bulk/interrupt/isochronous
udd_ep_trans_complet(ep);
#else
udd_get_fifo_rp();
if (udd_endpoint_transfer_complete(udd_ep_get_ctrl(0))) {
udd_endpoint_ack_transfer_complete(udd_ep_get_ctrl(0));
udd_ctrl_out_received();
}else{
udd_endpoint_ack_transfer_complete(udd_ep_get_ctrl(0 | USB_EP_DIR_IN));
udd_ctrl_in_sent();
}
#endif
udd_interrupt_tc_end:
return;
}
//--------------------------------------------------------
//--- INTERNAL ROUTINES TO INITIALIZE ENDPOINT
static void udd_ep_init(udd_ep_id_t ep, uint8_t bmAttributes,
uint16_t MaxEndpointSize)
{
USB_EP_TYPE_t type;
USB_EP_BUFSIZE_t size;
UDD_EP_t *ep_ctrl;
#if (0!=USB_DEVICE_MAX_EP)
// Translate USB attribute to hardware defines
switch (bmAttributes & USB_EP_TYPE_MASK) {
case USB_EP_TYPE_CONTROL:
type = USB_EP_TYPE_CONTROL_gc;
break;
case USB_EP_TYPE_ISOCHRONOUS:
type = USB_EP_TYPE_ISOCHRONOUS_gc;
break;
case USB_EP_TYPE_BULK:
case USB_EP_TYPE_INTERRUPT: //interrupt behaves as bulk
type = USB_EP_TYPE_BULK_gc;
break;
default:
Assert(false); // Wrong value
return;
}
#else
type = USB_EP_TYPE_CONTROL_gc;
#endif
// Translate USB endpoint size to hardware defines
switch (MaxEndpointSize) {
default:
Assert(false); // Wrong value
case 8:
size = USB_EP_BUFSIZE_8_gc;
break;
case 16:
size = USB_EP_BUFSIZE_16_gc;
break;
case 32:
size = USB_EP_BUFSIZE_32_gc;
break;
case 64:
size = USB_EP_BUFSIZE_64_gc;
break;
#if (0!=USB_DEVICE_MAX_EP)
case 128:
size = USB_EP_BUFSIZE_128_gc;
break;
case 256:
size = USB_EP_BUFSIZE_256_gc;
break;
case 512:
size = USB_EP_BUFSIZE_512_gc;
break;
case 1023:
size =USB_EP_BUFSIZE_1023_gc;
break;
#endif
}
// Enable endpoint
ep_ctrl = udd_ep_get_ctrl(ep);
udd_endpoint_disable(ep_ctrl);
udd_endpoint_clear_status(ep_ctrl);
udd_endpoint_set_control(ep_ctrl, (uint8_t) type | (uint8_t) size);
}
static UDD_EP_t *udd_ep_get_ctrl(udd_ep_id_t ep)
{
return &udd_sram.ep_ctrl[(2 * (ep & USB_EP_ADDR_MASK) +
((ep & USB_EP_DIR_IN) ? 1 : 0))];
}
//--------------------------------------------------------
//--- INTERNAL ROUTINES TO MANAGED THE CONTROL ENDPOINT
static void udd_ctrl_init(void)
{
udd_disable_overflow_interrupt();
udd_disable_underflow_interrupt();
// Clear status flag from control endpoints
// Mandatory for ATxmega128A1 Rev. K
udd_control_in_set_NACK0();
udd_control_in_set_bytecnt(0);
udd_control_in_ack_tc();
udd_control_ack_in_underflow();
udd_control_out_ack_tc();
udd_control_ack_out_overflow();
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)
{
if (UDD_EPCTRL_SETUP != udd_ep_control_state) {
if ((UDD_EPCTRL_HANDSHAKE_WAIT_IN_ZLP == udd_ep_control_state)
|| (UDD_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP == udd_ep_control_state)) {
// Accept that ZLP event can be hidden by setup packet event
// in case of setup packet sending quickly after a ZLP
udd_ctrl_endofrequest();
}
// Reinitializes control endpoint management
udd_ctrl_init();
}
// Fill setup request structure
if (8 != udd_control_out_get_bytecnt())
return; // Error data number don't correspond to SETUP packet
memcpy((uint8_t *) & udd_g_ctrlreq.req, udd_ctrl_buffer, 8);
// To detect a protocol error on setup, enable nak interrupt on IN/OUT of control endpoint
udd_enable_overflow_interrupt();
udd_enable_underflow_interrupt();
// Decode setup request
if (udc_process_setup() == false) {
// Setup request unknown then stall it
udd_ctrl_stall_data();
return;
}
if (Udd_setup_is_in()) {
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 {
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;
// Clear packet to receive first packet
udd_control_out_clear_NACK0();
}
}
static void udd_ctrl_in_sent(void)
{
static bool b_shortpacket = false;
uint16_t nb_remain;
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_nb_trans;
if (0 == nb_remain) {
// Update number of total data sending by previous playload 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();
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
// nb_remain == 0 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 an 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;
}
udd_control_in_set_bytecnt(nb_remain);
// Link payload buffer directly on USB hardware
udd_control_in_set_buf(udd_g_ctrlreq.payload + udd_ctrl_payload_nb_trans);
udd_ctrl_payload_nb_trans += nb_remain;
// Valid and sent the data available in control endpoint buffer
udd_control_in_clear_NACK0();
}
static void udd_ctrl_out_received(void)
{
uint16_t nb_data;
if (UDD_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP == udd_ep_control_state) {
// Valid end of setup request
udd_ctrl_endofrequest();
// Reinitializes control endpoint management
udd_ctrl_init();
return;
}
Assert(udd_ep_control_state == UDD_EPCTRL_DATA_OUT);
// Read data received during OUT phase
nb_data = udd_control_out_get_bytecnt();
if (udd_g_ctrlreq.payload_size < (udd_ctrl_payload_nb_trans + nb_data)) {
// Payload buffer too small, ignore data remaining
nb_data = udd_g_ctrlreq.payload_size - udd_ctrl_payload_nb_trans;
}
memcpy((uint8_t *) (udd_g_ctrlreq.payload + udd_ctrl_payload_nb_trans),
udd_ctrl_buffer, nb_data);
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
// or all data are transfered
// 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();
return;
}
}
// Send IN ZLP to ACK setup request
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();
return;
}
if (!udd_g_ctrlreq.over_under_run()) {
// No new payload buffer delivered
udd_ctrl_stall_data();
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 OUT control endpoint to authorize next reception
udd_control_out_clear_NACK0();
}
static void udd_ctrl_underflow(void)
{
if (udd_is_tc_event() || udd_ctrl_interrupt_tc_setup()) {
return; // underflow ignored if a transfer complete has been no processed
}
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 and following status stage
udd_control_in_enable_stall();
udd_control_out_enable_stall();
}
}
static void udd_ctrl_overflow(void)
{
if (udd_is_tc_event() || udd_ctrl_interrupt_tc_setup()) {
return; // overflow ignored if a transfer complete has been no processed
}
if (UDD_EPCTRL_DATA_IN == udd_ep_control_state) {
// Host want to stop IN transaction
// then stop to wait IN data phase and wait OUT ZLP handshake
udd_ctrl_send_zlp_out();
} else 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 and following status stage
udd_control_in_enable_stall();
udd_control_out_enable_stall();
}
}
static void udd_ctrl_stall_data(void)
{
// Stall all packets on IN & OUT control endpoint
udd_ep_control_state = UDD_EPCTRL_STALL_REQ;
udd_control_in_enable_stall();
udd_control_out_enable_stall();
}
static void udd_ctrl_send_zlp_in(void)
{
udd_ep_control_state = UDD_EPCTRL_HANDSHAKE_WAIT_IN_ZLP;
// Valid and sent empty IN packet on control endpoint
udd_control_in_set_bytecnt(0);
udd_control_in_clear_NACK0();
}
static void udd_ctrl_send_zlp_out(void)
{
udd_ep_control_state = UDD_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP;
// Valid reception of OUT packet on control endpoint
udd_control_out_clear_NACK0();
}
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_error(void)
{
// Underflow only managed for control endpoint
if (udd_is_underflow_event()) {
udd_ack_underflow_event();
if (udd_control_in_underflow()) {
udd_ctrl_underflow();
}
return true;
}
// Overflow only managed for control endpoint
if (udd_is_overflow_event()) {
udd_ack_overflow_event();
if (udd_control_out_overflow()) {
udd_ctrl_overflow();
}
return true;
}
return false;
}
static bool udd_ctrl_interrupt_tc_setup(void)
{
if (!udd_is_setup_event()) {
return false;
}
udd_ack_setup_event();
// Clear eventually previous stall events
udd_control_out_ack_stall();
udd_control_in_ack_stall();
udd_ack_stall_event();
Assert(udd_control_setup()); // A setup must be received on control endpoint
// Ack SETUP packet and decode request
udd_control_ack_setup();
udd_ctrl_setup_received();
return true;
}
//--------------------------------------------------------
//--- INTERNAL ROUTINES TO MANAGED THE BULK/INTERRUPT/ISOCHRONOUS ENDPOINTS
#if (0!=USB_DEVICE_MAX_EP)
static uint16_t udd_ep_get_size(UDD_EP_t * ep_ctrl)
{
// Translate hardware defines to USB endpoint size
switch (udd_endpoint_get_size_field(ep_ctrl)) {
default:
case USB_EP_BUFSIZE_8_gc:
return 8;
case USB_EP_BUFSIZE_16_gc:
return 16;
case USB_EP_BUFSIZE_32_gc:
return 32;
case USB_EP_BUFSIZE_64_gc:
return 64;
case USB_EP_BUFSIZE_128_gc:
return 128;
case USB_EP_BUFSIZE_256_gc:
return 256;
case USB_EP_BUFSIZE_512_gc:
return 512;
case USB_EP_BUFSIZE_1023_gc:
return 1023;
}
}
static udd_ep_job_t *udd_ep_get_job(udd_ep_id_t ep)
{
return &udd_ep_job[(2 * (ep & USB_EP_ADDR_MASK) +
((ep & USB_EP_DIR_IN) ? 1 : 0)) - 2];
}
bool udd_ep_is_valid(udd_ep_id_t ep)
{
ep &= USB_EP_ADDR_MASK;
if (ep == 0) {
return false;
}
return (USB_DEVICE_MAX_EP >= ep);
}
static void udd_ep_trans_complet(udd_ep_id_t ep)
{
UDD_EP_t *ep_ctrl;
udd_ep_job_t *ptr_job;
uint16_t ep_size, nb_trans;
iram_size_t next_trans;
ptr_job = udd_ep_get_job(ep);
ep_ctrl = udd_ep_get_ctrl(ep);
ep_size = udd_ep_get_size(ep_ctrl);
if (USB_EP_DIR_IN == (ep & USB_EP_DIR_IN)) {
// Transfer complete on IN
nb_trans = udd_endpoint_in_nb_sent(ep_ctrl);
// Update number of data transfered
ptr_job->nb_trans += nb_trans;
// Need to send other data
if (ptr_job->nb_trans != ptr_job->buf_size) {
next_trans = ptr_job->buf_size - ptr_job->nb_trans;
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 -
(UDD_ENDPOINT_MAX_TRANS % ep_size);
}
// Need ZLP, if requested and last packet is not a short packet
ptr_job->b_shortpacket = ptr_job->b_shortpacket
&& (0==(next_trans % ep_size));
udd_endpoint_in_reset_nb_sent(ep_ctrl);
udd_endpoint_in_set_bytecnt(ep_ctrl, next_trans);
// Link the user buffer directly on USB hardware DMA
udd_endpoint_set_buf(ep_ctrl, &ptr_job->buf[ptr_job->nb_trans]);
udd_endpoint_clear_NACK0(ep_ctrl);
return;
}
// Need to send a ZLP after all data transfer
if (ptr_job->b_shortpacket) {
ptr_job->b_shortpacket = false;
udd_endpoint_in_reset_nb_sent(ep_ctrl);
udd_endpoint_in_set_bytecnt(ep_ctrl, 0);
udd_endpoint_clear_NACK0(ep_ctrl);
return;
}
}
else
{
// Transfer complete on OUT
nb_trans = udd_endpoint_out_nb_receiv(ep_ctrl);
// Can be necessary to copy data receive from cache buffer to user buffer
if (ptr_job->b_use_out_cache_buffer) {
memcpy(&ptr_job->buf[ptr_job->nb_trans]
, udd_ep_out_cache_buffer[ep - 1]
, ptr_job->buf_size % ep_size);
}
// Update number of data transfered
ptr_job->nb_trans += nb_trans;
if (ptr_job->nb_trans > ptr_job->buf_size) {
ptr_job->nb_trans = ptr_job->buf_size;
}
// If all previous data requested are received and user buffer not full
// then need to receive other data
if ((nb_trans == udd_endpoint_out_get_nbbyte_requested(ep_ctrl))
&& (ptr_job->nb_trans != ptr_job->buf_size)) {
next_trans = ptr_job->buf_size - ptr_job->nb_trans;
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
- (UDD_ENDPOINT_MAX_TRANS % ep_size);
} else {
next_trans -= next_trans % ep_size;
}
udd_endpoint_out_reset_nb_received(ep_ctrl);
if (next_trans < ep_size) {
// Use the cache buffer for Bulk or Interrupt size endpoint
ptr_job->b_use_out_cache_buffer = true;
udd_endpoint_set_buf( ep_ctrl,
udd_ep_out_cache_buffer[ep - 1]);
udd_endpoint_out_set_nbbyte(ep_ctrl, ep_size);
} else {
// Link the user buffer directly on USB hardware DMA
udd_endpoint_set_buf(ep_ctrl, &ptr_job->buf[ptr_job->nb_trans]);
udd_endpoint_out_set_nbbyte(ep_ctrl, next_trans);
}
// Start transfer
udd_endpoint_clear_NACK0(ep_ctrl);
return;
}
}
// Job complete then call callback
if (ptr_job->busy) {
ptr_job->busy = false;
if (NULL != ptr_job->call_trans) {
ptr_job->call_trans(UDD_EP_TRANSFER_OK,
ptr_job->nb_trans,
ep);
}
}
return;
}
#endif // (0!=USB_DEVICE_MAX_EP)
//@}