Skip to content
Snippets Groups Projects
lpc214x_usbdev.c 80.7 KiB
Newer Older
/*******************************************************************************
 * arch/arm/src/lpc214x/lpc214x_usbdev.c
 *
 *   Copyright (C) 2008 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <spudmonkey@racsa.co.cr>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 *******************************************************************************/

/*******************************************************************************
 * Included Files
 *******************************************************************************/

#include <nuttx/config.h>
#include <sys/types.h>

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <queue.h>
#include <debug.h>

#include <nuttx/arch.h>
#include <nuttx/usb.h>
#include <nuttx/usbdev.h>
#include <nuttx/usbdev_trace.h>

#include <arch/irq.h>
#include <arch/board/board.h>

#include "up_arch.h"
#include "up_internal.h"
#include "lpc214x_usbdev.h"
#include "lpc214x_power.h"

/*******************************************************************************
 * Definitions
 *******************************************************************************/

/* Configuration ***************************************************************/

#ifndef CONFIG_USBDEV_EP0_MAXSIZE
#  define CONFIG_USBDEV_EP0_MAXSIZE 64
#endif

#ifndef  CONFIG_USBDEV_MAXPOWER
#  define CONFIG_USBDEV_MAXPOWER 100  /* mA */
#endif

#define USB_SLOW_INT USBDEV_DEVINT_EPSLOW
#define USB_DEVSTATUS_INT USBDEV_DEVINT_DEVSTAT

#ifdef CONFIG_LPC214X_USBDEV_EPFAST_INTERRUPT
#  define USB_FAST_INT USBDEV_DEVINT_EPFAST
#else
#  define USB_FAST_INT 0
#endif

/* Extremely detailed register debug that you would normally never want
 * enabled.
 */

#undef CONFIG_LPC214X_USBDEV_REGDEBUG

/* Enable reading SOF from interrupt handler vs. simply reading on demand.  Probably
 * a bad idea... Unless there is some issue with sampling the SOF from hardware
 * asynchronously.
 */

#ifdef CONFIG_LPC214X_USBDEV_FRAME_INTERRUPT
#  define USB_FRAME_INT USBDEV_DEVINT_FRAME
#else
#  define USB_FRAME_INT 0
#endif

#ifdef CONFIG_DEBUG
#  define USB_ERROR_INT USBDEV_DEVINT_EPRINT
#else
#  define USB_ERROR_INT 0
#endif

/* Number of DMA descriptors */

#ifdef CONFIG_LPC214X_USBDEV_DMA
# error DMA SUPPORT NOT YET FULLY IMPLEMENTED
#  ifndef CONFIG_LPC214X_USBDEV_NDMADESCRIPTORS
#    define CONFIG_LPC214X_USBDEV_NDMADESCRIPTORS 8
#  elif CONFIG_LPC214X_USBDEV_NDMADESCRIPTORS > 30
#    define CONFIG_LPC214X_USBDEV_NDMADESCRIPTORS 30
#  endif
#endif

/* Debug ***********************************************************************/

#ifndef CONFIG_USBDEV_TRACE
#  undef usbtrace
#  define usbtrace(a,b) ulldbg("%04x:%04x\n", a, b)
#endif

/* Trace error codes */

#define LPC214X_TRACEERR_ALLOCFAIL        0x0001
#define LPC214X_TRACEERR_BADEPNO          0x0002
#define LPC214X_TRACEERR_BADEPTYPE        0x0003
#define LPC214X_TRACEERR_BADREQUEST       0x0004
#define LPC214X_TRACEERR_BINDFAILED       0x0005
#define LPC214X_TRACEERR_DMABUSY          0x0006
#define LPC214X_TRACEERR_DRIVER           0x0007
#define LPC214X_TRACEERR_DRIVERREGISTERED 0x0008
#define LPC214X_TRACEERR_EPREAD           0x0009
#define LPC214X_TRACEERR_INVALIDPARMS     0x000a
#define LPC214X_TRACEERR_IRQREGISTRATION  0x000b
#define LPC214X_TRACEERR_NODMADESC        0x000c
#define LPC214X_TRACEERR_NOEP             0x000d
#define LPC214X_TRACEERR_NOTCONFIGURED    0x000e
#define LPC214X_TRACEERR_NULLPACKET       0x000f
#define LPC214X_TRACEERR_NULLREQUEST      0x0010
#define LPC214X_TRACEERR_STALLED          0x0011

/* Trace interrupt codes */

#define LPC214X_TRACEINTID_USB            0x0001
#define LPC214X_TRACEINTID_CLEARFEATURE   0x0002
#define LPC214X_TRACEINTID_CONNECTCHG     0x0003
#define LPC214X_TRACEINTID_CONNECTED      0x0004
#define LPC214X_TRACEINTID_DEVGETSTATUS   0x0005
#define LPC214X_TRACEINTID_DEVRESET       0x0006
#define LPC214X_TRACEINTID_DEVSTAT        0x0007
#define LPC214X_TRACEINTID_DISCONNECTED   0x0008
#define LPC214X_TRACEINTID_DISPATCH       0x0009
#define LPC214X_TRACEINTID_EP0IN          0x000a
#define LPC214X_TRACEINTID_EP0OUT         0x000b
#define LPC214X_TRACEINTID_EP0SETUP       0x000c
#define LPC214X_TRACEINTID_EPDMA          0x000d
#define LPC214X_TRACEINTID_EPFAST         0x000e
#define LPC214X_TRACEINTID_EPGETSTATUS    0x000f
#define LPC214X_TRACEINTID_EPIN           0x0010
#define LPC214X_TRACEINTID_EPOUT          0x0011
#define LPC214X_TRACEINTID_EPRINT         0x0012
#define LPC214X_TRACEINTID_EPSLOW         0x0013
#define LPC214X_TRACEINTID_FRAME          0x0014
#define LPC214X_TRACEINTID_IFGETSTATUS    0x0015
#define LPC214X_TRACEINTID_SETADDRESS     0x0017
#define LPC214X_TRACEINTID_SETFEATURE     0x0018
#define LPC214X_TRACEINTID_SUSPENDCHG     0x0019

/* Hardware interface **********************************************************/

/* Macros for testing the device status response */

#define DEVSTATUS_CONNECT(s)    (((s)&USBDEV_DEVSTATUS_CONNECT)!=0)
#define DEVSTATUS_CONNCHG(s)    (((s)&USBDEV_DEVSTATUS_CONNCHG)!=0)
#define DEVSTATUS_SUSPEND(s)    (((s)&USBDEV_DEVSTATUS_SUSPEND)!=0)
#define DEVSTATUS_SUSPCHG(s)    (((s)&USBDEV_DEVSTATUS_SUSPCHG)!=0)
#define DEVSTATUS_RESET(s)      (((s)&USBDEV_DEVSTATUS_RESET)!=0)

/* If this bit is set in the lpc214x_epread response, it means that the
 * recevied packet was overwritten by a later setup packet (ep0 only).
 */

#define LPC214X_READOVERRUN_BIT (0x80000000)
#define LPC214X_READOVERRUN(s)  (((s) & LPC214X_READOVERRUN_BIT) != 0)

/* USB RAM  ********************************************************************
 *
 * UBS_UDCA is is list of 32 pointers to DMA desciptors located at the
 * beginning of USB RAM.  Each pointer points to a DMA descriptor with
 * assocated DMA buffer.
 */

#define USB_UDCA           (uint32*)LPC214X_USBDEV_RAMBASE)
#define USB_USCASIZE       (LPC214X_NPHYSENDPOINTS*sizeof(uint32))

/* Each descriptor must be aligned to a 128 address boundary */

#define USB_DDALIGNDOWN(a) ((a)&~0x7f)
#define USB_DDALIGNUP(a)   USB_DDALIGNDOWN((a)+0x7f)

#define USB_DDSIZE USB_DDALIGNDOWN((LPC214X_USBDEV_RAMSIZE-USB_USCASIZE)/CONFIG_LPC214X_USBDEV_NDMADESCRIPTORS)
#define USB_DDESC  ((struct lpc214x_dmadesc_s*)(LPC214X_USBDEV_RAMBASE+USB_USCASIZE))

#ifdef CONFIG_USBDEV_ISOCHRONOUS
#  define USB_DDESCSIZE (5*sizeof(uint32))
#else
#  define USB_DDESCSIZE (4*sizeof(uint32))
#endif

/* Endpoints ******************************************************************/

/* Number of endpoints */

#define LPC214X_NLOGENDPOINTS        (16)          /* ep0-15 */
#define LPC214X_NPHYSENDPOINTS       (32)          /* x2 for IN and OUT */

/* Mapping to more traditional endpoint numbers */

#define LPC214X_EP_LOG2PHYOUT(ep)    ((ep)&0x0f)<<1))
#define LPC214X_EP_LOG2PHYIN(ep)     (LPC214X_EP_OUT(ep)|0x01)
#define LPC214X_EP_LOG2PHY(ep)       ((((ep)&0x0f)<<1)|(((ep)&0x80)>>7))

/* Each endpoint has somewhat different characteristics */

#define LPC214X_EPALLSET             (0xffffffff)  /* All endpoints */
#define LPC214X_EPOUTSET             (0x55555555)  /* Even phy endpoint numbers are OUT EPs */
#define LPC214X_EPINSET              (0xaaaaaaaa)  /* Odd endpoint numbers are IN EPs */
#define LPC214X_EPCTRLSET            (0x00000003)  /* EP0 IN/OUT are control endpoints */
#define LPC214X_EPINTRSET            (0x0c30c30c)  /* Interrupt endpoints */
#define LPC214X_EPBULKSET            (0xf0c30c30)  /* Bulk endpoints */
#define LPC214X_EPISOCSET            (0x030c30c0)  /* Isochronous endpoints */

#define LPC214X_EP0MAXPACKET         (64)          /* EP0 max packet size */
#define LPC214X_BULKMAXPACKET        (64)          /* Bulk endpoint max packet */
#define LPC214X_INTRMAXPACKET        (64)          /* Interrupt endpoint max packet */

/* EP0 status */

#define LPC214X_EP0IDLE              (0)           /* Nothing in progress */
#define LPC214X_EP0STATUSIN          (1)           /* Status sent */
#define LPC214X_EP0STATUSOUT         (2)           /* Status received */
#define LPC214X_EP0SHORTWRITE        (3)           /* Short data sent with no request */
#define LPC214X_EP0SHORTWRSENT       (4)           /* Short data write complete */
#define LPC214X_EP0SETADDRESS        (5)           /* Set address received */

/*******************************************************************************
 * Private Types
 *******************************************************************************/

/* A container for a request so that the request make be retained in a list */

struct lpc214x_req_s
{
  struct usbdev_req_s    req;           /* Standard USB request */
  sq_entry_t             sqe;           /* Supports a singly linked list */
};

/* This is the internal representation of an endpoint */

struct lpc214x_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 lpc214x_ep_s.
   */

  struct usbdev_ep_s      ep;            /* Standard endpoint structure */

  /* LPC214X-specific fields */

  struct lpc214x_usbdev_s *dev;          /* Reference to private driver data */
  sq_queue_t              reqlist;       /* Request list for this endpoint */
  ubyte                   eplog;         /* Logical EP address from descriptor */
  ubyte                   epphy;         /* Physical EP address */
  ubyte                   stalled:1;     /* Endpoint is halted */
  ubyte                   in:1;          /* Endpoint is IN only */
  ubyte                   halted:1;      /* Endport feature halted */
};

/* This represents a DMA descriptor */

#ifdef CONFIG_LPC214X_USBDEV_DMA
struct lpc214x_dmadesc_s
{
  uint32                  nextdesc;      /* Address of the next DMA descripto in RAM */
  uint32                  config;        /* Misc. bit encoded configuration information */
  uint32                  start;         /* DMA start address */
  uint32                  status;        /* Misc. bit encoded status inforamation */
#ifdef CONFIG_USBDEV_ISOCHRONOUS
  uint32                  size;          /* Isochronous packet size address */
#endif
  ubyte                   buffer[USB_DDSIZE-USB_DDESCSIZE];
};
#endif

/* This structure retains the state of the USB device controller */

struct lpc214x_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 structlpc214x_usbdev_s.
   */

  struct usbdev_s         usbdev;

  /* The bound device class driver */

  struct usbdevclass_driver_s *driver;

  /* LPC214X-specific fields */

  ubyte                   devstatus;     /* Last response to device status command */
  ubyte                   ep0state;      /* State of certain EP0 operations */
  ubyte                   stalled:1;     /* 1: Protocol stalled */
  ubyte                   selfpowered:1; /* 1: Device is self powered */
  ubyte                   paddrset:1;    /* 1: Peripheral addr has been set */
  ubyte                   attached:1;    /* 1: Host attached */
  ubyte                   rxpending:1;   /* 1: RX pending */
  uint32                  epavail;       /* Available endpoints */
  uint32                  softprio;      /* Bitset of high priority interrupts */
  uint32                  wravail;       /* Bitset of available endpoints */
#ifdef CONFIG_LPC214X_USBDEV_FRAME_INTERRUPT
  uint32                  sof;           /* Last start-of-frame */
#endif

  /* Allocated DMA descriptor */

#ifdef CONFIG_LPC214X_USBDEV_DMA
  struct lpc214x_dmadesc_s *dmadesc;
#endif

  /* The endpoint list */

  struct lpc214x_ep_s     eplist[LPC214X_NPHYSENDPOINTS];
};

/*******************************************************************************
 * Private Function Prototypes
 *******************************************************************************/

/* Register operations */

#if defined(CONFIG_LPC214X_USBDEV_REGDEBUG) && defined(CONFIG_DEBUG)
static uint32 lpc214x_getreg(uint32 addr);
static void lpc214x_putreg(uint32 val, uint32 addr);
#else
# define lpc214x_getreg(addr)     getreg32(addr)
# define lpc214x_putreg(val,addr) putreg32(val,addr)
#endif

/* Command operations */

static uint32 lpc214x_usbcmd(uint16 cmd, ubyte data);

/* Low level data transfers and request operations */

static void lpc214x_epwrite(ubyte epphy, const ubyte *data, uint32 nbytes);
static int  lpc214x_epread(ubyte epphy, ubyte *data, uint32 nbytes);
static void lpc214x_reqcomplete(struct lpc214x_ep_s *privep, sint16 result);
static int  lpc214x_wrrequest(struct lpc214x_ep_s *privep);
static int  lpc214x_rdrequest(struct lpc214x_ep_s *privep);
static void lpc214x_cancelrequests(struct lpc214x_ep_s *privep);

/* Interrupt handling */

static void lpc214x_eprealize(struct lpc214x_ep_s *privep, boolean prio, uint32 packetsize);
static ubyte lpc214x_epclrinterrupt(ubyte epphy);
static inline void lpc214x_ep0configure(struct lpc214x_usbdev_s *priv);
#ifdef CONFIG_LPC214X_USBDEV_DMA
static inline void lpc214x_dmareset(uint32 enable);
#endif
static void lpc214x_usbreset(struct lpc214x_usbdev_s *priv);
static void lpc214x_dispatchrequest(struct lpc214x_usbdev_s *priv,
patacongo's avatar
patacongo committed
              const struct usb_ctrlreq_s *ctrl);
static inline void lpc214x_ep0setup(struct lpc214x_usbdev_s *priv);
static inline void lpc214x_ep0dataoutinterrupt(struct lpc214x_usbdev_s *priv);
static inline void lpc214x_ep0dataininterrupt(struct lpc214x_usbdev_s *priv);
static int lpc214x_usbinterrupt(int irq, FAR void *context);

#ifdef CONFIG_LPC214X_USBDEV_DMA
static int  lpc214x_dmasetup(struct lpc214x_usbdev_s *priv, ubyte epphy,
              uint32 epmaxsize, uint32 nbytes, uint32 *isocpacket,
              boolean isochronous);
static void lpc214x_dmarestart(ubyte epphy, uint32 descndx);
static void lpc214x_dmadisable(ubyte epphy);
#endif /* CONFIG_LPC214X_USBDEV_DMA */

/* Endpoint operations */

static int  lpc214x_epconfigure(FAR struct usbdev_ep_s *ep,
              const struct usb_epdesc_s *desc);
static int  lpc214x_epdisable(FAR struct usbdev_ep_s *ep);
static FAR struct usbdev_req_s *lpc214x_epallocreq(FAR struct usbdev_ep_s *ep);
static void lpc214x_epfreereq(FAR struct usbdev_ep_s *ep,
              FAR struct usbdev_req_s *);
#ifdef CONFIG_LPC214X_USBDEV_DMA
static FAR void *lpc214x_epallocbuffer(FAR struct usbdev_ep_s *ep,
              uint16 nbytes);
static void lpc214x_epfreebuffer(FAR struct usbdev_ep_s *ep, void *buf);
#endif
static int  lpc214x_epsubmit(FAR struct usbdev_ep_s *ep,
              struct usbdev_req_s *req);
static int  lpc214x_epcancel(FAR struct usbdev_ep_s *ep,
              struct usbdev_req_s *req);
static int  lpc214x_epstall(FAR struct usbdev_ep_s *ep, boolean resume);

/* USB device controller operations */

static FAR struct usbdev_ep_s *lcp214x_allocep(FAR struct usbdev_s *dev,
              ubyte epno, boolean in, ubyte eptype);
static void lpc214x_freeep(FAR struct usbdev_s *dev, FAR struct usbdev_ep_s *ep);
static int  lpc214x_getframe(struct usbdev_s *dev);
static int  lpc214x_wakeup(struct usbdev_s *dev);
static int  lpc214x_selfpowered(struct usbdev_s *dev, boolean selfpowered);
static int  lpc214x_pullup(struct usbdev_s *dev, boolean enable);

/*******************************************************************************
 * Private Data
 *******************************************************************************/

/* Since there is only a single USB interface, all status information can be
 * be simply retained in a single global instance.
 */

static struct lpc214x_usbdev_s g_usbdev;

static const struct usbdev_epops_s g_epops =
{
  .configure   = lpc214x_epconfigure,
  .disable     = lpc214x_epdisable,
  .allocreq    = lpc214x_epallocreq,
  .freereq     = lpc214x_epfreereq,
#ifdef CONFIG_LPC214X_USBDEV_DMA
  .allocbuffer = lpc214x_epallocbuffer,
  .freebuffer  = lpc214x_epfreebuffer,
#endif
  .submit      = lpc214x_epsubmit,
  .cancel      = lpc214x_epcancel,
  .stall       = lpc214x_epstall,
};

static const struct usbdev_ops_s g_devops =
{
  .allocep     = lcp214x_allocep,
  .freeep      = lpc214x_freeep,
  .getframe    = lpc214x_getframe,
  .wakeup      = lpc214x_wakeup,
  .selfpowered = lpc214x_selfpowered,
  .pullup      = lpc214x_pullup,
};

/*******************************************************************************
 * Public Data
 *******************************************************************************/

/*******************************************************************************
 * Private Functions
 *******************************************************************************/

/*******************************************************************************
 * Name: lpc214x_getreg
 *
 * Description:
 *   Get the contents of an LPC214x register
 *
 *******************************************************************************/

#if defined(CONFIG_LPC214X_USBDEV_REGDEBUG) && defined(CONFIG_DEBUG)
static uint32 lpc214x_getreg(uint32 addr)
{
  uint32 val = getreg32(addr);
  lldbg("%04x->%04x\n", addr, val);
  return val;
}
#endif

/*******************************************************************************
 * Name: lpc214x_putreg
 *
 * Description:
 *   Set the contents of an LPC214x register to a value
 *
 *******************************************************************************/

#if defined(CONFIG_LPC214X_USBDEV_REGDEBUG) && defined(CONFIG_DEBUG)
static void lpc214x_putreg(uint32 val, uint32 addr)
{
  lldbg("%04x<-%04x\n", addr, val);
  putreg32(val, addr);
}
#endif

/*******************************************************************************
 * Name: lpc214x_usbcmd
 *
 * Description:
 *   Transmit commands to the USB engine
 *
 *******************************************************************************/

static uint32 lpc214x_usbcmd(uint16 cmd, ubyte data)
{
  irqstate_t flags;
  uint32 tmp = 0;

  /* Disable interrupt and clear CDFULL and CCEMPTY interrupt status */

  flags = irqsave();
  lpc214x_putreg(USBDEV_DEVINT_CDFULL|USBDEV_DEVINT_CCEMTY, LPC214X_USBDEV_DEVINTCLR);

  /* Load commonad in USB engine */

  lpc214x_putreg(((cmd & 0xff) << 16) + CMD_USB_CMDWR, LPC214X_USBDEV_CMDCODE);

  /* Wait until command is accepted */

  while ((lpc214x_getreg(LPC214X_USBDEV_DEVINTST) & USBDEV_DEVINT_CCEMTY) == 0);

  /* Clear command register empty (CCEMPTY) interrupt */

  lpc214x_putreg(USBDEV_DEVINT_CCEMTY, LPC214X_USBDEV_DEVINTCLR);

  /* determinate next phase of the command */

  switch (cmd)
    {
    case CMD_USB_DEV_SETADDRESS:
    case CMD_USB_DEV_CONFIG:
    case CMD_USB_DEV_SETMODE:
    case CMD_USB_DEV_SETSTATUS:
      lpc214x_putreg((data << 16) + CMD_USB_DATAWR, LPC214X_USBDEV_CMDCODE);
      while ((lpc214x_getreg(LPC214X_USBDEV_DEVINTST) & USBDEV_DEVINT_CCEMTY) == 0);
      break;

    case CMD_USB_DEV_READFRAMENO:
    case CMD_USB_DEV_READTESTREG:
      lpc214x_putreg((cmd << 16) + CMD_USB_DATARD, LPC214X_USBDEV_CMDCODE);
      while ((lpc214x_getreg(LPC214X_USBDEV_DEVINTST) & USBDEV_DEVINT_CDFULL) == 0);
      lpc214x_putreg(USBDEV_DEVINT_CDFULL, LPC214X_USBDEV_DEVINTCLR);
      tmp = lpc214x_getreg(LPC214X_USBDEV_CMDDATA);
      lpc214x_putreg((cmd << 16) + CMD_USB_DATARD, LPC214X_USBDEV_CMDCODE);
      while ((lpc214x_getreg(LPC214X_USBDEV_DEVINTST) & USBDEV_DEVINT_CDFULL) == 0);
      tmp |= lpc214x_getreg(LPC214X_USBDEV_CMDDATA) << 8;
      break;

    case CMD_USB_DEV_GETSTATUS:
    case CMD_USB_DEV_GETERRORCODE:
    case CMD_USB_DEV_READERRORSTATUS:
    case CMD_USB_EP_CLRBUFFER:
      lpc214x_putreg((cmd << 16) + CMD_USB_DATARD, LPC214X_USBDEV_CMDCODE);
      while ((lpc214x_getreg(LPC214X_USBDEV_DEVINTST) & USBDEV_DEVINT_CDFULL) == 0);
      tmp = lpc214x_getreg(LPC214X_USBDEV_CMDDATA);
      break;

    default:
      switch (cmd & 0x1e0)
        {
        case CMD_USB_EP_SELECT:
        case CMD_USB_EP_SELECTCLEAR:
          lpc214x_putreg((cmd << 16) + CMD_USB_DATARD, LPC214X_USBDEV_CMDCODE);
          while ((lpc214x_getreg(LPC214X_USBDEV_DEVINTST) & USBDEV_DEVINT_CDFULL) == 0);
          tmp = lpc214x_getreg(LPC214X_USBDEV_CMDDATA);
          break;

        case CMD_USB_EP_SETSTATUS:
          lpc214x_putreg((data << 16) + CMD_USB_DATAWR, LPC214X_USBDEV_CMDCODE);
          while ((lpc214x_getreg(LPC214X_USBDEV_DEVINTST) & USBDEV_DEVINT_CCEMTY) == 0);
          break;
        }
      break;
    }

  /* Restore the interrupt flags */

  irqrestore(flags);
  return tmp;
}

/*******************************************************************************
 * Name: lpc214x_epwrite
 *
 * Description:
 *   Endpoint write (IN)
 *
 *******************************************************************************/

static void lpc214x_epwrite(ubyte epphy, const ubyte *data, uint32 nbytes)
{
  /* Set the write enable bit for this physical EP address */

  lpc214x_putreg(((epphy << 1) & LPC214X_USBCTRL_EPMASK) | LPC214X_USBCTRL_WREN,
                 LPC214X_USBDEV_CTRL);

  /* Set the transmit packet length (nbytes must be less than 2048) */

  putreg32(nbytes, LPC214X_USBDEV_TXPLEN);

  /* Transfer the packet data */

  do
    {
      /* Zero length packets are a special case */

      if (nbytes)
        {
          lpc214x_putreg(*data++, LPC214X_USBDEV_TXDATA);
        }
      else
        {
          /* Zero length packet */

          lpc214x_putreg(0, LPC214X_USBDEV_TXDATA);
  while ((lpc214x_getreg(LPC214X_USBDEV_CTRL) & LPC214X_USBCTRL_WREN) != 0);
  lpc214x_putreg(0, LPC214X_USBDEV_CTRL);
  (void)lpc214x_usbcmd(CMD_USB_EP_SELECT | epphy, 0);
  (void)lpc214x_usbcmd(CMD_USB_EP_VALIDATEBUFFER, 0);
}

/*******************************************************************************
 * Name: lpc214x_epread
 *
 * Description:
 *   Endpoint read (OUT)
 *
 *******************************************************************************/

static int lpc214x_epread(ubyte epphy, ubyte *data, uint32 nbytes)
{
  uint32 pktlen;
  uint32 result;
  uint32 value;

  /* Set the read enable bit for this physical EP address */

  lpc214x_putreg(((epphy << 1) & LPC214X_USBCTRL_EPMASK) | LPC214X_USBCTRL_RDEN,
                 LPC214X_USBDEV_CTRL);

  /* Wait for packet buffer ready for reading */

  while ((lpc214x_getreg(LPC214X_USBDEV_RXPLEN) & USBDEV_RXPLEN_PKTRDY) == 0);

  /* Get the about of data to be read */

  pktlen = lpc214x_getreg(LPC214X_USBDEV_RXPLEN) & USBDEV_RXPLEN_PKTLENGTH;

  /* Read data from input buffer while read data is valid (DV) */

  while ((lpc214x_getreg(LPC214X_USBDEV_RXPLEN) & USBDEV_RXPLEN_DV) != 0)
      value = lpc214x_getreg(LPC214X_USBDEV_RXDATA);
  lpc214x_putreg(0, LPC214X_USBDEV_CTRL);
  (void)lpc214x_usbcmd(CMD_USB_EP_SELECT | epphy, 0);
  result = lpc214x_usbcmd(CMD_USB_EP_CLRBUFFER, 0);

  /* The packet overrun bit in the clear buffer response is applicable only
   * on EP0 transfers.  If set it means that the recevied packet was overwritten
   * by a later setup packet.
   */

  if (epphy == LPC214X_EP0_OUT && (result & CMD_USB_CLRBUFFER_PO) != 0)
    {
      /* Pass this information in bit 31 */

      pktlen |= LPC214X_READOVERRUN_BIT;
    }
  return pktlen;
}

/*******************************************************************************
 * Name: lpc214x_reqcomplete
 *
 * Description:
 *   Handle termination of a request.
 *
 *******************************************************************************/

static void lpc214x_reqcomplete(struct lpc214x_ep_s *privep, sint16 result)
{
  struct lpc214x_req_s *privreq;
  int stalled = privep->stalled;
  irqstate_t flags;

  /* Remove the complete request at the head of the endpoint request list */

  flags = irqsave();
  privreq = (struct lpc214x_req_s *)sq_remfirst(&privep->reqlist);
  irqrestore(flags);

  if (privreq)
    {
      /* If endpoint 0, temporarily reflect the state of protocol stalled
       * in the callback.
       */

      if (privep->epphy == 0)
        {
          if (privep->dev->stalled)
            {
              privep->stalled = 1;
            }
        }

      /* Save the result in the request structure */

      privreq->req.result = result;

      /* Callback to the request completion handler */

      privreq->sqe.flink = NULL;
      privreq->req.callback(&privep->ep, &privreq->req);

      /* Restore the stalled indication */

      privep->stalled = stalled;
    }
}

/*******************************************************************************
 * Name: lpc214x_wrrequest
 *
 * Description:
 *   Send from the next queued write request
 *
 * Returned Value:
 *  0:not finished; 1:completed; <0:error
 *
 *******************************************************************************/

static int lpc214x_wrrequest(struct lpc214x_ep_s *privep)
{
  struct lpc214x_req_s *privreq;
  ubyte *buf;
  int nbytes;
  int bytesleft;

  /* Check the request from the head of the endpoint request queue */

  privreq = (struct lpc214x_req_s *)sq_peek(&privep->reqlist);
  if (!privreq)
    {
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_NULLREQUEST), 0);
      return OK;
    }

  bytesleft = privreq->req.len;
  for (;;)
    {
      /* Send the next packet if (1) there are more bytes to be sent, or
       * (2) the last packet was exactly maxpacketsize.
       */

      usbtrace(TRACE_WRITE(privep->epphy), privreq->req.xfrd);
      if (privreq->req.len >= privreq->req.xfrd)
        {
          /* Try to send maxpacketsize -- unless we don't have that many
           * bytes to send.
           */

          nbytes = privep->ep.maxpacket;
          if (nbytes > bytesleft)
            {
              nbytes = bytesleft;
            }

          /* Send the largest number of bytes that we can in this packet */

          buf = privreq->req.buf + privreq->req.xfrd;
          lpc214x_epwrite(privep->epphy, buf, nbytes);

          /* Update for the next time through the loop */

          privreq->req.xfrd += nbytes;
          bytesleft          = privreq->req.len - privreq->req.xfrd;
        }

      /* If all of the bytes were sent (and the last packet was not full)
       * then we are finished with the transfer)
       */

      if (privreq->req.len < privreq->req.xfrd)
        {
          usbtrace(TRACE_COMPLETE(privep->epphy), privreq->req.xfrd);
          lpc214x_reqcomplete(privep, OK);
          return OK;
        }
    }

  return OK; /* Won't get here */
}

/*******************************************************************************
 * Name: lpc214x_rdrequest
 *
 * Description:
 *   Receive to the next queued read request
 *
 *******************************************************************************/

static int lpc214x_rdrequest(struct lpc214x_ep_s *privep)
{
  struct lpc214x_req_s *privreq;
  ubyte *buf;
  int nbytesread;

  /* Check the request from the head of the endpoint request queue */

  privreq = (struct lpc214x_req_s *)sq_peek(&privep->reqlist);
  if (!privreq)
    {
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_NULLREQUEST), 0);
      return OK;
    }

  usbtrace(TRACE_READ(privep->epphy), privreq->req.xfrd);
  for (;;)
    {
      /* Receive the next packet if (1) there are more bytes to be receive, or
       * (2) the last packet was exactly maxpacketsize.
       */

      buf        = privreq->req.buf + privreq->req.xfrd;
      nbytesread = lpc214x_epread(privep->epphy, buf, privep->ep.maxpacket);
      if (nbytesread < 0)
        {
          usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_EPREAD), nbytesread);
          return ERROR;
        }

      /* If the receive buffer is full or if the last packet was not full
       * then we are finished with the transfer.
       */

      privreq->req.xfrd += nbytesread;
      if (privreq->req.len < privreq->req.xfrd || nbytesread < privep->ep.maxpacket)
        {
          usbtrace(TRACE_COMPLETE(privep->epphy), privreq->req.xfrd);
          lpc214x_reqcomplete(privep, OK);
          return OK;
        }
    }

  return OK; /* Won't get here */
}

/*******************************************************************************
 * Name: lpc214x_cancelrequests
 *
 * Description:
 *   Cancel all pending requests for an endpoint
 *
 *******************************************************************************/

static void lpc214x_cancelrequests(struct lpc214x_ep_s *privep)
{
  while (!sq_empty(&privep->reqlist))
    {
patacongo's avatar
patacongo committed
      usbtrace(TRACE_COMPLETE(privep->epphy),
               ((struct lpc214x_req_s *)sq_peek(&privep->reqlist))->req.xfrd);
      lpc214x_reqcomplete(privep, -ESHUTDOWN);
    }
}

/*******************************************************************************
 * Name: lpc214x_eprealize
 *
 * Description:
 *   Enable or disable an endpoint
 *
 *******************************************************************************/

static void lpc214x_eprealize(struct lpc214x_ep_s *privep, boolean prio, uint32 packetsize)
{
  struct lpc214x_usbdev_s *priv = privep->dev;
  uint32 mask;
  uint32 reg;

  /* Initialize endpoint software priority */

  mask = 1 << privep->epphy;
  if (prio)
    {
      priv->softprio = priv->softprio | mask;
    }
  else
    {
      priv->softprio = priv->softprio & ~mask;
    }

  /* Clear realize interrupt bit */

  lpc214x_putreg(USBDEV_DEVINT_EPRLZED, LPC214X_USBDEV_DEVINTCLR);

  /* Realize the endpoint */

  reg  = lpc214x_getreg(LPC214X_USBDEV_REEP);
  reg |= (1 << privep->epphy);
  lpc214x_putreg(reg, LPC214X_USBDEV_REEP);

  /* Set endpoint maximum packet size */

  lpc214x_putreg(privep->epphy, LPC214X_USBDEV_EPIND);
  lpc214x_putreg(packetsize, LPC214X_USBDEV_MAXPSIZE);

  /* Wait for Realize complete */

  while ((lpc214x_getreg(LPC214X_USBDEV_DEVINTST) & USBDEV_DEVINT_EPRLZED) == 0);

  /* Clear realize interrupt bit */

  lpc214x_putreg(USBDEV_DEVINT_EPRLZED,LPC214X_USBDEV_DEVINTCLR);
}

/*******************************************************************************
 * Name: lpc214x_epclrinterrupt
 *
 * Description:
 *   Clear the EP interrupt flag and return the current EP status
 *
 *******************************************************************************/

static ubyte lpc214x_epclrinterrupt(ubyte epphy)
{
  /* Clear the endpoint interrupt */

  lpc214x_putreg(1 << epphy, LPC214X_USBDEV_EPINTCLR);

  /* Wait for data in the command data register */

  while ((lpc214x_getreg(LPC214X_USBDEV_DEVINTST) & USBDEV_DEVINT_CDFULL) == 0);

  /* Return the value of the command data register */

  return lpc214x_getreg(LPC214X_USBDEV_CMDDATA);
}

/*******************************************************************************
 * Name: lpc214x_ep0configure
 *
 * Description:
 *   Configure endpoint 0
 *
 *******************************************************************************/

static inline void lpc214x_ep0configure(struct lpc214x_usbdev_s *priv)
{
  uint32 inten;

  /* EndPoint 0 initialization */

  lpc214x_eprealize(&priv->eplist[LPC214X_CTRLEP_OUT], 0, CONFIG_USBDEV_EP0_MAXSIZE);
  lpc214x_eprealize(&priv->eplist[LPC214X_CTRLEP_IN], 1, CONFIG_USBDEV_EP0_MAXSIZE);

  /* Enable EP0 interrupts (not DMA) */

  inten = lpc214x_getreg(LPC214X_USBDEV_EPINTEN);
  inten |= 3; /* EP0 Rx and Tx */
  lpc214x_putreg(inten, LPC214X_USBDEV_EPINTEN);
}

/*******************************************************************************
 * Name: lpc214x_dmareset
 *
 * Description: Reset USB DMA
 *
 *******************************************************************************/

#ifdef CONFIG_LPC214X_USBDEV_DMA
static inline void lpc214x_dmareset(uint32 enable)
{
  int i;

  /* Disable All DMA interrupts */

  lpc214x_putreg(0, LPC214X_USBDEV_DMAINTEN);
  lpc214x_putreg(0xffffffff, LPC214X_USBDEV_EPDMADIS);

  /* DMA Request clear */

  putreq32(0xffffffff, LPC214X_USBDEV_DMARCLR);