59bbcfdb创建于 4月21日历史提交
/****************************************************************************
 * arch/arm/src/samd5e5/sam_usb.c
 *
 * 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/mutex.h>
#include <nuttx/spinlock.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 Type Definitions
 ****************************************************************************/

#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 desciptive 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 desciptive 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 protocoll
       */

      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
 *   re-used 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 enpoint 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 wakup 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.
           */

          nxsig_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 nxsig_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)
                    {
                      nxsig_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. */

  nxsig_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 swtich 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 harware 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 */