Skip to content
Permalink
b49b2bfda7
Go to file
 
 
Cannot retrieve contributors at this time
1847 lines (1651 sloc) 46.6 KB
/**
* \file
*
* \brief USB host driver
* Compliance with common driver UHD
*
* 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>
*/
// FIXED: samdoshi 2016-03-17 - added missing import
#include "pm.h"
#include "conf_usb_host.h"
#include "sysclk.h"
#include "uhd.h"
#include "usbb_otg.h"
#include "usbb_host.h"
#include <string.h>
#include <stdlib.h>
// Fix the fact that, for some IAR header files, the AVR32_USBB_IRQ_GROUP
// define has been defined as AVR32_USB_IRQ_GROUP instead.
#if !defined(AVR32_USBB_IRQ_GROUP)
#define AVR32_USBB_IRQ_GROUP AVR32_USB_IRQ_GROUP
#endif
#ifdef UDD_ENABLE
// Dual (device/host) mode enabled
extern void udd_interrupt(void);
extern void udc_stop(void);
extern void udc_start(void);
#else
// Host mode only = Device mode disabled
# define udd_interrupt() Assert(false) // System error
# define udc_start()
# define udc_stop()
#endif
#ifdef UHD_START_MODE_MANUAL
// Automatic start mode is disabled
# define uhc_start()
# define udc_start()
#endif
#ifndef UHD_USB_INT_LEVEL
# define UHD_USB_INT_LEVEL 0 // By default USB interrupt have low priority
#endif
// Optional UHC callbacks
#ifndef UHC_MODE_CHANGE
# define UHC_MODE_CHANGE(arg)
#endif
#ifndef UHC_SOF_EVENT
# define UHC_SOF_EVENT()
#endif
#ifndef UHC_VBUS_CHANGE
# define UHC_VBUS_CHANGE(b_present)
#endif
#ifndef UHC_VBUS_ERROR
# define UHC_VBUS_ERROR()
#endif
/**
* \ingroup uhd_group
* \defgroup uhd_usbb_group USBB Host Driver
*
* \section USB_CONF USB dual role configuration
* The defines UHD_ENABLE and UDD_ENABLE must be added in project to allow
* the USB low level (UHD) to manage or not the dual role (device and host).
*
* \section USBB_CONF USBB Custom configuration
* The following USBB driver configuration must be defined in conf_usb_host.h
* file of the application.
*
* UHD_USB_INT_LEVEL<br>
* Option to change the interrupt priority (0 to 3) by default 0 (recommended).
*
* UHD_ISOCHRONOUS_NB_BANK<br>
* Feature to reduce or increase isochronous endpoints buffering (1 to 2).
* Default value 2.
*
* UHD_BULK_NB_BANK<br>
* Feature to reduce or increase bulk endpoints buffering (1 to 2).
* Default value 2.
*
* UHD_INTERRUPT_NB_BANK<br>
* Feature to reduce or increase interrupt endpoints buffering (1 to 2).
* Default value 1.
*
* \section Callbacks management
* The USB driver is fully managed by interrupt and does not request periodic
* task. Thereby, the USB events use callbacks to transfer the information.
* The callbacks can be declared in static during compilation
* or dynamically during code execution.
*
* \section Power mode management
* The driver uses the sleepmgr service to manage the different sleep modes.
* The sleep mode depends on USB driver state (uhd_usbb_state_enum).
* @{
*/
// Check USB host configuration
#ifndef AVR32_USBB_USBSTA_SPEED_HIGH
# ifdef USB_HOST_HS_SUPPORT
# error The High speed mode is not supported on this part, please remove USB_HOST_HS_SUPPORT in conf_usb_host.h
# endif
#endif
#ifndef UHD_ISOCHRONOUS_NB_BANK
# define UHD_ISOCHRONOUS_NB_BANK 2
#else
# if (UHD_ISOCHRONOUS_NB_BANK < 1) || (UHD_ISOCHRONOUS_NB_BANK > 2)
# error UHD_ISOCHRONOUS_NB_BANK must be define with 1 or 2.
# endif
#endif
#ifndef UHD_BULK_NB_BANK
# define UHD_BULK_NB_BANK 2
#else
# if (UHD_BULK_NB_BANK < 1) || (UHD_BULK_NB_BANK > 2)
# error UHD_BULK_NB_BANK must be define with 1 or 2.
# endif
#endif
#ifndef UHD_INTERRUPT_NB_BANK
# define UHD_INTERRUPT_NB_BANK 1
#else
# if (UHD_INTERRUPT_NB_BANK < 1) || (UHD_INTERRUPT_NB_BANK > 2)
# error UHD_INTERRUPT_NB_BANK must be define with 1 or 2.
# endif
#endif
/**
* \name Power management
*/
//@{
#ifndef UHD_NO_SLEEP_MGR
#include "sleepmgr.h"
//! States of USBB interface
enum uhd_usbb_state_enum {
UHD_STATE_OFF = 0,
UHD_STATE_WAIT_ID_HOST = 1,
UHD_STATE_NO_VBUS = 2,
UHD_STATE_DISCONNECT = 3,
UHD_STATE_SUSPEND = 4,
UHD_STATE_IDLE = 5,
};
/*! \brief Manages the sleep mode following the USBB state
*
* \param new_state New USBB state
*/
static void uhd_sleep_mode(enum uhd_usbb_state_enum new_state)
{
enum sleepmgr_mode sleep_mode[] = {
SLEEPMGR_STATIC, // UHD_STATE_OFF (not used)
SLEEPMGR_STATIC, // UHD_STATE_WAIT_ID_HOST
#ifdef AVR32_USBB_USBSTA_SPEED_HIGH
SLEEPMGR_FROZEN, // UHD_STATE_NO_VBUS
#else
SLEEPMGR_STOP, // UHD_STATE_NO_VBUS
#endif
SLEEPMGR_IDLE, // UHD_STATE_DISCONNECT
SLEEPMGR_STATIC, // UHD_STATE_SUSPEND
SLEEPMGR_IDLE, // UHD_STATE_IDLE
};
static enum uhd_usbb_state_enum uhd_state = UHD_STATE_OFF;
if (uhd_state == new_state) {
return; // No change
}
if (new_state != UHD_STATE_OFF) {
// Lock new limit
sleepmgr_lock_mode( sleep_mode[new_state] );
}
if (uhd_state != UHD_STATE_OFF) {
// Unlock old limit
sleepmgr_unlock_mode( sleep_mode[uhd_state] );
}
uhd_state = new_state;
}
#else
# define uhd_sleep_mode(arg)
#endif // UHD_NO_SLEEP_MGR
//@}
//! State of USBB OTG initialization
static bool otg_initialized = false;
//! Store the callback to be call at the end of reset signal
static uhd_callback_reset_t uhd_reset_callback = NULL;
/**
* \name Control endpoint low level management routine.
*
* This function performs control endpoint management.
* It handles the SETUP/DATA/HANDSHAKE phases of a control transaction.
*/
//@{
/**
* \brief Structure to store the high level setup request
*/
struct uhd_ctrl_request_t {
//! USB address of control endpoint
usb_add_t add;
//! Setup request definition
usb_setup_req_t req;
//! Buffer to store the setup DATA phase
uint8_t *payload;
//! Size of buffer used in DATA phase
uint16_t payload_size;
//! Callback called when buffer is empty or full
uhd_callback_setup_run_t callback_run;
//! Callback called when request is completed
uhd_callback_setup_end_t callback_end;
//! Next setup request to process
struct uhd_ctrl_request_t *next_request;
};
//! Entry points of setup request list
struct uhd_ctrl_request_t *uhd_ctrl_request_first;
struct uhd_ctrl_request_t *uhd_ctrl_request_last;
//! Remaining time for on-going setup request (No request on-going if equal 0)
volatile uint16_t uhd_ctrl_request_timeout;
//! Number of transfered byte on DATA phase of current setup request
uint16_t uhd_ctrl_nb_trans;
//! Flag to delay a suspend request after all on-going setup request
static bool uhd_b_suspend_requested;
//! Bit definitions to store setup request state machine
typedef enum {
//! Wait a SETUP packet
UHD_CTRL_REQ_PHASE_SETUP = 0,
//! Wait a OUT data packet
UHD_CTRL_REQ_PHASE_DATA_OUT = 1,
//! Wait a IN data packet
UHD_CTRL_REQ_PHASE_DATA_IN = 2,
//! Wait a IN ZLP packet
UHD_CTRL_REQ_PHASE_ZLP_IN = 3,
//! Wait a OUT ZLP packet
UHD_CTRL_REQ_PHASE_ZLP_OUT = 4,
} uhd_ctrl_request_phase_t;
uhd_ctrl_request_phase_t uhd_ctrl_request_phase;
//@}
/**
* \name Management of bulk/interrupt/isochronous endpoints
*
* The UHD manages the data transfer on endpoints:
* - Start data transfer on endpoint with USB DMA
* - Send a ZLP packet if requested
* - Call registered callback to signal end of transfer
* The transfer abort and stall feature are supported.
*/
//@{
//! Structure definition to store registered jobs on a pipe
typedef struct {
//! A job is registered on this pipe
uint8_t busy:1;
//! A short packet is requested for this job on endpoint IN
uint8_t b_shortpacket:1;
//! timeout on this request (ms)
uint16_t timeout;
//! 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 transfered data on endpoint
iram_size_t nb_trans;
//! Callback to call at the end of transfer
uhd_callback_trans_t call_end;
} uhd_pipe_job_t;
//! Array to register a job on bulk/interrupt/isochronous endpoint
static uhd_pipe_job_t uhd_pipe_job[AVR32_USBB_EPT_NUM - 1];
//! Variables to manage the suspend/resume sequence
static uint8_t uhd_suspend_start;
static uint8_t uhd_resume_start;
static uint8_t uhd_pipes_unfreeze;
//@}
static void uhd_interrupt(void);
static void uhd_sof_interrupt(void);
static void uhd_ctrl_interrupt(void);
static void uhd_ctrl_phase_setup(void);
static void uhd_ctrl_phase_data_in_start(void);
static void uhd_ctrl_phase_data_in(void);
static void uhd_ctrl_phase_zlp_in(void);
static void uhd_ctrl_phase_data_out(void);
static void uhd_ctrl_phase_zlp_out(void);
static void uhd_ctrl_request_end(uhd_trans_status_t status);
static uhd_trans_status_t uhd_pipe_get_error(uint8_t pipe);
static uint8_t uhd_get_pipe(usb_add_t add, usb_ep_t endp);
static void uhd_pipe_trans_complet(uint8_t pipe);
static void uhd_pipe_interrupt_dma(uint8_t pipe);
static void uhd_pipe_interrupt(uint8_t pipe);
static void uhd_ep_abort_pipe(uint8_t pipe, uhd_trans_status_t status);
static void uhd_pipe_finish_job(uint8_t pipe, uhd_trans_status_t status);
/**
* \internal
* \brief Function called by USBB interrupt handler to manage USB interrupts
*
* It manages the interrupt redirection between host or device interrupt.
* It answers to OTG events (ID pin change).
*
* Note:
* Here, the global interrupt mask is not cleared when an USB interrupt
* is enabled because this one can not occurred during the USB ISR
* (=during INTX is masked).
* See Technical reference $3.8.3 Masking interrupt requests
* in peripheral modules.
*/
ISR(otg_interrupt, AVR32_USBB_IRQ_GROUP, UHD_USB_INT_LEVEL)
{
bool b_mode_device;
#ifdef USB_ID
if (Is_otg_id_transition()) {
while (!Is_otg_clock_usable());
otg_unfreeze_clock();
otg_ack_id_transition();
otg_freeze_clock();
if (Is_otg_id_device()) {
uhc_stop(false);
UHC_MODE_CHANGE(false);
udc_start();
} else {
udc_stop();
UHC_MODE_CHANGE(true);
uhc_start();
}
return;
}
b_mode_device = Is_otg_id_device();
#else
b_mode_device = Is_otg_device_mode_forced();
#endif
// Redirection to host or device interrupt
if (b_mode_device) {
udd_interrupt();
} else {
uhd_interrupt();
}
otg_data_memory_barrier();
}
bool otg_dual_enable(void)
{
if (otg_initialized) {
return false; // Dual role already initialized
}
otg_initialized = true;
//* Enable USB hardware clock
sysclk_enable_usb();
//* Link USB interrupt on OTG interrupt in dual role
irq_register_handler(otg_interrupt, AVR32_USBB_IRQ, UHD_USB_INT_LEVEL);
// Always authorize asynchronous USB interrupts to exit of sleep mode
pm_asyn_wake_up_enable(AVR32_PM_AWEN_USB_WAKEN_MASK);
# ifdef USB_ID
// By default USBB is already configured with ID pin enable
// The USBB must be enabled to provide interrupt
otg_input_id_pin();
otg_unfreeze_clock();
otg_enable();
otg_enable_id_interrupt();
otg_ack_id_transition();
otg_freeze_clock();
if (Is_otg_id_device()) {
uhd_sleep_mode(UHD_STATE_WAIT_ID_HOST);
UHC_MODE_CHANGE(false);
udc_start();
} else {
UHC_MODE_CHANGE(true);
uhc_start();
}
// End of host or device startup,
// the current mode selected is already started now
return true; // ID pin management has been enabled
# else
uhd_sleep_mode(UHD_STATE_OFF);
return false; // ID pin management has not been enabled
# endif
}
void otg_dual_disable(void)
{
if (!otg_initialized) {
return; // Dual role not initialized
}
otg_initialized = false;
// Do not authorize asynchronous USB interrupts
AVR32_PM.AWEN.usb_waken = 0;
otg_unfreeze_clock();
# ifdef USB_ID
otg_disable_id_interrupt();
# endif
otg_disable();
otg_disable_pad();
sysclk_disable_usb();
uhd_sleep_mode(UHD_STATE_OFF);
}
void uhd_enable(void)
{
irqflags_t flags;
// To avoid USB interrupt before end of initialization
flags = cpu_irq_save();
if (otg_dual_enable()) {
// The current mode has been started by otg_dual_enable()
cpu_irq_restore(flags);
return;
}
#ifdef USB_ID
// Check that the host mode is selected by ID pin
if (!Is_otg_id_host()) {
cpu_irq_restore(flags);
return; // Host is not the current mode
}
#else
// ID pin not used then force host mode
otg_disable_id_pin();
otg_force_host_mode();
#endif
// Enable USB hardware
#ifdef USB_VBOF
uhd_output_vbof_pin();
# if USB_VBOF_ACTIVE_LEVEL == HIGH
uhd_set_vbof_active_high();
# else // USB_VBOF_ACTIVE_LEVEL == LOW
uhd_set_vbof_active_low();
# endif
#endif
otg_enable_pad();
otg_enable();
uhd_ctrl_request_first = NULL;
uhd_ctrl_request_last = NULL;
uhd_ctrl_request_timeout = 0;
uhd_suspend_start = 0;
uhd_resume_start = 0;
uhd_b_suspend_requested = false;
otg_unfreeze_clock();
#ifndef USB_HOST_HS_SUPPORT
# ifdef AVR32_USBB_USBSTA_SPEED_HIGH
uhd_disable_high_speed_mode();
# endif
#endif
// Clear all interrupts that may have been set by a previous host mode
AVR32_USBB.uhintclr = AVR32_USBB_UHINTCLR_DCONNIC_MASK
| AVR32_USBB_UHINTCLR_DDISCIC_MASK | AVR32_USBB_UHINTCLR_HSOFIC_MASK
| AVR32_USBB_UHINTCLR_HWUPIC_MASK | AVR32_USBB_UHINTCLR_RSMEDIC_MASK
| AVR32_USBB_UHINTCLR_RSTIC_MASK | AVR32_USBB_UHINTCLR_RXRSMIC_MASK;
otg_ack_vbus_transition();
// Enable Vbus change and error interrupts
// Disable automatic Vbus control after Vbus error
Set_bits(AVR32_USBB.usbcon, AVR32_USBB_USBCON_VBUSHWC_MASK
|AVR32_USBB_USBCON_VBUSTE_MASK
|AVR32_USBB_USBCON_VBERRE_MASK);
uhd_enable_vbus();
// Force Vbus interrupt when Vbus is always high
// This is possible due to a short timing between a Host mode stop/start.
if (Is_otg_vbus_high()) {
otg_raise_vbus_transition();
}
// Enable main control interrupt
// Connection, SOF and reset
AVR32_USBB.uhinteset = AVR32_USBB_UHINTESET_DCONNIES_MASK
| AVR32_USBB_UHINTESET_HSOFIES_MASK
| AVR32_USBB_UHINTESET_RSTIES_MASK;
otg_freeze_clock();
uhd_sleep_mode(UHD_STATE_NO_VBUS);
cpu_irq_restore(flags);
}
void uhd_disable(bool b_id_stop)
{
irqflags_t flags;
// Check USB clock ready after a potential sleep mode < IDLE
while (!Is_otg_clock_usable());
otg_unfreeze_clock();
// Disable Vbus change and error interrupts
Clr_bits(AVR32_USBB.usbcon, AVR32_USBB_USBCON_VBUSTE_MASK
| AVR32_USBB_USBCON_VBERRE_MASK);
// Disable main control interrupt
// (Connection, disconnection, SOF and reset)
AVR32_USBB.uhinteclr = AVR32_USBB_UHINTECLR_DCONNIEC_MASK
| AVR32_USBB_UHINTECLR_DDISCIEC_MASK
| AVR32_USBB_UHINTECLR_HSOFIEC_MASK
| AVR32_USBB_UHINTECLR_RSTIEC_MASK
| AVR32_USBB_UHINTECLR_HWUPIEC_MASK
| AVR32_USBB_UHINTECLR_RSMEDIEC_MASK
| AVR32_USBB_UHINTECLR_RXRSMIEC_MASK;
uhd_disable_sof();
uhd_disable_vbus();
uhc_notify_connection(false);
otg_freeze_clock();
#ifdef USB_ID
uhd_sleep_mode(UHD_STATE_WAIT_ID_HOST);
if (!b_id_stop) {
return; // No need to disable host, it is done automatically by hardware
}
#endif
flags = cpu_irq_save();
otg_dual_disable();
cpu_irq_restore(flags);
}
uhd_speed_t uhd_get_speed(void)
{
switch (uhd_get_speed_mode()) {
#ifdef USB_HOST_HS_SUPPORT
case AVR32_USBB_USBSTA_SPEED_HIGH:
return UHD_SPEED_HIGH;
#endif
case AVR32_USBB_USBSTA_SPEED_FULL:
return UHD_SPEED_FULL;
case AVR32_USBB_USBSTA_SPEED_LOW:
return UHD_SPEED_LOW;
default:
Assert(false);
return UHD_SPEED_LOW;
}
}
uint16_t uhd_get_frame_number(void)
{
return uhd_get_sof_number();
}
uint16_t uhd_get_microframe_number(void)
{
return uhd_get_microsof_number();
}
void uhd_send_reset(uhd_callback_reset_t callback)
{
uhd_reset_callback = callback;
uhd_start_reset();
}
void uhd_suspend(void)
{
if (uhd_ctrl_request_timeout) {
// Delay suspend after setup requests
uhd_b_suspend_requested = true;
return;
}
// Save pipe freeze states and freeze pipes
uhd_pipes_unfreeze = 0;
for (uint8_t pipe = 1; pipe < AVR32_USBB_EPT_NUM; pipe++) {
uhd_pipes_unfreeze |= (!Is_uhd_pipe_frozen(pipe)) << pipe;
uhd_freeze_pipe(pipe);
}
// Wait three SOFs before entering in suspend state
uhd_suspend_start = 3;
}
bool uhd_is_suspend(void)
{
return !Is_uhd_sof_enabled();
}
void uhd_resume(void)
{
if (Is_uhd_sof_enabled()) {
// Currently in IDLE mode (!=Suspend)
if (uhd_suspend_start) {
// Suspend mode on going
// then stop it and start resume event
uhd_suspend_start = 0;
uhd_resume_start = 1;
}
return;
}
// Check USB clock ready after a potential sleep mode < IDLE
while (!Is_otg_clock_usable());
otg_unfreeze_clock();
uhd_enable_sof();
uhd_send_resume();
uhd_sleep_mode(UHD_STATE_IDLE);
}
bool uhd_ep0_alloc(usb_add_t add, uint8_t ep_size)
{
if (ep_size < 8) {
return false;
}
#ifdef USB_HOST_HUB_SUPPORT
if (Is_uhd_pipe_enabled(0)) {
// Already allocated
#error TODO Add USB address in a list
return true;
}
#endif
uhd_enable_pipe(0);
uhd_configure_pipe(0, // Pipe 0
0, // No frequency
0, // endpoint 0
AVR32_USBB_UECFG0_EPTYPE_CONTROL,
AVR32_USBB_UPCFG0_PTOKEN_SETUP,
#ifdef USB_HOST_HUB_SUPPORT
64, // Max size for control endpoint
#else
ep_size,
#endif
AVR32_USBB_UECFG0_EPBK_SINGLE, 0);
uhd_allocate_memory(0);
if (!Is_uhd_pipe_configured(0)) {
uhd_disable_pipe(0);
return false;
}
uhd_configure_address(0, add);
// Always enable stall and error interrupts of control endpoint
uhd_enable_stall_interrupt(0);
uhd_enable_pipe_error_interrupt(0);
uhd_enable_pipe_interrupt(0);
return true;
}
bool uhd_ep_alloc(usb_add_t add, usb_ep_desc_t * ep_desc)
{
uint8_t ep_addr;
uint8_t ep_type;
uint8_t ep_dir;
uint8_t ep_interval;
uint8_t bank;
for (uint8_t pipe = 1; pipe < AVR32_USBB_EPT_NUM; pipe++) {
if (Is_uhd_pipe_enabled(pipe)) {
continue;
}
uhd_enable_pipe(pipe);
ep_addr = ep_desc->bEndpointAddress & USB_EP_ADDR_MASK;
ep_dir = (ep_desc->bEndpointAddress & USB_EP_DIR_IN)?
AVR32_USBB_UPCFG0_PTOKEN_IN:
AVR32_USBB_UPCFG0_PTOKEN_OUT,
ep_type = ep_desc->bmAttributes&USB_EP_TYPE_MASK;
// Bank choice
switch(ep_type) {
case USB_EP_TYPE_ISOCHRONOUS:
bank = UHD_ISOCHRONOUS_NB_BANK;
ep_interval = ep_desc->bInterval;
break;
case USB_EP_TYPE_INTERRUPT:
bank = UHD_INTERRUPT_NB_BANK;
ep_interval = ep_desc->bInterval;
break;
case USB_EP_TYPE_BULK:
bank = UHD_BULK_NB_BANK;
// 0 is required by USBB hardware for bulk
ep_interval = 0;
break;
default:
Assert(false);
return false;
}
switch(bank) {
case 1:
bank = AVR32_USBB_UECFG0_EPBK_SINGLE;
break;
case 2:
bank = AVR32_USBB_UECFG0_EPBK_DOUBLE;
break;
case 3:
bank = AVR32_USBB_UECFG0_EPBK_TRIPLE;
break;
default:
Assert(false);
return false;
}
uhd_configure_pipe(pipe, ep_interval, ep_addr, ep_type, ep_dir,
le16_to_cpu(ep_desc->wMaxPacketSize),
bank, AVR32_USBB_UPCFG0_AUTOSW_MASK);
uhd_allocate_memory(pipe);
if (!Is_uhd_pipe_configured(pipe)) {
uhd_disable_pipe(pipe);
return false;
}
uhd_configure_address(pipe, add);
uhd_enable_pipe(pipe);
// Enable endpoint interrupts
uhd_enable_pipe_dma_interrupt(pipe);
uhd_enable_stall_interrupt(pipe);
uhd_enable_pipe_error_interrupt(pipe);
uhd_enable_pipe_interrupt(pipe);
return true;
}
return false;
}
void uhd_ep_free(usb_add_t add, usb_ep_t endp)
{
#ifdef USB_HOST_HUB_SUPPORT
if (endp == 0) {
// Control endpoint does not be unallocated
#error TODO the list address must be updated
if (uhd_ctrl_request_timeout
&& (uhd_ctrl_request_first->add == add)) {
// Disable setup request if on this device
uhd_ctrl_request_end(UHD_TRANS_DISCONNECT);
}
return;
}
#endif
// Search endpoint(s) in all pipes
for (uint8_t pipe = 0; pipe < AVR32_USBB_EPT_NUM; pipe++) {
if (!Is_uhd_pipe_enabled(pipe)) {
continue;
}
if (add != uhd_get_configured_address(pipe)) {
continue;
}
if (endp != 0xFF) {
// Disable specific endpoint number
if (endp != uhd_get_pipe_endpoint_address(pipe)) {
continue; // Mismatch
}
}
// Unalloc pipe
uhd_disable_pipe(pipe);
uhd_unallocate_memory(pipe);
// Stop transfer on this pipe
#ifndef USB_HOST_HUB_SUPPORT
if (pipe == 0) {
// Endpoint control
if (uhd_ctrl_request_timeout) {
uhd_ctrl_request_end(UHD_TRANS_DISCONNECT);
}
continue;
}
#endif
// Endpoint interrupt, bulk or isochronous
uhd_ep_abort_pipe(pipe, UHD_TRANS_DISCONNECT);
}
}
bool uhd_setup_request(
usb_add_t add,
usb_setup_req_t *req,
uint8_t *payload,
uint16_t payload_size,
uhd_callback_setup_run_t callback_run,
uhd_callback_setup_end_t callback_end)
{
irqflags_t flags;
struct uhd_ctrl_request_t *request;
bool b_start_request = false;
request = malloc( sizeof(struct uhd_ctrl_request_t) );
if (request == NULL) {
Assert(false);
return false;
}
// Fill structure
request->add = (uint8_t) add;
memcpy(&request->req, req, sizeof(usb_setup_req_t));
request->payload = payload;
request->payload_size = payload_size;
request->callback_run = callback_run;
request->callback_end = callback_end;
request->next_request = NULL;
// Add this request in the queue
flags = cpu_irq_save();
if (uhd_ctrl_request_first == NULL) {
uhd_ctrl_request_first = request;
b_start_request = true;
} else {
uhd_ctrl_request_last->next_request = request;
}
uhd_ctrl_request_last = request;
cpu_irq_restore(flags);
if (b_start_request) {
// Start immediately request
uhd_ctrl_phase_setup();
}
return true;
}
bool uhd_ep_run(
usb_add_t add,
usb_ep_t endp,
bool b_shortpacket,
uint8_t *buf,
iram_size_t buf_size,
uint16_t timeout,
uhd_callback_trans_t callback)
{
irqflags_t flags;
uint8_t pipe;
uhd_pipe_job_t *ptr_job;
pipe = uhd_get_pipe(add,endp);
if (pipe == AVR32_USBB_EPT_NUM) {
return false; // pipe not found
}
// Get job about pipe
ptr_job = &uhd_pipe_job[pipe-1];
flags = cpu_irq_save();
if (ptr_job->busy == true) {
cpu_irq_restore(flags);
return false; // Job already on going
}
ptr_job->busy = true;
// No job running. Let's setup a new one.
ptr_job->buf = buf;
ptr_job->buf_size = buf_size;
ptr_job->nb_trans = 0;
ptr_job->timeout = timeout;
ptr_job->b_shortpacket = b_shortpacket;
ptr_job->call_end = callback;
cpu_irq_restore(flags);
// Request first transfer
uhd_pipe_trans_complet(pipe);
return true;
}
void uhd_ep_abort(usb_add_t add, usb_ep_t endp)
{
uint8_t pipe;
pipe = uhd_get_pipe(add,endp);
if (pipe == AVR32_USBB_EPT_NUM) {
return; // pipe not found
}
uhd_ep_abort_pipe(pipe,UHD_TRANS_ABORTED);
}
#ifdef USB_HOST_HS_SUPPORT
void uhd_test_mode_j(void)
{
// Not available
Assert(false);
}
void uhd_test_mode_k(void)
{
// Not available
Assert(false);
}
void uhd_test_mode_se0_nak(void)
{
// Not available
Assert(false);
}
void uhd_test_mode_packet(void)
{
// Not available
Assert(false);
}
#endif // USB_HOST_HS_SUPPORT
/**
* \internal
* \brief Function called by USBB interrupt to manage USB host interrupts
*
* USB host interrupt events are split into four sections:
* - USB line events
* (VBus error, device dis/connection, SOF, reset, suspend, resume, wakeup)
* - control endpoint events
* (setup reception, end of data transfer, underflow, overflow, stall, error)
* - bulk/interrupt/isochronous endpoints events
* (end of data transfer, stall, error)
*/
static void uhd_interrupt(void)
{
uint8_t pipe_int;
// Manage SOF interrupt
if (Is_uhd_sof()) {
uhd_ack_sof();
uhd_sof_interrupt();
return;
}
// Manage pipe interrupts
pipe_int = uhd_get_interrupt_pipe_number();
if (pipe_int == 0) {
// Interrupt acked by control endpoint managed
uhd_ctrl_interrupt();
return;
}
if (pipe_int != AVR32_USBB_EPT_NUM) {
// Interrupt acked by bulk/interrupt/isochronous endpoint
uhd_pipe_interrupt(pipe_int);
return;
}
pipe_int = uhd_get_pipe_dma_interrupt_number();
if (pipe_int != AVR32_USBB_EPT_NUM) {
// Interrupt DMA acked by bulk/interrupt/isochronous endpoint
uhd_pipe_interrupt_dma(pipe_int);
return;
}
// USB bus reset detection
if (Is_uhd_reset_sent()) {
uhd_ack_reset_sent();
if (uhd_reset_callback != NULL) {
uhd_reset_callback();
}
return;
}
// Manage dis/connection event
if (Is_uhd_disconnection() && Is_uhd_disconnection_int_enabled()) {
uhd_ack_disconnection();
uhd_disable_disconnection_int();
// Stop reset signal, in case of disconnection during reset
uhd_stop_reset();
// Disable wakeup/resumes interrupts,
// in case of disconnection during suspend mode
AVR32_USBB.uhinteclr = AVR32_USBB_UHINTECLR_HWUPIEC_MASK
| AVR32_USBB_UHINTECLR_RSMEDIEC_MASK
| AVR32_USBB_UHINTECLR_RXRSMIEC_MASK;
uhd_sleep_mode(UHD_STATE_DISCONNECT);
uhd_enable_connection_int();
uhd_suspend_start = 0;
uhd_resume_start = 0;
uhc_notify_connection(false);
return;
}
if (Is_uhd_connection() && Is_uhd_connection_int_enabled()) {
uhd_ack_connection();
uhd_disable_connection_int();
uhd_enable_disconnection_int();
uhd_enable_sof();
uhd_sleep_mode(UHD_STATE_IDLE);
uhd_suspend_start = 0;
uhd_resume_start = 0;
uhc_notify_connection(true);
return;
}
// Manage Vbus error
if (Is_uhd_vbus_error_interrupt()) {
uhd_ack_vbus_error_interrupt();
UHC_VBUS_ERROR();
return;
}
// Check USB clock ready after asynchronous interrupt
while (!Is_otg_clock_usable());
otg_unfreeze_clock();
if (Is_uhd_wakeup_interrupt_enabled() && (Is_uhd_wakeup() ||
Is_uhd_downstream_resume() || Is_uhd_upstream_resume())) {
// Disable wakeup/resumes interrupts
AVR32_USBB.uhinteclr = AVR32_USBB_UHINTECLR_HWUPIEC_MASK
| AVR32_USBB_UHINTECLR_RSMEDIEC_MASK
| AVR32_USBB_UHINTECLR_RXRSMIEC_MASK;
uhd_enable_sof();
if ((!Is_uhd_downstream_resume())
&&(!Is_uhd_disconnection())) {
// It is a upstream resume
// Note: When the CPU exits from a deep sleep mode, the event
// Is_uhd_upstream_resume() can be not detected
// because the USB clock are not available.
// In High speed mode a downstream resume must be sent
// after a upstream to avoid a disconnection.
if (Is_uhd_high_speed_mode()) {
uhd_send_resume();
}
}
// Wait 50ms before restarting transfer
uhd_resume_start = 50;
uhd_sleep_mode(UHD_STATE_IDLE);
return;
}
// Manage Vbus state change
if (Is_otg_vbus_transition()) {
otg_ack_vbus_transition();
if (Is_otg_vbus_high()) {
uhd_sleep_mode(UHD_STATE_DISCONNECT);
UHC_VBUS_CHANGE(true);
} else {
uhd_sleep_mode(UHD_STATE_NO_VBUS);
otg_freeze_clock();
UHC_VBUS_CHANGE(false);
}
return;
}
Assert(false); // Interrupt event no managed
}
/**
* \internal
* \brief Manages timeouts and actions based on SOF events
* - Suspend delay
* - Resume delay
* - Setup packet delay
* - Timeout on endpoint control transfer
* - Timeouts on bulk/interrupt/isochronous endpoint transfers
* - UHC user notification
* - SOF user notification
*/
static void uhd_sof_interrupt(void)
{
// Manage the micro SOF
if (Is_uhd_high_speed_mode()) {
static uint8_t msof_cpt;
if (++msof_cpt % 8) {
// It is a micro SOF
if (!uhd_suspend_start && !uhd_resume_start) {
// If no resume and no suspend on going
// then send Micro start of frame event (each 125 microsecond)
uhc_notify_sof(true);
}
return;
}
}
// Manage a delay to enter in suspend
if (uhd_suspend_start) {
if (--uhd_suspend_start == 0) {
// In case of high CPU frequency,
// the current Keep-Alive/SOF can be always on-going
// then wait end of SOF generation
// to be sure that disable SOF has been accepted
#ifdef AVR32_USBB_USBSTA_SPEED_HIGH // If UTMI
while (115<uhd_get_frame_position()) {
#else
while (185<uhd_get_frame_position()) {
#endif
if (Is_uhd_disconnection()) {
break;
}
}
uhd_disable_sof();
// Ack previous wakeup and resumes interrupts
AVR32_USBB.uhintclr = AVR32_USBB_UHINTCLR_HWUPIC_MASK
|AVR32_USBB_UHINTCLR_RSMEDIC_MASK
|AVR32_USBB_UHINTCLR_RXRSMIC_MASK;
// Enable wakeup/resumes interrupts
AVR32_USBB.uhinteset = AVR32_USBB_UHINTESET_HWUPIES_MASK
|AVR32_USBB_UHINTESET_RSMEDIES_MASK
|AVR32_USBB_UHINTESET_RXRSMIES_MASK;
otg_freeze_clock();
uhd_sleep_mode(UHD_STATE_SUSPEND);
}
return; // Abort SOF events
}
// Manage a delay to exit of suspend
if (uhd_resume_start) {
if (--uhd_resume_start == 0) {
// Restore pipes unfreezed
for (uint8_t pipe = 1; pipe < AVR32_USBB_EPT_NUM; pipe++) {
if ((uhd_pipes_unfreeze >> pipe) & 0x01) {
uhd_unfreeze_pipe(pipe);
}
}
uhc_notify_resume();
}
return; // Abort SOF events
}
// Manage the timeout on endpoint control transfer
if (uhd_ctrl_request_timeout) {
// Setup request on-going
if (--uhd_ctrl_request_timeout == 0) {
// Stop request
uhd_freeze_pipe(0);
uhd_ctrl_request_end(UHD_TRANS_TIMEOUT);
}
}
// Manage the timeouts on endpoint transfer
uhd_pipe_job_t *ptr_job;
for (uint8_t pipe = 1; pipe < AVR32_USBB_EPT_NUM; pipe++) {
ptr_job = &uhd_pipe_job[pipe-1];
if (ptr_job->busy == true) {
if (ptr_job->timeout) {
// Timeout enabled on this job
if (--ptr_job->timeout == 0) {
// Abort job
uhd_ep_abort_pipe(pipe,UHD_TRANS_TIMEOUT);
}
}
}
}
// Notify the UHC
uhc_notify_sof(false);
// Notify the user application
UHC_SOF_EVENT();
}
/**
* \internal
* \brief Manages the events related to control endpoint
*/
static void uhd_ctrl_interrupt(void)
{
// A setup request is on-going
Assert(uhd_ctrl_request_timeout!=0);
// Disable setup, IN and OUT interrupts of control endpoint
AVR32_USBB.upcon0clr = AVR32_USBB_UPCON0CLR_TXSTPEC_MASK
| AVR32_USBB_UPCON0CLR_RXINEC_MASK
| AVR32_USBB_UPCON0CLR_TXOUTEC_MASK;
// Search event on control endpoint
if (Is_uhd_setup_ready(0)) {
// SETUP packet sent
uhd_freeze_pipe(0);
uhd_ack_setup_ready(0);
Assert(uhd_ctrl_request_phase == UHD_CTRL_REQ_PHASE_SETUP);
// Start DATA phase
if ((uhd_ctrl_request_first->req.bmRequestType & USB_REQ_DIR_MASK)
== USB_REQ_DIR_IN ) {
uhd_ctrl_phase_data_in_start();
} else {
if (uhd_ctrl_request_first->req.wLength) {
uhd_ctrl_phase_data_out();
} else {
// No DATA phase
uhd_ctrl_phase_zlp_in();
}
}
return;
}
if (Is_uhd_in_received(0)) {
// In case of low USB speed and with a high CPU frequency,
// a ACK from host can be always running on USB line
// then wait end of ACK on IN pipe.
while(!Is_uhd_pipe_frozen(0));
// IN packet received
uhd_ack_in_received(0);
switch(uhd_ctrl_request_phase) {
case UHD_CTRL_REQ_PHASE_DATA_IN:
uhd_ctrl_phase_data_in();
break;
case UHD_CTRL_REQ_PHASE_ZLP_IN:
uhd_ctrl_request_end(UHD_TRANS_NOERROR);
break;
default:
Assert(false);
break;
}
return;
}
if (Is_uhd_out_ready(0)) {
// OUT packet sent
uhd_freeze_pipe(0);
uhd_ack_out_ready(0);
switch(uhd_ctrl_request_phase) {
case UHD_CTRL_REQ_PHASE_DATA_OUT:
uhd_ctrl_phase_data_out();
break;
case UHD_CTRL_REQ_PHASE_ZLP_OUT:
uhd_ctrl_request_end(UHD_TRANS_NOERROR);
break;
default:
Assert(false);
break;
}
return;
}
if (Is_uhd_stall(0)) {
// Stall Handshake received
uhd_ack_stall(0);
uhd_ctrl_request_end(UHD_TRANS_STALL);
return;
}
if (Is_uhd_pipe_error(0)) {
// Get and ack error
uhd_ctrl_request_end(uhd_pipe_get_error(0));
return;
}
Assert(false); // Error system
}
/**
* \internal
* \brief Sends a USB setup packet to start a control request sequence
*/
static void uhd_ctrl_phase_setup(void)
{
union{
usb_setup_req_t req;
uint64_t value64;
} setup;
volatile uint64_t *ptr_ep_data;
uhd_ctrl_request_phase = UHD_CTRL_REQ_PHASE_SETUP;
memcpy( &setup.req, &uhd_ctrl_request_first->req, sizeof(usb_setup_req_t));
// Manage LSB/MSB to fit with CPU usage
setup.req.wValue = cpu_to_le16(setup.req.wValue);
setup.req.wIndex = cpu_to_le16(setup.req.wIndex);
setup.req.wLength = cpu_to_le16(setup.req.wLength);
uhd_ctrl_nb_trans = 0;
// Check pipe
#ifdef USB_HOST_HUB_SUPPORT
if (!Is_uhd_pipe_enabled(0)) {
uhd_ctrl_request_end(UHD_TRANS_DISCONNECT);
return; // Endpoint not valid
}
#error TODO check address in list
// Reconfigure USB address of pipe 0 used for all control endpoints
uhd_configure_address(0, uhd_ctrl_request_first->add);
#else
if (!Is_uhd_pipe_enabled(0) ||
(uhd_ctrl_request_first->add != uhd_get_configured_address(0))) {
uhd_ctrl_request_end(UHD_TRANS_DISCONNECT);
return; // Endpoint not valid
}
#endif
// Fill pipe
uhd_configure_pipe_token(0, AVR32_USBB_PTOKEN_SETUP);
uhd_ack_setup_ready(0);
Assert(sizeof(setup) == sizeof(uint64_t));
ptr_ep_data = (volatile uint64_t *)&uhd_get_pipe_fifo_access(0, 64);
*ptr_ep_data = setup.value64;
uhd_ctrl_request_timeout = 5000;
uhd_enable_setup_ready_interrupt(0);
uhd_ack_fifocon(0);
uhd_unfreeze_pipe(0);
}
/**
* \internal
* \brief Starts the DATA IN phase on control endpoint
*/
static void uhd_ctrl_phase_data_in_start(void)
{
uhd_ctrl_request_phase = UHD_CTRL_REQ_PHASE_DATA_IN;
uhd_configure_pipe_token(0, AVR32_USBB_PTOKEN_IN);
uhd_ack_in_received(0);
uhd_ack_short_packet(0);
uhd_enable_in_received_interrupt(0);
uhd_ack_fifocon(0);
uhd_unfreeze_pipe(0);
}
/**
* \internal
* \brief Manages the DATA IN phase on control endpoint
*/
static void uhd_ctrl_phase_data_in(void)
{
bool b_short_packet;
uint8_t *ptr_ep_data;
uint8_t nb_byte_received;
// Get information to read data
nb_byte_received = uhd_byte_count(0);
#ifdef USB_HOST_HUB_SUPPORT
//! In HUB mode, the control pipe is always configured to 64B
//! thus the short packet flag must be computed
b_short_packet = (nb_byte_received != uhd_get_pipe_size(0));
uhd_ack_short_packet(0);
#else
b_short_packet = Is_uhd_short_packet(0);
#endif
ptr_ep_data = (uint8_t *) & uhd_get_pipe_fifo_access(0, 8);
uhd_ctrl_receiv_in_read_data:
// Copy data from pipe to payload buffer
while (uhd_ctrl_request_first->payload_size && nb_byte_received) {
*uhd_ctrl_request_first->payload++ = *ptr_ep_data++;
uhd_ctrl_nb_trans++;
uhd_ctrl_request_first->payload_size--;
nb_byte_received--;
}
if (!uhd_ctrl_request_first->payload_size && nb_byte_received) {
// payload buffer is full to store data remaining
if (uhd_ctrl_request_first->callback_run == NULL
|| !uhd_ctrl_request_first->callback_run(
uhd_get_configured_address(0),
&uhd_ctrl_request_first->payload,
&uhd_ctrl_request_first->payload_size)) {
// DATA phase aborted by host
goto uhd_ctrl_phase_data_in_end;
}
// The payload buffer has been updated by the callback
// thus the data load can restart.
goto uhd_ctrl_receiv_in_read_data;
}
// Test short packet
if ((uhd_ctrl_nb_trans == uhd_ctrl_request_first->req.wLength)
|| b_short_packet) {
// End of DATA phase or DATA phase abort from device
uhd_ctrl_phase_data_in_end:
uhd_ctrl_phase_zlp_out();
return;
}
// Send a new IN packet request
uhd_enable_in_received_interrupt(0);
uhd_ack_fifocon(0);
uhd_unfreeze_pipe(0);
}
/**
* \internal
* \brief Starts the ZLP IN phase on control endpoint
*/
static void uhd_ctrl_phase_zlp_in(void)
{
uhd_ctrl_request_phase = UHD_CTRL_REQ_PHASE_ZLP_IN;
uhd_configure_pipe_token(0, AVR32_USBB_PTOKEN_IN);
uhd_ack_in_received(0);
uhd_ack_short_packet(0);
uhd_enable_in_received_interrupt(0);
uhd_ack_fifocon(0);
uhd_unfreeze_pipe(0);
}
/**
* \internal
* \brief Manages the DATA OUT phase on control endpoint
*/
static void uhd_ctrl_phase_data_out(void)
{
uint8_t *ptr_ep_data;
uint8_t ep_ctrl_size;
uhd_ctrl_request_phase = UHD_CTRL_REQ_PHASE_DATA_OUT;
if (uhd_ctrl_nb_trans == uhd_ctrl_request_first->req.wLength) {
// End of DATA phase
uhd_ctrl_phase_zlp_in();
return;
}
if (!uhd_ctrl_request_first->payload_size) {
// Buffer empty, then request a new buffer
if (uhd_ctrl_request_first->callback_run==NULL
|| !uhd_ctrl_request_first->callback_run(
uhd_get_configured_address(0),
&uhd_ctrl_request_first->payload,
&uhd_ctrl_request_first->payload_size)) {
// DATA phase aborted by host
uhd_ctrl_phase_zlp_in();
return;
}
}
#ifdef USB_HOST_HUB_SUPPORT
// TODO
#else
ep_ctrl_size = uhd_get_pipe_size(0);
#endif
// Fill pipe
uhd_configure_pipe_token(0, AVR32_USBB_PTOKEN_OUT);
uhd_ack_out_ready(0);
ptr_ep_data = (uint8_t *) & uhd_get_pipe_fifo_access(0, 8);
while ((uhd_ctrl_nb_trans < uhd_ctrl_request_first->req.wLength)
&& ep_ctrl_size && uhd_ctrl_request_first->payload_size) {
*ptr_ep_data++ = *uhd_ctrl_request_first->payload++;
uhd_ctrl_nb_trans++;
ep_ctrl_size--;
uhd_ctrl_request_first->payload_size--;
}
uhd_enable_out_ready_interrupt(0);
uhd_ack_fifocon(0);
uhd_unfreeze_pipe(0);
}
/**
* \internal
* \brief Starts the ZLP OUT phase on control endpoint
*/
static void uhd_ctrl_phase_zlp_out(void)
{
uhd_ctrl_request_phase = UHD_CTRL_REQ_PHASE_ZLP_OUT;
uhd_configure_pipe_token(0, AVR32_USBB_PTOKEN_OUT);
uhd_ack_out_ready(0);
uhd_enable_out_ready_interrupt(0);
uhd_ack_fifocon(0);
uhd_unfreeze_pipe(0);
}
/**
* \internal
* \brief Call the callback linked to control request
* and start the next request from the queue.
*/
static void uhd_ctrl_request_end(uhd_trans_status_t status)
{
irqflags_t flags;
uhd_callback_setup_end_t callback_end;
struct uhd_ctrl_request_t *request_to_free;
bool b_new_request;
uhd_ctrl_request_timeout = 0;
// Remove request from the control request list
callback_end = uhd_ctrl_request_first->callback_end;
request_to_free = uhd_ctrl_request_first;
flags = cpu_irq_save();
uhd_ctrl_request_first = uhd_ctrl_request_first->next_request;
b_new_request = (uhd_ctrl_request_first != NULL);
cpu_irq_restore(flags);
free(request_to_free);
// Call callback
if (callback_end != NULL) {
callback_end(uhd_get_configured_address(0), status, uhd_ctrl_nb_trans);
}
// If a setup request is pending and no started by previous callback
if (b_new_request) {
uhd_ctrl_phase_setup();
}
if (uhd_b_suspend_requested) {
// A suspend request has been delay after all setup request
uhd_b_suspend_requested = false;
uhd_suspend();
}
}
/**
* \internal
* \brief Translates the USBB pipe error to UHD error
*
* \param pipe Pipe number to use
*
* \return UHD transfer error
*/
static uhd_trans_status_t uhd_pipe_get_error(uint8_t pipe)
{
uint32_t error = uhd_error_status(pipe) &
(AVR32_USBB_UPERR0_DATATGL_MASK |
AVR32_USBB_UPERR0_TIMEOUT_MASK |
AVR32_USBB_UPERR0_PID_MASK |
AVR32_USBB_UPERR0_DATAPID_MASK);
uhd_ack_all_errors(pipe);
switch(error) {
case AVR32_USBB_UPERR0_DATATGL_MASK:
return UHD_TRANS_DT_MISMATCH;
case AVR32_USBB_UPERR0_TIMEOUT:
return UHD_TRANS_NOTRESPONDING;
case AVR32_USBB_UPERR0_DATAPID_MASK:
case AVR32_USBB_UPERR0_PID_MASK:
default:
return UHD_TRANS_PIDFAILURE;
}
}
/**
* \internal
* \brief Returns the pipe number matching a USB endpoint
*
* \param add USB address
* \param endp Endpoint number
*
* \return Pipe number
*/
static uint8_t uhd_get_pipe(usb_add_t add, usb_ep_t endp)
{
uint8_t pipe;
// Search pipe
for (pipe = 0; pipe < AVR32_USBB_EPT_NUM; pipe++) {
if (!Is_uhd_pipe_enabled(pipe)) {
continue;
}
if (add != uhd_get_configured_address(pipe)) {
continue;
}
if (endp != uhd_get_pipe_endpoint_address(pipe)) {
continue;
}
break;
}
return pipe;
}
/**
* \internal
* \brief Computes and starts the next transfer on a pipe
*
* \param pipe Pipe number
*/
static void uhd_pipe_trans_complet(uint8_t pipe)
{
uint32_t uhd_dma_ctrl = 0;
uhd_pipe_job_t *ptr_job;
iram_size_t max_trans;
iram_size_t next_trans;
irqflags_t flags;
// Get job corresponding at endpoint
ptr_job = &uhd_pipe_job[pipe - 1];
if (!ptr_job->busy) {
return; // No job is running, then ignore it (system error)
}
if (ptr_job->nb_trans != ptr_job->buf_size) {
// Need to send or receive other data
next_trans = ptr_job->buf_size - ptr_job->nb_trans;
max_trans = UHD_PIPE_MAX_TRANS;
if (uhd_is_pipe_in(pipe)) {
// 256 is the maximum of IN requests via UPINRQ
if ((256L*uhd_get_pipe_size(pipe))<UHD_PIPE_MAX_TRANS) {
max_trans = 256L * uhd_get_pipe_size(pipe);
}
}
if (max_trans < next_trans) {
// The USB hardware supports a maximum
// transfer size of UHD_PIPE_MAX_TRANS Bytes
next_trans = max_trans;
}
if (next_trans == UHD_PIPE_MAX_TRANS) {
// Set 0 to transfer the maximum
uhd_dma_ctrl = (0 <<
AVR32_USBB_UHDMA1_CONTROL_CH_BYTE_LENGTH_OFFSET)
& AVR32_USBB_UHDMA1_CONTROL_CH_BYTE_LENGTH_MASK;
} else {
uhd_dma_ctrl = (next_trans <<
AVR32_USBB_UHDMA1_CONTROL_CH_BYTE_LENGTH_OFFSET)
& AVR32_USBB_UHDMA1_CONTROL_CH_BYTE_LENGTH_MASK;
}
if (uhd_is_pipe_out(pipe)) {
if (0 != next_trans % uhd_get_pipe_size(pipe)) {
// Enable short packet option
// else the DMA transfer is accepted
// and interrupt DMA valid but nothing is sent.
uhd_dma_ctrl |= AVR32_USBB_UHDMA1_CONTROL_DMAEND_EN_MASK;
// No need to request another ZLP
ptr_job->b_shortpacket = false;
}
} else {
if ((USB_EP_TYPE_ISOCHRONOUS != uhd_get_pipe_type(pipe))
|| (next_trans <= uhd_get_pipe_size(pipe))) {
// Enable short packet reception
uhd_dma_ctrl |= AVR32_USBB_UHDMA1_CONTROL_EOT_IRQ_EN_MASK
| AVR32_USBB_UHDMA1_CONTROL_BUFF_CLOSE_IN_EN_MASK;
}
}
// Start USB DMA to fill or read fifo of the selected endpoint
uhd_pipe_dma_set_addr(pipe, (U32) &ptr_job->buf[ptr_job->nb_trans]);
uhd_dma_ctrl |= AVR32_USBB_UHDMA1_CONTROL_EOBUFF_IRQ_EN_MASK |
AVR32_USBB_UHDMA1_CONTROL_CH_EN_MASK;
// Disable IRQs to have a short sequence
// between read of EOT_STA and DMA enable
flags = cpu_irq_save();
if( !(uhd_pipe_dma_get_status(pipe)
& AVR32_USBB_UHDMA1_STATUS_EOT_STA_MASK)) {
if (uhd_is_pipe_in(pipe)) {
uhd_in_request_number(pipe,
(next_trans+uhd_get_pipe_size(pipe)-1)/uhd_get_pipe_size(pipe));
}
uhd_disable_bank_interrupt(pipe);
uhd_unfreeze_pipe(pipe);
uhd_pipe_dma_set_control(pipe, uhd_dma_ctrl);
ptr_job->nb_trans += next_trans;
cpu_irq_restore(flags);
return;
}
cpu_irq_restore(flags);
// Here a ZLP has been received
// and the DMA transfer must be not started.
// It is the end of transfer
ptr_job->buf_size = ptr_job->nb_trans;
}
if (uhd_is_pipe_out(pipe)) {
if (ptr_job->b_shortpacket) {
// Need to send a ZLP (No possible with USB DMA)
// enable interrupt to wait a free bank to sent ZLP
uhd_ack_out_ready(pipe);
if (Is_uhd_write_enabled(pipe)) {
// Force interrupt in case of pipe already free
uhd_raise_out_ready(pipe);
}
uhd_enable_out_ready_interrupt(pipe);
return;
}
}
// Call callback to signal end of transfer
uhd_pipe_finish_job(pipe, UHD_TRANS_NOERROR);
}
/**
* \internal
* \brief Manages the pipe DMA interrupt
*
* \param pipe Pipe number
*/
static void uhd_pipe_interrupt_dma(uint8_t pipe)
{
uhd_pipe_job_t *ptr_job;
uint32_t nb_remaining;
if (uhd_pipe_dma_get_status(pipe)
& AVR32_USBB_UHDMA1_STATUS_CH_EN_MASK) {
return; // Ignore EOT_STA interrupt
}
// Save number of data no transfered
nb_remaining = (uhd_pipe_dma_get_status(pipe) &
AVR32_USBB_UHDMA1_STATUS_CH_BYTE_CNT_MASK)
>> AVR32_USBB_UHDMA1_STATUS_CH_BYTE_CNT_OFFSET;
if (nb_remaining) {
// Get job corresponding at endpoint
ptr_job = &uhd_pipe_job[pipe - 1];
// Transfer no complete (short packet or ZLP) then:
// Update number of transfered data
ptr_job->nb_trans -= nb_remaining;
// Set transfer complete to stop the transfer
ptr_job->buf_size = ptr_job->nb_trans;
}
if (uhd_is_pipe_out(pipe)) {
// Wait that all banks are free to freeze clock of OUT endpoint
// and call callback
uhd_enable_bank_interrupt(pipe);
} else {
if (!Is_uhd_pipe_frozen(pipe)) {
// Pipe is not freeze in case of :
// - incomplete transfer when the request number INRQ is not complete.
// - low USB speed and with a high CPU frequency,
// a ACK from host can be always running on USB line.
if (nb_remaining) {
// Freeze pipe in case of incomplete transfer
uhd_freeze_pipe(pipe);
} else {
// Wait freeze in case of ASK on going
while (!Is_uhd_pipe_frozen(pipe)) {
}
}
}
uhd_pipe_trans_complet(pipe);
}
}
/**
* \internal
* \brief Manages the following pipe interrupts:
* - Real end of USB transfers (bank empty)
* - One bank is free to send a OUT ZLP
* - Stall received
* - Error during transfer
*
* \param pipe Pipe number
*/
static void uhd_pipe_interrupt(uint8_t pipe)
{
if (Is_uhd_bank_interrupt_enabled(pipe) && (0==uhd_nb_busy_bank(pipe))) {
uhd_disable_bank_interrupt(pipe);
uhd_pipe_finish_job(pipe, UHD_TRANS_NOERROR);
return;
}
if (Is_uhd_out_ready_interrupt_enabled(pipe) && Is_uhd_out_ready(pipe)) {
uhd_disable_out_ready_interrupt(pipe);
// One bank is free then send a ZLP
uhd_ack_out_ready(pipe);
uhd_ack_fifocon(pipe);
uhd_unfreeze_pipe(pipe);
uhd_enable_bank_interrupt(pipe);
return;
}
if (Is_uhd_stall(pipe)) {
uhd_ack_stall(pipe);
uhd_reset_data_toggle(pipe);
uhd_ep_abort_pipe(pipe, UHD_TRANS_STALL);
return;
}
if (Is_uhd_pipe_error(pipe)) {
// Get and ack error
uhd_ep_abort_pipe(pipe, uhd_pipe_get_error(pipe));
return;
}
Assert(false); // Error system
}
/**
* \internal
* \brief Aborts the on going transfer on a pipe
*
* \param pipe Pipe number
* \param status Reason of abort
*/
static void uhd_ep_abort_pipe(uint8_t pipe, uhd_trans_status_t status)
{
// Stop transfer
uhd_reset_pipe(pipe);
// Autoswitch bank and interrupts has been reseted, then re-enable it
uhd_enable_pipe_bank_autoswitch(pipe);
uhd_enable_stall_interrupt(pipe);
uhd_enable_pipe_error_interrupt(pipe);
uhd_disable_out_ready_interrupt(pipe);
uhd_pipe_dma_set_control(pipe, 0);
uhd_pipe_finish_job(pipe, status);
}
/**
* \internal
* \brief Call the callback linked to the end of pipe transfer
*
* \param pipe Pipe number
* \param status Status of the transfer
*/
static void uhd_pipe_finish_job(uint8_t pipe, uhd_trans_status_t status)
{
uhd_pipe_job_t *ptr_job;
// Get job corresponding at endpoint
ptr_job = &uhd_pipe_job[pipe - 1];
if (ptr_job->busy == false) {
return; // No job running
}
ptr_job->busy = false;
if (NULL == ptr_job->call_end) {
return; // No callback linked to job
}
ptr_job->call_end(uhd_get_configured_address(pipe),
uhd_get_pipe_endpoint_address(pipe),
status, ptr_job->nb_trans);
}
//@}