mirror of https://github.com/EspoTek/Labrador.git
1461 lines
39 KiB
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)
|
|
//@}
|