blob: 885b855ce43d41696cb76b0123b6f3b0d75263ed [file] [log] [blame]
/****************************************************************************
* arch/arm/src/samd5e5/sam_usb.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* USB core features:
*
* - Compatible with the USB 2.1 specification
* - USB Embedded Host and Device mode
* - Supports full (12Mbit/s) and low (1.5Mbit/s) speed communication
* - Supports Link Power Management (LPM-L1) protocol
* - On-chip transceivers with built-in pull-ups and pull-downs
* - On-Chip USB serial resistors
* - 1kHz SOF clock available on external pin
*
* Device mode
* - Supports 8 IN endpoints and 8 OUT endpoints
* - No endpoint size limitations
* - Built-in DMA with multi-packet and dual bank for all endpoints
* - Supports feedback endpoint
* - Supports crystal less clock
*
* Host mode
* - Supports 8 physical pipes
* - No pipe size limitations
* - Supports multiplexed virtual pipe on one physical pipe to allow an
* unlimited USB tree
* - Built-in DMA with multi-packet support and dual bank for all pipes
* - Supports feedback endpoint
* - Supports the USB 2.0 Phase-locked SOFs feature
*
****************************************************************************/
/****************************************************************************
* WIP NOTES:
*
* DS 38.1 (859)
* To maximize throughput, an endpoint can be configured for ping-pong
* operation.
* When this is done the input and output endpoint with the same address are
* used in the same direction. The CPU or DMA Controller can then read/write
* one data buffer while the USB module writes/reads from the other buffer.
* This gives double buffered communication.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/param.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/arch.h>
#include <nuttx/kmalloc.h>
#include <nuttx/irq.h>
#include <nuttx/mutex.h>
#include <nuttx/usb/usb.h>
#include <nuttx/usb/usbdev.h>
#include <nuttx/usb/usbdev_trace.h>
#include <nuttx/usb/usbhost.h>
#include <nuttx/usb/usbhost_devaddr.h>
#include <nuttx/usb/usbhost_trace.h>
#include <arch/board/board.h>
#include "arm_internal.h"
#include "chip.h"
#include "hardware/sam_pinmap.h"
#include "hardware/sam_fuses.h"
#include "sam_gclk.h"
#include "sam_port.h"
#include "sam_periphclks.h"
#include "sam_usb.h"
#include "sam_usbhost.h"
#if defined(CONFIG_SAMD5E5_USB)
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Configuration ************************************************************/
#ifndef CONFIG_USB_EP0_DEFSIZE
# define CONFIG_USB_EP0_DEFSIZE 8
#endif
#ifndef CONFIG_USBDEV_EP0_MAXSIZE
# define CONFIG_USBDEV_EP0_MAXSIZE 64
#endif
/* Extremely detailed register debug that you would normally never want
* enabled.
*/
#ifndef CONFIG_DEBUG_USB
# undef CONFIG_SAMD5E5_USB_REGDEBUG
#endif
/* Driver Definitions *******************************************************/
#define EP0 (0)
#define SAM_EPSET_ALL (0xff) /* All endpoints */
#define SAM_EPSET_NOTEP0 (0xfe) /* All endpoints except EP0 */
#define SAM_EP_BIT(ep) (1 << (ep))
#define SAM_EP0_MAXPACKET (CONFIG_USBDEV_EP0_MAXSIZE) /* EP0 Max. packet size */
#define SAM_MAX_MULTIPACKET_SIZE (0x3fff)
#define SAM_RETRY_COUNT 3 /* Number of ctrl transfer retries */
/* Delays */
#define SAM_SETUP_DELAY SEC2TICK(5) /* 5 seconds in system ticks */
#define SAM_DATANAK_DELAY SEC2TICK(5) /* 5 seconds in system ticks */
#define USB_CTRL_DPKT_TIMEOUT (500) /* Timeout between control data packets : 500ms */
#define USB_CTRL_STAT_TIMEOUT (50) /* Timeout of status packet : 50ms */
/* Maximum size of a descriptor */
#ifndef CONFIG_SAM_DESCSIZE
# define CONFIG_SAM_DESCSIZE 128
#endif
/* Request queue operations *************************************************/
#define sam_rqempty(q) ((q)->head == NULL)
#define sam_rqpeek(q) ((q)->head)
/* USB trace ****************************************************************/
/* Trace error codes */
#define SAM_TRACEERR_ALLOCFAIL 0x0001
#define SAM_TRACEERR_BADCLEARFEATURE 0x0002
#define SAM_TRACEERR_BADDEVGETSTATUS 0x0003
#define SAM_TRACEERR_BADEPGETSTATUS 0x0004
#define SAM_TRACEERR_BADEOBSTATE 0x0005
#define SAM_TRACEERR_BADEPNO 0x0006
#define SAM_TRACEERR_BADEPTYPE 0x0007
#define SAM_TRACEERR_BADGETCONFIG 0x0008
#define SAM_TRACEERR_BADGETSETDESC 0x0009
#define SAM_TRACEERR_BADGETSTATUS 0x000a
#define SAM_TRACEERR_BADSETADDRESS 0x000b
#define SAM_TRACEERR_BADSETCONFIG 0x000c
#define SAM_TRACEERR_BADSETFEATURE 0x000d
#define SAM_TRACEERR_BINDFAILED 0x000e
#define SAM_TRACEERR_DISPATCHSTALL 0x000f
#define SAM_TRACEERR_DRIVER 0x0010
#define SAM_TRACEERR_DRIVERREGISTERED 0x0011
#define SAM_TRACEERR_EP0SETUPOUTSIZE 0x0012
#define SAM_TRACEERR_EP0SETUPSTALLED 0x0013
#define SAM_TRACEERR_EPOUTNULLPACKET 0x0014
#define SAM_TRACEERR_EPRESERVE 0x0015
#define SAM_TRACEERR_INVALIDCTRLREQ 0x0016
#define SAM_TRACEERR_INVALIDPARMS 0x0017
#define SAM_TRACEERR_IRQREGISTRATION 0x0018
#define SAM_TRACEERR_NOTCONFIGURED 0x0019
#define SAM_TRACEERR_REQABORTED 0x001a
#define SAM_TRACEERR_RXDATABKERR 0x001b
#define SAM_TRACEERR_TXCOMPERR 0x001c
#define SAM_TRACEERR_UNSUPPEPTYPE 0x001d
/* Trace interrupt codes */
#define SAM_TRACEINTID_INTERRUPT 0x0001
#define SAM_TRACEINTID_PENDING 0x0002
#define SAM_TRACEINTID_PENDING_EP 0x0003
#define SAM_TRACEINTID_SUSPEND 0x0004
#define SAM_TRACEINTID_SOF 0x0005
#define SAM_TRACEINTID_EORST 0x0006
#define SAM_TRACEINTID_WAKEUP 0x0007
#define SAM_TRACEINTID_EORSM 0x0008
#define SAM_TRACEINTID_UPRSM 0x0009
#define SAM_TRACEINTID_RAMACER 0x000a
#define SAM_TRACEINTID_LPMNYET 0x000b
#define SAM_TRACEINTID_LPMSUSP 0x000c
#define SAM_TRACEINTID_EPNO 0x000d
#define SAM_TRACEINTID_EPINTFLAGS 0x000e
#define SAM_TRACEINTID_EPTRCPT0 0x000f
#define SAM_TRACEINTID_EPTRCPT1 0x0010
#define SAM_TRACEINTID_EPTRFAIL0 0x0011
#define SAM_TRACEINTID_EPTRFAIL1 0x0012
#define SAM_TRACEINTID_EPRXSTP 0x0013
#define SAM_TRACEINTID_EPSTALL0 0x0014
#define SAM_TRACEINTID_EPSTALL1 0x0015
#define SAM_TRACEINTID_EPINQEMPTY 0x0016
#define SAM_TRACEINTID_EPOUTQEMPTY 0x0017
#define SAM_TRACEINTID_EP0SETUPOUT 0x0018
#define SAM_TRACEINTID_EP0SETUPIN 0x0019
#define SAM_TRACEINTID_EP0SETUPSETADDRESS 0x001a
#define SAM_TRACEINTID_NOSTDREQ 0x001b
#define SAM_TRACEINTID_GETSTATUS 0x001c
#define SAM_TRACEINTID_DEVGETSTATUS 0x001d
#define SAM_TRACEINTID_IFGETSTATUS 0x001e
#define SAM_TRACEINTID_CLEARFEATURE 0x001f
#define SAM_TRACEINTID_SETFEATURE 0x0020
#define SAM_TRACEINTID_GETSETDESC 0x0021
#define SAM_TRACEINTID_GETCONFIG 0x0022
#define SAM_TRACEINTID_SETCONFIG 0x0023
#define SAM_TRACEINTID_GETSETIF 0x0024
#define SAM_TRACEINTID_SYNCHFRAME 0x0025
#define SAM_TRACEINTID_DISPATCH 0x0026
#define SAM_TRACEINTID_ADDRESSED 0x0027
#define SAM_TRACEINTID_EPCONF 0x0028
#define SAM_TRACEINTID_EPINTEN 0x0029
#define SAM_TRACEINTID_EP0WRSTATUS 0x002a
#define SAM_TRACEINTID_EPTRCPT0_LEN 0x002b
#define SAM_TRACEINTID_PENDING_PIPE 0x002c
#define SAM_TRACEINTID_PIPENO 0x002d
/* Byte ordering in host-based values */
#ifdef CONFIG_ENDIAN_BIG
# define LSB 1
# define MSB 0
#else
# define LSB 0
# define MSB 1
#endif
/****************************************************************************
* Private Types
****************************************************************************/
#ifdef CONFIG_USBDEV
/* State of an endpoint */
enum sam_epstate_e
{
USB_EPSTATE_DISABLED = 0, /* Endpoint is disabled */
USB_EPSTATE_STALLED, /* Endpoint is stalled */
USB_EPSTATE_IDLE, /* Endpoint is idle (i.e. ready for transmission) */
USB_EPSTATE_SENDING, /* Endpoint is sending data */
USB_EPSTATE_RXSTOPPED, /* OUT endpoint is stopped waiting for a read request */
/* --- Endpoint 0 Only --- */
USB_EPSTATE_EP0DATAOUT, /* Endpoint 0 is receiving SETUP OUT data */
USB_EPSTATE_EP0STATUSIN, /* Endpoint 0 is sending SETUP status */
USB_EPSTATE_EP0ADDRESS /* Address change is pending completion of status */
};
/* The overall state of the device */
enum sam_devstate_e
{
USB_DEVSTATE_SUSPENDED = 0, /* The device is currently suspended */
USB_DEVSTATE_POWERED, /* Host is providing +5V through the USB cable */
USB_DEVSTATE_DEFAULT, /* Device has been reset */
USB_DEVSTATE_ADDRESSED, /* The device has been given an address on the bus */
USB_DEVSTATE_CONFIGURED /* A valid configuration has been selected. */
};
/* The result of EP0 SETUP processing */
enum sam_ep0setup_e
{
USB_EP0SETUP_SUCCESS = 0, /* The SETUP was handle without incident */
USB_EP0SETUP_DISPATCHED, /* The SETUP was forwarded to the class driver */
USB_EP0SETUP_ADDRESS, /* A new device address is pending */
USB_EP0SETUP_STALL /* An error occurred */
};
union wb_u
{
uint16_t w;
uint8_t b[2];
};
/* A container for a request so that the request may be retained in a list */
struct sam_req_s
{
struct usbdev_req_s req; /* Standard USB request */
struct sam_req_s *flink; /* Supports a singly linked list */
uint16_t inflight; /* Number of TX bytes tansmitting or
* number of RX bytes we are waiting */
};
/* The head of a queue of requests */
struct sam_rqhead_s
{
struct sam_req_s *head; /* Requests are added to the head of the list */
struct sam_req_s *tail; /* Requests are removed from the tail of the list */
};
/* This is the internal representation of an endpoint */
struct sam_ep_s
{
/* Common endpoint fields. This must be the first thing defined in the
* structure so that it is possible to simply cast from struct usbdev_ep_s
* to struct sam_ep_s.
*/
struct usbdev_ep_s ep; /* Standard endpoint structure */
/* SAMD5E5-specific fields */
struct sam_usbdev_s *dev; /* Reference to private driver data */
struct sam_rqhead_s reqq; /* Read/write request queue */
struct sam_rqhead_s pendq; /* Write requests pending stall sent */
struct usbdev_epdesc_s *descb[2]; /* Pointers to this endpoints descriptors */
volatile uint8_t epstate; /* State of the endpoint (see enum sam_epstate_e) */
uint8_t stalled:1; /* true: Endpoint is stalled */
uint8_t pending:1; /* true: IN Endpoint stall is pending */
uint8_t halted:1; /* true: Endpoint feature halted */
uint8_t zlpsent:1; /* Zero length packet has been sent */
uint8_t txbusy:1; /* Write request queue is busy (recursion avoidance kludge) */
uint8_t rxactive:1; /* read request is active (for top of queue) */
};
struct sam_usbdev_s
{
/* Common device fields. This must be the first thing defined in the
* structure so that it is possible to simply cast from struct usbdev_s
* to struct sam_usbdev_s.
*/
struct usbdev_s usbdev;
/* The bound device class driver */
struct usbdevclass_driver_s *driver;
/* USB-specific fields */
struct usb_ctrlreq_s ctrl; /* Last EP0 request */
uint8_t devstate; /* State of the device (see enum sam_devstate_e) */
uint8_t prevstate; /* Previous state of the device before SUSPEND */
uint8_t devaddr; /* Assigned device address */
uint8_t selfpowered:1; /* 1: Device is self powered */
uint16_t epavail; /* Bitset of available endpoints */
/* The endpoint list */
aligned_data(4) struct sam_ep_s eplist[SAM_USB_NENDPOINTS];
/* Endpoint descriptors 2 banks for each endpoint */
aligned_data(4)
struct usbdev_epdesc_s ep_descriptors[SAM_USB_NENDPOINTS *
SAM_USB_NBANKS()];
/* EP0 data buffer. For data that is included in an EP0 SETUP OUT
* transaction. In this case, no request is in place from the class
* driver and the incoming data is caught in this buffer. The size
* of valid data in the buffer is given by ctrlreg.len[]. For the
* case of EP0 SETUP IN transaction, the normal request mechanism is
* used and the class driver provides the buffering.
*/
aligned_data(4) uint8_t ep0out[SAM_EP0_MAXPACKET];
};
#endif
#ifdef CONFIG_USBHOST
/* The overall state of the device */
enum sam_hoststate_e
{
USB_HOSTSTATE_SUSPENDED = 0, /* The device is currently suspended */
USB_HOSTSTATE_POWERED, /* Host is providing +5V through the USB cable */
USB_HOSTSTATE_DEFAULT, /* Device has been reset */
USB_HOSTSTATE_ADDRESSED, /* The device has been given an address on the bus */
USB_HOSTSTATE_CONFIGURED /* A valid configuration has been selected. */
};
/* USB HCD pipe states */
enum usb_h_pipe_state
{
USB_H_PIPE_S_FREE = 0x00, /* Pipe is free to allocate */
USB_H_PIPE_S_CFG = 0x01, /* Pipe is in configuration */
USB_H_PIPE_S_IDLE = 0x02, /* Pipe is allocated and idle */
USB_H_PIPE_S_SETUP = 0x03, /* Pipe in control setup stage */
USB_H_PIPE_S_DATI = 0x05, /* Pipe in data IN stage */
USB_H_PIPE_S_DATO = 0x06, /* Pipe in data OUT stage */
USB_H_PIPE_S_ZLPI = 0x07, /* Pipe in data IN ZLP stage */
USB_H_PIPE_S_ZLPO = 0x08, /* Pipe in data OUT ZLP stage */
USB_H_PIPE_S_STATI = 0x09, /* Pipe in control status IN stage */
USB_H_PIPE_S_STATO = 0x0a, /* Pipe in control status OUT stage */
USB_H_PIPE_S_TAKEN = 0x10 /* Taken by physical pipe (in process) */
};
/* USB HCD status code */
enum usb_h_status
{
USB_H_OK = 0, /* OK */
USB_H_BUSY = -4, /* Busy */
USB_H_DENIED = -17, /* Denied */
USB_H_TIMEOUT = -8, /* Timeout */
USB_H_ABORT = -3, /* Abort */
USB_H_STALL = -25, /* Stall protocol */
USB_H_RESET = -7, /* Transfer reset by pipe re-configure */
USB_H_ERR_ARG = -13, /* Argument error */
USB_H_ERR_UNSP_OP = -27, /* Operation not supported */
USB_H_ERR_NO_RSC = -28, /* No resource */
USB_H_ERR_NOT_INIT = -20, /* Not initialized */
USB_H_ERR = -6 /* Some general error */
};
/* The following enumeration represents the various states of the USB host
* state machine (for debug purposes only)
*/
enum sam_smstate_e
{
SMSTATE_DETACHED = 0, /* Not attached to a device */
SMSTATE_ATTACHED, /* Attached to a device */
SMSTATE_ENUM, /* Attached, enumerating */
SMSTATE_CLASS_BOUND, /* Enumeration complete, class bound */
};
/* This enumeration provides the reason for the channel halt. */
enum sam_chreason_e
{
CHREASON_IDLE = 0, /* Inactive (initial state) */
CHREASON_FREED, /* Channel is no longer in use */
CHREASON_XFRC, /* Transfer complete */
CHREASON_NAK, /* NAK received */
CHREASON_NYET, /* NotYet received */
CHREASON_STALL, /* Endpoint stalled */
CHREASON_TXERR, /* Transfer error received */
CHREASON_DTERR, /* Data toggle error received */
CHREASON_FRMOR, /* Frame overrun */
CHREASON_CANCELLED /* Transfer cancelled */
};
/* Transfer descriptor for control transfer
*
* Timing in USB 2.0 spec.:
* - 9.2.6.1 : USB sets an upper limit of 5 seconds as the upper
* limit for any command to be processed.
* - 9.2.6.3 : if a device receives a SetAddress() request,
* the device must be able to complete processing
* of the request and be able to
* successfully complete the Status stage of the request within
* 50 ms.
* After successful completion of the Status stage, the device is
* allowed a SetAddress() recovery interval of 2 ms. At the end of
* this interval, the device must be able to accept Setup packets
* addressed to the new address.
* - 9.2.6.4 : For standard device requests that require no Data stage,
* must be able to complete the request and be able to successfully
* complete the Status stage of the request within 50 ms of receipt
* of the request. This limitation applies to requests to the
* device, interface, or endpoint. For standard device requests
* that require data stage transfer to the host, the
* device must be able to return the first data packet
* to the host within 500 ms of receipt of the request. For
* subsequent data packets, if any, the device must be able to
* return them within 500 ms of successful completion of the
* transmission of the previous packet. The device must then be
* able to successfully complete the status stage
* within 50 ms after returning the last data packet.
* For standard device requests that require a data stage transfer
* to the device, the 5-second limit applies.
* - 9.2.6.5 : Unless specifically exempted in the class document, all
* class-specific requests must meet the timing limitations for
* standard device requests.
*
* Conclusion:
* 1. Whole request with data: 5 seconds
* 2. Whole request without data: 50 ms
* 3. Data packets: 500 ms
*/
struct usb_h_ctrl_xfer
{
uint8_t *data; /* Pointer to transfer data */
uint8_t *setup; /* Pointer to setup packet */
uint16_t size; /* Expected transfer size */
uint16_t count; /* Transfer count */
int16_t req_timeout; /* Timeout for request, -1 if disable timeout */
int16_t pkt_timeout; /* Timeout between packets */
uint16_t pkt_size; /* Packet size during transfer (<= allocate max packet size) */
uint8_t state; /* Transfer state */
int8_t status; /* Last transfer status */
};
/* Transfer descriptor for bulk / interrupt / iso transfer */
struct usb_h_bulk_int_iso_xfer
{
uint32_t size; /* Expected transfer size */
uint32_t count; /* Transfer count */
uint8_t *data; /* Pointer to transfer data */
uint16_t reserved[3];
uint8_t state; /* Transfer state */
int8_t status; /* Last transfer status */
};
/* Transfer descriptor for periodic high bandwidth transfer */
struct usb_h_high_bw_xfer
{
uint32_t size; /* Expected transfer size */
uint32_t count; /* Transfer count */
uint8_t *data; /* Pointer to transfer data */
uint16_t pkt_size[3]; /* Micro frame packet sizes */
uint8_t state; /* Transfer state */
int8_t status; /* Last transfer status */
};
/* General transfer descriptor */
struct usb_h_xfer
{
/* Reserved for different transfer */
union
{
uint16_t u16[9];
uint8_t u8[18];
} reserved;
uint8_t state; /* Transfer state */
int8_t status; /* Last transfer status */
};
/* USB Host Controller Driver Pipe structure */
/* This is the internal representation of an pipe */
struct sam_pipe_s
{
struct usbhost_pipedesc_s *descb[2]; /* Pointers to this pipe descriptors */
volatile uint8_t pipestate; /* State of the pipe (see enum usb_h_pipe_state) */
volatile uint8_t pipestatus; /* Status of the pipe */
volatile int8_t pipestatus_general; /* Status of the pipe */
volatile int8_t pipestate_general;
int16_t result; /* The result of the transfer */
uint32_t size; /* Expected transfer size */
uint32_t count; /* Transfer count */
uint8_t *data; /* Pointer to transfer data */
int16_t pkt_timeout; /* Timeout between packets (500ms for data and 50ms for status), -1 if disabled */
uint8_t zlp:1; /* Transfer ZLP support */
uint8_t stalled:1; /* true: Endpoint is stalled */
uint8_t pending:1; /* true: IN Endpoint stall is pending */
uint8_t halted:1; /* true: Endpoint feature halted */
uint8_t zlpsent:1; /* Zero length packet has been sent */
uint8_t txbusy:1; /* Write request queue is busy (recursion avoidance kludge) */
uint8_t rxactive:1; /* read request is active (for top of queue) */
bool inuse; /* True: This pipe is "in use" */
bool in; /* True: IN endpoint */
uint8_t idx; /* Pipe index */
uint8_t epno; /* Device endpoint number (0-127) */
uint8_t eptype; /* See _EPTYPE_* definitions */
uint8_t funcaddr; /* Device function address */
uint8_t speed; /* Device speed */
uint8_t interval; /* Interrupt/isochronous EP polling interval */
uint16_t maxpacket; /* Max packet size */
sem_t waitsem; /* Channel wait semaphore */
volatile bool waiter; /* True: Thread is waiting for a channel event */
#ifdef CONFIG_USBHOST_ASYNCH
usbhost_asynch_t callback; /* Transfer complete callback */
void *arg; /* Argument that accompanies the callback */
#endif
#ifdef HPL_USB_HOST /* from: Atmel Start hpl_usb_host.h */
uint16_t max_pkt_size; /* Endpoint max packet size (bits 10..0) */
uint8_t ep; /* Endpoint address */
uint8_t type; /* Endpoint type: Control, Isochronous, Bulk or Interrupt */
uint8_t toggle; /* Current toggle (driver dependent) */
uint8_t bank : 2; /* Endpoint number of banks (HW dependent) */
uint8_t high_bw_out : 1;
uint8_t dma : 1; /* Uses DMA (on transfer) */
uint8_t periodic_start : 1; /* Transfer periodic */
/* Transfer status */
union
{
struct usb_h_xfer general; /* General transfer info */
struct usb_h_ctrl_xfer ctrl; /* Control transfer status */
struct usb_h_bulk_int_iso_xfer bii; /* Bulk interrupt iso transfer status */
struct usb_h_high_bw_xfer hbw; /* Periodic high bandwidth transfer status */
} x;
#endif
};
/* This structure retains the state of the USB host controller */
struct sam_usbhost_s
{
/* Common device fields. This must be the first thing defined in the
* structure so that it is possible to simply cast from struct usbhost_s
* to struct sam_usbhost_s.
*/
struct usbhost_driver_s drvr;
/* This is the hub port description understood by class drivers */
struct usbhost_roothubport_s rhport;
/* Overall driver status */
uint8_t hoststate; /* State of the device (see enum sam_hoststate_e) */
uint8_t prevstate; /* Previous state of the device before SUSPEND */
uint16_t epavail; /* Bitset of available endpoints */
mutex_t lock; /* Support mutually exclusive access */
bool connected; /* Connected to device */
bool change; /* Connection change */
bool pscwait; /* True: Thread is waiting for a port event */
uint8_t smstate; /* The state of the USB host state machine */
uint8_t irqset; /* Set of enabled interrupts */
uint8_t xfrtype; /* See enum _hxfrdn_e */
sem_t pscsem; /* Semaphore to wait for a port event */
uint16_t pipes_unfreeze; /* Pipes to unfreeze after wakeup */
int8_t suspend_start; /* Delayed suspend time in ms */
int8_t resume_start; /* Delayed resume time in ms */
int8_t n_ctrl_req_user; /* Control transfer request user count */
int8_t n_sof_user; /* SOF user count (callback, suspend, resume, ctrl request) */
uint8_t pipe_pool_size; /* Pipe pool size in number of pipes */
#ifdef CONFIG_USBHOST_HUB
/* Used to pass external hub port events */
volatile struct usbhost_hubport_s *hport;
#endif
struct usbhost_devaddr_s devgen; /* Address generation data */
/* The pipe list */
aligned_data(4)
struct sam_pipe_s pipelist[SAM_USB_NENDPOINTS];
/* Pipe descriptors 2 banks for each pipe */
aligned_data(4)
struct usbhost_pipedesc_s pipe_descriptors[SAM_USB_NENDPOINTS *
SAM_USB_NBANKS()];
/* CTRL */
usbhost_ep_t ep0; /* Root hub port EP0 description */
aligned_data(4) uint8_t ctrl_buffer[64];
};
#endif
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Register operations */
#ifdef CONFIG_SAMD5E5_USB_REGDEBUG
static void sam_printreg(uintptr_t regaddr, uint32_t regval, bool iswrite);
static void sam_checkreg(uintptr_t regaddr, uint32_t regval, bool iswrite);
static uint32_t sam_getreg32(uintptr_t regaddr);
static void sam_putreg32(uint32_t regval, uintptr_t regaddr);
static uint32_t sam_getreg16(uintptr_t regaddr);
static void sam_putreg16(uint16_t regval, uintptr_t regaddr);
static uint32_t sam_getreg8(uintptr_t regaddr);
static void sam_putreg8(uint8_t regval, uintptr_t regaddr);
static void sam_dumpep(struct sam_usbdev_s *priv, uint8_t epno);
#ifdef CONFIG_USBHOST
static void sam_dumppipe(struct sam_usbhost_s *priv, uint8_t epno);
#endif
#else
static inline uint32_t sam_getreg32(uintptr_t regaddr);
static inline void sam_putreg32(uint32_t regval, uintptr_t regaddr);
static inline uint32_t sam_getreg16(uintptr_t regaddr);
static inline void sam_putreg16(uint16_t regval, uintptr_t regaddr);
static inline uint32_t sam_getreg8(uintptr_t regaddr);
static inline void sam_putreg8(uint8_t regval, uintptr_t regaddr);
# define sam_dumpep(priv, epno)
#ifdef CONFIG_USBHOST
# define sam_dumppipe(priv, epno)
#endif
#endif
static inline void sam_modifyreg8(uint32_t clrbits,
uint32_t setbits,
uintptr_t regaddr);
/* Clks */
static void sam_enableclks(void);
#ifdef CONFIG_USBDEV
static void sam_disableclks(void);
/* Suspend/Resume Helpers */
static void sam_suspend(struct sam_usbdev_s *priv);
static void sam_resume(struct sam_usbdev_s *priv);
/* Request Helpers */
static struct sam_req_s *
sam_req_dequeue(struct sam_rqhead_s *queue);
static void sam_req_enqueue(struct sam_rqhead_s *queue,
struct sam_req_s *req);
static void sam_req_complete(struct sam_ep_s *privep, int16_t result);
static void sam_req_wrsetup(struct sam_usbdev_s *priv,
struct sam_ep_s *privep, struct sam_req_s *privreq);
static int sam_req_write(struct sam_usbdev_s *priv,
struct sam_ep_s *privep);
static int sam_req_read(struct sam_usbdev_s *priv,
struct sam_ep_s *privep, uint16_t recvsize);
static void sam_req_cancel(struct sam_ep_s *privep, int16_t status);
/* Interrupt level processing */
static void sam_ep0_ctrlread(struct sam_usbdev_s *priv);
static void sam_ep0_wrstatus(struct sam_usbdev_s *priv,
const uint8_t *buffer, size_t buflen);
static void sam_ep0_dispatch(struct sam_usbdev_s *priv);
static void sam_setdevaddr(struct sam_usbdev_s *priv, uint8_t value);
static void sam_ep0_setup(struct sam_usbdev_s *priv);
static void sam_ep_interrupt(struct sam_usbdev_s *priv, int epno);
static int sam_usb_interrupt(int irq, void *context, void *arg);
/* Endpoint helpers */
static void sam_ep_reset(struct sam_usbdev_s *priv, uint8_t epno);
static void sam_epset_reset(struct sam_usbdev_s *priv, uint16_t epset);
static int sam_ep_stall(struct sam_ep_s *privep);
static int sam_ep_resume(struct sam_ep_s *privep);
static inline struct sam_ep_s *
sam_ep_reserve(struct sam_usbdev_s *priv, uint8_t epset);
static inline void
sam_ep_unreserve(struct sam_usbdev_s *priv,
struct sam_ep_s *privep);
static int sam_ep_configure_internal(struct sam_ep_s *privep,
const struct usb_epdesc_s *desc);
/* Endpoint operations */
static int sam_ep_configure(struct usbdev_ep_s *ep,
const struct usb_epdesc_s *desc, bool last);
static int sam_ep_disable(struct usbdev_ep_s *ep);
static struct usbdev_req_s *
sam_ep_allocreq(struct usbdev_ep_s *ep);
#ifdef CONFIG_USBDEV_DMA
static void *sam_ep_allocbuffer(struct usbdev_ep_s *ep, uint16_t nbytes);
static void sam_ep_freebuffer(struct usbdev_ep_s *ep, void *buf);
#endif
static void sam_ep_freereq(struct usbdev_ep_s *ep,
struct usbdev_req_s *);
static int sam_ep_submit(struct usbdev_ep_s *ep,
struct usbdev_req_s *req);
static int sam_ep_cancel(struct usbdev_ep_s *ep,
struct usbdev_req_s *req);
static int sam_ep_stallresume(struct usbdev_ep_s *ep, bool resume);
/* USB device controller operations */
static struct usbdev_ep_s *
sam_allocep(struct usbdev_s *dev, uint8_t epno, bool in,
uint8_t eptype);
static void sam_freeep(struct usbdev_s *dev, struct usbdev_ep_s *ep);
static int sam_getframe(struct usbdev_s *dev);
static int sam_wakeup(struct usbdev_s *dev);
static int sam_selfpowered(struct usbdev_s *dev, bool selfpowered);
static int sam_pullup(struct usbdev_s *dev, bool enable);
/* Initialization/Reset */
static void sam_reset(struct sam_usbdev_s *priv);
static void sam_hw_setup(struct sam_usbdev_s *priv);
static void sam_sw_setup(struct sam_usbdev_s *priv);
static void sam_hw_shutdown(struct sam_usbdev_s *priv);
static void sam_sw_shutdown(struct sam_usbdev_s *priv);
#endif
#ifdef CONFIG_USBHOST
#undef CONFIG_SAM_USBHOST_PKTDUMP
#ifdef CONFIG_SAM_USBHOST_PKTDUMP
# define sam_pktdump(m,b,n) lib_dumpbuffer(m,b,n)
#else
# define sam_pktdump(m,b,n)
#endif
/* Pipe helpers */
static void sam_reset_pipes(struct sam_usbhost_s *priv, bool warm_reset);
static void sam_pipe_reset(struct sam_usbhost_s *priv, uint8_t epno);
static void sam_pipeset_reset(struct sam_usbhost_s *priv, uint16_t epset);
/* Byte stream access helper functions */
static inline uint16_t sam_getle16(const uint8_t *val);
/* Pipe management */
static int sam_pipe_alloc(struct sam_usbhost_s *priv);
static inline void sam_pipe_free(struct sam_usbhost_s *priv,
int idx);
static void sam_pipe_configure(struct sam_usbhost_s *priv, int idx);
static int sam_pipe_waitsetup(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe);
#ifdef CONFIG_USBHOST_ASYNCH
static int sam_pipe_asynchsetup(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
usbhost_asynch_t callback, void *arg);
#endif
static int sam_pipe_wait(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe);
static void sam_pipe_wakeup(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe);
static int sam_ctrlep_alloc(struct sam_usbhost_s *priv,
const struct usbhost_epdesc_s *epdesc,
usbhost_ep_t *ep);
static int sam_xfrep_alloc(struct sam_usbhost_s *priv,
const struct usbhost_epdesc_s *epdesc,
usbhost_ep_t *ep);
/* Control/data transfer logic */
static void sam_transfer_terminate(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe, int result);
static void sam_transfer_abort(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe, int result);
/* OUT transfers */
static void sam_send_continue(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe);
static void sam_send_start(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe);
static ssize_t sam_out_transfer(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
uint8_t *buffer,
size_t buflen);
#ifdef CONFIG_USBHOST_ASYNCH
static void sam_out_next(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe);
static int sam_out_asynch(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
uint8_t *buffer, size_t buflen,
usbhost_asynch_t callback, void *arg);
#endif
/* Control transfers */
static int sam_ctrl_sendsetup(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
const struct usb_ctrlreq_s *req);
static int sam_ctrl_senddata(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
uint8_t *buffer, unsigned int buflen);
static int sam_ctrl_recvdata(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
uint8_t *buffer, unsigned int buflen);
static int sam_in_setup(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe);
static int sam_out_setup(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe);
/* IN transfers */
static void sam_recv_continue(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe);
static void sam_recv_restart(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe);
static void sam_recv_start(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe);
static ssize_t sam_in_transfer(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
uint8_t *buffer,
size_t buflen);
#ifdef CONFIG_USBHOST_ASYNCH
static void sam_in_next(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe);
static int sam_in_asynch(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
uint8_t *buffer, size_t buflen,
usbhost_asynch_t callback, void *arg);
#endif
/* Interrupt handling */
/* Lower level interrupt handlers */
static void sam_gint_connected(struct sam_usbhost_s *priv);
static void sam_gint_disconnected(struct sam_usbhost_s *priv);
static void sam_pipe_interrupt(struct sam_usbhost_s *priv, int idx);
static int sam_usbhost_interrupt(int irq, void *context, void *arg);
/* USB host controller operations */
static int sam_wait(struct usbhost_connection_s *conn,
struct usbhost_hubport_s **hport);
static int sam_rh_enumerate(struct sam_usbhost_s *priv,
struct usbhost_connection_s *conn,
struct usbhost_hubport_s *hport);
static int sam_enumerate(struct usbhost_connection_s *conn,
struct usbhost_hubport_s *hport);
static int sam_ep0configure(struct usbhost_driver_s *drvr,
usbhost_ep_t ep0, uint8_t funcaddr,
uint8_t speed,
uint16_t maxpacketsize);
static int sam_epalloc(struct usbhost_driver_s *drvr,
const struct usbhost_epdesc_s *epdesc,
usbhost_ep_t *ep);
static int sam_epfree(struct usbhost_driver_s *drvr, usbhost_ep_t ep);
static int sam_alloc(struct usbhost_driver_s *drvr,
uint8_t **buffer, size_t *maxlen);
static int sam_free(struct usbhost_driver_s *drvr,
uint8_t *buffer);
static int sam_ioalloc(struct usbhost_driver_s *drvr,
uint8_t **buffer, size_t buflen);
static int sam_iofree(struct usbhost_driver_s *drvr,
uint8_t *buffer);
static int sam_ctrlin(struct usbhost_driver_s *drvr,
usbhost_ep_t ep0,
const struct usb_ctrlreq_s *req,
uint8_t *buffer);
static int sam_ctrlout(struct usbhost_driver_s *drvr,
usbhost_ep_t ep0,
const struct usb_ctrlreq_s *req,
const uint8_t *buffer);
static ssize_t sam_transfer(struct usbhost_driver_s *drvr,
usbhost_ep_t ep,
uint8_t *buffer, size_t buflen);
#ifdef CONFIG_USBHOST_ASYNCH
static int sam_asynch(struct usbhost_driver_s *drvr,
usbhost_ep_t ep,
uint8_t *buffer, size_t buflen,
usbhost_asynch_t callback, void *arg);
#endif
static int sam_cancel(struct usbhost_driver_s *drvr,
usbhost_ep_t ep);
#ifdef CONFIG_USBHOST_HUB
static int sam_connect(struct usbhost_driver_s *drvr,
struct usbhost_hubport_s *hport,
bool connected);
#endif
static void sam_disconnect(struct usbhost_driver_s *drvr,
struct usbhost_hubport_s *hport);
static void sam_hostreset(struct sam_usbhost_s *priv);
static void sam_add_sof_user(struct sam_usbhost_s *priv);
#endif
/****************************************************************************
* Private Data
****************************************************************************/
#ifdef CONFIG_USBHOST
/* In this driver implementation, support is provided for only a single
* USB host. All status information can be simply retained in a single global
* instance.
*/
static struct sam_usbhost_s g_usbhost =
{
.lock = NXMUTEX_INITIALIZER,
.pscsem = SEM_INITIALIZER(0),
};
/* This is the connection/enumeration interface */
static struct usbhost_connection_s g_usbconn =
{
.wait = sam_wait,
.enumerate = sam_enumerate,
};
#endif
#ifdef CONFIG_USBDEV
/* Since there is only a single USB interface, all status information can be
* be simply retained in a single global instance.
*/
static struct sam_usbdev_s g_usbd;
static const struct usbdev_epops_s g_epops =
{
.configure = sam_ep_configure,
.disable = sam_ep_disable,
.allocreq = sam_ep_allocreq,
.freereq = sam_ep_freereq,
#ifdef CONFIG_USBDEV_DMA
.allocbuffer = sam_ep_allocbuffer,
.freebuffer = sam_ep_freebuffer,
#endif
.submit = sam_ep_submit,
.cancel = sam_ep_cancel,
.stall = sam_ep_stallresume,
};
static const struct usbdev_ops_s g_devops =
{
.allocep = sam_allocep,
.freeep = sam_freeep,
.getframe = sam_getframe,
.wakeup = sam_wakeup,
.selfpowered = sam_selfpowered,
.pullup = sam_pullup,
};
/* This describes endpoint 0 */
static const struct usb_epdesc_s g_ep0desc =
{
.len = USB_SIZEOF_EPDESC,
.type = USB_DESC_TYPE_ENDPOINT,
.addr = EP0,
.attr = USB_EP_ATTR_XFER_CONTROL,
.mxpacketsize =
{
64, 0
},
.interval = 0
};
#endif
/* Device error strings that may be enabled for more descriptive USB trace
* output.
*/
#ifdef CONFIG_USBDEV_TRACE_STRINGS
const struct trace_msg_t g_usb_trace_strings_deverror[] =
{
TRACE_STR(SAM_TRACEERR_ALLOCFAIL),
TRACE_STR(SAM_TRACEERR_BADCLEARFEATURE),
TRACE_STR(SAM_TRACEERR_BADDEVGETSTATUS),
TRACE_STR(SAM_TRACEERR_BADEPGETSTATUS),
TRACE_STR(SAM_TRACEERR_BADEOBSTATE),
TRACE_STR(SAM_TRACEERR_BADEPNO),
TRACE_STR(SAM_TRACEERR_BADEPTYPE),
TRACE_STR(SAM_TRACEERR_BADGETCONFIG),
TRACE_STR(SAM_TRACEERR_BADGETSETDESC),
TRACE_STR(SAM_TRACEERR_BADGETSTATUS),
TRACE_STR(SAM_TRACEERR_BADSETADDRESS),
TRACE_STR(SAM_TRACEERR_BADSETCONFIG),
TRACE_STR(SAM_TRACEERR_BADSETFEATURE),
TRACE_STR(SAM_TRACEERR_BINDFAILED),
TRACE_STR(SAM_TRACEERR_DISPATCHSTALL),
TRACE_STR(SAM_TRACEERR_DRIVER),
TRACE_STR(SAM_TRACEERR_DRIVERREGISTERED),
TRACE_STR(SAM_TRACEERR_EP0SETUPOUTSIZE),
TRACE_STR(SAM_TRACEERR_EP0SETUPSTALLED),
TRACE_STR(SAM_TRACEERR_EPOUTNULLPACKET),
TRACE_STR(SAM_TRACEERR_EPRESERVE),
TRACE_STR(SAM_TRACEERR_INVALIDCTRLREQ),
TRACE_STR(SAM_TRACEERR_INVALIDPARMS),
TRACE_STR(SAM_TRACEERR_IRQREGISTRATION),
TRACE_STR(SAM_TRACEERR_NOTCONFIGURED),
TRACE_STR(SAM_TRACEERR_REQABORTED),
TRACE_STR(SAM_TRACEERR_RXDATABKERR),
TRACE_STR(SAM_TRACEERR_TXCOMPERR),
TRACE_STR(SAM_TRACEERR_UNSUPPEPTYPE),
TRACE_STR_END
};
#endif
/* Interrupt event strings that may be enabled for more descriptive USB trace
* output.
*/
#ifdef CONFIG_USBDEV_TRACE_STRINGS
const struct trace_msg_t g_usb_trace_strings_intdecode[] =
{
TRACE_STR(SAM_TRACEINTID_INTERRUPT),
TRACE_STR(SAM_TRACEINTID_PENDING),
TRACE_STR(SAM_TRACEINTID_PENDING_EP),
TRACE_STR(SAM_TRACEINTID_SUSPEND),
TRACE_STR(SAM_TRACEINTID_SOF),
TRACE_STR(SAM_TRACEINTID_EORST),
TRACE_STR(SAM_TRACEINTID_WAKEUP),
TRACE_STR(SAM_TRACEINTID_EORSM),
TRACE_STR(SAM_TRACEINTID_UPRSM),
TRACE_STR(SAM_TRACEINTID_RAMACER),
TRACE_STR(SAM_TRACEINTID_LPMNYET),
TRACE_STR(SAM_TRACEINTID_LPMSUSP),
TRACE_STR(SAM_TRACEINTID_EPNO),
TRACE_STR(SAM_TRACEINTID_EPINTFLAGS),
TRACE_STR(SAM_TRACEINTID_EPTRCPT0),
TRACE_STR(SAM_TRACEINTID_EPTRCPT1),
TRACE_STR(SAM_TRACEINTID_EPTRFAIL0),
TRACE_STR(SAM_TRACEINTID_EPTRFAIL1),
TRACE_STR(SAM_TRACEINTID_EPRXSTP),
TRACE_STR(SAM_TRACEINTID_EPSTALL0),
TRACE_STR(SAM_TRACEINTID_EPSTALL1),
TRACE_STR(SAM_TRACEINTID_EPINQEMPTY),
TRACE_STR(SAM_TRACEINTID_EPOUTQEMPTY),
TRACE_STR(SAM_TRACEINTID_EP0SETUPOUT),
TRACE_STR(SAM_TRACEINTID_EP0SETUPIN),
TRACE_STR(SAM_TRACEINTID_EP0SETUPSETADDRESS),
TRACE_STR(SAM_TRACEINTID_NOSTDREQ),
TRACE_STR(SAM_TRACEINTID_GETSTATUS),
TRACE_STR(SAM_TRACEINTID_DEVGETSTATUS),
TRACE_STR(SAM_TRACEINTID_IFGETSTATUS),
TRACE_STR(SAM_TRACEINTID_CLEARFEATURE),
TRACE_STR(SAM_TRACEINTID_SETFEATURE),
TRACE_STR(SAM_TRACEINTID_GETSETDESC),
TRACE_STR(SAM_TRACEINTID_GETCONFIG),
TRACE_STR(SAM_TRACEINTID_SETCONFIG),
TRACE_STR(SAM_TRACEINTID_GETSETIF),
TRACE_STR(SAM_TRACEINTID_SYNCHFRAME),
TRACE_STR(SAM_TRACEINTID_DISPATCH),
TRACE_STR(SAM_TRACEINTID_ADDRESSED),
TRACE_STR(SAM_TRACEINTID_EPCONF),
TRACE_STR(SAM_TRACEINTID_EPINTEN),
TRACE_STR(SAM_TRACEINTID_EP0WRSTATUS),
TRACE_STR(SAM_TRACEINTID_EPTRCPT0_LEN),
TRACE_STR(SAM_TRACEINTID_PENDING_PIPE),
TRACE_STR(SAM_TRACEINTID_PIPENO),
TRACE_STR_END
};
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Register Operations
****************************************************************************/
/****************************************************************************
* Name: sam_printreg
*
* Description:
* Print the SAMD5E5 USB register access
*
****************************************************************************/
#ifdef CONFIG_SAMD5E5_USB_REGDEBUG
static void sam_printreg(uintptr_t regaddr, uint32_t regval, bool iswrite)
{
uinfo("%p%s0x%08x\n", regaddr, iswrite ? "<-" : "->", regval);
}
#endif
/****************************************************************************
* Name: sam_checkreg
*
* Description:
* Check if it is time to output debug information for accesses
* to a SAMD5E5 USB registers.
*
****************************************************************************/
#ifdef CONFIG_SAMD5E5_USB_REGDEBUG
static void sam_checkreg(uintptr_t regaddr, uint32_t regval, bool iswrite)
{
static uintptr_t prevaddr = 0;
static uint32_t preval = 0;
static uint32_t count = 0;
static bool prevwrite = false;
/* Is this the same value that we read from/wrote
* to the same register last time?
* Are we polling the register? If so, suppress the output.
*/
if (regaddr == prevaddr && regval == preval && prevwrite == iswrite)
{
/* Yes.. Just increment the count */
count++;
}
else
{
/* No this is a new address or value or operation. Were there any
* duplicate accesses before this one?
*/
if (count > 0)
{
/* Yes.. Just one? */
if (count == 1)
{
/* Yes.. Just one */
sam_printreg(prevaddr, preval, prevwrite);
}
else
{
/* No.. More than one. */
uinfo("[repeats %d more times]\n", count);
}
}
/* Save the new address, value, count, and operation for next time */
prevaddr = regaddr;
preval = regval;
count = 0;
prevwrite = iswrite;
/* Show the new register access */
sam_printreg(regaddr, regval, iswrite);
}
}
#endif
/****************************************************************************
* Name: sam_getreg32
*
* Description:
* Get the contents of an 32-bit SAMD5E5 USB register
*
****************************************************************************/
#ifdef CONFIG_SAMD5E5_USB_REGDEBUG
static uint32_t sam_getreg32(uintptr_t regaddr)
{
/* Read the value from the register */
uint32_t regval = getreg32(regaddr);
/* Check if we need to print this value */
sam_checkreg(regaddr, regval, false);
return regval;
}
#else
static inline uint32_t sam_getreg32(uintptr_t regaddr)
{
return getreg32(regaddr);
}
#endif
/****************************************************************************
* Name: sam_putreg32
*
* Description:
* Set the contents of an 32-bit SAMD5E5 USB register to a value
*
****************************************************************************/
#ifdef CONFIG_SAMD5E5_USB_REGDEBUG
static void sam_putreg32(uint32_t regval, uintptr_t regaddr)
{
/* Check if we need to print this value */
sam_checkreg(regaddr, regval, true);
/* Write the value */
putreg32(regval, regaddr);
}
#else
static inline void sam_putreg32(uint32_t regval, uintptr_t regaddr)
{
putreg32(regval, regaddr);
}
#endif
/****************************************************************************
* Name: sam_getreg16
*
* Description:
* Get the contents of an 16-bit SAMD5E5 USB register
*
****************************************************************************/
#ifdef CONFIG_SAMD5E5_USB_REGDEBUG
static uint32_t sam_getreg16(uintptr_t regaddr)
{
/* Read the value from the register */
uint32_t regval = getreg16(regaddr);
/* Check if we need to print this value */
sam_checkreg(regaddr, regval, false);
return regval;
}
#else
static inline uint32_t sam_getreg16(uintptr_t regaddr)
{
return getreg16(regaddr);
}
#endif
/****************************************************************************
* Name: sam_putreg16
*
* Description:
* Set the contents of an 16-bit SAMD5E5 USB register to a value
*
****************************************************************************/
#ifdef CONFIG_SAMD5E5_USB_REGDEBUG
static void sam_putreg16(uint16_t regval, uintptr_t regaddr)
{
/* Check if we need to print this value */
sam_checkreg(regaddr, regval, true);
/* Write the value */
putreg16(regval, regaddr);
}
#else
static inline void sam_putreg16(uint16_t regval, uintptr_t regaddr)
{
putreg16(regval, regaddr);
}
#endif
/****************************************************************************
* Name: sam_getreg8
*
* Description:
* Get the contents of an 8-bit SAMD5E5 USB register
*
****************************************************************************/
#ifdef CONFIG_SAMD5E5_USB_REGDEBUG
static uint32_t sam_getreg8(uintptr_t regaddr)
{
/* Read the value from the register */
uint32_t regval = getreg8(regaddr);
/* Check if we need to print this value */
sam_checkreg(regaddr, regval, false);
return regval;
}
#else
static inline uint32_t sam_getreg8(uintptr_t regaddr)
{
return getreg8(regaddr);
}
#endif
/****************************************************************************
* Name: sam_putreg8
*
* Description:
* Set the contents of an 8-bit SAMD5E5 USB register to a value
*
****************************************************************************/
#ifdef CONFIG_SAMD5E5_USB_REGDEBUG
static void sam_putreg8(uint8_t regval, uintptr_t regaddr)
{
/* Check if we need to print this value */
sam_checkreg(regaddr, regval, true);
/* Write the value */
putreg8(regval, regaddr);
}
#else
static inline void sam_putreg8(uint8_t regval, uintptr_t regaddr)
{
putreg8(regval, regaddr);
}
#endif
/****************************************************************************
* Name: sam_dumpep
****************************************************************************/
#if defined(CONFIG_SAMD5E5_USB_REGDEBUG) && defined(CONFIG_DEBUG_USB)
#ifdef CONFIG_USBDEV
static void sam_dumpep(struct sam_usbdev_s *priv, uint8_t epno)
{
/* Global Registers */
uinfo("Global Registers:\n");
uinfo(" CTRLB: 0x%04x\n", sam_getreg16(SAM_USBDEV_CTRLB));
uinfo(" FNUM: 0x%04x\n", sam_getreg16(SAM_USBDEV_FNUM));
uinfo(" DADD: 0x%02x\n", sam_getreg8(SAM_USBDEV_DADD));
uinfo(" INTENSET: 0x%04x\n", sam_getreg16(SAM_USBDEV_INTENSET));
uinfo(" STATUS: 0x%02x\n", sam_getreg8(SAM_USBDEV_STATUS));
uinfo(" INTFLAG: 0x%04x\n", sam_getreg16(SAM_USBDEV_INTFLAG));
uinfo(" EPCFG[%d]: 0x%02x\n", epno, sam_getreg8(SAM_USBDEV_EPCFG(epno)));
uinfo("EPSTATUS[%d]: 0x%02x\n", epno,
sam_getreg8(SAM_USBDEV_EPSTATUS(epno)));
}
#endif
#ifdef CONFIG_USBHOST
static void sam_dumppipe(struct sam_usbhost_s *priv, uint8_t epno)
{
/* Global Registers */
uinfo("Global Host Registers:\n");
uinfo(" CTRLB: 0x%04x\n", sam_getreg16(SAM_USBHOST_CTRLB));
uinfo(" FNUM: 0x%04x\n", sam_getreg16(SAM_USBHOST_FNUM));
uinfo(" HSOFC: 0x%02x\n", sam_getreg8(SAM_USBHOST_HSOFC));
uinfo(" INTENSET: 0x%04x\n", sam_getreg16(SAM_USBHOST_INTENSET));
uinfo(" STATUS: 0x%02x\n", sam_getreg8(SAM_USBHOST_STATUS));
uinfo(" INTFLAG: 0x%04x\n", sam_getreg16(SAM_USBHOST_INTFLAG));
uinfo(" PIPECFG[%d]: 0x%02x\n", epno, sam_getreg8(SAM_USBHOST_PCFG(epno)));
uinfo(" PSTATUS[%d]: 0x%02x\n", epno,
sam_getreg8(SAM_USBHOST_PSTATUS(epno)));
}
#endif
#endif
/****************************************************************************
* Name: sam_modifyreg8
*
* Description:
* Modify selected bits of an SAM register.
*
****************************************************************************/
static inline void sam_modifyreg8(uint32_t clrbits,
uint32_t setbits, uintptr_t regaddr)
{
sam_putreg8((((sam_getreg8(regaddr)) & (~clrbits)) | setbits), regaddr);
}
/****************************************************************************
* Name: sam_enableclks
* Description:
* Enable USB clock
****************************************************************************/
static void sam_enableclks(void)
{
sam_gclk_chan_enable(GCLK_CHAN_USB, BOARD_USB_GCLKGEN, false);
sam_ahb_usb_enableperiph();
sam_apb_usb_enableperiph();
}
/****************************************************************************
* Name: sam_ctrla_write
*
* Description:
* writes value to CTRLA register some bits needs write-synchronisation
*
****************************************************************************/
static void sam_ctrla_write(uint8_t value)
{
sam_putreg8(value, SAM_USB_CTRLA);
if (value & USB_CTRLA_SWRST)
{
/* Due to synchronization there is a delay from writing CTRLA.SWRST
* until the reset is complete. CTRLA.SWRST and SYNCBUSY.SWRST will
* both be cleared when the reset is complete.
*/
while ((sam_getreg8(SAM_USB_CTRLA) & USB_CTRLA_SWRST) &&
(sam_getreg8(SAM_USB_SYNCBUSY) & USB_SYNCBUSY_SWRST))
;
return;
}
if (value & USB_CTRLA_ENABLE)
{
/* Due to synchronization there is delay from writing CTRLA.ENABLE
* until the peripheral is enabled/disabled.
* SYNCBUSY.ENABLE will be cleared when the operation is complete.
*/
while ((sam_getreg8(SAM_USB_SYNCBUSY) & USB_SYNCBUSY_ENABLE))
;
}
}
#ifdef CONFIG_USBDEV
/****************************************************************************
* Name: sam_disableclks
* Description:
* Disable USB clock
****************************************************************************/
static void sam_disableclks(void)
{
sam_gclk_chan_disable(GCLK_CHAN_USB);
sam_apb_usb_disableperiph();
}
/****************************************************************************
* Request Helpers
****************************************************************************/
/****************************************************************************
* Name: sam_req_dequeue
****************************************************************************/
static struct sam_req_s *sam_req_dequeue(struct sam_rqhead_s *queue)
{
struct sam_req_s *ret = queue->head;
if (ret)
{
queue->head = ret->flink;
if (!queue->head)
{
queue->tail = NULL;
}
ret->flink = NULL;
}
return ret;
}
/****************************************************************************
* Name: sam_req_enqueue
****************************************************************************/
static void sam_req_enqueue(struct sam_rqhead_s *queue,
struct sam_req_s *req)
{
req->flink = NULL;
if (!queue->head)
{
queue->head = req;
queue->tail = req;
}
else
{
queue->tail->flink = req;
queue->tail = req;
}
}
/****************************************************************************
* Name: sam_req_complete
****************************************************************************/
static void sam_req_complete(struct sam_ep_s *privep, int16_t result)
{
struct sam_req_s *privreq;
irqstate_t flags;
uinfo("ENTRY\n");
/* Remove the completed request at the head of the endpoint request list */
flags = enter_critical_section();
privreq = sam_req_dequeue(&privep->reqq);
leave_critical_section(flags);
if (privreq)
{
/* Save the result in the request structure */
privreq->req.result = result;
/* Callback to the request completion handler */
privreq->flink = NULL;
privreq->req.callback(&privep->ep, &privreq->req);
/* mark endpoint ready to next transmission */
privep->epstate = USB_EPSTATE_IDLE;
privep->zlpsent = false;
}
}
/****************************************************************************
* Name: sam_req_wrsetup
*
* Description:
* Process the next queued write request.
*
****************************************************************************/
static void sam_req_wrsetup(struct sam_usbdev_s *priv,
struct sam_ep_s *privep,
struct sam_req_s *privreq)
{
const uint8_t *buf;
uint8_t epno;
int nbytes;
uint32_t packetsize;
/* Get the unadorned endpoint number */
epno = USB_EPNO(privep->ep.eplog);
/* Get the number of bytes remaining to be sent. */
DEBUGASSERT(privreq->req.xfrd < privreq->req.len);
nbytes = privreq->req.len - privreq->req.xfrd;
/* Either send the maxpacketsize(multi) or all of the remaining data in
* the request.
*/
if (nbytes >= SAM_MAX_MULTIPACKET_SIZE)
{
nbytes = SAM_MAX_MULTIPACKET_SIZE;
}
/* This is the new number of bytes "in-flight" */
privreq->inflight = nbytes;
usbtrace(TRACE_WRITE(USB_EPNO(privep->ep.eplog)), nbytes);
/* The new buffer pointer is the start of the buffer plus the number of
* bytes successfully transferred plus the number of bytes previously
* "in-flight".
*/
buf = privreq->req.buf + privreq->req.xfrd;
/* setup TX transfer using ep configured maxpacket size */
priv->eplist[epno].descb[1]->addr = (uint32_t) buf;
uinfo("addr=%p\n", buf);
packetsize = priv->eplist[epno].descb[1]->pktsize;
packetsize &= ~USBDEV_PKTSIZE_BCNT_MASK;
packetsize &= ~USBDEV_PKTSIZE_MPKTSIZE_MASK;
packetsize |= USBDEV_PKTSIZE_BCNT(nbytes);
/* also set automatic ZLP sending if requested on req */
if (privreq->req.flags & USBDEV_REQFLAGS_NULLPKT)
{
packetsize |= USBDEV_PKTSIZE_AUTOZLP;
}
priv->eplist[epno].descb[1]->pktsize = packetsize;
/* Indicate that we are in the sending state
* This indication will be need in interrupt processing (TRCPT1)
* in order to properly terminate the request.
*/
privep->epstate = USB_EPSTATE_SENDING;
/* Set BK1RDY to notify the USB hardware that TX data is ready on
* descriptor bank1. We will be notified that the descriptor has been
* transmitted by the USB device when TRCPT1 in the endpoint's EPINTFLAG
* register has been set.
*/
sam_putreg8(USBDEV_EPSTATUS_BK1RDY, SAM_USBDEV_EPSTATUSSET(epno));
}
/****************************************************************************
* Name: sam_req_write
*
* Description:
* Process the next queued write request. This function is called in one
* of three contexts:
* (1) When the endpoint is IDLE and a new write request is submitted
* (with interrupts disabled),
* (2) from TRCPT1 interrupt handling when the current Tx transfer
* completes
* (3) when resuming a stalled IN or control endpoint.
*
* Calling rules:
*
* The transfer state must IDLE
*
* When a request is queued, the request 'len' is the number of bytes
* to transfer and 'xfrd' and 'inflight' must be zero.
*
* When this function starts a transfer it will update the request
* 'inflight' field to indicate the size of the transfer.
*
* When the transfer completes, the 'inflight' field must hold the
* number of bytes that have completed the transfer. This function will
* update 'xfrd' with the new size of the transfer.
*
****************************************************************************/
static int sam_req_write(struct sam_usbdev_s *priv, struct sam_ep_s *privep)
{
struct sam_req_s *privreq;
uint8_t epno;
int bytesleft;
/* Get the unadorned endpoint number */
epno = USB_EPNO(privep->ep.eplog);
/* We get here when an IN endpoint interrupt occurs. So now we know that
* there is no TX transfer in progress (epstate should be IDLE).
*/
DEBUGASSERT(privep->epstate == USB_EPSTATE_IDLE);
while (privep->epstate == USB_EPSTATE_IDLE)
{
/* Check the request from the head of the endpoint request queue */
privreq = sam_rqpeek(&privep->reqq);
if (!privreq)
{
/* There is no TX transfer in progress and no new pending TX
* requests to send.
*/
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPINQEMPTY), epno);
/* Was there a pending endpoint stall? */
if (privep->pending)
{
/* Yes... stall the endpoint now */
sam_ep_stall(privep);
}
return -ENOENT;
}
uinfo("epno=%d req=%p: len=%zu xfrd=%zu inflight=%d\n",
epno, privreq, privreq->req.len, privreq->req.xfrd,
privreq->inflight);
/* Handle any bytes in flight. */
privreq->req.xfrd += privreq->inflight;
privreq->inflight = 0;
/* Get the number of bytes left to be sent in the packet */
bytesleft = privreq->req.len - privreq->req.xfrd;
if (bytesleft > 0)
{
/* Perform the write operation. epstate will become SENDING. */
sam_req_wrsetup(priv, privep, privreq);
}
/* No data to send...
* This can happen on one of two ways:
* (1) The last packet sent was the final packet of a transfer.
* Or
* (2) called with a request packet that has len == 0
*
* len == 0 means that it is requested to send a zero length packet
* required by protocol.
*/
else if ((privreq->req.len == 0) && !privep->zlpsent)
{
/* If we get here, we requested to send the zero length packet now.
*/
privep->epstate = USB_EPSTATE_SENDING;
privep->zlpsent = true;
privreq->inflight = 0;
usbtrace(TRACE_WRITE(epno), 0);
/* setup 0 length TX transfer */
priv->eplist[0].descb[1]->addr = (uint32_t) &priv->ep0out[0];
uinfo("addr=%p\n", &priv->ep0out[0]);
priv->eplist[0].descb[1]->pktsize &= ~USBDEV_PKTSIZE_BCNT_MASK;
priv->eplist[0].descb[1]->pktsize &= ~USBDEV_PKTSIZE_MPKTSIZE_MASK;
priv->eplist[0].descb[1]->pktsize |= USBDEV_PKTSIZE_BCNT(0);
sam_putreg8(USBDEV_EPSTATUS_BK1RDY, SAM_USBDEV_EPSTATUSSET(epno));
}
/* If all of the bytes were sent (including any final zero length
* packet) then we are finished with the request buffer and we can
* return the request buffer to the class driver. The state will
* remain IDLE only if nothing else was put in flight.
*
* Note that we will then loop to check to check the next queued
* write request.
*/
if (privep->epstate == USB_EPSTATE_IDLE)
{
/* Return the write request to the class driver. Set the txbusy
* bit to prevent being called recursively from any new submission
* generated by returning the write request.
*/
usbtrace(TRACE_COMPLETE(epno), privreq->req.xfrd);
DEBUGASSERT(privreq->req.len == privreq->req.xfrd);
privep->txbusy = true;
sam_req_complete(privep, OK);
privep->txbusy = false;
}
}
return OK;
}
/****************************************************************************
* Name: sam_req_read
*
* Description:
* Complete the last read request. full or partial.
* The USB core has transferred the data to user request buffer.
* return the completed read request to the class
* implementation, and try to start the next queued read request.
*
* REVISIT:
* This function is called in one of two contexts:
* The normal case is
* (1) When the endpoint is IDLE and a new read request is submitted
* (with interrupts disabled),
* (2) from interrupt handling when the current RX transfer completes.
* But there is also a special case
* (3) when the OUT endpoint is stopped because there are no
* available read requests.
*
* Calling rules:
*
* The transfer state must IDLE
*
* When a request is queued, the request 'len' is size of the request
* buffer. Any OUT request can be received that will fit in this
* buffer. 'xfrd' and 'inflight' in the request must be zero
* If sam_req_read() is called to start a new transfer, the recvsize
* parameter must be zero.
*
* When the transfer completes, the 'recvsize' is the number of bytes
* ready on req->buffer.
*
****************************************************************************/
static int sam_req_read(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
uint16_t recvsize)
{
struct sam_req_s *privreq;
int epno;
DEBUGASSERT(priv && privep && privep->epstate == USB_EPSTATE_IDLE);
/* Check the request from the head of the endpoint request queue */
epno = USB_EPNO(privep->ep.eplog);
do
{
/* Peek at the next read request in the request queue */
privreq = sam_rqpeek(&privep->reqq);
if (!privreq)
{
/* No read request to receive data */
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPOUTQEMPTY), epno);
/* When no read requests are pending no EP descriptors are set to
* ready. HW sends NAK to host if it tries to send something.
*/
privep->epstate = USB_EPSTATE_RXSTOPPED;
return -ENOENT;
}
uinfo("EP%d: req.len=%zu xfrd=%zu recvsize=%d\n",
epno, privreq->req.len, privreq->req.xfrd, recvsize);
/* Ignore any attempt to receive a zero length packet */
if (privreq->req.len == 0)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_EPOUTNULLPACKET), 0);
sam_req_complete(privep, OK);
privreq = NULL;
}
/* complete read request with available data */
if ((privreq->inflight) && (recvsize != 0))
{
usbtrace(TRACE_READ(USB_EPNO(privep->ep.eplog)), recvsize);
/* Update the total number of bytes transferred */
privreq->req.xfrd += recvsize;
privreq->inflight = 0;
usbtrace(TRACE_COMPLETE(epno), privreq->req.xfrd);
sam_req_complete(privep, OK);
/* need to set recvsize to zero. When calling sam_req_complete()
* class driver could call submit() again and we have new request
* ready on next while() loop.
*/
privep->rxactive = false;
recvsize = 0;
privreq = NULL;
}
}
while (privreq == NULL);
DEBUGASSERT(recvsize == 0);
/* activate new read request from queue */
privep->rxactive = true;
privreq->req.xfrd = 0;
privreq->inflight = privreq->req.len;
priv->eplist[epno].descb[0]->addr = (uint32_t) privreq->req.buf;
uinfo("addr=%p\n", privreq->req.buf);
sam_putreg8(USBDEV_EPSTATUS_BK0RDY, SAM_USBDEV_EPSTATUSCLR(epno));
return OK;
}
/****************************************************************************
* Name: sam_req_cancel
****************************************************************************/
static void sam_req_cancel(struct sam_ep_s *privep, int16_t result)
{
/* Complete every queued request with the specified status */
while (!sam_rqempty(&privep->reqq))
{
usbtrace(TRACE_COMPLETE(USB_EPNO(privep->ep.eplog)),
(sam_rqpeek(&privep->reqq))->req.xfrd);
sam_req_complete(privep, result);
}
}
/****************************************************************************
* Name: sam_ep_configure_internal
*
* Description:
* This is the internal implementation of the endpoint configuration logic
* and implements the endpoint configuration method of the usbdev_ep_s
* interface. As an internal interface, it will be used to configure
* endpoint 0 which is not available to the class implementation.
*
****************************************************************************/
static int sam_ep_configure_internal(struct sam_ep_s *privep,
const struct usb_epdesc_s *desc)
{
uint8_t epconf;
uint16_t maxpacket;
uint8_t epno;
uint8_t eptype;
uint8_t intflags;
uint32_t ephwsize;
bool dirin;
DEBUGASSERT(privep && privep->dev && desc);
uinfo("len: 0x%02x type: 0x%02x addr: 0x%02x attr: 0x%02x "
"maxpacketsize: 0x%02x%02x interval: 0x%02x\n",
desc->len, desc->type, desc->addr, desc->attr,
desc->mxpacketsize[1], desc->mxpacketsize[0],
desc->interval);
/* Decode the endpoint descriptor */
epno = USB_EPNO(desc->addr);
dirin = (desc->addr & USB_DIR_MASK) == USB_REQ_DIR_IN;
eptype = (desc->attr & USB_EP_ATTR_XFERTYPE_MASK);
maxpacket = GETUINT16(desc->mxpacketsize);
if (maxpacket <= 8)
{
ephwsize = USBDEV_PKTSIZE_SIZE_8B;
}
else if (maxpacket <= 16)
{
ephwsize = USBDEV_PKTSIZE_SIZE_16B;
}
else if (maxpacket <= 32)
{
ephwsize = USBDEV_PKTSIZE_SIZE_32B;
}
else if (maxpacket <= 64)
{
ephwsize = USBDEV_PKTSIZE_SIZE_64B;
}
else if ((maxpacket <= 128) && (eptype == USB_EP_ATTR_XFER_ISOC))
{
ephwsize = USBDEV_PKTSIZE_SIZE_128B;
}
else if ((maxpacket <= 256) && (eptype == USB_EP_ATTR_XFER_ISOC))
{
ephwsize = USBDEV_PKTSIZE_SIZE_256B;
}
else if ((maxpacket <= 512) && (eptype == USB_EP_ATTR_XFER_ISOC))
{
ephwsize = USBDEV_PKTSIZE_SIZE_512B;
}
else if ((maxpacket <= 1023) && (eptype == USB_EP_ATTR_XFER_ISOC))
{
ephwsize = USBDEV_PKTSIZE_SIZE_1023B;
}
else
{
return -EINVAL;
}
/* update endpoint descriptors to correct size */
privep->descb[0]->pktsize = ephwsize;
privep->descb[1]->pktsize = ephwsize;
/* Initialize the endpoint structure */
privep->ep.eplog = desc->addr; /* Includes direction */
privep->ep.maxpacket = maxpacket;
privep->epstate = USB_EPSTATE_IDLE;
/* get current config IN and OUT */
epconf = 0x00;
sam_putreg8(0x00, SAM_USBDEV_EPCFG(epno));
if (dirin)
{
/* Disable bank1 (IN) */
intflags = USBDEV_EPINT_TRCPT1 | USBDEV_EPINT_STALL1;
}
else
{
/* Disable bank0 (OUT) */
intflags = USBDEV_EPINT_TRCPT0 | USBDEV_EPINT_STALL0;
}
/* write back disabled config */
sam_putreg8(0x7f, SAM_USBDEV_EPINTENCLR(epno));
sam_putreg8(0x7f, SAM_USBDEV_EPINTFLAG(epno));
/* Re-configure and enable the endpoint */
switch (eptype)
{
case USB_EP_ATTR_XFER_CONTROL:
{
epconf = USBDEV_EPCCFG_EPTYPE0_CTRLOUT |
USBDEV_EPCCFG_EPTYPE1_CTRLIN;
/* Also enable IN interrupts */
intflags = USBDEV_EPINT_TRCPT0 | USBDEV_EPINT_STALL0;
intflags |= USBDEV_EPINT_TRCPT1 | USBDEV_EPINT_STALL1;
intflags |= USBDEV_EPINT_RXSTP;
sam_putreg8(USBDEV_EPSTATUS_BK0RDY, SAM_USBDEV_EPSTATUSSET(0));
sam_putreg8(USBDEV_EPSTATUS_BK1RDY, SAM_USBDEV_EPSTATUSCLR(0));
}
break;
#ifdef CONFIG_USBDEV_ISOCHRONOUS
case USB_EP_ATTR_XFER_ISOC:
if (dirin)
{
epconf |= USBDEV_EPCCFG_EPTYPE1_ISOCIN;
}
else
{
epconf |= USBDEV_EPCCFG_EPTYPE0_ISOCOUT;
}
break;
#endif
case USB_EP_ATTR_XFER_BULK:
if (dirin)
{
epconf |= USBDEV_EPCCFG_EPTYPE1_BULKIN;
}
else
{
epconf |= USBDEV_EPCCFG_EPTYPE0_BULKOUT;
}
break;
case USB_EP_ATTR_XFER_INT:
if (dirin)
{
epconf |= USBDEV_EPCCFG_EPTYPE1_INTIN;
}
else
{
epconf |= USBDEV_EPCCFG_EPTYPE0_INTOUT;
}
break;
default:
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADEPTYPE),
eptype >> USB_EP_ATTR_XFERTYPE_SHIFT);
return -EINVAL;
}
sam_putreg8(epconf, SAM_USBDEV_EPCFG(epno));
/* Enable endpoint interrupts */
sam_putreg8(intflags, SAM_USBDEV_EPINTENSET(epno));
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPCONF), epno << 8 | epconf);
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPINTEN), epno << 8 | intflags);
sam_dumpep(privep->dev, epno);
return OK;
}
/****************************************************************************
* Name: sam_ep_reserve
*
* Description:
* Find and un-reserved endpoint number and reserve it for the caller.
*
****************************************************************************/
static inline struct sam_ep_s *
sam_ep_reserve(struct sam_usbdev_s *priv, uint8_t epset)
{
struct sam_ep_s *privep = NULL;
irqstate_t flags;
int epndx = 0;
flags = enter_critical_section();
epset &= priv->epavail;
if (epset)
{
/* Select the lowest bit in the set of matching, available endpoints
* (skipping EP0)
*/
for (epndx = 1; epndx < SAM_USB_NENDPOINTS; epndx++)
{
uint8_t bit = SAM_EP_BIT(epndx);
if ((epset & bit) != 0)
{
/* Mark the endpoint no longer available */
priv->epavail &= ~bit;
/* And return the pointer to the standard endpoint
* structure.
*/
privep = &priv->eplist[epndx];
break;
}
}
}
leave_critical_section(flags);
return privep;
}
/****************************************************************************
* Name: sam_ep_unreserve
*
* Description:
* The endpoint is no long in-used. It will be unreserved and can be
* reused if needed.
*
****************************************************************************/
static inline void
sam_ep_unreserve(struct sam_usbdev_s *priv, struct sam_ep_s *privep)
{
irqstate_t flags = enter_critical_section();
priv->epavail |= SAM_EP_BIT(USB_EPNO(privep->ep.eplog));
leave_critical_section(flags);
}
/****************************************************************************
* Endpoint operations
****************************************************************************/
/****************************************************************************
* Name: sam_ep_configure
*
* Description:
* This is the endpoint configuration method of the usbdev_ep_s interface.
*
****************************************************************************/
static int sam_ep_configure(struct usbdev_ep_s *ep,
const struct usb_epdesc_s *desc,
bool last)
{
struct sam_ep_s *privep = (struct sam_ep_s *)ep;
int ret;
/* Verify parameters. Endpoint 0 is not available at this interface */
#if defined(CONFIG_DEBUG_USB) || defined(CONFIG_USBDEV_TRACE)
uint8_t epno = USB_EPNO(desc->addr);
usbtrace(TRACE_EPCONFIGURE, (uint16_t)epno);
DEBUGASSERT(ep && desc && epno > 0 && epno < SAM_USB_NENDPOINTS);
DEBUGASSERT(epno == USB_EPNO(ep->eplog));
#endif
/* This logic is implemented in sam_ep_configure_internal */
ret = sam_ep_configure_internal(privep, desc);
/* If this was the last endpoint, then the class driver is fully
* configured.
*/
if (ret == OK && last)
{
struct sam_usbdev_s *priv = privep->dev;
/* Go to the configured state (we should have been in the addressed
* state)
*/
DEBUGASSERT(priv && priv->devstate == USB_DEVSTATE_ADDRESSED);
priv->devstate = USB_DEVSTATE_CONFIGURED;
}
return ret;
}
/****************************************************************************
* Name: sam_ep_disable
*
* Description:
* This is the disable() method of the USB device endpoint structure.
*
****************************************************************************/
static int sam_ep_disable(struct usbdev_ep_s *ep)
{
struct sam_ep_s *privep = (struct sam_ep_s *)ep;
struct sam_usbdev_s *priv;
irqstate_t flags;
uint8_t epno;
#ifdef CONFIG_DEBUG_USB
if (!ep)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0);
uerr("ERROR: ep=%p\n", ep);
return -EINVAL;
}
#endif
epno = USB_EPNO(ep->eplog);
usbtrace(TRACE_EPDISABLE, epno);
/* Reset the endpoint and cancel any ongoing activity */
flags = enter_critical_section();
priv = privep->dev;
sam_ep_reset(priv, epno);
/* Revert to the addressed-but-not-configured state */
sam_setdevaddr(priv, priv->devaddr);
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Name: sam_ep_allocreq
*
* Description:
* This is the allocreq() method of the USB device endpoint structure.
*
****************************************************************************/
static struct usbdev_req_s *sam_ep_allocreq(struct usbdev_ep_s *ep)
{
struct sam_req_s *privreq;
#ifdef CONFIG_DEBUG_USB
if (!ep)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0);
return NULL;
}
#endif
usbtrace(TRACE_EPALLOCREQ, USB_EPNO(ep->eplog));
privreq = kmm_zalloc(sizeof(struct sam_req_s));
if (!privreq)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_ALLOCFAIL), 0);
return NULL;
}
return &privreq->req;
}
/****************************************************************************
* Name: sam_ep_freereq
*
* Description:
* This is the freereq() method of the USB device endpoint structure.
*
****************************************************************************/
static void sam_ep_freereq(struct usbdev_ep_s *ep, struct usbdev_req_s *req)
{
struct sam_req_s *privreq = (struct sam_req_s *)req;
#ifdef CONFIG_DEBUG_USB
if (!ep || !req)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0);
return;
}
#endif
usbtrace(TRACE_EPFREEREQ, USB_EPNO(ep->eplog));
kmm_free(privreq);
}
/****************************************************************************
* Name: sam_ep_allocbuffer
*
* Description:
* This is the allocbuffer() method of the USB device endpoint structure.
*
****************************************************************************/
#ifdef CONFIG_USBDEV_DMA
static void *sam_ep_allocbuffer(struct usbdev_ep_s *ep, uint16_t nbytes)
{
/* There is not special buffer allocation requirement */
return kumm_malloc(nbytes);
}
#endif
/****************************************************************************
* Name: sam_ep_freebuffer
*
* Description:
* This is the freebuffer() method of the USB device endpoint structure.
*
****************************************************************************/
#ifdef CONFIG_USBDEV_DMA
static void sam_ep_freebuffer(struct usbdev_ep_s *ep, void *buf)
{
/* There is not special buffer allocation requirement */
kumm_free(buf);
}
#endif
/****************************************************************************
* Name: sam_ep_submit
*
* Description:
* This is the submit() method of the USB device endpoint structure.
*
****************************************************************************/
static int sam_ep_submit(struct usbdev_ep_s *ep, struct usbdev_req_s *req)
{
struct sam_req_s *privreq = (struct sam_req_s *)req;
struct sam_ep_s *privep = (struct sam_ep_s *)ep;
struct sam_usbdev_s *priv;
irqstate_t flags;
uint8_t epno;
int ret = OK;
#ifdef CONFIG_DEBUG_USB
if (!req || !req->callback || !req->buf || !ep)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0);
uerr("ERROR: req=%p callback=%p buf=%p ep=%p\n", req, req->callback,
req->buf, ep);
return -EINVAL;
}
#endif
usbtrace(TRACE_EPSUBMIT, USB_EPNO(ep->eplog));
priv = privep->dev;
#ifdef CONFIG_DEBUG_USB
if (!priv->driver)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_NOTCONFIGURED),
priv->usbdev.speed);
uerr("ERROR: driver=%p\n", priv->driver);
return -ESHUTDOWN;
}
#endif
/* Handle the request from the class driver */
epno = USB_EPNO(ep->eplog);
req->result = -EINPROGRESS;
req->xfrd = 0;
privreq->inflight = 0;
flags = enter_critical_section();
/* Handle IN (device-to-host) requests. NOTE: If the class device is
* using the bi-directional EP0, then we assume that they intend the EP0
* IN functionality (EP0 SETUP OUT data receipt does not use requests).
*/
if (USB_ISEPIN(ep->eplog) || epno == EP0)
{
/* Check if the endpoint is stalled (or there is a stall pending) */
if (privep->stalled || privep->pending)
{
/* Yes.. in this case, save the new they will get in a special
* "pending" they will get queue until the stall is cleared.
*/
uinfo("Pending stall clear\n");
sam_req_enqueue(&privep->pendq, privreq);
usbtrace(TRACE_INREQQUEUED(epno), req->len);
ret = OK;
}
else
{
/* Add the new request to the request queue for the IN endpoint */
sam_req_enqueue(&privep->reqq, privreq);
usbtrace(TRACE_INREQQUEUED(epno), req->len);
/* If the IN endpoint is IDLE and there is not write queue
* processing in progress, then transfer the data now.
*/
if (privep->epstate == USB_EPSTATE_IDLE && !privep->txbusy)
{
ret = sam_req_write(priv, privep);
}
}
}
/* Handle OUT (host-to-device) requests */
else
{
/* Add the new request to the request queue for the OUT endpoint */
sam_req_enqueue(&privep->reqq, privreq);
usbtrace(TRACE_OUTREQQUEUED(epno), req->len);
/* Check if we have stopped RX receipt due to lack of read
* requests. In that case we are not receiving anything from host.
* and HW sends NAK to host. see sam_req_read()
* so this "state" is actually not required (at least yet)
*/
if (privep->epstate == USB_EPSTATE_RXSTOPPED)
{
privep->epstate = USB_EPSTATE_IDLE;
}
/* start new read if no active yet */
if (!privep->rxactive)
{
ret = sam_req_read(priv, privep, 0);
}
}
leave_critical_section(flags);
return ret;
}
/****************************************************************************
* Name: sam_ep_cancel
****************************************************************************/
static int sam_ep_cancel(struct usbdev_ep_s *ep, struct usbdev_req_s *req)
{
struct sam_ep_s *privep = (struct sam_ep_s *)ep;
irqstate_t flags;
#ifdef CONFIG_DEBUG_USB
if (!ep || !req)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0);
return -EINVAL;
}
#endif
usbtrace(TRACE_EPCANCEL, USB_EPNO(ep->eplog));
flags = enter_critical_section();
sam_req_cancel(privep, -EAGAIN);
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Name: sam_ep_stallresume
****************************************************************************/
static int sam_ep_stallresume(struct usbdev_ep_s *ep, bool resume)
{
struct sam_ep_s *privep;
uint8_t epno;
irqstate_t flags;
int ret;
#ifdef CONFIG_DEBUG_USB
if (!ep)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0);
return -EINVAL;
}
#endif
/* Handle the resume condition */
privep = (struct sam_ep_s *)ep;
if (resume)
{
ret = sam_ep_resume(privep);
}
/* Handle the stall condition */
else
{
/* If this is an IN endpoint (and not EP0) and if there are queued
* write requests, then we cannot stall now. Perhaps this is a
* protocol stall. In that case, we will need to drain the write
* requests before sending the stall.
*/
flags = enter_critical_section();
epno = USB_EPNO(ep->eplog);
if (epno != 0 && USB_ISEPIN(ep->eplog))
{
/* Are there any unfinished write requests in the request
* queue?
*/
if (!sam_rqempty(&privep->reqq))
{
/* Just set a flag to indicate that the endpoint must be
* stalled on the next TRCPTx interrupt when the request
* queue becomes empty.
*/
privep->pending = true;
leave_critical_section(flags);
return OK;
}
}
/* Not an IN endpoint, endpoint 0, or no pending write requests.
* Stall the endpoint now.
*/
ret = sam_ep_stall(privep);
leave_critical_section(flags);
}
return ret;
}
/****************************************************************************
* Device Controller Operations
****************************************************************************/
/****************************************************************************
* Name: sam_allocep
*
* Description:
* This is the allocep() method of the USB device driver interface
*
****************************************************************************/
static struct usbdev_ep_s *sam_allocep(struct usbdev_s *dev, uint8_t epno,
bool in, uint8_t eptype)
{
struct sam_usbdev_s *priv = (struct sam_usbdev_s *)dev;
struct sam_ep_s *privep = NULL;
uint16_t epset = SAM_EPSET_NOTEP0;
usbtrace(TRACE_DEVALLOCEP, (uint16_t)epno);
#ifdef CONFIG_DEBUG_USB
if (!dev)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0);
return NULL;
}
#endif
/* Ignore any direction bits in the logical address */
epno = USB_EPNO(epno);
/* A logical address of 0 means that any endpoint will do */
if (epno > 0)
{
/* Otherwise, we will return the endpoint structure only for the
* requested 'logical' endpoint. All of the other checks will still
* be performed.
*
* First, verify that the logical endpoint is in the range supported by
* by the hardware.
*/
if (epno >= SAM_USB_NENDPOINTS)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADEPNO), (uint16_t)epno);
return NULL;
}
/* Convert the logical address to a physical OUT endpoint address and
* remove all of the candidate endpoints from the bitset except for the
* the IN/OUT pair for this logical address.
*/
epset = SAM_EP_BIT(epno);
}
/* Check if the selected endpoint number is available */
privep = sam_ep_reserve(priv, epset);
if (!privep)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_EPRESERVE), (uint16_t)epset);
return NULL;
}
return &privep->ep;
}
/****************************************************************************
* Name: sam_freeep
*
* Description:
* This is the freeep() method of the USB device driver interface
*
****************************************************************************/
static void sam_freeep(struct usbdev_s *dev, struct usbdev_ep_s *ep)
{
struct sam_usbdev_s *priv;
struct sam_ep_s *privep;
#ifdef CONFIG_DEBUG_USB
if (!ep || !req)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0);
return -EINVAL;
}
#endif
priv = (struct sam_usbdev_s *)dev;
privep = (struct sam_ep_s *)ep;
usbtrace(TRACE_DEVFREEEP, (uint16_t)USB_EPNO(ep->eplog));
if (priv && privep)
{
/* Mark the endpoint as available */
sam_ep_unreserve(priv, privep);
}
}
/****************************************************************************
* Name: sam_getframe
*
* Description:
* This is the getframe() method of the USB device driver interface
*
****************************************************************************/
static int sam_getframe(struct usbdev_s *dev)
{
uint32_t regval;
uint16_t frameno;
#ifdef CONFIG_DEBUG_USB
if (!dev)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0);
return -EINVAL;
}
#endif
/* Return the last frame number detected by the hardware */
regval = sam_getreg16(SAM_USBDEV_FNUM);
frameno = (regval & USBDEV_FNUM_MASK) >> USBDEV_FNUM_SHIFT;
usbtrace(TRACE_DEVGETFRAME, frameno);
return frameno;
}
/****************************************************************************
* Name: sam_wakeup
*
* Description:
* This is the wakeup() method of the USB device driver interface
*
****************************************************************************/
static int sam_wakeup(struct usbdev_s *dev)
{
struct sam_usbdev_s *priv = (struct sam_usbdev_s *)dev;
irqstate_t flags;
uint16_t regval;
usbtrace(TRACE_DEVWAKEUP, 0);
#ifdef CONFIG_DEBUG_USB
if (!dev)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0);
return -EINVAL;
}
#endif
/* Resume normal operation */
flags = enter_critical_section();
sam_resume(priv);
/* Activate a remote wakeup. (aka upstream resume)
* Setting the Remote Wakeup bit in CTRLB.UPRSM starts the
* Remote Wake Up procedure.
*
* This will automatically be done by the controller after 5 ms of
* inactivity on the USB bus.
*
* When the controller sends the Upstream Resume INTFLAG.WAKEUP is set
* and INTFLAG.SUSPEND is cleared.
* The CTRLB.UPRSM is cleared at the end of the transmitting Upstream
* Resume.
*/
regval = sam_getreg16(SAM_USBDEV_CTRLB);
regval |= USBDEV_CTRLB_UPRSM;
sam_putreg16(regval, SAM_USBDEV_CTRLB);
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Name: sam_selfpowered
*
* Description:
* This is the selfpowered() method of the USB device driver interface
*
****************************************************************************/
static int sam_selfpowered(struct usbdev_s *dev, bool selfpowered)
{
struct sam_usbdev_s *priv = (struct sam_usbdev_s *)dev;
usbtrace(TRACE_DEVSELFPOWERED, (uint16_t)selfpowered);
#ifdef CONFIG_DEBUG_USB
if (!dev)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0);
return -ENODEV;
}
#endif
priv->selfpowered = selfpowered;
return OK;
}
/****************************************************************************
* Suspend/Resume Helpers
****************************************************************************/
/****************************************************************************
* Name: sam_suspend
****************************************************************************/
static void sam_suspend(struct sam_usbdev_s *priv)
{
/* Don't do anything if the device is already suspended */
if (priv->devstate != USB_DEVSTATE_SUSPENDED)
{
/* Notify the class driver of the suspend event */
if (priv->driver)
{
CLASS_SUSPEND(priv->driver, &priv->usbdev);
}
/* Switch to the Suspended state */
priv->prevstate = priv->devstate;
priv->devstate = USB_DEVSTATE_SUSPENDED;
/* Disable clocking to the USB peripheral */
sam_disableclks();
/* Let the board-specific logic know that we have entered the
* suspend state. This may trigger additional reduced power
* consumption measures.
*/
sam_usb_suspend((struct usbdev_s *)priv, false);
}
}
/****************************************************************************
* Name: sam_resume
****************************************************************************/
static void sam_resume(struct sam_usbdev_s *priv)
{
/* This function is called when either (1) a WAKEUP interrupt is received
* from the host PC, or (2) the class device implementation calls the
* wakeup() method.
*/
/* Don't do anything if the device was not suspended */
if (priv->devstate == USB_DEVSTATE_SUSPENDED)
{
/* Revert to the previous state */
priv->devstate = priv->prevstate;
/* Restore clocking to the USB peripheral */
sam_enableclks();
/* Restore full power -- whatever that means for this
* particular board
*/
sam_usb_suspend((struct usbdev_s *)priv, true);
/* Notify the class driver of the resume event */
if (priv->driver)
{
CLASS_RESUME(priv->driver, &priv->usbdev);
}
}
}
/****************************************************************************
* Initialization/Reset
****************************************************************************/
/****************************************************************************
* Name: sam_reset
****************************************************************************/
static void sam_reset(struct sam_usbdev_s *priv)
{
uint16_t regval;
uint8_t epno;
/* Make sure that clocking is enabled to the USB peripheral. */
sam_enableclks();
/* Tell the class driver that we are disconnected. The class driver
* should then accept any new configurations.
*/
CLASS_DISCONNECT(priv->driver, &priv->usbdev);
/* The device enters the Default state (un-addressed and un-configured) */
priv->devaddr = 0;
sam_setdevaddr(priv, 0);
priv->devstate = USB_DEVSTATE_DEFAULT;
/* Reset and disable all endpoints. Then re-configure EP0 */
sam_epset_reset(priv, SAM_EPSET_ALL);
sam_ep_configure_internal(&priv->eplist[EP0], &g_ep0desc);
/* set EP0 waiting for SETUP */
sam_ep0_ctrlread(priv);
/* Reset endpoint data structures */
for (epno = 0; epno < SAM_USB_NENDPOINTS; epno++)
{
struct sam_ep_s *privep = &priv->eplist[epno];
/* Cancel any queued requests. Since they are cancelled
* with status -ESHUTDOWN, then will not be requeued
* until the configuration is reset. NOTE: This should
* not be necessary... the CLASS_DISCONNECT above should
* result in the class implementation calling sam_ep_disable
* for each of its configured endpoints.
*/
sam_req_cancel(privep, -ESHUTDOWN);
/* Reset endpoint status */
privep->stalled = false;
privep->pending = false;
privep->halted = false;
privep->zlpsent = false;
privep->txbusy = false;
privep->rxactive = false;
}
/* Re-configure the USB controller in its initial, unconnected state */
priv->usbdev.speed = USB_SPEED_FULL;
priv->usbdev.dualspeed = 0;
/* Clear all pending interrupt status */
regval = USBDEV_INT_SUSPEND | USBDEV_INT_SOF | USBDEV_INT_EORST |
USBDEV_INT_WAKEUP | USBDEV_INT_EORSM | USBDEV_INT_UPRSM |
USBDEV_INT_RAMACER | USBDEV_INT_LPMNYET | USBDEV_INT_LPMSUSP;
sam_putreg16(regval, SAM_USBDEV_INTFLAG);
/* Enable normal operational interrupts
* endpoint 0 is enabled on sam_ep_configure_internal()
*/
regval = USBDEV_INT_SOF | USBDEV_INT_WAKEUP | USBDEV_INT_SUSPEND;
sam_putreg16(regval, SAM_USBDEV_INTENSET);
sam_dumpep(priv, EP0);
}
/****************************************************************************
* Interrupt Level Processing
****************************************************************************/
/****************************************************************************
* Name: sam_ep0_wrstatus
*
* Description:
* write ep0 status reply back to host
*
****************************************************************************/
static void sam_ep0_wrstatus(struct sam_usbdev_s *priv,
const uint8_t *buffer, size_t buflen)
{
uint32_t packetsize;
/* we need to make copy of data as source is in stack
* reusing the static ep0 setup buffer
*/
DEBUGASSERT(buflen < SAM_EP0_MAXPACKET);
memcpy(&priv->ep0out[0], buffer, buflen);
/* set read for next setup OUT */
sam_ep0_ctrlread(priv);
/* setup TX transfer */
priv->eplist[0].descb[1]->addr = (uint32_t) &priv->ep0out[0];
uinfo("addr=%p\n", &priv->ep0out[0]);
packetsize = priv->eplist[0].descb[1]->pktsize;
packetsize &= ~USBDEV_PKTSIZE_BCNT_MASK;
packetsize &= ~USBDEV_PKTSIZE_MPKTSIZE_MASK;
packetsize |= USBDEV_PKTSIZE_BCNT(buflen);
priv->eplist[0].descb[1]->pktsize = packetsize;
/* Set BK1RDY to notify the USB hardware that TX data is ready on
* descriptor bank1. We will be notified that the descriptor has been
* transmitted by the USB device when TRCPT1 in the endpoint's EPINTFLAG
* register has been set.
*/
sam_putreg8(USBDEV_EPSTATUS_BK1RDY, SAM_USBDEV_EPSTATUSSET(0));
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EP0WRSTATUS), buflen);
}
/****************************************************************************
* Name: sam_ep0_dispatch
****************************************************************************/
static void sam_ep0_dispatch(struct sam_usbdev_s *priv)
{
uint8_t *dataout;
size_t outlen;
int ret;
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_DISPATCH), 0);
if (priv && priv->driver)
{
/* Assume IN SETUP (or OUT SETUP with no data) */
dataout = NULL;
outlen = 0;
/* Was this an OUT SETUP command? */
if (USB_REQ_ISOUT(priv->ctrl.type))
{
uint16_t tmplen = GETUINT16(priv->ctrl.len);
if (tmplen > 0)
{
dataout = priv->ep0out;
outlen = tmplen;
}
}
/* Forward to the control request to the class driver implementation */
ret = CLASS_SETUP(priv->driver, &priv->usbdev, &priv->ctrl,
dataout, outlen);
if (ret < 0)
{
/* Stall on failure */
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_DISPATCHSTALL), 0);
sam_ep_stall(&priv->eplist[EP0]);
}
}
}
/****************************************************************************
* Name: sam_setdevaddr
*
* Description:
* This function is called after the completion of the STATUS phase to
* instantiate the device address that was received during the SETUP
* phase. This enters the ADDRESSED state from either the DEFAULT or the
* CONFIGURED states.
*
* If called with address == 0, then function will revert to the DEFAULT,
* un-configured and un-addressed state.
*
****************************************************************************/
static void sam_setdevaddr(struct sam_usbdev_s *priv, uint8_t address)
{
uinfo("ENTRY address=0x%x\n", address);
DEBUGASSERT(address <= 0x7f);
if (address)
{
/* Enable the address */
address |= USBDEV_DADD_ADDEN;
sam_putreg8(address, SAM_USBDEV_DADD);
/* Go to the addressed but not configured state */
priv->devstate = USB_DEVSTATE_ADDRESSED;
}
else
{
/* Set address to zero. clear ADDEN bit */
sam_putreg8(0x00, SAM_USBDEV_DADD);
/* Revert to the un-addressed, default state */
priv->devstate = USB_DEVSTATE_DEFAULT;
}
}
/****************************************************************************
* Name: sam_ep0_setup
*
* Description:
* This function is called after the receiving of the SETUP packet
* data is ready on usb_ctrlreq_s struct
*
****************************************************************************/
static void sam_ep0_setup(struct sam_usbdev_s *priv)
{
struct sam_ep_s *ep0 = &priv->eplist[EP0];
struct sam_ep_s *privep;
union wb_u value;
union wb_u index;
union wb_u len;
union wb_u response;
enum sam_ep0setup_e ep0result;
uint8_t epno;
int nbytes = 0; /* Assume zero-length packet */
int ret;
/* Terminate any pending requests */
sam_req_cancel(ep0, -EPROTO);
/* Assume NOT stalled; no TX in progress */
ep0->stalled = false;
ep0->pending = false;
ep0->epstate = USB_EPSTATE_IDLE;
/* And extract the little-endian 16-bit values to host order */
value.w = GETUINT16(priv->ctrl.value);
index.w = GETUINT16(priv->ctrl.index);
len.w = GETUINT16(priv->ctrl.len);
uinfo("SETUP: type=%02x req=%02x value=%04x index=%04x len=%04x\n",
priv->ctrl.type, priv->ctrl.req, value.w, index.w, len.w);
/* Dispatch any non-standard requests */
if ((priv->ctrl.type & USB_REQ_TYPE_MASK) != USB_REQ_TYPE_STANDARD)
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_NOSTDREQ), priv->ctrl.type);
/* Let the class implementation handle all non-standard requests */
sam_ep0_dispatch(priv);
return;
}
/* Handle standard request. Pick off the things of interest to the
* USB device controller driver; pass what is left to the class driver
*/
ep0result = USB_EP0SETUP_SUCCESS;
switch (priv->ctrl.req)
{
case USB_REQ_GETSTATUS:
{
/* type: device-to-host; recipient = device, interface, endpoint
* value: 0
* index: zero interface endpoint
* len: 2; data = status
*/
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_GETSTATUS), priv->ctrl.type);
if (len.w != 2 || (priv->ctrl.type & USB_REQ_DIR_IN) == 0 ||
index.b[MSB] != 0 || value.w != 0)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADEPGETSTATUS), 0);
ep0result = USB_EP0SETUP_STALL;
}
else
{
switch (priv->ctrl.type & USB_REQ_RECIPIENT_MASK)
{
case USB_REQ_RECIPIENT_ENDPOINT:
{
epno = USB_EPNO(index.b[LSB]);
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_GETSTATUS), epno);
if (epno >= SAM_USB_NENDPOINTS)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADEPGETSTATUS),
epno);
ep0result = USB_EP0SETUP_STALL;
}
else
{
privep = &priv->eplist[epno];
response.w = 0; /* Not stalled */
nbytes = 2; /* Response size: 2 bytes */
if (privep->stalled)
{
/* Endpoint stalled */
response.b[LSB] = 1; /* Stalled */
}
}
}
break;
case USB_REQ_RECIPIENT_DEVICE:
{
if (index.w == 0)
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_DEVGETSTATUS),
0);
/* Features: Remote Wakeup=YES; selfpowered=? */
response.w = 0;
response.b[LSB] = (priv->selfpowered <<
USB_FEATURE_SELFPOWERED) |
(1 << USB_FEATURE_REMOTEWAKEUP);
nbytes = 2; /* Response size: 2 bytes */
}
else
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADDEVGETSTATUS),
0);
ep0result = USB_EP0SETUP_STALL;
}
}
break;
case USB_REQ_RECIPIENT_INTERFACE:
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_IFGETSTATUS), 0);
response.w = 0;
nbytes = 2; /* Response size: 2 bytes */
}
break;
default:
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADGETSTATUS), 0);
ep0result = USB_EP0SETUP_STALL;
}
break;
}
}
}
break;
case USB_REQ_CLEARFEATURE:
{
/* type: host-to-device; recipient = device, interface or endpoint
* value: feature selector
* index: zero interface endpoint;
* len: zero, data = none
*/
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_CLEARFEATURE),
priv->ctrl.type);
if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) !=
USB_REQ_RECIPIENT_ENDPOINT)
{
/* Let the class implementation handle all
* recipients (except for the
* endpoint recipient)
*/
sam_ep0_dispatch(priv);
ep0result = USB_EP0SETUP_DISPATCHED;
}
else
{
/* Endpoint recipient */
epno = USB_EPNO(index.b[LSB]);
if (epno < SAM_USB_NENDPOINTS && index.b[MSB] == 0 &&
value.w == USB_FEATURE_ENDPOINTHALT && len.w == 0)
{
privep = &priv->eplist[epno];
privep->halted = false;
ret = sam_ep_resume(privep);
if (ret < 0)
{
ep0result = USB_EP0SETUP_STALL;
}
}
else
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADCLEARFEATURE), 0);
ep0result = USB_EP0SETUP_STALL;
}
}
}
break;
case USB_REQ_SETFEATURE:
{
/* type: host-to-device; recipient = device, interface, endpoint
* value: feature selector
* index: zero interface endpoint;
* len: 0; data = none
*/
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_SETFEATURE),
priv->ctrl.type);
if (((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) ==
USB_REQ_RECIPIENT_DEVICE) &&
value.w == USB_FEATURE_TESTMODE)
{
/* Special case recipient=device test mode */
uinfo("test mode: %d\n", index.w);
}
else if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) !=
USB_REQ_RECIPIENT_ENDPOINT)
{
/* The class driver handles all recipients
* except recipient=endpoint
*/
sam_ep0_dispatch(priv);
ep0result = USB_EP0SETUP_DISPATCHED;
}
else
{
/* Handler recipient=endpoint */
epno = USB_EPNO(index.b[LSB]);
if (epno < SAM_USB_NENDPOINTS && index.b[MSB] == 0 &&
value.w == USB_FEATURE_ENDPOINTHALT && len.w == 0)
{
privep = &priv->eplist[epno];
privep->halted = true;
ret = sam_ep_stall(privep);
if (ret < 0)
{
ep0result = USB_EP0SETUP_STALL;
}
}
else
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADSETFEATURE), 0);
ep0result = USB_EP0SETUP_STALL;
}
}
}
break;
case USB_REQ_SETADDRESS:
{
/* type: host-to-device; recipient = device
* value: device address
* index: 0
* len: 0; data = none
*/
if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) !=
USB_REQ_RECIPIENT_DEVICE ||
index.w != 0 || len.w != 0 || value.w > 127)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADSETADDRESS), 0);
ep0result = USB_EP0SETUP_STALL;
}
else
{
/* Note that setting of the device address will be deferred.
* A zero-length packet will be sent and the device address will
* be set when the zero-length packet transfer completes.
*/
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EP0SETUPSETADDRESS),
value.w);
priv->devaddr = value.w;
ep0result = USB_EP0SETUP_ADDRESS;
}
}
break;
case USB_REQ_GETDESCRIPTOR:
/* type: device-to-host; recipient = device
* value: descriptor type and index
* index: 0 or language ID;
* len: descriptor len; data = descriptor
*/
case USB_REQ_SETDESCRIPTOR:
/* type: host-to-device; recipient = device
* value: descriptor type and index
* index: 0 or language ID;
* len: descriptor len; data = descriptor
*/
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_GETSETDESC),
priv->ctrl.type);
if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) ==
USB_REQ_RECIPIENT_DEVICE)
{
/* The request seems valid... let the class implementation
* handle it.
*/
sam_ep0_dispatch(priv);
ep0result = USB_EP0SETUP_DISPATCHED;
}
else
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADGETSETDESC), 0);
ep0result = USB_EP0SETUP_STALL;
}
}
break;
case USB_REQ_GETCONFIGURATION:
/* type: device-to-host; recipient = device
* value: 0;
* index: 0;
* len: 1; data = configuration value
*/
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_GETCONFIG),
priv->ctrl.type);
if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) ==
USB_REQ_RECIPIENT_DEVICE &&
value.w == 0 && index.w == 0 && len.w == 1)
{
/* The request seems valid... let the class implementation
* handle it.
*/
sam_ep0_dispatch(priv);
ep0result = USB_EP0SETUP_DISPATCHED;
}
else
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADGETCONFIG), 0);
ep0result = USB_EP0SETUP_STALL;
}
}
break;
case USB_REQ_SETCONFIGURATION:
/* type: host-to-device; recipient = device
* value: configuration value
* index: 0;
* len: 0; data = none
*/
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_SETCONFIG),
priv->ctrl.type);
if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) ==
USB_REQ_RECIPIENT_DEVICE &&
index.w == 0 && len.w == 0)
{
/* The request seems valid... let the class implementation
* handle it. If the class implementation accepts it new
* configuration, it will call sam_ep_configure() to configure
* the endpoints.
*/
sam_ep0_dispatch(priv);
ep0result = USB_EP0SETUP_DISPATCHED;
}
else
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADSETCONFIG), 0);
ep0result = USB_EP0SETUP_STALL;
}
}
break;
case USB_REQ_GETINTERFACE:
/* type: device-to-host; recipient = interface
* value: 0
* index: interface;
* len: 1; data = alt interface
*/
case USB_REQ_SETINTERFACE:
/* type: host-to-device; recipient = interface
* value: alternate setting
* index: interface;
* len: 0; data = none
*/
{
/* Let the class implementation handle the request */
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_GETSETIF), priv->ctrl.type);
sam_ep0_dispatch(priv);
ep0result = USB_EP0SETUP_DISPATCHED;
}
break;
case USB_REQ_SYNCHFRAME:
/* type: device-to-host; recipient = endpoint
* value: 0
* index: endpoint;
* len: 2; data = frame number
*/
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_SYNCHFRAME), 0);
}
break;
default:
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDCTRLREQ),
priv->ctrl.req);
ep0result = USB_EP0SETUP_STALL;
}
break;
}
/* Restrict the data length to the length requested in the setup packet */
if (nbytes > len.w)
{
nbytes = len.w;
}
/* At this point, the request has been handled and there are three
* (or four) possible outcomes:
*
* 1a. ep0result == USB_EP0SETUP_SUCCESS
*
* The setup request was successfully handled above and a response
* packet must be sent (may be a zero length packet).
*
* 1b. ep0result == USB_EP0SETUP_ADDRESS
*
* A special case is the case where epstate=USB_EPSTATE_EP0ADDRESS.
* This means that the above processing generated an additional state
* where we need to wait until we complete the status phase before
* applying the new device address.
*
* 2. ep0result == USB_EP0SETUP_DISPATCHED;
*
* The request was forwarded to the class implementation. In case,
* EP0 IN data may have already been sent and the EP0 IN response
* has already been queued? Or perhaps the endpoint has already
* been stalled? This is all under the control of the class driver.
*
* NOTE that for the case of non-standard SETUP requested, those
* requests were forwarded to the class driver and we don't even get
* to this logic.
*
* 3. ep0result == USB_EP0SETUP_STALL;
*
* An error was detected in either the above logic or by the class
* implementation logic.
*/
switch (ep0result)
{
case USB_EP0SETUP_SUCCESS:
{
/* Send the response (might be a zero-length packet) */
ep0->epstate = USB_EPSTATE_EP0STATUSIN;
sam_ep0_wrstatus(priv, response.b, nbytes);
}
break;
case USB_EP0SETUP_ADDRESS:
{
/* Send the response (might be a zero-length packet) */
ep0->epstate = USB_EPSTATE_EP0ADDRESS;
sam_ep0_wrstatus(priv, response.b, nbytes);
}
break;
case USB_EP0SETUP_STALL:
{
/* Stall EP0 */
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_EP0SETUPSTALLED),
priv->ctrl.req);
sam_ep_stall(&priv->eplist[EP0]);
}
break;
case USB_EP0SETUP_DISPATCHED:
default:
break;
}
}
/****************************************************************************
* Name: sam_ep_trcpt_interrupt
*
* Description:
* Transmit completed on Bank 0/1
* Normal:
* OUT data transmit has been completed bank=0
* Ping-Pong:
* TODO:
*
****************************************************************************/
static void sam_ep_trcpt_interrupt(struct sam_usbdev_s *priv,
struct sam_ep_s *privep,
uint32_t flags, int bank)
{
uint32_t eptype;
uint16_t pktsize;
uint8_t epno;
/* Get the endpoint type */
epno = USB_EPNO(privep->ep.eplog);
eptype = sam_getreg8(SAM_USBDEV_EPCFG(epno)) & USBDEV_EPCFG_EPTYPE0_MASK;
/* Are we receiving data for a read request? EP0 does not receive data
* using read requests.
*/
/* Get the size of the packet that we just received */
pktsize = privep->descb[bank]->pktsize & USBDEV_PKTSIZE_BCNT_MASK;
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPTRCPT0_LEN), (uint16_t)pktsize);
if (privep->epstate == USB_EPSTATE_IDLE && epno != 0)
{
/* continue processing the read request. */
sam_req_read(priv, privep, pktsize);
}
/* Did we just receive the data associated with an OUT SETUP command? */
else if (privep->epstate == USB_EPSTATE_EP0DATAOUT)
{
uint16_t len;
DEBUGASSERT(epno == EP0 && bank == 0);
/* Yes.. back to the IDLE state */
privep->epstate = USB_EPSTATE_IDLE;
/* Get the size that we expected to receive */
len = GETUINT16(priv->ctrl.len);
if (len == pktsize)
{
/* And handle the EP0 SETUP now. */
sam_ep0_setup(priv);
}
else
{
/* Then stall. */
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_EP0SETUPOUTSIZE), pktsize);
sam_ep_stall(privep);
}
}
/* Check for a EP0 STATUS packet returned by the host at the end of a
* SETUP status phase
*/
else if ((eptype == USBDEV_EPCCFG_EPTYPE0_CTRLOUT) && pktsize == 0)
{
DEBUGASSERT(epno == EP0 && bank == 0);
}
/* Otherwise there is a problem. Complain an clear the interrupt */
else
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_RXDATABKERR), privep->epstate);
}
}
/****************************************************************************
* Name: sam_ep0_ctrlread
*
* Description:
* setup 8-byte read req for ep0-ctrl setup phase.
* data is received on priv->ep0out buffer. This is notified by
* endpoint TRCPT0 interrupt
*
****************************************************************************/
static void sam_ep0_ctrlread(struct sam_usbdev_s *priv)
{
priv->eplist[0].descb[0]->addr = (uint32_t) &priv->ep0out[0];
uinfo("addr=%p\n", &priv->ep0out[0]);
priv->eplist[0].descb[0]->pktsize = USBDEV_PKTSIZE_MPKTSIZE(8) |
USBDEV_PKTSIZE_SIZE_64B;
sam_putreg8(USBDEV_EPSTATUS_BK0RDY, SAM_USBDEV_EPSTATUSCLR(0));
}
/****************************************************************************
* Name: sam_ep_interrupt
*
* Description:
* Handle the USB endpoint interrupt
*
****************************************************************************/
static void sam_ep_interrupt(struct sam_usbdev_s *priv, int epno)
{
struct sam_ep_s *privep;
uint16_t flags;
DEBUGASSERT((unsigned)epno < SAM_USB_NENDPOINTS);
/* Get the endpoint structure */
privep = &priv->eplist[epno];
/* Get the endpoint irq */
flags = sam_getreg8(SAM_USBDEV_EPINTFLAG(epno));
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPINTFLAGS), flags);
/* TRCPT1: IN packet sent and acknowledged by the host */
if ((flags & USBDEV_EPINT_TRCPT1) != 0)
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPTRCPT1), flags);
/* Clear the TRCPT1 interrupt */
sam_putreg8(USBDEV_EPINT_TRCPT1, SAM_USBDEV_EPINTFLAG(epno));
/* Sending state. This is the completion of a "normal" write request
* transfer. In this case, we need to resume request processing in
* order to send the next outgoing packet.
*/
if (privep->epstate == USB_EPSTATE_SENDING ||
privep->epstate == USB_EPSTATE_EP0STATUSIN)
{
/* Continue/resume processing the write requests */
privep->epstate = USB_EPSTATE_IDLE;
sam_req_write(priv, privep);
}
/* Setting of the device address is a special case. The address was
* obtained when a preceding SETADDRESS SETUP command was processed.
* But the address is not set until the final SETUP status phase
* completes. This interrupt indicates the completion of that status
* phase and now we set the address.
*/
else if (privep->epstate == USB_EPSTATE_EP0ADDRESS)
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_ADDRESSED), priv->devaddr);
DEBUGASSERT(epno == EP0);
/* Set the device address */
privep->epstate = USB_EPSTATE_IDLE;
sam_setdevaddr(priv, priv->devaddr);
}
else
{
/* Unexpected TRCPT1 interrupt */
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_TXCOMPERR), privep->epstate);
}
}
/* OUT packet received */
if ((flags & USBDEV_EPINT_TRCPT0) != 0)
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPTRCPT0), flags);
/* Clear the TRCPT0 interrupt. */
sam_putreg8(USBDEV_EPINT_TRCPT0, SAM_USBDEV_EPINTFLAG(epno));
sam_ep_trcpt_interrupt(priv, privep, flags, 0);
}
/* Endpoint stall */
if ((flags & USBDEV_EPINT_STALL0) != 0)
{
sam_putreg8(USBDEV_EPINT_STALL0, SAM_USBDEV_EPINTFLAG(epno));
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPSTALL0), flags);
/* If EP is not halted, clear STALL */
if (privep->epstate != USB_EPSTATE_STALLED)
{
sam_putreg8(USBDEV_EPSTATUS_STALLRQ0,
SAM_USBDEV_EPSTATUSCLR(epno));
}
}
if ((flags & USBDEV_EPINT_STALL1) != 0)
{
sam_putreg8(USBDEV_EPINT_STALL1, SAM_USBDEV_EPINTFLAG(epno));
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPSTALL1), flags);
/* If EP is not halted, clear STALL */
if (privep->epstate != USB_EPSTATE_STALLED)
{
sam_putreg8(USBDEV_EPSTATUS_STALLRQ1,
SAM_USBDEV_EPSTATUSCLR(epno));
}
}
/* Transmit FAIL! */
if ((flags & USBDEV_EPINT_TRFAIL0) != 0)
{
sam_putreg8(USBDEV_EPINT_TRFAIL0, SAM_USBDEV_EPINTFLAG(epno));
privep->descb[0]->stausbk &= ~USBDEV_STATUSBK_ERRORFLOW;
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPTRFAIL0), flags);
}
if ((flags & USBDEV_EPINT_TRFAIL1) != 0)
{
sam_putreg8(USBDEV_EPINT_TRFAIL1, SAM_USBDEV_EPINTFLAG(epno));
privep->descb[1]->stausbk &= ~USBDEV_STATUSBK_ERRORFLOW;
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPTRFAIL1), flags);
}
/* SETUP packet received */
if ((flags & USBDEV_EPINT_RXSTP) != 0)
{
uint16_t len;
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPRXSTP), flags);
/* If a write request transfer was pending, complete it. */
if (privep->epstate == USB_EPSTATE_SENDING)
{
sam_req_complete(privep, -EPROTO);
}
/* SETUP data is ready in the ep0out buffer. */
memcpy((uint8_t *)&priv->ctrl, (uint8_t *)&priv->ep0out[0],
USB_SIZEOF_CTRLREQ);
/* Check for a SETUP IN transaction with data. */
len = GETUINT16(priv->ctrl.len);
if (USB_REQ_ISOUT(priv->ctrl.type) && len > 0)
{
/* Yes.. then we have to wait for the OUT data phase to complete
* before processing the SETUP command.
*/
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EP0SETUPOUT),
priv->ctrl.req);
privep->epstate = USB_EPSTATE_EP0DATAOUT;
/* Clear the RXSTP indication. */
sam_putreg8(USBDEV_EPINT_RXSTP, SAM_USBDEV_EPINTFLAG(epno));
}
else
{
/* This is an SETUP IN command (or a SETUP IN with no data). */
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EP0SETUPIN), len);
privep->epstate = USB_EPSTATE_IDLE;
/* Clear the RXSTP indication. */
sam_putreg8(USBDEV_EPINT_RXSTP, SAM_USBDEV_EPINTFLAG(epno));
/* Handle the SETUP OUT command now */
sam_ep0_setup(priv);
}
/* ready for next setup data */
sam_ep0_ctrlread(priv);
}
}
/****************************************************************************
* Name: sam_usb_interrupt
*
* Description:
* Handle the USB interrupt.
* Device Mode only
*
****************************************************************************/
static int sam_usb_interrupt(int irq, void *context, void *arg)
{
struct sam_usbdev_s *priv = (struct sam_usbdev_s *)arg;
uint16_t isr;
uint16_t pending;
uint16_t regval;
uint16_t pendingep;
int i;
/* Get the set of pending device interrupts */
isr = sam_getreg16(SAM_USBDEV_INTFLAG);
regval = sam_getreg16(SAM_USBDEV_INTENSET);
pending = isr & regval;
/* Get the set of pending endpoint interrupts */
pendingep = sam_getreg16(SAM_USBDEV_EPINTSMRY);
/* Handle all pending USB interrupts */
/* Serve Endpoint Interrupts first */
if (pendingep)
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_PENDING_EP), pendingep);
for (i = 0; i < SAM_USB_NENDPOINTS; i++)
{
if ((pendingep & USBDEV_EPINTSMRY_EPINT(i)))
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPNO), (uint16_t)i);
sam_ep_interrupt(priv, i);
}
}
}
/* Suspend, treated last */
if (pending == USBDEV_INT_SUSPEND)
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_SUSPEND), pending);
/* Enable wakeup interrupts */
sam_putreg16(USBDEV_INT_SUSPEND, SAM_USBDEV_INTENCLR);
sam_putreg16(USBDEV_INT_UPRSM | USBDEV_INT_WAKEUP | USBDEV_INT_EORSM,
SAM_USBDEV_INTENSET);
/* Clear the pending suspend (and any wakeup) interrupts */
sam_putreg16(USBDEV_INT_SUSPEND | USBDEV_INT_WAKEUP,
SAM_USBDEV_INTFLAG);
/* Perform board-specific suspend operations.
*
* The USB device peripheral clocks can be switched off.
* Resume event is asynchronously detected. MCK and USBCK can be
* switched off in the Power Management controller and
* Other board-specific operations could also be performed.
*/
}
/* SOF interrupt */
if ((pending & USBDEV_INT_SOF) != 0)
{
/* Clear the pending SOF interrupt */
sam_putreg16(USBDEV_INT_SOF, SAM_USBDEV_INTFLAG);
/* TODO: do we need check frame errors FNUM.FNCERR */
}
/* Resume or wakeup. REVISIT: Treat the same? */
if ((pending & (USBDEV_INT_WAKEUP | USBDEV_INT_EORSM)) != 0)
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_WAKEUP), (uint16_t)pending);
sam_resume(priv);
/* Clear the pending wakeup, resume, (and any suspend) interrupts */
sam_putreg16(USBDEV_INT_WAKEUP | USBDEV_INT_EORSM |
USBDEV_INT_SUSPEND, SAM_USBDEV_INTFLAG);
/* Disable wakeup and endofresume. Enable suspend interrupt */
sam_putreg16(USBDEV_INT_WAKEUP |
USBDEV_INT_EORSM, SAM_USBDEV_INTENCLR);
sam_putreg16(USBDEV_INT_SUSPEND, SAM_USBDEV_INTENSET);
}
/* End of Reset. Set by hardware when an End Of Reset has been
* detected by the USB controller.
*/
if ((pending & USBDEV_INT_EORST) != 0)
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EORST), pending);
/* Clear the end-of-reset interrupt */
sam_putreg16(USBDEV_INT_EORST, SAM_USBDEV_INTFLAG);
/* Handle the reset */
sam_reset(priv);
/* REVISIT: Set the device speed Why here ?? */
priv->usbdev.speed = USB_SPEED_FULL;
priv->usbdev.dualspeed = 0;
}
return OK;
}
/****************************************************************************
* Endpoint Helpers
****************************************************************************/
/****************************************************************************
* Name: sam_ep_reset
*
* Description:
* Reset and disable one endpoints.
*
****************************************************************************/
static void sam_ep_reset(struct sam_usbdev_s *priv, uint8_t epno)
{
struct sam_ep_s *privep = &priv->eplist[epno];
/* Disable endpoint interrupts */
sam_putreg8(0x7f, SAM_USBDEV_EPINTENCLR(epno));
sam_putreg8(0x7f, SAM_USBDEV_EPINTFLAG(epno));
/* Cancel any queued requests. Since they are cancelled with status
* -ESHUTDOWN, then will not be requeued until the configuration is reset.
* NOTE: This should not be necessary... the CLASS_DISCONNECT above
* should result in the class implementation calling sam_ep_disable
* for each of its configured endpoints.
*/
sam_req_cancel(privep, -ESHUTDOWN);
/* Reset endpoint status */
privep->epstate = USB_EPSTATE_DISABLED;
privep->stalled = false;
privep->pending = false;
privep->halted = false;
privep->zlpsent = false;
privep->txbusy = false;
privep->rxactive = false;
}
/****************************************************************************
* Name: sam_epset_reset
*
* Description:
* Reset and disable a set of endpoints.
*
****************************************************************************/
static void sam_epset_reset(struct sam_usbdev_s *priv, uint16_t epset)
{
uint32_t bit;
int epno;
/* Reset each endpoint in the set */
for (epno = 0, bit = 1, epset &= SAM_EPSET_ALL;
epno < SAM_USB_NENDPOINTS && epset != 0;
epno++, bit <<= 1)
{
/* Is this endpoint in the set? */
if ((epset & bit) != 0)
{
/* Yes.. reset and disable it */
sam_ep_reset(priv, epno);
epset &= ~bit;
}
}
}
/****************************************************************************
* Name: sam_ep_stall
****************************************************************************/
static int sam_ep_stall(struct sam_ep_s *privep)
{
irqstate_t flags;
uint8_t epno;
/* Check that endpoint is in Idle state */
DEBUGASSERT(/* privep->epstate == UDP_EPSTATE_IDLE && */ privep->dev);
/* Check that endpoint is enabled and not already in Halt state */
flags = enter_critical_section();
if ((privep->epstate != USB_EPSTATE_DISABLED) &&
(privep->epstate != USB_EPSTATE_STALLED))
{
epno = USB_EPNO(privep->ep.eplog);
usbtrace(TRACE_EPSTALL, epno);
/* If this is an IN endpoint (or endpoint 0), then cancel any
* write requests in progress.
*/
if (epno == 0 || USB_ISEPIN(privep->ep.eplog))
{
sam_req_cancel(privep, -EPERM);
}
/* Put endpoint into stalled state */
privep->epstate = USB_EPSTATE_STALLED;
privep->stalled = true;
privep->pending = false;
sam_putreg8(USBDEV_EPSTATUS_STALLRQ0 | USBDEV_EPSTATUS_STALLRQ1,
SAM_USBDEV_EPSTATUSSET(epno));
}
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Name: sam_ep_resume
****************************************************************************/
static int sam_ep_resume(struct sam_ep_s *privep)
{
struct sam_usbdev_s *priv;
struct sam_req_s *req;
irqstate_t flags;
uint8_t epno;
/* Check that endpoint is in Idle state */
DEBUGASSERT(/* privep->epstate == UDP_EPSTATE_IDLE && */ privep->dev);
flags = enter_critical_section();
/* Check if the endpoint is stalled */
if (privep->epstate == USB_EPSTATE_STALLED)
{
epno = USB_EPNO(privep->ep.eplog);
usbtrace(TRACE_EPRESUME, epno);
priv = (struct sam_usbdev_s *)privep->dev;
/* Return endpoint to Idle state */
privep->stalled = false;
privep->pending = false;
privep->epstate = USB_EPSTATE_IDLE;
/* Clear STALLRQx request and reset data toggle */
if (USB_ISEPIN(privep->ep.eplog))
{
sam_putreg8(USBDEV_EPSTATUS_STALLRQ1,
SAM_USBDEV_EPSTATUSCLR(epno));
sam_putreg8(USBDEV_EPSTATUS_DTGLIN,
SAM_USBDEV_EPSTATUSCLR(epno));
sam_putreg8(USBDEV_EPINT_STALL1,
SAM_USBDEV_EPINTFLAG(epno));
}
else
{
sam_putreg8(USBDEV_EPSTATUS_STALLRQ0,
SAM_USBDEV_EPSTATUSCLR(epno));
sam_putreg8(USBDEV_EPSTATUS_DTGLOUT,
SAM_USBDEV_EPSTATUSCLR(epno));
sam_putreg8(USBDEV_EPINT_STALL0,
SAM_USBDEV_EPINTFLAG(epno));
}
/* Copy any requests in the pending request queue to the working
* request queue.
*/
while ((req = sam_req_dequeue(&privep->pendq)) != NULL)
{
sam_req_enqueue(&privep->reqq, req);
}
/* Resuming any blocked data transfers on the endpoint */
if (epno == 0 || USB_ISEPIN(privep->ep.eplog))
{
/* IN endpoint (or EP0). Restart any queued write requests */
sam_req_write(priv, privep);
}
}
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Name: sam_pullup
*
* Description:
* This is the pullup() method of the USB device driver interface
*
****************************************************************************/
static int sam_pullup(struct usbdev_s *dev, bool enable)
{
struct sam_usbdev_s *priv = (struct sam_usbdev_s *)dev;
uint32_t regval;
usbtrace(TRACE_DEVPULLUP, (uint16_t)enable);
/* Enable/disable the USB pull-up resistor */
regval = sam_getreg16(SAM_USBDEV_CTRLB);
if (enable)
{
/* Connect the 1.5 KOhm integrated pull-up on USB */
regval &= ~USBDEV_CTRLB_DETACH;
}
else
{
/* Disconnect the 1.5 KOhm integrated pull-up on USB */
regval |= USBDEV_CTRLB_DETACH;
/* Device returns to the Powered state */
if (priv->devstate > USB_DEVSTATE_POWERED)
{
priv->devstate = USB_DEVSTATE_POWERED;
}
}
sam_putreg16((uint16_t)regval, SAM_USBDEV_CTRLB);
return OK;
}
/****************************************************************************
* Initialization/Reset
****************************************************************************/
/****************************************************************************
* Name: sam_hw_setup
****************************************************************************/
static void sam_hw_setup(struct sam_usbdev_s *priv)
{
int i;
uint16_t regval;
uint32_t padcalib;
uint8_t calib_transn;
uint8_t calib_transp;
uint8_t calib_trim;
/* Set up the USB DP/DM pins */
sam_portconfig(PORT_USB_DP);
sam_portconfig(PORT_USB_DM);
/* To use the USB, the programmer must first configure the USB clock
* input,
*/
sam_enableclks();
/* full reset USB */
sam_ctrla_write(USB_CTRLA_SWRST);
while (sam_getreg32(SAM_USB_SYNCBUSY) == USB_SYNCBUSY_SWRST);
/* Enable USB core */
sam_ctrla_write(USB_CTRLA_ENABLE | USB_CTRLA_RUNSTBY |
USB_CTRLA_MODE_DEVICE);
while (sam_getreg32(SAM_USB_SYNCBUSY) == USB_SYNCBUSY_ENABLE);
/* Load USB factory calibration values from NVRAM */
calib_transn = (getreg32(SAM_FUSES_USBTRANSN_ADDR) &
SAM_FUSES_USBTRANSN_MASK) >> SAM_FUSES_USBTRANSN_SHIFT;
if (calib_transn == 0 || calib_transn == 0x1f)
calib_transn = 0x9;
calib_transp = (getreg32(SAM_FUSES_USBTRANSP_ADDR) &
SAM_FUSES_USBTRANSP_ADDR) >> SAM_FUSES_USBTRANSP_SHIFT;
if (calib_transp == 0 || calib_transp == 0x1f)
calib_transp = 0x19;
calib_trim = (getreg32(SAM_FUSES_USBTRIM_ADDR) &
SAM_FUSES_USBTRIM_MASK) >> SAM_FUSES_USBTRIM_SHIFT;
if (calib_trim == 0 || calib_trim == 0x7)
calib_trim = 0x6;
padcalib = USB_PADCAL_TRANSP(calib_transp) |
USB_PADCAL_TRANSN(calib_transn) |
USB_PADCAL_TRIM(calib_trim);
sam_putreg16(padcalib, SAM_USB_PADCAL);
uinfo("PADCAL: 0x%x\n", padcalib);
/* set config
* NREPLY = Any transaction to endpoint 0 will be ignored except SETUP
* cleared by hardware when receiving a SETUP packet.
* DETACH = The internal device pull-ups are disabled
*/
regval = USBDEV_CTRLB_NREPLY | USBDEV_CTRLB_DETACH;
/* do we need config to set LOW_SPEED mode? */
regval |= USBDEV_CTRLB_SPDCONF_FULL;
sam_putreg16(regval, SAM_USBDEV_CTRLB);
/* Reset and disable endpoints */
sam_epset_reset(priv, SAM_EPSET_ALL);
/* Initialize Endpoints */
for (i = 0; i < SAM_USB_NENDPOINTS; i++)
{
/* Reset endpoint configuration */
sam_putreg8(0, SAM_USBDEV_EPCFG(i));
}
/* Init descriptor base address */
sam_putreg32((uint32_t)&priv->ep_descriptors, SAM_USB_DESCADD);
/* clear all previous descriptor data so no accidental
* DMA transfers could happen.
*/
memset((uint8_t *)(&priv->ep_descriptors[0]), 0,
sizeof(priv->ep_descriptors));
/* Disable all interrupts */
sam_putreg16(USBDEV_INT_SUSPEND | USBDEV_INT_SOF | USBDEV_INT_EORST |
USBDEV_INT_WAKEUP | USBDEV_INT_EORSM | USBDEV_INT_UPRSM |
USBDEV_INT_RAMACER | USBDEV_INT_LPMNYET | USBDEV_INT_LPMSUSP,
SAM_USBDEV_INTENCLR);
sam_pullup(&priv->usbdev, false);
}
/****************************************************************************
* Name: sam_hw_shutdown
****************************************************************************/
static void sam_hw_shutdown(struct sam_usbdev_s *priv)
{
priv->usbdev.speed = USB_SPEED_UNKNOWN;
/* Disable all interrupts */
sam_putreg16(USBDEV_INT_SUSPEND | USBDEV_INT_SOF | USBDEV_INT_EORST |
USBDEV_INT_WAKEUP | USBDEV_INT_EORSM | USBDEV_INT_UPRSM |
USBDEV_INT_RAMACER | USBDEV_INT_LPMNYET | USBDEV_INT_LPMSUSP,
SAM_USBDEV_INTENCLR);
/* Clear all pending interrupt status */
sam_putreg16(USBDEV_INT_SUSPEND | USBDEV_INT_SOF | USBDEV_INT_EORST |
USBDEV_INT_WAKEUP | USBDEV_INT_EORSM | USBDEV_INT_UPRSM |
USBDEV_INT_RAMACER | USBDEV_INT_LPMNYET | USBDEV_INT_LPMSUSP,
SAM_USBDEV_INTFLAG);
/* Disconnect the device / disable the pull-up */
sam_pullup(&priv->usbdev, false);
/* Disable clocking to the UDP peripheral */
sam_disableclks();
}
/****************************************************************************
* Name: sam_sw_setup
****************************************************************************/
static void sam_sw_setup(struct sam_usbdev_s *priv)
{
int epno;
/* Initialize the device state structure. NOTE: many fields
* have the initial value of zero and, hence, are not explicitly
* initialized here.
*/
memset(priv, 0, sizeof(struct sam_usbdev_s));
priv->usbdev.ops = &g_devops;
priv->usbdev.ep0 = &priv->eplist[EP0].ep;
priv->epavail = SAM_EPSET_ALL & ~SAM_EP_BIT(EP0);
priv->devstate = USB_DEVSTATE_SUSPENDED;
priv->prevstate = USB_DEVSTATE_POWERED;
/* Initialize the endpoint list */
for (epno = 0; epno < SAM_USB_NENDPOINTS; epno++)
{
/* Set endpoint operations, reference to driver structure (not
* really necessary because there is only one controller), and
* the (physical) endpoint number which is just the index to the
* endpoint.
*/
priv->eplist[epno].ep.ops = &g_epops;
priv->eplist[epno].dev = priv;
priv->eplist[epno].ep.eplog = epno;
/* We will use a maxpacket size for supported for each endpoint */
priv->eplist[epno].ep.maxpacket = SAM_USB_MAXPACKETSIZE(epno);
/* set descriptor addresses */
priv->eplist[epno].descb[0] = &priv->ep_descriptors[(epno << 1)];
priv->eplist[epno].descb[1] = &priv->ep_descriptors[(epno << 1) + 1];
}
/* Select a smaller endpoint size for EP0 */
#if SAM_EP0_MAXPACKET < 64
priv->eplist[EP0].ep.maxpacket = SAM_EP0_MAXPACKET;
#endif
}
/****************************************************************************
* Name: sam_sw_shutdown
****************************************************************************/
static void sam_sw_shutdown(struct sam_usbdev_s *priv)
{
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: usbdev_register
*
* Description:
* Register a USB device class driver. The class driver's bind() method
* will be called to bind it to a USB device driver.
*
****************************************************************************/
int usbdev_register(struct usbdevclass_driver_s *driver)
{
/* For now there is only one USB controller, but we will always refer to
* it using a pointer to make any future ports to multiple USB controllers
* easier.
*/
struct sam_usbdev_s *priv = &g_usbd;
int ret;
uinfo("driver: 0x%x\n", driver);
usbtrace(TRACE_DEVREGISTER, 0);
#ifdef CONFIG_DEBUG_USB
if (!driver || !driver->ops->bind || !driver->ops->unbind ||
!driver->ops->disconnect || !driver->ops->setup)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0);
return -EINVAL;
}
if (priv->driver)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_DRIVER), 0);
return -EBUSY;
}
#endif
/* First hook up the driver */
priv->driver = driver;
/* Then bind the class driver */
ret = CLASS_BIND(driver, &priv->usbdev);
if (ret)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BINDFAILED), (uint16_t)-ret);
priv->driver = NULL;
}
else
{
/* Enable USB controller interrupts at the NVIC. */
sam_hw_setup(priv);
up_enable_irq(SAM_IRQ_USB);
up_enable_irq(SAM_IRQ_USBSOF);
up_enable_irq(SAM_IRQ_USBTRCPT0);
up_enable_irq(SAM_IRQ_USBTRCPT1);
/* Enable EORST irq */
sam_putreg16(USBDEV_INT_EORST, SAM_USBDEV_INTENSET);
/* Enable pull-up to connect the device. The host should enumerate us
* some time after this. The next thing we expect is the EORST
* interrupt.
*/
sam_pullup(&priv->usbdev, true);
priv->usbdev.speed = USB_SPEED_FULL;
priv->usbdev.dualspeed = 0;
}
return ret;
}
/****************************************************************************
* Name: usbdev_unregister
*
* Description:
* Un-register usbdev class driver. If the USB device is connected to a
* USB host, it will first disconnect(). The driver is also requested to
* unbind() and clean up any device state, before this procedure finally
* returns.
*
****************************************************************************/
int usbdev_unregister(struct usbdevclass_driver_s *driver)
{
/* For now there is only one USB controller, but we will always refer to
* it using a pointer to make any future ports to multiple USB controllers
* easier.
*/
struct sam_usbdev_s *priv = &g_usbd;
irqstate_t flags;
usbtrace(TRACE_DEVUNREGISTER, 0);
#ifdef CONFIG_DEBUG_USB
if (driver != priv->driver)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0);
return -EINVAL;
}
#endif
/* Reset the hardware and cancel all requests. All requests must be
* canceled while the class driver is still bound.
*/
flags = enter_critical_section();
/* Unbind the class driver */
CLASS_UNBIND(driver, &priv->usbdev);
/* Disable USB controller interrupts (but keep them attached) */
up_disable_irq(SAM_IRQ_USB);
up_disable_irq(SAM_IRQ_USBSOF);
up_disable_irq(SAM_IRQ_USBTRCPT0);
up_disable_irq(SAM_IRQ_USBTRCPT1);
/* Put the hardware in an inactive state. Then bring the hardware back up
* in the initial state. This is essentially the same state as we were
* in when arm_usbinitialize() was first called.
*/
sam_hw_shutdown(priv);
sam_sw_shutdown(priv);
sam_sw_setup(priv);
sam_hw_setup(priv);
/* Unhook the driver */
priv->driver = NULL;
leave_critical_section(flags);
return OK;
}
void sam_usb_suspend(struct usbdev_s *dev, bool resume)
{
}
void arm_usbuninitialize(void)
{
uinfo("arm_usbuninitialize()\n");
}
void arm_usbinitialize(void)
{
/* For now there is only one USB controller, but we will always refer to
* it using a pointer to make any future ports to multiple USB controllers
* easier.
*/
struct sam_usbdev_s *priv = &g_usbd;
uinfo("INIT\n");
usbtrace(TRACE_DEVINIT, 0);
/* Software initialization */
sam_sw_setup(priv);
/* Power up and initialize USB controller. Interrupt from the USB
* controller is initialized here, but will not be enabled at the AIC
* until the class driver is installed.
*/
sam_hw_setup(priv);
/* Attach USB controller interrupt handlers. The hardware will not be
* initialized and interrupts will not be enabled until the class device
* driver is bound. Getting the IRQs here only makes sure that we have
* them when we need them later.
*/
if (irq_attach(SAM_IRQ_USB, sam_usb_interrupt, priv) != 0)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_IRQREGISTRATION),
(uint16_t)SAM_IRQ_USB);
goto errout;
}
if (irq_attach(SAM_IRQ_USBSOF, sam_usb_interrupt, priv) != 0)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_IRQREGISTRATION),
(uint16_t)SAM_IRQ_USBSOF);
goto errout;
}
if (irq_attach(SAM_IRQ_USBTRCPT0, sam_usb_interrupt, priv) != 0)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_IRQREGISTRATION),
(uint16_t)SAM_IRQ_USBTRCPT0);
goto errout;
}
if (irq_attach(SAM_IRQ_USBTRCPT1, sam_usb_interrupt, priv) != 0)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_IRQREGISTRATION),
(uint16_t)SAM_IRQ_USBTRCPT1);
goto errout;
}
return;
errout:
arm_usbuninitialize();
}
#endif /* CONFIG_USBDEV */
#ifdef CONFIG_USBHOST
/****************************************************************************
* Name: sam_getle16
*
* Description:
* Get a (possibly unaligned) 16-bit little endian value.
*
****************************************************************************/
static inline uint16_t sam_getle16(const uint8_t *val)
{
return (uint16_t)val[1] << 8 | (uint16_t)val[0];
}
/****************************************************************************
* Name: sam_add_sof_user
*
* Description:
* Add one SOF IRQ user and enable SOF interrupt
*
****************************************************************************/
static void sam_add_sof_user(struct sam_usbhost_s *priv)
{
priv->n_sof_user++;
sam_putreg16(USBHOST_INT_HSOF, SAM_USBHOST_INTENSET);
}
/****************************************************************************
* Name: sam_pipe_alloc
*
* Description:
* Allocate a pipe.
*
****************************************************************************/
static int sam_pipe_alloc(struct sam_usbhost_s *priv)
{
int idx;
/* Search the table of pipes */
for (idx = 0; idx < SAM_USB_NENDPOINTS; idx++)
{
/* Is this pipe available? */
if (!priv->pipelist[idx].inuse)
{
/* Yes... make it "in use" and return the index */
priv->pipelist[idx].inuse = true;
return idx;
}
}
/* All of the pipes are "in-use" */
return -EBUSY;
}
/****************************************************************************
* Name: sam_pipe_free
*
* Description:
* Free a previoiusly allocated pipe.
*
****************************************************************************/
static void sam_pipe_free(struct sam_usbhost_s *priv, int idx)
{
struct sam_pipe_s *pipe = &priv->pipelist[idx];
uinfo("pipe%d\n", idx);
DEBUGASSERT((unsigned)idx < SAM_USB_NENDPOINTS);
/* Halt the pipe */
sam_putreg8(0, SAM_USBHOST_PCFG(pipe->idx));
/* Mark the pipe available */
priv->pipelist[idx].inuse = false;
}
/* Look up table PSIZE -> size of bytes */
static const uint16_t psize_2_size[] =
{
8,
16,
32,
64,
128,
256,
512,
1024
};
/****************************************************************************
* Name: sam_get_psize
*
* Description:
* Convert bank size of bytes to PIPCFG.PSIZE -> size Size of bytes
*
****************************************************************************/
int8_t sam_get_psize(uint16_t size)
{
uint8_t i;
for (i = 0; i < sizeof(psize_2_size) / sizeof(uint16_t); i++)
{
/* Size should be exactly PSIZE values */
if (size <= psize_2_size[i])
return i;
}
return 7;
}
/****************************************************************************
* Name: sam_pipe_configure
*
* Description:
* Configure or re-configure a host pipe. Host pipes are configured
* when pipe is allocated and EP0 (only) is re-configured with the
* max packet size or device address changes.
*
****************************************************************************/
static void sam_pipe_configure(struct sam_usbhost_s *priv, int idx)
{
struct sam_pipe_s *pipe = &priv->pipelist[idx];
/* Clear any old pending interrupts for this host pipe. */
sam_putreg8(0x3f, SAM_USBHOST_PINTFLAG(pipe->idx));
/* Enable pipe interrupts required for transfers on this pipe. */
switch (pipe->eptype)
{
case USB_EP_ATTR_XFER_CONTROL:
case USB_EP_ATTR_XFER_BULK:
{
#ifdef HAVE_USBHOST_TRACE_VERBOSE
uint16_t intrace;
uint16_t outtrace;
/* Determine the definitive trace ID to use below */
if (pipe->eptype == USB_EP_ATTR_XFER_CONTROL)
{
intrace = SAM_VTRACE2_PIPECONF_CTRL_IN;
outtrace = SAM_VTRACE2_PIPECONF_CTRL_OUT;
}
else
{
intrace = SAM_VTRACE2_PIPECONF_BULK_IN;
outtrace = SAM_VTRACE2_PIPECONF_BULK_OUT;
}
/* Interrupts required for CTRL and BULK endpoints */
/* Additional setting for IN/OUT endpoints */
if (pipe->in)
{
usbhost_vtrace2(intrace, idx, pipe->epno);
}
else
{
usbhost_vtrace2(outtrace, idx, pipe->epno);
}
#endif
}
break;
case USB_EP_ATTR_XFER_INT:
{
/* Interrupts required for INTR endpoints */
/* Additional setting for IN endpoints */
#ifdef HAVE_USBHOST_TRACE_VERBOSE
if (pipe->in)
{
usbhost_vtrace2(SAM_VTRACE2_PIPECONF_INTR_IN, idx, pipe->epno);
}
else
{
usbhost_vtrace2(SAM_VTRACE2_PIPECONF_INTR_OUT, idx, pipe->epno);
}
#endif
}
break;
case USB_EP_ATTR_XFER_ISOC:
{
/* Interrupts required for ISOC endpoints */
/* Additional setting for IN endpoints */
#ifdef HAVE_USBHOST_TRACE_VERBOSE
if (pipe->in)
{
usbhost_vtrace2(SAM_VTRACE2_PIPECONF_ISOC_IN, idx, pipe->epno);
}
else
{
usbhost_vtrace2(SAM_VTRACE2_PIPECONF_ISOC_OUT, idx, pipe->epno);
}
#endif
}
break;
}
/* Write the pipe configuration */
pipe->descb[0]->ctrlpipe = USBHOST_CTRLPIPE_PDADDR(pipe->funcaddr) |
USBHOST_CTRLPIPE_PEPNUM(pipe->epno & USB_EPNO_MASK);
pipe->descb[0]->pktsize = USBHOST_PKTSIZE_SIZE(
sam_get_psize(pipe->maxpacket));
sam_putreg8(USBHOST_PCFG_PTYPE(pipe->eptype + 1) |
(pipe->eptype == USB_EP_ATTR_XFER_CONTROL ?
USBHOST_PCFG_PTOKEN_SETUP :
(pipe->in ? USBHOST_PCFG_PTOKEN_IN :
USBHOST_PCFG_PTOKEN_OUT)),
SAM_USBHOST_PCFG(pipe->idx));
sam_putreg8(pipe->interval, SAM_USBHOST_BINTERVAL(pipe->idx));
/* Enable general error and stall interrupts */
pipe->pipestatus_general = 0;
sam_putreg8((USBHOST_PINTFLAG_TRFAIL |
USBHOST_PINTFLAG_PERR |
USBHOST_PINTFLAG_STALL), SAM_USBHOST_PINTFLAG(pipe->idx));
sam_putreg8((USBHOST_PINTFLAG_TRFAIL |
USBHOST_PINTFLAG_PERR |
USBHOST_PINTFLAG_STALL), SAM_USBHOST_PINTENSET(pipe->idx));
pipe->pipestate_general = USB_H_PIPE_S_IDLE;
uinfo("pipe%d pktsize=0x%x ctrl=0x%x status=0x%x\n", pipe->idx,
pipe->descb[0]->pktsize,
pipe->descb[0]->ctrlpipe,
pipe->descb[0]->statuspipe);
}
/****************************************************************************
* Name: sam_pipe_waitsetup
*
* Description:
* Set the request for the transfer complete event well
* BEFORE enabling the transfer (as soon as we are
* absolutely committed to the to avoid transfer).
* We do this to minimize race conditions.
* This logic would have to be expanded
* if we want to have more than one packet in flight at a time!
*
* Assumptions:
* Called from a normal thread context BEFORE the transfer has been started.
*
****************************************************************************/
static int sam_pipe_waitsetup(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe)
{
irqstate_t flags = enter_critical_section();
int ret = -ENODEV;
DEBUGASSERT(priv != NULL && pipe != NULL);
/* Is the device still connected? */
if (priv->connected)
{
/* Yes.. then set waiter to indicate that we expect
* to be informed when either (1) the device is disconnected,
* or (2) the transfer completed.
*/
pipe->waiter = true;
#ifdef CONFIG_USBHOST_ASYNCH
pipe->callback = NULL;
pipe->arg = NULL;
#endif
ret = OK;
}
leave_critical_section(flags);
return ret;
}
/****************************************************************************
* Name: sam_pipe_asynchsetup
*
* Description:
* Set the request for the transfer complete event well BEFORE enabling the
* transfer.
* We do this to minimize race conditions.
* This logic would have to be expanded
* if we want to have more than one packet in flight at a time!
*
* Assumptions:
* Might be called from the level of an interrupt handler
*
****************************************************************************/
#ifdef CONFIG_USBHOST_ASYNCH
static int sam_pipe_asynchsetup(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
usbhost_asynch_t callback, void *arg)
{
irqstate_t flags = enter_critical_section();
int ret = -ENODEV;
DEBUGASSERT(priv != NULL && pipe != NULL);
/* Is the device still connected? */
if (priv->connected)
{
pipe->waiter = false; /* No waiter */
pipe->callback = callback;
pipe->arg = arg;
ret = OK;
}
leave_critical_section(flags);
return ret;
}
#endif
/****************************************************************************
* Name: sam_pipe_wait
*
* Description:
* Wait for a transfer on a pipe to complete.
*
* Assumptions:
* Called from a normal thread context
*
****************************************************************************/
static int sam_pipe_wait(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe)
{
irqstate_t flags;
int ret;
/* Disable interrupts so that the following operations will be atomic. On
* the host global interrupt needs to be disabled. However, here we disable
* all interrupts to exploit that fact that interrupts will be re-enabled
* while we wait.
*/
flags = enter_critical_section();
/* Loop, testing for an end of transfer condition. The pipe 'result'
* was set to EBUSY and 'waiter' was set to the pipe expecting the
* response before the transfer was started; 'waiter' will be nullified
* and 'result' will be set appropriately when the transfer is completed.
*/
do
{
/* Wait for the transfer to complete. NOTE the transfer may already
* completed before we get here or the transfer may complete while we
* wait here.
*/
ret = nxsem_wait(&pipe->waitsem);
/* nxsem_wait should succeed. But it is possible that we could be
* awakened by a signal too.
*/
DEBUGASSERT(ret == OK || ret == -EINTR);
}
while (pipe->waiter);
/* The transfer is complete re-enable interrupts and return the result */
ret = -(int)pipe->result;
leave_critical_section(flags);
return ret;
}
/****************************************************************************
* Name: sam_pipe_wakeup
*
* Description:
* A pipe transfer has completed... wakeup any threads waiting for the
* transfer to complete.
*
* Assumptions:
* This function is called from the transfer complete interrupt handler for
* the pipe. Interrupts are disabled.
*
****************************************************************************/
static void sam_pipe_wakeup(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe)
{
/* Is the transfer complete? */
if (pipe->result != EBUSY)
{
/* Is there a thread waiting for this transfer to complete? */
if (pipe->waiter)
{
#ifdef CONFIG_USBHOST_ASYNCH
/* Yes.. there should not also be a callback scheduled */
DEBUGASSERT(pipe->callback == NULL);
#endif
/* Wake'em up! */
usbhost_vtrace2(pipe->in ? SAM_VTRACE2_PIPEWAKEUP_IN :
SAM_VTRACE2_PIPEWAKEUP_OUT,
pipe->epno, pipe->result);
nxsem_post(&pipe->waitsem);
pipe->waiter = false;
}
#ifdef CONFIG_USBHOST_ASYNCH
/* No.. is an asynchronous callback expected
* when the transfer completes?
*/
else if (pipe->callback)
{
/* Handle continuation of IN/OUT pipes */
if (pipe->in)
{
sam_in_next(priv, pipe);
}
else
{
sam_out_next(priv, pipe);
}
}
#endif
}
}
/****************************************************************************
* Name: sam_ctrlep_alloc
*
* Description:
* Allocate a container and pipes for control pipe.
*
* Input Parameters:
* priv - The private USB host driver state.
* epdesc - Describes the endpoint to be allocated.
* ep - A memory location provided by the caller in which to receive the
* allocated endpoint descriptor.
*
* Returned Value:
* On success, zero (OK) is returned. On a failure, a negated errno value
* is returned indicating the nature of the failure
*
* Assumptions:
* This function will *not* be called from an interrupt handler.
*
****************************************************************************/
static int sam_ctrlep_alloc(struct sam_usbhost_s *priv,
const struct usbhost_epdesc_s *epdesc,
usbhost_ep_t *ep)
{
struct usbhost_hubport_s *hport;
struct sam_pipe_s *pipe;
int idx;
/* Sanity check. NOTE that this method should only be called if
* a device is connected (because we need a valid low speed indication).
*/
DEBUGASSERT(epdesc->hport != NULL);
hport = epdesc->hport;
idx = sam_pipe_alloc(priv);
if (idx < 0)
{
usbhost_trace1(SAM_TRACE1_PIPEALLOC_FAIL, -idx);
uerr("ERROR: Failed to allocate a host pipe\n");
return -ENOMEM;
}
pipe = &priv->pipelist[idx];
pipe->epno = epdesc->addr & USB_EPNO_MASK;
pipe->in = false;
pipe->eptype = USB_EP_ATTR_XFER_CONTROL;
pipe->funcaddr = hport->funcaddr;
pipe->speed = hport->speed;
pipe->interval = 0;
pipe->maxpacket = SAM_EP0_MAXPACKET;
/* Configure control OUT pipe */
pipe->pipestate_general = USB_H_PIPE_S_CFG;
sam_pipe_configure(priv, idx);
/* Return a pointer to the control pipe container as the pipe "handle" */
*ep = (usbhost_ep_t)idx;
return OK;
}
/****************************************************************************
* Name: sam_xfrep_alloc
*
* Description:
* Allocate and configure one unidirectional pipe.
*
* Input Parameters:
* priv - The private USB host driver state.
* epdesc - Describes the endpoint to be allocated.
* ep - A memory location provided by the caller in which to receive the
* allocated endpoint descriptor.
*
* Returned Value:
* On success, zero (OK) is returned. On a failure, a negated
* errno value is returned indicating the nature of the failure
*
* Assumptions:
* This function will *not* be called from an interrupt handler.
*
****************************************************************************/
static int sam_xfrep_alloc(struct sam_usbhost_s *priv,
const struct usbhost_epdesc_s *epdesc,
usbhost_ep_t *ep)
{
struct usbhost_hubport_s *hport;
struct sam_pipe_s *pipe;
int idx;
/* Sanity check. NOTE that this method should only be called if a device
* is connected (because we need a valid low speed indication).
*/
DEBUGASSERT(epdesc->hport != NULL);
hport = epdesc->hport;
DEBUGASSERT(hport != NULL);
/* Allocate a host pipe for the endpoint */
idx = sam_pipe_alloc(priv);
if (idx < 0)
{
usbhost_trace1(SAM_TRACE1_PIPEALLOC_FAIL, -idx);
uerr("ERROR: Failed to allocate a host pipe\n");
return -ENOMEM;
}
/* Decode the endpoint descriptor to initialize the pipe data structures.
* Note: Here we depend on the fact that the endpoint point type is
* encoded in the same way in the endpoint descriptor as it is in the OTG
* HS hardware.
*/
pipe = &priv->pipelist[idx];
pipe->epno = epdesc->addr & USB_EPNO_MASK;
pipe->in = epdesc->in;
pipe->eptype = epdesc->xfrtype;
pipe->funcaddr = hport->funcaddr;
pipe->speed = hport->speed;
pipe->interval = epdesc->interval;
pipe->maxpacket = epdesc->mxpacketsize;
pipe->pipestate_general = pipe->in ? USB_H_PIPE_S_DATI : USB_H_PIPE_S_DATO;
/* Then configure the endpoint */
sam_pipe_configure(priv, idx);
/* Return the endpoint number as the endpoint "handle" */
*ep = (usbhost_ep_t)idx;
return OK;
}
/****************************************************************************
* Name: sam_transfer_terminate
*
* Description:
* Terminate a IN or OUT transfer due to an error (or because a zero-
* length OUT transfer occurred).
*
* Returned value:
* OK - Transfer successful
* -EAGAIN - If devices NAKs the transfer.
* -EPERM - If the endpoint stalls
* -BUSY - The transfer is not complete
* -EIO - Other, undecoded error
*
* Assumptions:
*
****************************************************************************/
static void sam_transfer_terminate(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
int result)
{
/* Wake up any waiters for the end of transfer event */
sam_pipe_wakeup(priv, pipe);
if (pipe->pipestate_general < USB_H_PIPE_S_SETUP ||
pipe->pipestate_general > USB_H_PIPE_S_STATO)
return; /* Not busy */
if (pipe->eptype == USB_EP_ATTR_XFER_CONTROL)
{
if (priv->n_ctrl_req_user)
priv->n_ctrl_req_user--;
if (priv->n_sof_user)
priv->n_sof_user--;
}
pipe->pipestate_general = USB_H_PIPE_S_IDLE;
pipe->pipestatus_general = result;
/* Suspend delayed due to control request: start it */
if (priv->n_ctrl_req_user == 0 && priv->suspend_start < 0)
{
uint8_t i;
if (priv->n_ctrl_req_user)
{
/* Delay suspend after setup requests */
priv->suspend_start = -1;
return;
}
/* Save pipe freeze states and freeze pipes */
priv->pipes_unfreeze = 0;
for (i = 0; i < SAM_USB_NENDPOINTS; i++)
{
/* Skip frozen pipes */
if ((sam_getreg16(SAM_USBHOST_PSTATUS(i)) &
USBHOST_PSTATUS_PFREEZE) >> 4)
continue;
/* Log unfrozen pipes */
priv->pipes_unfreeze |= 1 << i;
/* Freeze it to suspend */
sam_putreg8(USBHOST_PSTATUS_PFREEZE, SAM_USBHOST_PSTATUSSET(i));
}
/* Wait 3 SOFs before entering in suspend state */
sam_add_sof_user(priv); /* SOF user: delayed suspend */
priv->suspend_start = 3;
}
}
static void sam_transfer_abort(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
int code)
{
/* Stop transfer */
sam_putreg8(USBHOST_PSTATUS_PFREEZE, SAM_USBHOST_PSTATUSSET(pipe->idx));
/* Update byte count */
if (pipe->in == 0)
pipe->count += (pipe->descb[0]->pktsize &
USBHOST_PKTSIZE_MPKTSIZE_MASK) >>
USBHOST_PKTSIZE_MPKTSIZE_SHIFT;
/* Disable interrupts */
sam_putreg8((USBHOST_PINTFLAG_TRCPT0 |
USBHOST_PINTFLAG_TRCPT1),
SAM_USBHOST_PINTENCLR(pipe->idx));
sam_transfer_terminate(priv, pipe, code);
}
/****************************************************************************
* Name: sam_send_continue
*
* Description:
* Continue the send operation started by sam_send_start().
*
* Assumptions:
*
****************************************************************************/
static void sam_send_continue(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe)
{
uint8_t * src;
uint32_t size;
uint32_t count;
uint32_t n_tx = 0;
uint32_t n_remain;
if (pipe->pipestate_general == USB_H_PIPE_S_STATO)
{
/* Control status : ZLP OUT done */
sam_transfer_terminate(priv, pipe, OK);
return;
}
else if (pipe->pipestate_general != USB_H_PIPE_S_DATO)
return;
/* Reset packet timeout for control pipes */
if (pipe->eptype == USB_EP_ATTR_XFER_CONTROL)
pipe->pkt_timeout = USB_CTRL_DPKT_TIMEOUT;
n_tx = (pipe->descb[0]->pktsize & USBHOST_PKTSIZE_BCNT_MASK) >>
USBHOST_PKTSIZE_BCNT_SHIFT;
/* ZLP cleared if it's short packet */
if (n_tx < pipe->maxpacket)
pipe->zlp = 0;
src = pipe->data;
size = pipe->size;
count = pipe->count;
if (n_tx)
{
count += n_tx;
pipe->count = count;
}
n_remain = size - count;
/* Now set n_tx to next transfer size */
if (n_remain > 16320)
n_tx = 16320;
else
n_tx = n_remain;
/* For Control, all data is done, to STATUS stage */
if (pipe->eptype == USB_EP_ATTR_XFER_CONTROL &&
pipe->count >= pipe->size &&
!pipe->zlp)
{
pipe->pipestate = USB_H_PIPE_S_STATI;
/* Start IN ZLP request */
pipe->pkt_timeout = USB_CTRL_STAT_TIMEOUT;
sam_putreg8(USBHOST_PSTATUS_DTGL, SAM_USBHOST_PSTATUSSET(pipe->idx));
sam_recv_restart(priv, pipe);
return;
}
/* All transfer done, including ZLP */
if (count >= size && !pipe->zlp)
{
/* At least one bank there, wait to freeze pipe */
if (pipe->eptype != USB_EP_ATTR_XFER_CONTROL)
{
/* Busy interrupt when all banks are empty */
sam_transfer_terminate(priv, pipe, OK);
}
else /* No busy interrupt for control EPs */
{
}
}
else
{
sam_putreg8((USBHOST_PINTFLAG_TRCPT0 |
USBHOST_PINTFLAG_TRCPT1),
SAM_USBHOST_PINTFLAG(pipe->idx));
sam_putreg8((USBHOST_PINTFLAG_TRCPT0 |
USBHOST_PINTFLAG_TRCPT1),
SAM_USBHOST_PINTENSET(pipe->idx));
pipe->descb[0]->addr = (uint32_t)&src[count];
pipe->descb[0]->pktsize &= ~(USBHOST_PKTSIZE_MPKTSIZE_MASK |
USBHOST_PKTSIZE_BCNT_MASK);
pipe->descb[0]->pktsize |= USBHOST_PKTSIZE_BCNT(n_remain);
/* Send the OUT token */
sam_modifyreg8(USBHOST_PCFG_PTOKEN_MASK,
USBHOST_PCFG_PTOKEN_OUT,
SAM_USBHOST_PCFG(pipe->idx));
sam_putreg8(USBHOST_PSTATUS_BK0RDY,
SAM_USBHOST_PSTATUSSET(pipe->idx));
sam_putreg8(USBHOST_PSTATUS_PFREEZE,
SAM_USBHOST_PSTATUSCLR(pipe->idx));
}
}
/****************************************************************************
* Name: sam_send_start
*
* Description:
* Start at transfer on the selected IN or OUT pipe.
*
****************************************************************************/
static void sam_send_start(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe)
{
/* Set up the initial state of the transfer */
usbhost_vtrace2(SAM_VTRACE2_STARTTRANSFER1, pipe->idx, pipe->size);
pipe->result = EBUSY;
pipe->count = 0;
/* Make sure the peripheral address is correct */
pipe->descb[0]->ctrlpipe &= ~USBHOST_CTRLPIPE_PDADDR_MASK;
pipe->descb[0]->ctrlpipe |= USBHOST_CTRLPIPE_PDADDR(pipe->funcaddr);
/* Checkout for zero length packet */
if (pipe->size > 0)
{
/* No.. we need to copy the outgoing data and start the transfer */
sam_putreg8((USBHOST_PINTFLAG_TRCPT0 |
USBHOST_PINTFLAG_TRCPT1),
SAM_USBHOST_PINTFLAG(pipe->idx));
sam_putreg8((USBHOST_PINTFLAG_TRCPT0 |
USBHOST_PINTFLAG_TRCPT1),
SAM_USBHOST_PINTENSET(pipe->idx));
pipe->descb[0]->addr = (uint32_t)pipe->data;
pipe->descb[0]->pktsize &= ~(USBHOST_PKTSIZE_MPKTSIZE_MASK |
USBHOST_PKTSIZE_BCNT_MASK);
pipe->descb[0]->pktsize |= USBHOST_PKTSIZE_BCNT(pipe->size);
/* Send the OUT token */
sam_modifyreg8(USBHOST_PCFG_PTOKEN_MASK,
USBHOST_PCFG_PTOKEN_OUT,
SAM_USBHOST_PCFG(pipe->idx));
sam_putreg8(USBHOST_PSTATUS_BK0RDY,
SAM_USBHOST_PSTATUSSET(pipe->idx));
sam_putreg8(USBHOST_PSTATUS_PFREEZE,
SAM_USBHOST_PSTATUSCLR(pipe->idx));
}
else
{
sam_putreg8((USBHOST_PINTFLAG_TRCPT0 |
USBHOST_PINTFLAG_TRCPT1),
SAM_USBHOST_PINTFLAG(pipe->idx));
sam_putreg8((USBHOST_PINTFLAG_TRCPT0 |
USBHOST_PINTFLAG_TRCPT1),
SAM_USBHOST_PINTENSET(pipe->idx));
sam_putreg8(USBHOST_PSTATUS_DTGL,
SAM_USBHOST_PSTATUSSET(pipe->idx));
/* Write the zero byte count */
pipe->descb[0]->addr = (uint32_t)priv->ctrl_buffer;
pipe->descb[0]->pktsize &= ~(USBHOST_PKTSIZE_MPKTSIZE_MASK |
USBHOST_PKTSIZE_BCNT_MASK);
pipe->descb[0]->pktsize |= USBHOST_PKTSIZE_AUTOZLP;
/* Send the OUT token */
sam_modifyreg8(USBHOST_PCFG_PTOKEN_MASK,
USBHOST_PCFG_PTOKEN_OUT,
SAM_USBHOST_PCFG(pipe->idx));
sam_putreg8(USBHOST_PSTATUS_BK0RDY,
SAM_USBHOST_PSTATUSSET(pipe->idx));
sam_putreg8(USBHOST_PSTATUS_PFREEZE,
SAM_USBHOST_PSTATUSCLR(pipe->idx));
}
}
/****************************************************************************
* Name: sam_out_transfer
*
* Description:
* Transfer the 'buflen' bytes in 'buffer' through an OUT pipe.
*
* Assumptions:
* This function is called only from the TRANSFER
* interface. The lock, for example,
* must be relinquished before waiting.
*
****************************************************************************/
static ssize_t sam_out_transfer(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
uint8_t *buffer, size_t buflen)
{
clock_t start;
clock_t elapsed;
size_t xfrlen;
ssize_t xfrd;
int ret;
/* Loop until the transfer completes (i.e., buflen is decremented to zero)
* or a fatal error occurs (any error other than a simple NAK)
*/
start = clock_systime_ticks();
xfrd = 0;
while (buflen > 0)
{
/* Transfer one packet at a time. The hardware is capable of queueing
* multiple OUT packets, but I just haven't figured out how to handle
* the case where a single OUT packet in the group is NAKed.
*/
xfrlen = MIN(pipe->maxpacket, buflen);
pipe->data = buffer;
pipe->size = xfrlen;
pipe->count = 0;
uinfo("pipe%d buffer:%p buflen:%d\n",
pipe->idx,
pipe->data,
pipe->size);
/* Set up for the wait BEFORE starting the transfer */
ret = sam_pipe_waitsetup(priv, pipe);
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_DEVDISCONN1, 0);
return (ssize_t)ret;
}
/* Set up for the transfer based
* on the direction and the endpoint type
*/
ret = sam_out_setup(priv, pipe);
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_OUTSETUP_FAIL1, -ret);
return (ssize_t)ret;
}
/* Wait for the transfer to complete and get the result */
ret = sam_pipe_wait(priv, pipe);
/* Handle transfer failures */
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_TRANSFER_FAILED1, ret);
/* Check for a special case: If (1) the transfer was NAKed and (2)
* no SNDFIFO empty or Rx FIFO not-empty event occurred, then we
* should be able to just flush the Rx and SNDFIFOs and try again.
* We can detect this latter case because then the transfer buffer
* pointer and buffer size will be unaltered.
*/
elapsed = clock_systime_ticks() - start;
if (ret != -EAGAIN || /* Not a NAK condition OR */
elapsed >= SAM_DATANAK_DELAY || /* Timeout has elapsed OR */
pipe->count > 0) /* Data has been partially transferred */
{
/* Break out and return the error */
usbhost_trace1(SAM_TRACE1_PIPEWAIT_FAIL, -ret);
return (ssize_t)ret;
}
/* Get the device a little time to catch up.
* Then retry the transfer
* using the same buffer pointer and length.
*/
nxsched_usleep(20 * 1000);
}
else
{
/* Successfully transferred. Update the buffer pointer and length */
buffer += xfrlen;
buflen -= xfrlen;
xfrd += pipe->count;
}
}
return xfrd;
}
/****************************************************************************
* Name: sam_out_next
*
* Description:
* Initiate the next of a sequence of asynchronous transfers.
*
* Assumptions:
* This function is always called from an interrupt handler
*
****************************************************************************/
#ifdef CONFIG_USBHOST_ASYNCH
static void sam_out_next(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe)
{
usbhost_asynch_t callback;
void *arg;
ssize_t nbytes;
int result;
int ret;
/* Is the full transfer complete?
* Did the last chunk transfer complete OK?
*/
result = -(int)pipe->result;
if (pipe->count < pipe->size && result == OK)
{
/* Yes.. Set up for the next transfer based on the direction and the
* endpoint type
*/
ret = sam_out_setup(priv, pipe);
if (ret >= 0)
{
return;
}
usbhost_trace1(SAM_TRACE1_OUTSETUP_FAIL2, -ret);
result = ret;
}
/* The transfer is complete, with or without an error */
usbhost_vtrace1(SAM_VTRACE1_TRANSFER_COMPLETE, result);
/* Extract the callback information */
callback = pipe->callback;
arg = pipe->arg;
nbytes = pipe->count;
pipe->callback = NULL;
pipe->arg = NULL;
pipe->count = 0;
/* Then perform the callback */
if (result < 0)
{
nbytes = (ssize_t)result;
}
callback(arg, nbytes);
}
#endif
/****************************************************************************
* Name: sam_out_asynch
*
* Description:
* Initiate the first of a sequence of asynchronous transfers.
*
* Assumptions:
* This function is called only from the ASYNCH.
* The lock, for example, must be relinquished before waiting.
*
****************************************************************************/
#ifdef CONFIG_USBHOST_ASYNCH
static int sam_out_asynch(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
uint8_t *buffer, size_t buflen,
usbhost_asynch_t callback, void *arg)
{
int ret;
/* Set up for the transfer data and callback
* BEFORE starting the first transfer
*/
pipe->data = buffer;
pipe->size = buflen;
pipe->count = 0;
ret = sam_pipe_asynchsetup(priv, pipe, callback, arg);
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_ASYNCHSETUP_FAIL1, -ret);
return ret;
}
/* Set up for the transfer based on the
* direction and the endpoint type
*/
ret = sam_out_setup(priv, pipe);
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_OUTSETUP_FAIL3, -ret);
}
/* And return with the transfer pending */
return ret;
}
#endif
/****************************************************************************
* Name: sam_ctrl_sendsetup
*
* Description:
* Send an IN/OUT SETUP packet.
*
* Assumptions:
* This function is called only from the CTRLIN and CTRLOUT interfaces.
*
****************************************************************************/
static int sam_ctrl_sendsetup(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
const struct usb_ctrlreq_s *req)
{
clock_t start;
clock_t elapsed;
int ret;
int i;
/* Loop while the device reports NAK (and a timeout is not exceeded */
start = clock_systime_ticks();
do
{
/* Send the SETUP packet */
pipe->data = (uint8_t *)req;
pipe->size = USB_SIZEOF_CTRLREQ;
pipe->count = 0;
pipe->result = EBUSY;
uinfo("pipe%d buffer:%p buflen:%d\n",
pipe->idx,
pipe->data,
pipe->size);
sam_pktdump("sam_ctrl_sendsetup", pipe->data, pipe->size);
/* Set up for the wait BEFORE starting the transfer */
ret = sam_pipe_waitsetup(priv, pipe);
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_DEVDISCONN2, 0);
return ret;
}
pipe->pipestate_general = USB_H_PIPE_S_SETUP;
sam_add_sof_user(priv);
priv->n_ctrl_req_user++;
/* Make sure the peripheral address is correct */
pipe->descb[0]->ctrlpipe &= ~USBHOST_CTRLPIPE_PDADDR_MASK;
pipe->descb[0]->ctrlpipe |= USBHOST_CTRLPIPE_PDADDR(pipe->funcaddr);
/* Write packet */
sam_putreg8(USBHOST_PINTFLAG_TXSTP, SAM_USBHOST_PINTFLAG(pipe->idx));
for (i = 0; i < USB_SIZEOF_CTRLREQ; i++)
priv->ctrl_buffer[i] = pipe->data[i];
pipe->descb[0]->addr = (uint32_t)pipe->data;
pipe->descb[0]->pktsize &= ~(USBHOST_PKTSIZE_MPKTSIZE_MASK |
USBHOST_PKTSIZE_BCNT_MASK);
pipe->descb[0]->pktsize |= USBHOST_PKTSIZE_BCNT(USB_SIZEOF_CTRLREQ);
pipe->descb[0]->ctrlpipe = USBHOST_CTRLPIPE_PDADDR(pipe->funcaddr) |
USBHOST_CTRLPIPE_PEPNUM(pipe->epno & USB_EPNO_MASK);
uinfo("pipe%d pktsize=0x%x ctrl=0x%x status=0x%x\n",
pipe->idx,
pipe->descb[0]->pktsize,
pipe->descb[0]->ctrlpipe,
pipe->descb[0]->statuspipe);
/* Send the SETUP token (always EP0) */
sam_modifyreg8(USBHOST_PCFG_PTOKEN_MASK,
USBHOST_PCFG_PTOKEN_SETUP,
SAM_USBHOST_PCFG(pipe->idx));
sam_putreg8(USBHOST_PSTATUS_DTGL, SAM_USBHOST_PSTATUSCLR(pipe->idx));
sam_putreg8(USBHOST_PSTATUS_BK0RDY, SAM_USBHOST_PSTATUSSET(pipe->idx));
sam_putreg8(USBHOST_PINTFLAG_TXSTP, SAM_USBHOST_PINTENSET(pipe->idx));
sam_putreg8(USBHOST_PSTATUS_PFREEZE,
SAM_USBHOST_PSTATUSCLR(pipe->idx));
/* Wait for the transfer to complete */
ret = sam_pipe_wait(priv, pipe);
/* Return on success and for all failures other than EAGAIN. EAGAIN
* means that the device NAKed the SETUP command and that we should
* try a few more times. NOTE: The USB spec says that a peripheral
* must always ACK a SETUP packet.
*/
if (ret != -EAGAIN)
{
/* Output some debug information if the transfer failed */
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_TRANSFER_FAILED2, ret);
}
/* Return the result in any event */
return ret;
}
/* Get the elapsed time (in frames) */
elapsed = clock_systime_ticks() - start;
}
while (elapsed < SAM_SETUP_DELAY);
return -ETIMEDOUT;
}
/****************************************************************************
* Name: sam_ctrl_senddata
*
* Description:
* Send data in the data phase of an OUT control transfer. Or send status
* in the status phase of an IN control transfer
*
* Assumptions:
* This function is called only from the CTRLOUT interface.
*
****************************************************************************/
static int sam_ctrl_senddata(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
uint8_t *buffer, unsigned int buflen)
{
int ret;
uinfo("pipe%d buffer:%p buflen:%d\n", pipe->idx, buffer, buflen);
/* Save buffer information */
pipe->pipestate_general = USB_H_PIPE_S_DATO;
pipe->in = false;
pipe->data = buffer;
pipe->size = buflen;
pipe->count = 0;
/* Set up for the wait BEFORE starting the transfer */
ret = sam_pipe_waitsetup(priv, pipe);
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_DEVDISCONN3, 0);
return ret;
}
/* Start the transfer */
sam_send_start(priv, pipe);
/* Wait for the transfer to complete and return the result */
return sam_pipe_wait(priv, pipe);
}
/****************************************************************************
* Name: sam_ctrl_recvdata
*
* Description:
* Receive data in the data phase of an IN control transfer.
* Or receive status in the status phase of
* an OUT control transfer.
*
* Assumptions:
* This function is called only from the CTRLIN interface.
*
****************************************************************************/
static int sam_ctrl_recvdata(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
uint8_t *buffer, unsigned int buflen)
{
int ret;
/* Save buffer information */
pipe->pipestate_general = USB_H_PIPE_S_DATI;
pipe->in = true;
pipe->data = buffer;
pipe->size = buflen;
pipe->count = 0;
/* Set up for the wait BEFORE starting the transfer */
ret = sam_pipe_waitsetup(priv, pipe);
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_DEVDISCONN4, 0);
return ret;
}
/* Start the transfer */
sam_recv_start(priv, pipe);
/* Wait for the transfer to complete and return the result */
ret = sam_pipe_wait(priv, pipe);
uinfo("pipe%d buffer:%p buflen:%d ADDR=0x%x PKTSIZE=0x%x\n",
pipe->idx, buffer, buflen,
pipe->descb[0]->addr,
pipe->descb[0]->pktsize);
uinfo("EXTREG=0x%x STATUSBK=0x%x CTRLPIPE=0x%x STATUSPIPE=0x%x\n",
pipe->descb[0]->extreg,
pipe->descb[0]->stausbk,
pipe->descb[0]->ctrlpipe,
pipe->descb[0]->statuspipe);
sam_pktdump("sam_ctrl_recvdata", pipe->data, pipe->size);
return ret;
}
/****************************************************************************
* Name: sam_in_setup
*
* Description:
* Initiate an IN transfer on an bulk, interrupt, or isochronous pipe.
*
****************************************************************************/
static int sam_in_setup(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe)
{
uinfo("pipe%d\n", pipe->idx);
/* Set up for the transfer based on the direction and the endpoint type */
switch (pipe->eptype)
{
default:
case USB_EP_ATTR_XFER_CONTROL: /* Control */
{
/* This kind of transfer on control endpoints other than EP0 are
* not currently supported
*/
return -ENOSYS;
}
case USB_EP_ATTR_XFER_ISOC: /* Isochronous */
{
/* Set up the IN DATA0 PID */
usbhost_vtrace2(SAM_VTRACE2_ISOCIN, pipe->idx, pipe->size);
}
break;
case USB_EP_ATTR_XFER_BULK: /* Bulk */
{
usbhost_vtrace2(SAM_VTRACE2_BULKIN, pipe->idx, pipe->size);
pipe->pipestate_general = pipe->in ?
USB_H_PIPE_S_DATI : USB_H_PIPE_S_DATO;
}
break;
case USB_EP_ATTR_XFER_INT: /* Interrupt */
{
usbhost_vtrace2(SAM_VTRACE2_INTRIN, pipe->idx, pipe->size);
}
break;
}
/* Start the transfer. */
sam_recv_start(priv, pipe);
return OK;
}
/****************************************************************************
* Name: sam_out_setup
*
* Description:
* Initiate an OUT transfer on an bulk, interrupt, or isochronous pipe.
*
****************************************************************************/
static int sam_out_setup(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe)
{
/* Set up for the transfer based on the direction and the endpoint type */
switch (pipe->eptype)
{
default:
case USB_EP_ATTR_XFER_CONTROL: /* Control */
{
/* This kind of transfer on control endpoints other than EP0 are
* not currently supported
*/
return -ENOSYS;
}
case USB_EP_ATTR_XFER_ISOC: /* Isochronous */
{
/* Set up the IN DATA0 PID */
usbhost_vtrace2(SAM_VTRACE2_ISOCOUT,
pipe->idx, pipe->size);
}
break;
case USB_EP_ATTR_XFER_BULK: /* Bulk */
{
usbhost_vtrace2(SAM_VTRACE2_BULKOUT,
pipe->idx, pipe->size);
pipe->pipestate_general = pipe->in ?
USB_H_PIPE_S_DATI : USB_H_PIPE_S_DATO;
}
break;
case USB_EP_ATTR_XFER_INT: /* Interrupt */
{
usbhost_vtrace2(SAM_VTRACE2_INTROUT,
pipe->idx, pipe->size);
}
break;
}
/* Start the transfer */
sam_send_start(priv, pipe);
return OK;
}
/****************************************************************************
* Name: sam_recv_continue
*
* Description:
* Continue the receive operation started by sam_recv_start(). This
* function is called from the interrupt handler worker when an interrupt
* indicates that new, incoming data is available
*
* Assumptions:
*
****************************************************************************/
static void sam_recv_continue(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe)
{
uint8_t *src;
uint8_t *dst;
uint32_t size;
uint32_t count;
uint32_t i;
uint32_t n_rx = 0;
uint32_t n_remain;
bool shortpkt = false;
bool full = false;
if (pipe->pipestate_general == USB_H_PIPE_S_STATI)
{
/* Control status : ZLP IN done */
sam_transfer_terminate(priv, pipe, OK);
return;
}
else if (pipe->pipestate_general != USB_H_PIPE_S_DATI)
return;
/* Read byte count */
n_rx = (pipe->descb[0]->pktsize & USBHOST_PKTSIZE_BCNT_MASK) >>
USBHOST_PKTSIZE_BCNT_SHIFT;
if (n_rx < pipe->maxpacket)
shortpkt = true;
if (n_rx)
{
dst = pipe->data;
size = pipe->size;
count = pipe->count;
n_remain = size - count;
src = (uint8_t *)pipe->descb[0]->addr;
dst = &dst[count];
if (n_rx >= n_remain)
{
n_rx = n_remain;
full = true;
}
count += n_rx;
for (i = 0; i < n_rx; i++)
*dst++ = *src++;
pipe->count = count;
}
/* Reset timeout for control pipes */
if (pipe->eptype == USB_EP_ATTR_XFER_CONTROL)
pipe->pkt_timeout = USB_CTRL_DPKT_TIMEOUT;
/* Finish on error or short packet */
if (full || shortpkt)
{
if (pipe->eptype == USB_EP_ATTR_XFER_CONTROL)
{
pipe->pipestate = USB_H_PIPE_S_STATO;
pipe->pkt_timeout = USB_CTRL_STAT_TIMEOUT;
sam_putreg8(USBHOST_PSTATUS_DTGL,
SAM_USBHOST_PSTATUSSET(pipe->idx));
sam_send_start(priv, pipe);
}
else
sam_transfer_terminate(priv, pipe, OK);
}
else
{
/* Just wait another packet */
sam_recv_restart(priv, pipe);
}
}
/****************************************************************************
* Name: sam_recv_restart
*
* Description:
* Start/Re-start the transfer on the selected IN or OUT pipe
*
****************************************************************************/
static void sam_recv_restart(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe)
{
/* Send the IN token. */
sam_putreg8((USBHOST_PINTFLAG_TRCPT0 |
USBHOST_PINTFLAG_TRCPT1), SAM_USBHOST_PINTFLAG(pipe->idx));
sam_putreg8((USBHOST_PINTFLAG_TRCPT0 |
USBHOST_PINTFLAG_TRCPT1), SAM_USBHOST_PINTENSET(pipe->idx));
if (pipe->eptype == USB_EP_ATTR_XFER_CONTROL)
{
pipe->descb[0]->addr = (uint32_t)pipe->data;
pipe->descb[0]->pktsize &= ~(USBHOST_PKTSIZE_MPKTSIZE_MASK |
USBHOST_PKTSIZE_BCNT_MASK);
pipe->descb[0]->pktsize |= USBHOST_PKTSIZE_MPKTSIZE(pipe->maxpacket);
}
else
{
uint32_t n_next = pipe->size - pipe->count;
pipe->descb[0]->addr = (uint32_t)&pipe->data[pipe->count];
if (n_next > 16384)
n_next = 16384;
pipe->descb[0]->pktsize &= ~(USBHOST_PKTSIZE_MPKTSIZE_MASK |
USBHOST_PKTSIZE_BCNT_MASK);
pipe->descb[0]->pktsize |= USBHOST_PKTSIZE_MPKTSIZE(n_next);
}
sam_modifyreg8(USBHOST_PCFG_PTOKEN_MASK,
USBHOST_PCFG_PTOKEN_IN,
SAM_USBHOST_PCFG(pipe->idx));
sam_putreg8(USBHOST_PSTATUS_BK0RDY, SAM_USBHOST_PSTATUSCLR(pipe->idx));
sam_putreg8(USBHOST_PSTATUS_PFREEZE, SAM_USBHOST_PSTATUSCLR(pipe->idx));
}
/****************************************************************************
* Name: sam_recv_start
*
* Description:
* Start at transfer on the selected IN or OUT pipe.
*
* Assumptions:
*
****************************************************************************/
static void sam_recv_start(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe)
{
/* Set up the initial state of the transfer */
usbhost_vtrace2(SAM_VTRACE2_STARTTRANSFER2, pipe->idx, pipe->size);
pipe->result = EBUSY;
pipe->count = 0;
/* Make sure the peripheral address is correct */
pipe->descb[0]->ctrlpipe &= ~USBHOST_CTRLPIPE_PDADDR_MASK;
pipe->descb[0]->ctrlpipe |= USBHOST_CTRLPIPE_PDADDR(pipe->funcaddr);
/* Start the transfer. */
sam_recv_restart(priv, pipe);
}
/****************************************************************************
* Name: sam_in_transfer
*
* Description:
* Transfer 'buflen' bytes into 'buffer' from an IN pipe.
*
* Assumptions:
* This function is called only from the TRANSFER.
* The lock, for example, must be relinquished before waiting.
*
****************************************************************************/
static ssize_t sam_in_transfer(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
uint8_t *buffer, size_t buflen)
{
clock_t start;
ssize_t xfrd;
int ret;
/* Loop until the transfer completes (i.e., buflen is decremented to zero)
* or a fatal error occurs any error other than a simple NAK. NAK would
* simply indicate the end of the transfer (short-transfer).
*/
pipe->data = buffer;
pipe->size = buflen;
pipe->count = 0;
xfrd = 0;
start = clock_systime_ticks();
while (pipe->count < pipe->size)
{
/* Set up for the wait BEFORE starting the transfer */
ret = sam_pipe_waitsetup(priv, pipe);
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_DEVDISCONN5, 0);
return (ssize_t)ret;
}
/* Set up for the transfer based on the direction
* and the endpoint type
*/
ret = sam_in_setup(priv, pipe);
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_INSETUP_FAIL1, -ret);
return (ssize_t)ret;
}
/* Wait for the transfer to complete and get the result */
ret = sam_pipe_wait(priv, pipe);
/* EAGAIN indicates that the device NAKed the transfer. */
if (ret < 0)
{
/* The transfer failed. If we received a NAK, return all data
* buffered so far (if any).
*/
if (ret == -EAGAIN)
{
/* Was data buffered prior to the NAK? */
if (xfrd > 0)
return xfrd;
else
{
useconds_t delay;
/* Get the elapsed time.
* Has the timeout elapsed?
* if not then try again.
*/
clock_t elapsed = clock_systime_ticks() - start;
if (elapsed >= SAM_DATANAK_DELAY)
{
/* Timeout out... break out returning the NAK as
* as a failure.
*/
return (ssize_t)ret;
}
/* Wait a bit before retrying after a NAK. */
if (pipe->eptype == USB_EP_ATTR_XFER_INT)
{
/* For interrupt (and isochronous) endpoints, the
* polling rate is determined by the bInterval field
* of the endpoint descriptor (in units of frames
* which we treat as milliseconds here).
*/
if (pipe->interval > 0)
{
/* Convert the delay to units of microseconds */
delay = (useconds_t)pipe->interval * 1000;
}
else
{
/* Out of range! For interrupt endpoints, the valid
* range is 1-255 frames. Assume one frame.
*/
delay = 1000;
}
}
else
{
/* For Isochronous endpoints, bInterval must be 1. Bulk
* endpoints do not have a polling interval. Rather,
* the should wait until data is received.
*
* REVISIT: For bulk endpoints this 1 msec delay is only
* intended to give the CPU a break from the bulk EP tight
* polling loop. But are there performance issues?
*/
delay = 1000;
}
/* Wait for the next polling interval. For interrupt and
* isochronous endpoints, this is necessary to assure the
* polling interval. It is used in other cases only to
* prevent the polling from consuming too much CPU bandwidth.
*
* Small delays could require more resolution than is provided
* by the system timer. For example, if the system timer
* resolution is 10MS, then nxsched_usleep(1000) will actually request
* a delay 20MS (due to both quantization and rounding).
*
* REVISIT: So which is better? To ignore tiny delays and
* hog the system bandwidth? Or to wait for an excessive
* amount and destroy system throughput?
*/
if (delay > CONFIG_USEC_PER_TICK)
{
nxsched_usleep(delay - CONFIG_USEC_PER_TICK);
}
}
}
else
{
/* Some unexpected, fatal error occurred. */
usbhost_trace1(SAM_TRACE1_TRANSFER_FAILED3, -ret);
/* Break out and return the error */
return (ssize_t)ret;
}
}
else
{
/* Successfully received another chunk of data... add that to the
* running total. Then continue reading until we read 'buflen'
* bytes of data or until the devices NAKs (implying a short
* packet).
*/
xfrd += pipe->count;
}
}
return xfrd;
}
/****************************************************************************
* Name: sam_in_next
*
* Description:
* Initiate the next of a sequence of asynchronous transfers.
*
* Assumptions:
* This function is always called from an interrupt handler
*
****************************************************************************/
#ifdef CONFIG_USBHOST_ASYNCH
static void sam_in_next(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe)
{
usbhost_asynch_t callback;
void *arg;
ssize_t nbytes;
int result;
int ret;
/* Is the full transfer complete?
* Did the last chunk transfer complete OK?
*/
result = -(int)pipe->result;
if (pipe->count < pipe->size && result == OK)
{
/* Yes.. Set up for the next transfer based on the direction and the
* endpoint type
*/
ret = sam_in_setup(priv, pipe);
if (ret >= 0)
{
return;
}
usbhost_trace1(SAM_TRACE1_INSETUP_FAIL2, -ret);
result = ret;
}
/* The transfer is complete, with or without an error */
usbhost_vtrace2(SAM_VTRACE2_XFRCOMPLETE,
(unsigned int)pipe->idx, pipe->size);
/* Extract the callback information */
callback = pipe->callback;
arg = pipe->arg;
nbytes = pipe->count;
pipe->callback = NULL;
pipe->arg = NULL;
pipe->count = 0;
/* Then perform the callback */
if (result < 0)
{
nbytes = (ssize_t)result;
}
callback(arg, nbytes);
}
#endif
/****************************************************************************
* Name: sam_in_asynch
*
* Description:
* Initiate the first of a sequence of asynchronous transfers.
*
* Assumptions:
* This function is called only from the ASYNCH interface.
* The lock, for example,
* must be relinquished before waiting.
*
****************************************************************************/
#ifdef CONFIG_USBHOST_ASYNCH
static int sam_in_asynch(struct sam_usbhost_s *priv,
struct sam_pipe_s *pipe,
uint8_t *buffer, size_t buflen,
usbhost_asynch_t callback, void *arg)
{
int ret;
/* Set up for the transfer data and callback
* BEFORE starting the first transfer
*/
pipe->data = buffer;
pipe->size = buflen;
pipe->count = 0;
ret = sam_pipe_asynchsetup(priv, pipe, callback, arg);
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_ASYNCHSETUP_FAIL2, -ret);
return ret;
}
/* Set up for the transfer based on the direction and the endpoint type */
ret = sam_in_setup(priv, pipe);
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_INSETUP_FAIL3, -ret);
}
/* And return with the transfer pending */
return ret;
}
#endif
/****************************************************************************
* Name: sam_gint_connected
*
* Description:
* Handle a connection event.
*
****************************************************************************/
static void sam_gint_connected(struct sam_usbhost_s *priv)
{
/* We we previously disconnected? */
if (!priv->connected)
{
/* Yes.. then now we are connected */
usbhost_vtrace1(SAM_VTRACE1_CONNECTED1, 0);
priv->connected = true;
priv->change = true;
DEBUGASSERT(priv->smstate == SMSTATE_DETACHED);
/* Notify any waiters */
priv->smstate = SMSTATE_ATTACHED;
if (priv->pscwait)
{
nxsem_post(&priv->pscsem);
priv->pscwait = false;
}
}
}
/****************************************************************************
* Name: sam_gint_disconnected
*
* Description:
* Handle a disconnection event.
*
****************************************************************************/
static void sam_gint_disconnected(struct sam_usbhost_s *priv)
{
/* Were we previously connected? */
if (priv->connected)
{
/* Yes.. then we no longer connected */
usbhost_vtrace1(SAM_VTRACE1_DISCONNECTED1, 0);
/* Are we bound to a class driver? */
if (priv->rhport.hport.devclass)
{
/* Yes.. Disconnect the class driver */
CLASS_DISCONNECTED(priv->rhport.hport.devclass);
priv->rhport.hport.devclass = NULL;
}
/* Re-Initialize Host for new Enumeration */
priv->smstate = SMSTATE_DETACHED;
priv->connected = false;
priv->change = true;
sam_reset_pipes(priv, false);
priv->rhport.hport.speed = USB_SPEED_FULL;
priv->rhport.hport.funcaddr = 0;
/* Notify any waiters that there is a
* change in the connection state
*/
if (priv->pscwait)
{
nxsem_post(&priv->pscsem);
priv->pscwait = false;
}
}
}
/****************************************************************************
* USB Host Controller Operations
****************************************************************************/
/****************************************************************************
* Name: sam_wait
*
* Description:
* Wait for a device to be connected or disconnected to/from a hub port.
*
* Input Parameters:
* conn - The USB host connection instance obtained as a parameter
* from the call to the USB driver initialization logic.
* hport - The location to return the hub port descriptor that detected the
* connection related event.
*
* Returned Value:
* Zero (OK) is returned on success when a device is connected or
* disconnected. This function will not return until either (1) a device is
* connected or disconnect to/from any hub port or until (2) some failure
* occurs. On a failure, a negated errno value is returned indicating the
* nature of the failure
*
* Assumptions:
* - Called from a single thread so no mutual exclusion is required.
* - Never called from an interrupt handler.
*
****************************************************************************/
static int sam_wait(struct usbhost_connection_s *conn,
struct usbhost_hubport_s **hport)
{
struct sam_usbhost_s *priv = &g_usbhost;
struct usbhost_hubport_s *connport;
irqstate_t flags;
/* Loop until a change in connection state is detected */
flags = enter_critical_section();
for (; ; )
{
/* Is there a change in the connection state of the single root hub
* port?
*/
if (priv->change)
{
connport = &priv->rhport.hport;
/* Yes. Remember the new state */
connport->connected = priv->connected;
priv->change = false;
/* And return the root hub port */
*hport = connport;
leave_critical_section(flags);
uinfo("RHport Connected: %s\n", connport->connected ?
"YES" : "NO");
return OK;
}
#ifdef CONFIG_USBHOST_HUB
/* Is a device connected to an external hub? */
if (priv->hport)
{
/* Yes.. return the external hub port */
connport = (struct usbhost_hubport_s *)priv->hport;
priv->hport = NULL;
*hport = connport;
leave_critical_section(flags);
uinfo("Hub port Connected: %s\n", connport->connected ?
"YES" : "NO");
return OK;
}
#endif
/* Wait for the next connection event */
priv->pscwait = true;
nxsem_wait_uninterruptible(&priv->pscsem);
}
}
/****************************************************************************
* Name: sam_rh_enumerate
*
* Description:
* Enumerate the connected device. As part of this enumeration process,
* the driver will (1) get the device's configuration descriptor, (2)
* extract the class ID info from the configuration descriptor, (3) call
* usbhost_findclass() to find the class that supports this device, (4)
* call the create() method on the struct usbhost_registry_s interface
* to get a class instance, and finally (5) call the connect() method
* of the struct usbhost_class_s interface. After that, the class is in
* charge of the sequence of operations.
*
* Input Parameters:
* conn - The USB host connection instance obtained as a parameter from
* the call to the USB driver initialization logic.
* hport - The descriptor of the hub port that has the newly connected
* device.
*
* Returned Value:
* On success, zero (OK) is returned. On a failure,
* a negated errno value is returned
* indicating the nature of the failure
*
* Assumptions:
* This function will *not* be called from an interrupt handler.
*
****************************************************************************/
static int sam_rh_enumerate(struct sam_usbhost_s *priv,
struct usbhost_connection_s *conn,
struct usbhost_hubport_s *hport)
{
uint32_t regval;
int ret;
uinfo("ENTRY\n");
DEBUGASSERT(conn != NULL && hport != NULL && hport->port == 0);
/* Are we connected to a device? The caller should have called the wait()
* method first to be assured that a device is connected.
*/
while (!priv->connected)
{
/* No, return an error */
usbhost_trace1(TRACE1_DEVDISCONN, 0);
return -ENODEV;
}
DEBUGASSERT(priv->smstate == SMSTATE_ATTACHED);
/* USB 2.0 spec says at least 50ms delay before port reset. */
nxsched_usleep(100 * 1000);
/* Reset the host port */
sam_hostreset(priv);
/* Get the current device speed */
regval = sam_getreg8(SAM_USBHOST_STATUS);
if ((regval & USBDEV_STATUS_SPEED_MASK) == USBDEV_STATUS_SPEED_LOW)
priv->rhport.hport.speed = USB_SPEED_LOW;
else
priv->rhport.hport.speed = USB_SPEED_FULL;
/* Allocate and initialize the root hub port EP0 pipes */
if (priv->pipelist[0].inuse == false)
{
struct usbhost_epdesc_s epdesc = {
.hport = hport,
.addr = 0
};
ret = sam_ctrlep_alloc(priv, &epdesc, &priv->ep0);
if (ret < 0)
uerr("ERROR: Failed to allocate a control endpoint: %d\n", ret);
}
else
ret = OK;
return ret;
}
static int sam_enumerate(struct usbhost_connection_s *conn,
struct usbhost_hubport_s *hport)
{
struct sam_usbhost_s *priv = &g_usbhost;
int ret;
uinfo("ENTRY\n");
DEBUGASSERT(hport);
/* If this is a connection on the root hub, then we need to go to
* little more effort to get the device speed. If it is a connection
* on an external hub, then we already have that information.
*/
#ifdef CONFIG_USBHOST_HUB
if (ROOTHUB(hport))
#endif
{
ret = sam_rh_enumerate(priv, conn, hport);
if (ret < 0)
{
return ret;
}
}
/* Then let the common usbhost_enumerate do the real enumeration. */
uinfo("Enumerate the device\n");
priv->smstate = SMSTATE_ENUM;
ret = usbhost_enumerate(hport, &hport->devclass);
/* The enumeration may fail either because of some HCD interfaces failure
* or because the device class is not supported. In either case, we just
* need to perform the disconnection operation and make ready for a new
* enumeration.
*/
if (ret < 0)
{
/* Return to the disconnected state */
uerr("ERROR: Enumeration failed: %d\n", ret);
sam_gint_disconnected(priv);
}
return ret;
}
/****************************************************************************
* Name: sam_ep0configure
*
* Description:
* Configure endpoint 0. This method is normally used internally by the
* enumerate() method but is made available at the interface to support an
* external implementation of the enumeration logic.
*
* Input Parameters:
* drvr - The USB host driver instance obtained as a parameter
* from the call to the class create() method.
* ep0 - The (opaque) EP0 endpoint instance
* funcaddr - The USB address of the function containing the
* endpoint that EP0 controls.
* speed - The speed of the port USB_SPEED_LOW, _FULL, or _HIGH
* maxpacketsize - The maximum number of bytes that can be sent to or
* received from the endpoint in a single data packet
*
* Returned Value:
* On success, zero (OK) is returned. On a failure, a negated
* errno value isreturned indicating the nature of the failure
*
* Assumptions:
* This function will *not* be called from an interrupt handler.
*
****************************************************************************/
static int sam_ep0configure(struct usbhost_driver_s *drvr,
usbhost_ep_t ep0,
uint8_t funcaddr,
uint8_t speed,
uint16_t maxpacketsize)
{
struct sam_usbhost_s *priv = (struct sam_usbhost_s *)drvr;
struct sam_pipe_s *pipe;
uinfo("funcaddr=%d speed=%d maxpacketsize=%d\n",
funcaddr, speed, maxpacketsize);
DEBUGASSERT(drvr != NULL && funcaddr < 128 && maxpacketsize <= 64 &&
(unsigned int)ep0 < SAM_USB_NENDPOINTS);
/* We must have exclusive access to the USB host
* hardware and state structures
*/
nxmutex_lock(&priv->lock);
/* Configure the EP0 pipe */
pipe = &priv->pipelist[(unsigned int)ep0];
pipe->funcaddr = funcaddr;
pipe->speed = speed;
pipe->maxpacket = maxpacketsize;
sam_pipe_configure(priv, pipe->idx);
nxmutex_unlock(&priv->lock);
return OK;
}
/****************************************************************************
* Name: sam_epalloc
*
* Description:
* Allocate and configure one endpoint.
*
* Input Parameters:
* drvr - The USB host driver instance obtained as a
* parameter from the call to the class create() method.
* epdesc - Describes the endpoint to be allocated.
* ep - A memory location provided by the caller in which to receive the
* allocated endpoint descriptor.
*
* Returned Value:
* On success, zero (OK) is returned. On a failure, a negated errno value
* is returned indicating the nature of the failure
*
* Assumptions:
* This function will *not* be called from an interrupt handler.
*
****************************************************************************/
static int sam_epalloc(struct usbhost_driver_s *drvr,
const struct usbhost_epdesc_s *epdesc,
usbhost_ep_t *ep)
{
struct sam_usbhost_s *priv = (struct sam_usbhost_s *)drvr;
int ret;
uwarn("addr=%d in=%d xfrtype=%d interval=%d mxpacketsize=%d\n",
epdesc->addr, epdesc->in, epdesc->xfrtype,
epdesc->interval, epdesc->mxpacketsize);
/* Sanity check. NOTE that this method should only be called if a device
* is connected (because we need a valid low speed indication).
*/
DEBUGASSERT(drvr != 0 && epdesc != NULL && ep != NULL);
/* We must have exclusive access to the USB
* host hardware and state structures
*/
nxmutex_lock(&priv->lock);
/* Handler control pipes differently from other endpoint types. This is
* because the normal, "transfer" endpoints are unidirectional an require
* only a single pipe. Control endpoints, however, are bi-diretional
* and require two pipes, one for the IN and one for the OUT direction.
*/
if (epdesc->xfrtype == USB_EP_ATTR_XFER_CONTROL)
{
ret = sam_ctrlep_alloc(priv, epdesc, ep);
}
else
{
ret = sam_xfrep_alloc(priv, epdesc, ep);
}
nxmutex_unlock(&priv->lock);
return ret;
}
/****************************************************************************
* Name: sam_epfree
*
* Description:
* Free and endpoint previously allocated by DRVR_EPALLOC.
*
* Input Parameters:
* drvr - The USB host driver instance obtained as a parameter from the
* call to the class create() method.
* ep - The endpoint to be freed.
*
* Returned Value:
* On success, zero (OK) is returned. On a failure, a negated errno value
* is returned indicating the nature of the failure
*
* Assumptions:
* This function will *not* be called from an interrupt handler.
*
****************************************************************************/
static int sam_epfree(struct usbhost_driver_s *drvr, usbhost_ep_t ep)
{
struct sam_usbhost_s *priv = (struct sam_usbhost_s *)drvr;
DEBUGASSERT(priv);
/* We must have exclusive access to the
* USB host hardware and state structures
*/
nxmutex_lock(&priv->lock);
/* Halt the pipe and mark the pipe available */
sam_pipe_free(priv, (intptr_t)ep);
nxmutex_unlock(&priv->lock);
return OK;
}
/****************************************************************************
* Name: sam_alloc
*
* Description:
* Some hardware supports special memory in which request and descriptor
* data can be accessed more efficiently.
* This method provides a mechanism to allocate
* the request/descriptor memory. If the underlying hardware does
* not support such "special" memory, this functions
* may simply map to kmm_malloc.
* This interface was optimized under a particular assumption. It was
* assumed that the driver maintains a pool of small, pre-allocated buffers
* for descriptor traffic. NOTE that size is not an input, but an output:
* The size of the pre-allocated buffer is returned.
*
* Input Parameters:
* drvr - The USB host driver instance obtained as a parameter
* from the call to the class create() method.
* buffer - The address of a memory location provided by
* the caller in which to return the allocated buffer memory address.
* maxlen - The address of a memory location provided by
* the caller in which to return the maximum size of the allocated
* buffer memory.
*
* Returned Value:
* On success, zero (OK) is returned. On a failure, a negated errno value
* is returned indicating the nature of the failure
*
* Assumptions:
* - Called from a single thread so no mutual exclusion is required.
* - Never called from an interrupt handler.
*
****************************************************************************/
static int sam_alloc(struct usbhost_driver_s *drvr,
uint8_t **buffer, size_t *maxlen)
{
uint8_t *alloc;
uinfo("ENTRY\n");
DEBUGASSERT(drvr && buffer && maxlen);
/* There is no special memory requirement for the SAM. */
alloc = kmm_malloc(CONFIG_SAM_DESCSIZE);
if (!alloc)
{
return -ENOMEM;
}
/* Return the allocated address and size of the descriptor buffer */
*buffer = alloc;
*maxlen = CONFIG_SAM_DESCSIZE;
return OK;
}
/****************************************************************************
* Name: sam_free
*
* Description:
* Some hardware supports special memory in which request and descriptor
* data can be accessed more efficiently.
* This method provides a mechanism to free that
* request/descriptor memory. If the underlying hardware does not support
* such "special" memory, this functions may simply map to kmm_free().
*
* Input Parameters:
* drvr - The USB host driver instance obtained as a parameter
* from the call to the class create() method.
* buffer - The address of the allocated buffer memory to be freed.
*
* Returned Value:
* On success, zero (OK) is returned. On a failure, a negated errno value
* is returned indicating the nature of the failure
*
* Assumptions:
* - Never called from an interrupt handler.
*
****************************************************************************/
static int sam_free(struct usbhost_driver_s *drvr, uint8_t *buffer)
{
/* There is no special memory requirement */
uinfo("ENTRY\n");
DEBUGASSERT(drvr && buffer);
kmm_free(buffer);
return OK;
}
/****************************************************************************
* Name: sam_ioalloc
*
* Description:
* Some hardware supports special memory in which larger IO buffers can
* be accessed more efficiently. This method provides a mechanism to
* allocate the request/descriptor memory.
* If the underlying hardware does not support
* such "special" memory, this functions may simply map to kmm_malloc.
*
* This interface differs from DRVR_ALLOC in that the
* buffers are variable-sized.
*
* Input Parameters:
* drvr - The USB host driver instance obtained as a parameter from the
* call to the class create() method.
* buffer - The address of a memory location provided by the caller
* in which to return the allocated buffer memory address.
* buflen - The size of the buffer required.
*
* Returned Value:
* On success, zero (OK) is returned. On a failure, a negated errno
* value is returned indicating the nature of the failure
*
* Assumptions:
* This function will *not* be called from an interrupt handler.
*
****************************************************************************/
static int sam_ioalloc(struct usbhost_driver_s *drvr,
uint8_t **buffer, size_t buflen)
{
uint8_t *alloc;
uinfo("ENTRY\n");
DEBUGASSERT(drvr && buffer && buflen > 0);
/* There is no special memory requirement */
alloc = kmm_malloc(buflen);
if (!alloc)
{
return -ENOMEM;
}
/* Return the allocated buffer */
*buffer = alloc;
return OK;
}
/****************************************************************************
* Name: sam_iofree
*
* Description:
* Some hardware supports special memory in which IO data can
* be accessed more efficiently.
* This method provides a mechanism to free that IO buffer
* memory. If the underlying hardware does not support such
*
* Input Parameters:
* drvr - The USB host driver instance obtained as a parameter from the
* call to the class create() method.
* buffer - The address of the allocated buffer memory to be freed.
*
* Returned Value:
* On success, zero (OK) is returned. On a failure, a negated errno
* value is returned indicating the nature of the failure
*
* Assumptions:
* This function will *not* be called from an interrupt handler.
*
****************************************************************************/
static int sam_iofree(struct usbhost_driver_s *drvr, uint8_t *buffer)
{
/* There is no special memory requirement */
uinfo("ENTRY\n");
DEBUGASSERT(drvr && buffer);
kmm_free(buffer);
return OK;
}
/****************************************************************************
* Name: sam_ctrlin and sam_ctrlout
*
* Description:
* Process a IN or OUT request on the control endpoint. These methods
* will enqueue the request and wait for it to complete.
* Only one transfer may be queued;
* Neither these methods nor the transfer() method can be called again
* until the control transfer functions returns.
*
* These are blocking methods; these functions will not return until the
* control transfer has completed.
*
* Input Parameters:
* drvr - The USB host driver instance obtained as a parameter from the
* call to the class create() method.
* ep0 - The control endpoint to send/receive the control request.
* req - Describes the request to be sent. This request must lie
* in memory created by DRVR_ALLOC.
* buffer - A buffer used for sending the request and for returning any
* responses. This buffer must be large enough to hold the length value
* in the request description.
* Buffer must have been allocated using DRVR_ALLOC.
*
* NOTE: On an IN transaction, req and buffer may refer to the same
* allocated memory.
*
* Returned Value:
* On success, zero (OK) is returned. On a failure, a negated errno
* value is returned indicating the nature of the failure
*
* Assumptions:
* - Called from a single thread so no mutual exclusion is required.
* - Never called from an interrupt handler.
*
****************************************************************************/
static int sam_ctrlin(struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
const struct usb_ctrlreq_s *req,
uint8_t *buffer)
{
struct sam_usbhost_s *priv = (struct sam_usbhost_s *)drvr;
struct sam_pipe_s *pipe;
uint16_t buflen;
clock_t start;
clock_t elapsed;
int retries;
int ret;
DEBUGASSERT(priv != NULL && req != NULL &&
(unsigned int)ep0 < SAM_USB_NENDPOINTS);
usbhost_vtrace2(SAM_VTRACE2_CTRLIN, req->type, req->req);
pipe = &priv->pipelist[(unsigned int)ep0];
/* Extract values from the request */
buflen = sam_getle16(req->len);
uinfo("type:0x%02x req:0x%02x value:0x%02x%02x index:0x%02x%02x len:%d\n",
req->type, req->req, req->value[1], req->value[0],
req->index[1], req->index[0], buflen);
/* We must have exclusive access to the USB
* host hardware and state structures
*/
nxmutex_lock(&priv->lock);
/* Loop, retrying until the retry time expires */
for (retries = 0; retries < SAM_RETRY_COUNT; retries++)
{
/* Send the SETUP request (TXSTP) */
ret = sam_ctrl_sendsetup(priv, pipe, req);
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_SENDSETUP_FAIL2, -ret);
continue;
}
/* Get the start time. Loop again until the timeout expires */
start = clock_systime_ticks();
do
{
/* Handle the IN data phase (if any) (TRCPT) */
if (buflen > 0)
{
ret = sam_ctrl_recvdata(priv, pipe, buffer, buflen);
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_RECVDATA_FAIL, -ret);
}
}
/* Handle the status OUT phase */
if (ret == OK)
{
ret = sam_ctrl_senddata(priv, pipe, NULL, 0);
if (ret == OK)
{
/* All success transactions exit here */
nxmutex_unlock(&priv->lock);
return OK;
}
usbhost_trace1(SAM_TRACE1_SENDSTATUS_FAIL, ret < 0 ?
-ret : ret);
}
/* Get the elapsed time (in frames) */
elapsed = clock_systime_ticks() - start;
}
while (elapsed < SAM_DATANAK_DELAY);
}
/* All failures exit here after all retries
* and timeouts have been exhausted
*/
nxmutex_unlock(&priv->lock);
return -ETIMEDOUT;
}
static int sam_ctrlout(struct usbhost_driver_s *drvr, usbhost_ep_t ep0,
const struct usb_ctrlreq_s *req,
const uint8_t *buffer)
{
struct sam_usbhost_s *priv = (struct sam_usbhost_s *)drvr;
struct sam_pipe_s *pipe;
uint16_t buflen;
clock_t start;
clock_t elapsed;
int retries;
int ret;
DEBUGASSERT(priv != NULL && req != NULL &&
(unsigned int)ep0 < SAM_USB_NENDPOINTS);
usbhost_vtrace2(SAM_VTRACE2_CTRLOUT, req->type, req->req);
pipe = &priv->pipelist[(unsigned int)ep0];
/* Extract values from the request */
buflen = sam_getle16(req->len);
uinfo("type:0x%02x req:0x%02x value:0x%02x%02x index:0x%02x%02x len:%d\n",
req->type, req->req, req->value[1], req->value[0],
req->index[1], req->index[0], buflen);
/* We must have exclusive access to the
* USB host hardware and state structures
*/
nxmutex_lock(&priv->lock);
/* Loop, retrying until the retry time expires */
for (retries = 0; retries < SAM_RETRY_COUNT; retries++)
{
/* Send the SETUP request */
ret = sam_ctrl_sendsetup(priv, pipe, req);
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_SENDSETUP_FAIL1, -ret);
continue;
}
/* Get the start time. Loop again until the timeout expires */
start = clock_systime_ticks();
do
{
/* Handle the data OUT phase (if any) */
if (buflen > 0)
{
/* Start DATA out transfer (only one DATA packet) */
ret = sam_ctrl_senddata(priv, pipe,
(uint8_t *)buffer, buflen);
if (ret < 0)
{
usbhost_trace1(SAM_TRACE1_SENDDATA_FAIL, -ret);
}
}
/* Handle the status IN phase */
if (ret == OK)
{
ret = sam_ctrl_recvdata(priv, pipe, NULL, 0);
if (ret == OK)
{
/* All success transactins exit here */
nxmutex_unlock(&priv->lock);
return OK;
}
usbhost_trace1(SAM_TRACE1_RECVSTATUS_FAIL,
ret < 0 ? -ret : ret);
}
/* Get the elapsed time (in frames) */
elapsed = clock_systime_ticks() - start;
}
while (elapsed < SAM_DATANAK_DELAY);
}
/* All failures exit here after all retries
* and timeouts have been exhausted
*/
nxmutex_unlock(&priv->lock);
return -ETIMEDOUT;
}
/****************************************************************************
* Name: sam_transfer
*
* Description:
* Process a request to handle a transfer descriptor. This method will
* enqueue the transfer request, blocking until the transfer completes.
* Only one transfer may be queued; Neither this method nor the ctrlin
* or ctrlout methods can be called again until this function returns.
*
* This is a blocking method; this functions will not return until the
* transfer has completed.
*
* Input Parameters:
* drvr - The USB host driver instance obtained as a parameter from the
* call to the class create() method.
* ep - The IN or OUT endpoint descriptor for the device endpoint on
* which to perform the transfer.
* buffer - A buffer containing the data to be sent (OUT endpoint) or
* received (IN endpoint). Buffer must have been allocated using
* DRVR_ALLOC.
* buflen - The length of the data to be sent or received.
*
* Returned Value:
* On success, a non-negative value is returned that indicates the number
* of bytes successfully transferred. On a failure, a negated errno value
* is returned that indicates the nature of the failure:
*
* EAGAIN - If devices NAKs the transfer (or NYET or other error where
* it may be appropriate to restart the entire transaction).
* EPERM - If the endpoint stalls
* EIO - On a TX or data toggle error
* EPIPE - Overrun errors
*
* Assumptions:
* - Called from a single thread so no mutual exclusion is required.
* - Never called from an interrupt handler.
*
****************************************************************************/
static ssize_t sam_transfer(struct usbhost_driver_s *drvr,
usbhost_ep_t ep,
uint8_t *buffer,
size_t buflen)
{
struct sam_usbhost_s *priv = (struct sam_usbhost_s *)drvr;
struct sam_pipe_s *pipe;
unsigned int idx = (unsigned int)ep;
ssize_t nbytes;
uwarn("pipe%d buffer:%p buflen:%d\n", idx, buffer, buflen);
DEBUGASSERT(priv && buffer && idx < SAM_USB_NENDPOINTS && buflen > 0);
pipe = &priv->pipelist[idx];
/* We must have exclusive access to the
* USB host hardware and state structures
*/
nxmutex_lock(&priv->lock);
/* Handle IN and OUT transfer slightly differently */
if (pipe->in)
{
nbytes = sam_in_transfer(priv, pipe, buffer, buflen);
}
else
{
nbytes = sam_out_transfer(priv, pipe, buffer, buflen);
}
nxmutex_unlock(&priv->lock);
return nbytes;
}
/****************************************************************************
* Name: sam_asynch
*
* Description:
* Process a request to handle a transfer descriptor. This method will
* enqueue the transfer request and return immediately. When the transfer
* completes, the callback will be invoked with the provided transfer.
* This method is useful for receiving interrupt transfers which may come
* infrequently.
*
* Only one transfer may be queued; Neither this method nor the ctrlin or
* ctrlout methods can be called again until the transfer completes.
*
* Input Parameters:
* drvr - The USB host driver instance obtained as a parameter from the
* call to the class create() method.
* ep - The IN or OUT endpoint descriptor for the device endpoint on
* which to perform the transfer.
* buffer - A buffer containing the data to be sent (OUT endpoint)
* or received (IN endpoint). Buffer must have been allocated using
* DRVR_ALLOC
* buflen - The length of the data to be sent or received.
* callback - This function will be called when the transfer completes.
* arg - The arbitrary parameter that will be passed to the callback
* function when the transfer completes.
*
* Returned Value:
* On success, zero (OK) is returned. On a failure, a negated errno
* value is returned indicating the nature of the failure
*
* Assumptions:
* - Called from a single thread so no mutual exclusion is required.
* - Never called from an interrupt handler.
*
****************************************************************************/
#ifdef CONFIG_USBHOST_ASYNCH
static int sam_asynch(struct usbhost_driver_s *drvr, usbhost_ep_t ep,
uint8_t *buffer, size_t buflen,
usbhost_asynch_t callback, void *arg)
{
struct sam_usbhost_s *priv = (struct sam_usbhost_s *)drvr;
struct sam_pipe_s *pipe;
unsigned int idx = (unsigned int)ep;
int ret;
uinfo("idx: %d buflen: %d\n", (unsigned int)ep, buflen);
DEBUGASSERT(priv && buffer && idx < SAM_USB_NENDPOINTS && buflen > 0);
pipe = &priv->pipelist[idx];
/* We must have exclusive access to the
* USB host hardware and state structures
*/
nxmutex_lock(&priv->lock);
/* Handle IN and OUT transfer slightly differently */
if (pipe->in)
{
ret = sam_in_asynch(priv, pipe, buffer, buflen, callback, arg);
}
else
{
ret = sam_out_asynch(priv, pipe, buffer, buflen, callback, arg);
}
nxmutex_unlock(&priv->lock);
return ret;
}
#endif /* CONFIG_USBHOST_ASYNCH */
/****************************************************************************
* Name: sam_cancel
*
* Description:
* Cancel a pending transfer on an endpoint. Cancelled synchronous or
* asynchronous transfer will complete normally with the error -ESHUTDOWN.
*
* Input Parameters:
* drvr - The USB host driver instance obtained as a parameter from the
* call to the class create() method.
* ep - The IN or OUT endpoint descriptor for the device endpoint on
* which an asynchronous transfer should be transferred.
*
* Returned Value:
* On success, zero (OK) is returned. On a failure, a negated errno value
* is returned indicating the nature of the failure.
*
****************************************************************************/
static int sam_cancel(struct usbhost_driver_s *drvr, usbhost_ep_t ep)
{
struct sam_usbhost_s *priv = (struct sam_usbhost_s *)drvr;
struct sam_pipe_s *pipe;
unsigned int idx = (unsigned int)ep;
irqstate_t flags;
uinfo("idx: %u: %d\n", idx);
DEBUGASSERT(priv && idx < SAM_USB_NENDPOINTS);
pipe = &priv->pipelist[idx];
/* We need to disable interrupts to avoid race conditions with the
* asynchronous completion of the transfer being cancelled.
*/
flags = enter_critical_section();
/* Halt the pipe */
sam_transfer_abort(priv, pipe, CHREASON_CANCELLED);
pipe->result = -ESHUTDOWN;
/* Is there a thread waiting for this transfer to complete? */
if (pipe->waiter)
{
#ifdef CONFIG_USBHOST_ASYNCH
/* Yes.. there should not also be a callback scheduled */
DEBUGASSERT(pipe->callback == NULL);
#endif
/* Wake'em up! */
nxsem_post(&pipe->waitsem);
pipe->waiter = false;
}
#ifdef CONFIG_USBHOST_ASYNCH
/* No.. is an asynchronous callback expected when the transfer
* completes?
*/
else if (pipe->callback)
{
usbhost_asynch_t callback;
void *arg;
/* Extract the callback information */
callback = pipe->callback;
arg = pipe->arg;
pipe->callback = NULL;
pipe->arg = NULL;
pipe->count = 0;
/* Then perform the callback */
callback(arg, -ESHUTDOWN);
}
#endif
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Name: sam_connect
*
* Description:
* New connections may be detected by an attached hub. This method is the
* mechanism that is used by the hub class to introduce a new connection
* and port description to the system.
*
* Input Parameters:
* drvr - The USB host driver instance obtained as a parameter from the
* call to the class create() method.
* hport - The descriptor of the hub port that detected the connection
* related event
* connected - True: device connected; false: device disconnected
*
* Returned Value:
* On success, zero (OK) is returned. On a failure, a negated errno value
* is returned indicating the nature of the failure.
*
****************************************************************************/
#ifdef CONFIG_USBHOST_HUB
static int sam_connect(struct usbhost_driver_s *drvr,
struct usbhost_hubport_s *hport,
bool connected)
{
struct sam_usbhost_s *priv = (struct sam_usbhost_s *)drvr;
irqstate_t flags;
DEBUGASSERT(priv != NULL && hport != NULL);
/* Set the connected/disconnected flag */
hport->connected = connected;
uinfo("Hub port %d connected: %s\n",
hport->port, connected ? "YES" : "NO");
/* Report the connection event */
flags = enter_critical_section();
priv->hport = hport;
if (priv->pscwait)
{
priv->pscwait = false;
nxsem_post(&priv->pscsem);
}
leave_critical_section(flags);
return OK;
}
#endif
/****************************************************************************
* Name: sam_disconnect
*
* Description:
* Called by the class when an error occurs and driver has been
* disconnected. The USB host driver should discard the handle to the
* class instance (it is stale) and not attempt any further interaction
* with the class driver instance (until a new instance is received from
* the create() method). The driver should not called the class
* disconnected() method.
*
* Input Parameters:
* drvr - The USB host driver instance obtained as a parameter from the
* call to the class create() method.
* hport - The port from which the device is being disconnected.
* Might be a port on a hub.
*
* Returned Value:
* None
*
* Assumptions:
* - Only a single class bound to a single device is supported.
* - Never called from an interrupt handler.
*
****************************************************************************/
static void sam_disconnect(struct usbhost_driver_s *drvr,
struct usbhost_hubport_s *hport)
{
DEBUGASSERT(hport != NULL);
hport->devclass = NULL;
}
/****************************************************************************
* Pipe Helpers
****************************************************************************/
/****************************************************************************
* Name: sam_reset_pipes
*
* Description:
* Reset pipes
* \param priv Pointer to driver instance
* \param[in] warm_reset Handles runtime USB reset
*
****************************************************************************/
static void sam_reset_pipes(struct sam_usbhost_s *priv, bool warm_reset)
{
struct sam_pipe_s *pipe;
uint8_t i;
/* Reset pipes */
for (i = 0; i < SAM_USB_NENDPOINTS; i++)
{
/* Get the pipe structure */
pipe = &priv->pipelist[i];
if (warm_reset)
{
/* Skip free pipes */
if (pipe->pipestate_general == USB_H_PIPE_S_FREE)
continue;
/* Restore physical pipe configurations */
sam_pipe_configure(priv, i);
/* Abort transfer (due to reset) */
if (pipe->eptype == USB_EP_ATTR_XFER_CONTROL)
{
/* Force a callback for control endpoints */
pipe->pipestate_general = USB_H_PIPE_S_SETUP;
}
sam_transfer_terminate(priv, pipe, USB_H_RESET);
}
else
pipe->pipestate_general = USB_H_PIPE_S_FREE;
}
}
/****************************************************************************
* Name: sam_pipe_reset
*
* Description:
* Reset and disable one pipe.
*
****************************************************************************/
static void sam_pipe_reset(struct sam_usbhost_s *priv, uint8_t epno)
{
struct sam_pipe_s *pipe = &priv->pipelist[epno];
uinfo("pipe%d\n", pipe->idx);
/* Disable pipe interrupts */
sam_putreg8(0x3f, SAM_USBHOST_PINTENCLR(epno));
sam_putreg8(0x3f, SAM_USBHOST_PINTFLAG(epno));
/* Reset pipe status */
pipe->pipestate = USB_H_PIPE_S_FREE;
pipe->stalled = false;
pipe->pending = false;
pipe->halted = false;
pipe->zlpsent = false;
pipe->txbusy = false;
pipe->rxactive = false;
}
/****************************************************************************
* Name: sam_pipeset_reset
*
* Description:
* Reset and disable a set of pipes.
*
****************************************************************************/
static void sam_pipeset_reset(struct sam_usbhost_s *priv, uint16_t epset)
{
uint32_t bit;
int epno;
uinfo("ENTRY\n");
/* Reset each pipe in the set */
for (epno = 0, bit = 1, epset &= SAM_EPSET_ALL;
epno < SAM_USB_NENDPOINTS && epset != 0;
epno++, bit <<= 1)
{
/* Is this pipe in the set? */
if ((epset & bit) != 0)
{
sam_pipe_reset(priv, epno);
epset &= ~bit;
}
}
}
/****************************************************************************
* Name: sam_vbusdrive
*
* Description:
* Drive the Vbus +5V.
*
* Input Parameters:
* priv - USB host driver private data structure.
* state - True: Drive, False: Don't drive
*
* Returned Value:
* None.
*
****************************************************************************/
static void sam_vbusdrive(struct sam_usbhost_s *priv, bool state)
{
/* Enable/disable the external charge pump */
sam_usbhost_vbusdrive(0, state);
up_mdelay(200);
}
/****************************************************************************
* Name: sam_pipe_interrupt
*
* Description:
* Handle the USB pipe interrupt
*
****************************************************************************/
static void sam_pipe_interrupt(struct sam_usbhost_s *priv, int idx)
{
struct sam_pipe_s *pipe;
uint8_t pipisr;
uint8_t pipimr;
DEBUGASSERT((unsigned)idx < SAM_USB_NENDPOINTS);
/* Get the pipe structure */
pipe = &priv->pipelist[idx];
/* Get the pipe irq */
pipisr = sam_getreg8(SAM_USBHOST_PINTFLAG(idx));
pipimr = sam_getreg8(SAM_USBHOST_PINTENSET(idx));
uinfo("pipe%d PINTFLAG:0x%x PINTENSET:0x%x\n", idx, pipisr, pipimr);
/* Host pipe stall interrupt */
if (pipisr & USBHOST_PINTFLAG_STALL)
{
/* clear the flag */
sam_putreg8(USBHOST_PINTFLAG_STALL, SAM_USBHOST_PINTFLAG(idx));
uwarn("STALL =0x%x pktsize=0x%x ctrlpipe=0x%x statuspipe=0x%x\n",
pipisr,
pipe->descb[0]->pktsize,
pipe->descb[0]->ctrlpipe,
pipe->descb[0]->statuspipe);
sam_transfer_abort(priv, pipe, USB_H_STALL);
return;
}
/* Host pipe error interrupt */
if (pipisr & USBHOST_PINTFLAG_PERR)
{
/* clear the flag */
sam_putreg8(USBHOST_PINTFLAG_PERR, SAM_USBHOST_PINTFLAG(idx));
uwarn("PERR =0x%x pktsize=0x%x ctrlpipe=0x%x statuspipe=0x%x\n",
pipisr,
pipe->descb[0]->pktsize,
pipe->descb[0]->ctrlpipe,
pipe->descb[0]->statuspipe);
/* Get and ACK error */
switch (pipe->descb[0]->statuspipe & (USBHOST_STATUSPIPE_DTGLER |
USBHOST_STATUSPIPE_TOUTER |
USBHOST_STATUSPIPE_DAPIDER))
{
case USBHOST_STATUSPIPE_DTGLER:
pipe->pipestatus_general = USB_H_ERR;
break;
case USBHOST_STATUSPIPE_TOUTER:
pipe->pipestatus_general = USB_H_TIMEOUT;
pipe->result = -ETIMEDOUT;
break;
case USBHOST_STATUSPIPE_PIDER:
case USBHOST_STATUSPIPE_DAPIDER:
default:
pipe->pipestatus_general = USB_H_ERR;
break;
}
pipe->descb[0]->statuspipe = 0;
sam_transfer_abort(priv, pipe, USB_H_ERR);
return;
}
/* Host pipe transfer fail interrupt */
if (pipisr & USBHOST_PINTFLAG_TRFAIL)
{
/* clear the flag */
sam_putreg8(USBHOST_PINTFLAG_TRFAIL, SAM_USBHOST_PINTFLAG(idx));
uwarn("TRFAIL =0x%x pktsize=0x%x ctrlpipe=0x%x statuspipe=0x%x\n",
pipisr,
pipe->descb[0]->pktsize,
pipe->descb[0]->ctrlpipe,
pipe->descb[0]->statuspipe);
uint8_t status_bk = pipe->descb[0]->stausbk;
if (status_bk)
{
pipe->descb[0]->stausbk = 0;
/* Ignore ERRORFLOW and handle CRCERR */
if (pipe->eptype != USB_EP_ATTR_XFER_ISOC &&
status_bk == USBHOST_STATUSBK_ERRORFLOW)
{
/* Ignore ERRORFLOW on none ISO pipes */
}
else
{
pipe->pipestatus_general = USB_H_ERR;
sam_transfer_abort(priv, pipe, USB_H_ERR);
}
}
return;
}
/* TRCPT: transfer complete */
if ((pipisr & pipimr & (USBHOST_PINTFLAG_TRCPT0 |
USBHOST_PINTFLAG_TRCPT1)) != 0)
{
/* clear the flag */
sam_putreg8((USBHOST_PINTFLAG_TRCPT0 |
USBHOST_PINTFLAG_TRCPT1), SAM_USBHOST_PINTFLAG(idx));
if ((sam_getreg8(SAM_USBHOST_PCFG(idx)) & USBHOST_PCFG_PTOKEN_MASK) ==
USBHOST_PCFG_PTOKEN_IN)
{
uwarn("pipe%d IN TRCPT pktsize=0x%x\n",
idx,
pipe->descb[0]->pktsize);
if (idx > 0)
sam_recv_continue(priv, pipe);
pipe->result = 0;
sam_pipe_wakeup(priv, pipe);
}
else
{
uwarn("pipe%d OUT TRCPT pktsize=0x%x\n",
idx,
pipe->descb[0]->pktsize);
if (idx > 0)
sam_send_continue(priv, pipe);
pipe->result = 0;
sam_pipe_wakeup(priv, pipe);
}
return;
}
/* Host pipe transmitted setup interrupt */
if (pipisr & pipimr & USBHOST_PINTFLAG_TXSTP)
{
/* clear the flag */
sam_putreg8(USBHOST_PINTFLAG_TXSTP, SAM_USBHOST_PINTFLAG(idx));
sam_putreg8(USBHOST_PINTFLAG_TXSTP, SAM_USBHOST_PINTENCLR(idx));
/* Reset data toggle for DATA */
sam_putreg8(USBHOST_PSTATUS_DTGL, SAM_USBHOST_PSTATUSSET(idx));
/* Start DATA phase */
if (priv->ctrl_buffer[0] & 0x80) /* 1 = Device to Host */
{
/* IN */
uwarn("pipe%d IN TXSTP\n", idx);
pipe->pipestate = USB_H_PIPE_S_DATI; /* Pipe in data IN stage */
/* Start IN requests */
pipe->result = 0;
sam_pipe_wakeup(priv, pipe);
}
else
{
/* OUT */
uwarn("pipe%d OUT TXSTP\n", idx);
if (priv->ctrl_buffer[6] || priv->ctrl_buffer[7]) /* setup packet wLength[2] */
{
pipe->pipestate = USB_H_PIPE_S_DATO; /* Pipe in data OUT stage */
/* Start OUT */
pipe->result = 0;
sam_pipe_wakeup(priv, pipe);
}
else
{
/* No DATA phase */
uwarn("pipe%d OUT TXSTP ZLP\n", idx);
pipe->pipestate = USB_H_PIPE_S_STATI; /* Pipe in control status IN stage */
/* Start IN ZLP request */
pipe->result = 0;
sam_pipe_wakeup(priv, pipe);
}
}
return;
}
}
/****************************************************************************
* Name: sam_usbhost_interrupt
*
* Description:
* Handle the USB interrupt.
* Host Mode
*
****************************************************************************/
static int sam_usbhost_interrupt(int irq, void *context, void *arg)
{
struct sam_usbhost_s *priv = (struct sam_usbhost_s *)arg;
uint16_t isr;
uint16_t imr;
uint16_t regval;
uint16_t pending;
uint16_t pendingpipe;
int i;
/* Get the set of pending device interrupts */
isr = sam_getreg16(SAM_USBHOST_INTFLAG);
imr = sam_getreg16(SAM_USBHOST_INTENSET);
pending = isr & imr;
/* Get the set of pending Pipe interrupts */
pendingpipe = sam_getreg16(SAM_USBHOST_PINTSMRY);
/* Handle all pending USB interrupts */
/* Serve Pipe Interrupts first */
if (pendingpipe)
{
usbhost_vtrace1(TRACE_INTDECODE(SAM_TRACEINTID_PENDING_PIPE),
pendingpipe);
for (i = 0; i < SAM_USB_NENDPOINTS; i++)
{
if ((pendingpipe & USBHOST_PINTSMRY_PIPEINT(i)))
{
usbhost_vtrace1(TRACE_INTDECODE(SAM_TRACEINTID_PIPENO),
(uint16_t)i);
sam_pipe_interrupt(priv, i);
}
}
}
else
{
/* Host SOF interrupt */
if (isr & USBHOST_INT_HSOF) /* Clear the flag */
sam_putreg16(USBHOST_INT_HSOF, SAM_USBHOST_INTFLAG);
if (isr & USBHOST_INT_RST) /* Host reset interrupt */
{
sam_putreg16(USBHOST_INT_RST, SAM_USBHOST_INTFLAG);
sam_reset_pipes(priv, true);
return OK;
}
else if (pending & USBHOST_INT_DDISC)
{
/* Host disconnect interrupt */
/* clear the flag */
sam_putreg16((USBHOST_INT_DDISC |
USBHOST_INT_DCONN), SAM_USBHOST_INTFLAG);
/* Disable disconnect interrupt.
* Disable wakeup/resumes interrupts,
* in case of disconnection during suspend mode
*/
sam_putreg16((USBHOST_INT_DDISC |
USBHOST_INT_WAKEUP |
USBHOST_INT_DNRSM |
USBHOST_INT_UPRSM), SAM_USBHOST_INTENCLR);
/* Stop reset signal, in case of disconnection during reset */
regval = sam_getreg16(SAM_USBHOST_CTRLB);
regval &= ~USBHOST_CTRLB_BUSRESET;
sam_putreg16(regval, SAM_USBHOST_CTRLB);
/* Enable connection and wakeup interrupts */
sam_putreg16((USBHOST_INT_DCONN |
USBHOST_INT_WAKEUP |
USBHOST_INT_DNRSM |
USBHOST_INT_UPRSM), SAM_USBHOST_INTFLAG);
sam_putreg16((USBHOST_INT_DCONN |
USBHOST_INT_WAKEUP |
USBHOST_INT_DNRSM |
USBHOST_INT_UPRSM), SAM_USBHOST_INTENSET);
priv->suspend_start = 0;
priv->resume_start = 0;
/* PORT_CONNECTION: connect status changed */
sam_gint_disconnected(priv);
return OK;
}
else if (pending & USBHOST_INT_DCONN)
{
/* Host connect interrupt */
/* clear the flag */
/* Reserve the CONN flag for connection check */
sam_putreg16 (USBHOST_INT_DCONN, SAM_USBHOST_INTENCLR);
/* Enable disconnection interrupt */
sam_putreg16(USBHOST_INT_DDISC, SAM_USBHOST_INTFLAG);
sam_putreg16(USBHOST_INT_DDISC, SAM_USBHOST_INTENSET);
/* Enable SOF */
regval = sam_getreg16(SAM_USBHOST_CTRLB);
regval |= USBHOST_CTRLB_SOFE | USBHOST_CTRLB_BUSRESET;
sam_putreg16(regval, SAM_USBHOST_CTRLB);
priv->suspend_start = 0;
priv->resume_start = 0;
/* PORT_CONNECTION: connect status changed */
sam_gint_connected(priv);
return OK;
}
/* Wake up to power */
if ((isr & USBHOST_INT_WAKEUP) && (imr & USBHOST_INT_DCONN))
{
/* clear the flag */
sam_putreg16(USBHOST_INT_WAKEUP, SAM_USBHOST_INTFLAG);
sam_vbusdrive(priv, true);
}
/* Resume */
if (pending & (USBHOST_INT_WAKEUP |
USBHOST_INT_UPRSM |
USBHOST_INT_DNRSM))
{
sam_putreg16((USBHOST_INT_WAKEUP |
USBHOST_INT_UPRSM |
USBHOST_INT_DNRSM), SAM_USBHOST_INTFLAG);
sam_putreg16((USBHOST_INT_WAKEUP |
USBHOST_INT_UPRSM |
USBHOST_INT_DNRSM), SAM_USBHOST_INTENCLR);
sam_putreg16((USBHOST_INT_RST |
USBHOST_INT_DDISC), SAM_USBHOST_INTENSET);
/* Enable SOF */
regval = sam_getreg16(SAM_USBHOST_CTRLB);
regval |= USBHOST_CTRLB_RESUME | USBHOST_CTRLB_SOFE;
sam_putreg16(regval, SAM_USBHOST_CTRLB);
/* Wait 50ms before restarting transfer */
priv->resume_start = 50;
sam_add_sof_user(priv);
return OK;
}
}
/* Just ignore unexpected interrupts */
sam_putreg16(isr, SAM_USBHOST_INTFLAG);
return OK;
}
/****************************************************************************
* Initialization/Reset
****************************************************************************/
/****************************************************************************
* Name: sam_hostreset
****************************************************************************/
static void sam_hostreset(struct sam_usbhost_s *priv)
{
uint16_t regval;
uinfo("ENTRY\n");
/* Make sure that clocking is enabled to the USB peripheral. */
sam_enableclks();
priv->hoststate = USB_HOSTSTATE_DEFAULT;
regval = sam_getreg16(SAM_USBHOST_CTRLB);
regval &= ~USBHOST_CTRLB_BUSRESET;
sam_putreg16(regval, SAM_USBHOST_CTRLB);
/* Clear all pending interrupt status */
regval = USBHOST_INT_HSOF | USBHOST_INT_RST | USBHOST_INT_WAKEUP |
USBHOST_INT_DNRSM | USBHOST_INT_UPRSM | USBHOST_INT_RAMACER |
USBHOST_INT_DCONN | USBHOST_INT_DDISC;
sam_putreg16(regval, SAM_USBHOST_INTFLAG);
/* Enable normal operational interrupts */
regval = USBHOST_INT_WAKEUP | USBHOST_INT_DDISC;
sam_putreg16(regval, SAM_USBHOST_INTENSET);
sam_dumppipe(priv, EP0);
}
/****************************************************************************
* Name: sam_host_initialize
*
* Description:
* Initialize/re-initialize hardware for host mode operation. At present,
* this function is called only from sam_hw_initialize(). But if OTG mode
* were supported, this function would also be called to switch between
* host and device modes on a connector ID change interrupt.
*
* Input Parameters:
* priv -- USB host driver private data structure.
*
* Returned Value:
* None.
*
****************************************************************************/
static void sam_host_initialize(struct sam_usbhost_s *priv)
{
/* Drive Vbus +5V (the smoke test). Should be done elsewhere in OTG mode. */
sam_vbusdrive(priv, true);
/* Enable interrupts to detect connection */
sam_putreg16(USBHOST_INT_DCONN |
USBHOST_INT_RST |
USBHOST_INT_WAKEUP, SAM_USBHOST_INTENSET);
}
/****************************************************************************
* Name: sam_sw_initialize
*
* Description:
* One-time setup of the host driver state structure.
*
* Input Parameters:
* priv -- USB host driver private data structure.
*
* Returned Value:
* None.
*
****************************************************************************/
static inline void sam_sw_initialize(struct sam_usbhost_s *priv)
{
struct usbhost_driver_s *drvr;
struct usbhost_hubport_s *hport;
int epno;
/* Initialize the device operations */
drvr = &priv->drvr;
drvr->ep0configure = sam_ep0configure;
drvr->epalloc = sam_epalloc;
drvr->epfree = sam_epfree;
drvr->alloc = sam_alloc;
drvr->free = sam_free;
drvr->ioalloc = sam_ioalloc;
drvr->iofree = sam_iofree;
drvr->ctrlin = sam_ctrlin;
drvr->ctrlout = sam_ctrlout;
drvr->transfer = sam_transfer;
#ifdef CONFIG_USBHOST_ASYNCH
drvr->asynch = sam_asynch;
#endif
drvr->cancel = sam_cancel;
#ifdef CONFIG_USBHOST_HUB
drvr->connect = sam_connect;
#endif
drvr->disconnect = sam_disconnect;
/* Initialize the public port representation */
hport = &priv->rhport.hport;
hport->drvr = drvr;
#ifdef CONFIG_USBHOST_HUB
hport->parent = NULL;
#endif
hport->ep0 = priv->ep0;
hport->speed = USB_SPEED_FULL;
/* Initialize function address generation logic */
usbhost_devaddr_initialize(&priv->devgen);
priv->rhport.pdevgen = &priv->devgen;
/* Initialize the pipe list */
for (epno = 0; epno < SAM_USB_NENDPOINTS; epno++)
{
priv->pipelist[epno].idx = epno;
nxsem_init(&priv->pipelist[epno].waitsem, 0, 0);
sam_putreg8(USBHOST_PSTATUS_PFREEZE, SAM_USBHOST_PSTATUSSET(epno));
/* set descriptor addresses */
priv->pipelist[epno].descb[0] = &priv->pipe_descriptors[(epno << 1)];
priv->pipelist[epno].descb[1] = &priv->pipe_descriptors[(epno << 1) + 1];
}
sam_reset_pipes(priv, false);
/* Initialize the driver state data */
priv->smstate = SMSTATE_DETACHED;
priv->connected = false;
priv->change = false;
priv->irqset = 0;
}
/****************************************************************************
* Name: sam_hw_initialize
*
* Description:
* One-time setup of the host controller hardware for normal operations.
*
* Input Parameters:
* priv -- USB host driver private data structure.
*
* Returned Value:
* Zero on success; a negated errno value on failure.
*
****************************************************************************/
static inline int sam_hw_initialize(struct sam_usbhost_s *priv)
{
uint16_t regval;
uint32_t padcalib;
uint8_t calib_transn;
uint8_t calib_transp;
uint8_t calib_trim;
/* Set up the USB DP/DM pins */
sam_portconfig(PORT_USB_DP | PORT_PULL_NONE);
sam_portconfig(PORT_USB_DM | PORT_PULL_NONE);
/* To use the USB, the programmer must first configure
* inputthe USB clock
*/
sam_enableclks();
/* full reset USB */
sam_ctrla_write(USB_CTRLA_SWRST);
/* Load USB factory calibration values from NVRAM */
calib_transn = (getreg32(SAM_FUSES_USBTRANSN_ADDR) &
SAM_FUSES_USBTRANSN_MASK) >> SAM_FUSES_USBTRANSN_SHIFT;
if (calib_transn == 0 || calib_transn == 0x1f)
calib_transn = 0x9;
calib_transp = (getreg32(SAM_FUSES_USBTRANSP_ADDR) &
SAM_FUSES_USBTRANSP_ADDR) >> SAM_FUSES_USBTRANSP_SHIFT;
if (calib_transp == 0 || calib_transp == 0x1f)
calib_transp = 0x19;
calib_trim = (getreg32(SAM_FUSES_USBTRIM_ADDR) &
SAM_FUSES_USBTRIM_MASK) >> SAM_FUSES_USBTRIM_SHIFT;
if (calib_trim == 0 || calib_trim == 0x7)
calib_trim = 0x6;
padcalib = USB_PADCAL_TRANSP(calib_transp) |
USB_PADCAL_TRANSN(calib_transn) |
USB_PADCAL_TRIM(calib_trim);
sam_putreg16(padcalib, SAM_USB_PADCAL);
uinfo("PADCAL: 0x%x\n", padcalib); /* 0x6259 */
sam_putreg8(USB_QOSCTRL_CQOS(3) | USB_QOSCTRL_DQOS(3), SAM_USB_QOSCTRL);
/* Enable USB core */
sam_ctrla_write(USB_CTRLA_ENABLE |
USB_CTRLA_RUNSTBY |
USB_CTRLA_MODE_HOST);
/* Reset and disable pipes */
sam_pipeset_reset(priv, SAM_EPSET_ALL);
/* clear all previous descriptor data so no accidental
* DMA transfers could happen
*/
memset((uint8_t *)(&priv->pipe_descriptors[0]), 0,
sizeof(priv->pipe_descriptors));
/* Init descriptor base address */
sam_putreg32((uint32_t)&priv->pipe_descriptors, SAM_USB_DESCADD);
regval = USBHOST_CTRLB_SOFE |
USBHOST_CTRLB_SPDCONF_LF |
USBHOST_CTRLB_VBUSOK;
sam_putreg16(regval, SAM_USBHOST_CTRLB);
/* Disable all interrupts */
sam_putreg16(USBHOST_INT_HSOF | USBHOST_INT_RST | USBHOST_INT_WAKEUP |
USBHOST_INT_DNRSM | USBHOST_INT_UPRSM | USBHOST_INT_RAMACER |
USBHOST_INT_DCONN | USBHOST_INT_DDISC,
SAM_USBHOST_INTENCLR);
/* Initialize host mode and return success */
sam_host_initialize(priv);
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: sam_usbhost_initialize
*
* Description:
* Initialize USB host device controller hardware.
*
* Input Parameters:
* controller -- If the device supports more than USB host controller, then
* this identifies which controller is being initialized. Normally, this
* is just zero.
*
* Returned Value:
* And instance of the USB host interface. The controlling task should
* use this interface to (1) call the wait() method to wait for a device
* to be connected, and (2) call the enumerate() method to bind the device
* to a class driver.
*
* Assumptions:
* - This function should called in the initialization sequence in order
* to initialize the USB device functionality.
* - Class drivers should be initialized prior to calling this function.
* Otherwise, there is a race condition if the device is already connected.
*
****************************************************************************/
struct usbhost_connection_s *sam_usbhost_initialize(int controller)
{
/* At present, there is only support for a single OTG FS host. Hence it is
* pre-allocated as g_usbhost. However, in most code, the private data
* structure will be referenced using the 'priv' pointer (rather than the
* global data) in order to simplify any
* future support for multiple devices.
*/
struct sam_usbhost_s *priv = &g_usbhost;
/* Sanity checks */
DEBUGASSERT(controller == 0);
/* Reset the state of the host driver */
sam_sw_initialize(priv);
/* Initialize the USB core */
sam_hw_initialize(priv);
priv->suspend_start = 0;
priv->resume_start = 0;
priv->pipes_unfreeze = 0;
priv->n_ctrl_req_user = 0;
priv->n_sof_user = 0;
/* Attach USB host controller interrupt handler */
if (irq_attach(SAM_IRQ_USB, sam_usbhost_interrupt, priv) != 0)
usbhost_vtrace1(TRACE_DEVERROR(SAM_TRACEERR_IRQREGISTRATION),
(uint16_t)SAM_IRQ_USB);
if (irq_attach(SAM_IRQ_USBSOF, sam_usbhost_interrupt, priv) != 0)
usbhost_vtrace1(TRACE_DEVERROR(SAM_TRACEERR_IRQREGISTRATION),
(uint16_t)SAM_IRQ_USBSOF);
if (irq_attach(SAM_IRQ_USBTRCPT0, sam_usbhost_interrupt, priv) != 0)
usbhost_vtrace1(TRACE_DEVERROR(SAM_TRACEERR_IRQREGISTRATION),
(uint16_t)SAM_IRQ_USBTRCPT0);
if (irq_attach(SAM_IRQ_USBTRCPT1, sam_usbhost_interrupt, priv) != 0)
usbhost_vtrace1(TRACE_DEVERROR(SAM_TRACEERR_IRQREGISTRATION),
(uint16_t)SAM_IRQ_USBTRCPT1);
/* Enable interrupts at the interrupt controller */
up_enable_irq(SAM_IRQ_USB);
up_enable_irq(SAM_IRQ_USBSOF);
up_enable_irq(SAM_IRQ_USBTRCPT0);
up_enable_irq(SAM_IRQ_USBTRCPT1);
return &g_usbconn;
}
#endif /* CONFIG_USBHOST */
#endif /* CONFIG_SAMD5E5_USB */