Skip to content
Snippets Groups Projects
lpc214x_usbdev.c 80.7 KiB
Newer Older

  /* Realize the endpoint */

  lpc214x_eprealize(privep, 1, GETUINT16(desc->mxpacketsize));

  /* Enable and reset EP -- twice */

  lpc214x_usbcmd(CMD_USB_EP_SETSTATUS | epphy, 0);
  lpc214x_usbcmd(CMD_USB_EP_SETSTATUS | epphy, 0);

#ifdef CONFIG_LPC214X_USBDEV_DMA
  /* Enable DMA Ep interrupt (WO) */

   lpc214x_putreg(1 << epphy, LPC214X_USBDEV_EPDMAEN);
#else
  /* Enable Ep interrupt (R/W) */

   inten = lpc214x_getreg(LPC214X_USBDEV_EPINTEN);
   inten |= (1 << epphy);
   lpc214x_putreg(inten, LPC214X_USBDEV_EPINTEN);
#endif
   return OK;
}

/*******************************************************************************
 * Name: lpc214x_epdisable
 *
 * Description:
 *   The endpoint will no longer be used
 *
 *******************************************************************************/

static int lpc214x_epdisable(FAR struct usbdev_ep_s *ep)
{
  FAR struct lpc214x_ep_s *privep = (FAR struct lpc214x_ep_s *)ep;
  irqstate_t flags;
  uint32 mask = (1 << privep->epphy);
  uint32 reg;

#ifdef CONFIG_DEBUG
  if (!ep)
    {
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_INVALIDPARMS), 0);
      return -EINVAL;
    }
#endif
  usbtrace(TRACE_EPDISABLE, privep->epphy);

  /* Cancel any ongoing activity */

  flags = irqsave();
  lpc214x_cancelrequests(privep);

  /* Disable endpoint and interrupt */

  reg  = lpc214x_getreg(LPC214X_USBDEV_REEP);
  lpc214x_putreg(reg, LPC214X_USBDEV_REEP);
  lpc214x_putreg(mask, LPC214X_USBDEV_EPDMADIS);
  reg  = lpc214x_getreg(LPC214X_USBDEV_EPINTEN);
  lpc214x_putreg(reg, LPC214X_USBDEV_EPINTEN);

  irqrestore(flags);
  return OK;
}

/*******************************************************************************
 * Name: lpc214x_epallocreq
 *
 * Description:
 *   Allocate an I/O request
 *
 *******************************************************************************/

static FAR struct usbdev_req_s *lpc214x_epallocreq(FAR struct usbdev_ep_s *ep)
{
  FAR struct lpc214x_req_s *privreq;

#ifdef CONFIG_DEBUG
  if (!ep)
    {
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_INVALIDPARMS), 0);
      return NULL;
    }
#endif
  usbtrace(TRACE_EPALLOCREQ, ((FAR struct lpc214x_ep_s *)ep)->epphy);

  privreq = (FAR struct lpc214x_req_s *)malloc(sizeof(struct lpc214x_req_s));
  if (!privreq)
    {
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_ALLOCFAIL), 0);
      return NULL;
    }

  memset(privreq, 0, sizeof(struct lpc214x_req_s));
  return &privreq->req;
}

/*******************************************************************************
 * Name: lpc214x_epfreereq
 *
 * Description:
 *   Free an I/O request
 *
 *******************************************************************************/

static void lpc214x_epfreereq(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req)
{
  FAR struct lpc214x_req_s *privreq = (FAR struct lpc214x_req_s *)req;

#ifdef CONFIG_DEBUG
  if (!ep || !req)
    {
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_INVALIDPARMS), 0);
      return;
    }
#endif
  usbtrace(TRACE_EPFREEREQ, ((FAR struct lpc214x_ep_s *)ep)->epphy);

  free(privreq);
}

/*******************************************************************************
 * Name: lpc214x_epallocbuffer
 *
 * Description:
 *   Allocate an I/O buffer
 *
 *******************************************************************************/

#ifdef CONFIG_LPC214X_USBDEV_DMA
static FAR void *lpc214x_epallocbuffer(FAR struct usbdev_ep_s *ep, uint16 nbytes)
{
  FAR struct lpc214x_ep_s *privep = (FAR struct lpc214x_ep_s *)ep;
  int descndx;

  usbtrace(TRACE_EPALLOCBUFFER, privep->epphy);

  /* Find a free  DMA description */

#error "LOGIC INCOMPLETE"

  /* Set UDCA to the allocated DMA descriptor for this endpoint */

  USB_UDCA[privep->epphy] = &USB_DDESC[descndx];
  return  &USB_DDESC[descndx]
}
#endif

/*******************************************************************************
 * Name: lpc214x_epfreebuffer
 *
 * Description:
 *   Free an I/O buffer
 *
 *******************************************************************************/

#ifdef CONFIG_LPC214X_USBDEV_DMA
static void lpc214x_epfreebuffer(FAR struct usbdev_ep_s *ep, FAR void *buf)
{
  FAR struct lpc214x_ep_s *privep = (FAR struct lpc214x_ep_s *)ep;

  usbtrace(TRACE_EPFREEBUFFER, privep->epphy);

  /* Indicate that there is no DMA descriptor associated with this endpoint  */

  USB_UDCA[privep->epphy] = NULL;

  /* Mark the DMA descriptor as free for re-allocation */

#error "LOGIC INCOMPLETE"
}
#endif

/*******************************************************************************
 * Name: lpc214x_epsubmit
 *
 * Description:
 *   Submit an I/O request to the endpoint
 *
 *******************************************************************************/

static int lpc214x_epsubmit(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req)
{
  FAR struct lpc214x_req_s *privreq = (FAR struct lpc214x_req_s *)req;
  FAR struct lpc214x_ep_s *privep = (FAR struct lpc214x_ep_s *)ep;
  FAR struct lpc214x_usbdev_s *priv;
  irqstate_t flags;
  int ret = OK;

#ifdef CONFIG_DEBUG
  if (!req || !req->callback || !req->buf || !ep)
    {
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_INVALIDPARMS), 0);
      return -EINVAL;
    }
#endif
  usbtrace(TRACE_EPSUBMIT, privep->epphy);
  priv = privep->dev;

  if (!priv->driver || priv->usbdev.speed == USB_SPEED_UNKNOWN)
    {
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_NOTCONFIGURED), 0);
      return -ESHUTDOWN;
    }

  req->result = -EINPROGRESS;
  req->xfrd = 0;

  /* Check for NULL packet */

  flags = irqsave();
  if (req->len == 0 && (privep->in || privep->epphy == 3))
    {
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_NULLPACKET), 0);
      sq_addlast(&privreq->sqe, &privep->reqlist);
      goto success_notransfer;
    }

  /* Has everything been sent? Or are we stalled? */

  if (!sq_empty(&privep->reqlist) && !privep->stalled)
    {
      /* Handle zero-length transfers on EP0 */

      if (privep->epphy == 0 && req->len == 0)
        {
          /* Nothing to transfer -- exit success, zero-bytes transferred */

patacongo's avatar
patacongo committed
          usbtrace(TRACE_COMPLETE(privep->epphy), privreq->req.xfrd);
          lpc214x_reqcomplete(privep, OK);
          goto success_notransfer;
        }

      /* Handle IN requests */

      if ((privep->in) || privep->epphy == 3)
        {
          ret = lpc214x_wrrequest(privep);
        }

      /* Handle pending OUT requests */

      else if (priv->rxpending)
        {
          ret = lpc214x_rdrequest(privep);
          priv->rxpending = 0;
        }
      else
        {
          usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_BADREQUEST), 0);
          ret = ERROR;
          goto errout;
        }
    }

  /* Add to endpoint's request queue */

  if (ret >= 0)
    {
patacongo's avatar
patacongo committed
      usbtrace(TRACE_REQQUEUED(privep->epphy), privreq->req.len);
      sq_addlast(&privreq->sqe, &privep->reqlist);
    }

success_notransfer:
errout:
  irqrestore(flags);
  return ret;
}

/*******************************************************************************
 * Name: lpc214x_epcancel
 *
 * Description:
 *   Cancel an I/O request previously sent to an endpoint
 *
 *******************************************************************************/

static int lpc214x_epcancel(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req)
{
  FAR struct lpc214x_ep_s *privep = (FAR struct lpc214x_ep_s *)ep;
  FAR struct lpc214x_usbdev_s *priv;
  irqstate_t flags;

#ifdef CONFIG_DEBUG
  if (!ep || !req)
    {
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_INVALIDPARMS), 0);
      return -EINVAL;
    }
#endif
  usbtrace(TRACE_EPCANCEL, privep->epphy);
  priv = privep->dev;

  flags = irqsave();
  lpc214x_cancelrequests(privep);
  irqrestore(flags);
  return OK;
}

/*******************************************************************************
 * Name: lpc214x_epstall
 *
 * Description:
 *   Stall or resume and endpoint
 *
 *******************************************************************************/

static int lpc214x_epstall(FAR struct usbdev_ep_s *ep, boolean resume)
{
  FAR struct lpc214x_ep_s *privep = (FAR struct lpc214x_ep_s *)ep;
  usbtrace(resume ? TRACE_EPRESUME : TRACE_EPSTALL, privep->epphy);
  lpc214x_usbcmd(CMD_USB_EP_SETSTATUS | privep->epphy, (resume ? 0 : USBDEV_EPSTALL));
  return OK;
}

/*******************************************************************************
 * Device operations
 *******************************************************************************/

/*******************************************************************************
 * Name: lcp214x_allocep
 *
 * Description:
 *   Allocate an endpoint matching the parameters.
 *
 * Input Parameters:
 *   epphy  - 7-bit physical endpoint number (without direction bit).  Zero means
 *            that any endpoint matching the other requirements will suffice.
 *   in     - TRUE: IN (device-to-host) endpoint requested
 *   eptype - Endpoint type.  One of {USB_EP_ATTR_XFER_ISOC, USB_EP_ATTR_XFER_BULK,
 *            USB_EP_ATTR_XFER_INT}
 *
 *******************************************************************************/

static FAR struct usbdev_ep_s *lcp214x_allocep(FAR struct usbdev_s *dev, ubyte epphy,
                                               boolean in, ubyte eptype)
{
  FAR struct lpc214x_usbdev_s *priv = (FAR struct lpc214x_usbdev_s *)dev;
  uint32 epset = LPC214X_EPALLSET;
  irqstate_t flags;
  int epndx = 0;

  usbtrace(TRACE_DEVALLOCEP, 0);

  /* epphy=0 means that any endpoint will do */

  if (epphy > 0)
    {
      if (epphy >= LPC214X_NLOGENDPOINTS)
        {
         usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_BADEPNO), 0);
         return NULL;
        }

      epset &= 3 << epphy;
    }

  /* Get the subset matching the requested direction */

  if (in)
    {
      epset &= LPC214X_EPINSET;
    }
  else
    {
      epset &= LPC214X_EPOUTSET;
    }

  /* Get the subset matching the requested type */

  switch (eptype)
    {
    case USB_EP_ATTR_XFER_INT: /* Interrupt endpoint */
      epset &= LPC214X_EPINTRSET;
      break;

    case USB_EP_ATTR_XFER_BULK: /* Bulk endpoint */
      epset &= LPC214X_EPBULKSET;
      break;

    case USB_EP_ATTR_XFER_ISOC: /* Isochronous endpoint */
      epset &= LPC214X_EPBULKSET;
      break;

    case USB_EP_ATTR_XFER_CONTROL: /* Control endpoint -- not a valid choice */
    default:
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_BADEPTYPE), 0);
      return NULL;
    }

  /* Is the resulting endpoint supported by the LPC214x? */

  if (epset)
    {
      /* Yes.. now see if any of the request endpoints are available */

      flags = irqsave();
      epset &= priv->epavail;
      if (epset)
        {
          /* Select the lowest bit in the set of matching, available endpoints */

          for (epndx = 2; epndx < LPC214X_NPHYSENDPOINTS; epndx++)
            {
              uint32 bit = 1 << epndx;
              if ((epset & bit) == 0)
                {
                  /* Mark the endpoint no longer available */

                  priv->wravail &= ~bit;
                  irqrestore(flags);

                  /* And return the pointer to the standard endpoint structure */

                  return &priv->eplist[epndx].ep;
                }
            }
          /* Shouldn't get here */
        }
      irqrestore(flags);
    }

  usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_NOEP), 0);
  return NULL;
}

/*******************************************************************************
 * Name: lpc214x_freeep
 *
 * Description:
 *   Free the previously allocated endpoint
 *
 *******************************************************************************/

static void lpc214x_freeep(FAR struct usbdev_s *dev, FAR struct usbdev_ep_s *ep)
{
  FAR struct lpc214x_usbdev_s *priv = (FAR struct lpc214x_usbdev_s *)dev;
  FAR struct lpc214x_ep_s *privep = (FAR struct lpc214x_ep_s *)ep;
  irqstate_t flags;

  usbtrace(TRACE_DEVFREEEP, (uint16)privep->epphy);

  if (priv && privep)
    {
      /* Mark the endpoint as available */

      flags = irqsave();
      priv->wravail &= ~(1 << privep->epphy);
      irqrestore(flags);
    }
}

/*******************************************************************************
 * Name: lpc214x_getframe
 *
 * Description:
 *   Returns the current frame number
 *
 *******************************************************************************/

static int lpc214x_getframe(struct usbdev_s *dev)
{
#ifdef CONFIG_LPC214X_USBDEV_FRAME_INTERRUPT
  FAR struct lpc214x_usbdev_s *priv = (FAR struct lpc214x_usbdev_s *)dev;

  /* Return last valid value of SOF read by the interrupt handler */

  usbtrace(TRACE_DEVGETFRAME, (uint16)priv->sof);
  return priv->sof;
#else
  /* Return the last frame number detected by the hardware */

  usbtrace(TRACE_DEVGETFRAME, 0);
  return (int)lpc214x_usbcmd(CMD_USB_DEV_READFRAMENO, 0);
#endif
}

/*******************************************************************************
 * Name: lpc214x_wakeup
 *
 * Description:
 *   Tries to wake up the host connected to this device
 *
 *******************************************************************************/

static int lpc214x_wakeup(struct usbdev_s *dev)
{
  ubyte arg = USBDEV_DEVSTATUS_SUSPEND;
  irqstate_t flags;

  usbtrace(TRACE_DEVWAKEUP, (uint16)g_usbdev.devstatus);

  flags = irqsave();
  if (DEVSTATUS_CONNECT(g_usbdev.devstatus))
    {
      arg |= USBDEV_DEVSTATUS_CONNECT;
    }

  lpc214x_usbcmd(CMD_USB_DEV_SETSTATUS, arg);
  irqrestore(flags);
  return OK;
}

/*******************************************************************************
 * Name: lpc214x_selfpowered
 *
 * Description:
 *   Sets/clears the device selfpowered feature 
 *
 *******************************************************************************/

static int lpc214x_selfpowered(struct usbdev_s *dev, boolean selfpowered)
{
  FAR struct lpc214x_usbdev_s *priv = (FAR struct lpc214x_usbdev_s *)dev;

  usbtrace(TRACE_DEVSELFPOWERED, (uint16)selfpowered);

#ifdef CONFIG_DEBUG
  if (!dev)
    {
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_INVALIDPARMS), 0);
      return -ENODEV;
    }
#endif

  priv->selfpowered = selfpowered;
  return OK;
}

/*******************************************************************************
 * Name: lpc214x_pullup
 *
 * Description:
 *   Software-controlled connect to/disconnect from USB host
 *
 *******************************************************************************/

static int lpc214x_pullup(struct usbdev_s *dev, boolean enable)
{
  usbtrace(TRACE_DEVPULLUP, (uint16)enable);

  /* Prohibit interruption during connection command */

  irqstate_t flags = irqsave();

  /* The USBDEV_DEVSTATUS_CONNECT bit in the CMD_USB_DEV_SETSTATUS command
   * controls the LPC214x SoftConnect_N output pin that is used for SoftConnect.
   */

  lpc214x_usbcmd(CMD_USB_DEV_SETSTATUS, (enable ? USBDEV_DEVSTATUS_CONNECT : 0));
  irqrestore(flags);
  return OK;
}

/*******************************************************************************
 * Public Functions
 *******************************************************************************/

/*******************************************************************************
 * Name: up_usbinitialize
 *
 * Description:
 *   Initialize USB hardware.
 *
 * Assumptions:
 * - This function is called very early in the initialization sequence
 * - PLL initialization is not performed here but should been in the low-level
 *   boot logic.  For the USB engine, FUSB=FOSC*PLL_M.  The USB device controller
 *   clock is a 48MHz clock.
 *
 *******************************************************************************/

void up_usbinitialize(void)
{
  struct lpc214x_usbdev_s *priv = &g_usbdev;
  uint32 reg;

  usbtrace(TRACE_DEVINIT, 0);

patacongo's avatar
patacongo committed
  /* Initialize the device state structure */

  memset(priv, 0, sizeof(struct lpc214x_usbdev_s));
  priv->usbdev.ops = &g_devops;
  priv->wravail    = LPC214X_EPALLSET;

  /* Turn on USB power and clocking */

  reg = lpc214x_getreg(LPC214X_PCON_PCONP);
  reg |= LPC214X_PCONP_PCUSB;
  lpc214x_putreg(reg, LPC214X_PCON_PCONP);

  /* Enable Vbus sense and Connect */

  reg = lpc214x_getreg(LPC214X_PINSEL1);
  reg = (reg & LPC214X_USBDEV_PINMASK) | LPC214X_USBDEV_PINSEL;
  lpc214x_putreg(reg, LPC214X_PINSEL1);

  /* Attach USB controller  interrupt handler */

  if (irq_attach(LPC214X_USB_IRQ, lpc214x_usbinterrupt) != 0)
    {
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_IRQREGISTRATION), 0);
      goto errout;
    }

  /* Enable USB inerrupts */

  up_enable_irq(LPC214X_USB_IRQ);

  /* Disconnect device */

  lpc214x_pullup(&priv->usbdev, FALSE);

  /* Enable EP0 for OUT (host-to-device) */

  lpc214x_usbcmd(CMD_USB_DEV_SETADDRESS, CMD_USB_SETADDRESS_DEVEN|LPC214X_EP0_OUT);
  lpc214x_usbcmd(CMD_USB_DEV_SETADDRESS, CMD_USB_SETADDRESS_DEVEN|LPC214X_EP0_OUT);

  /* Reset/Re-initialize the USB hardware */

  lpc214x_usbreset(priv);

  /* Init Device state structure */

  priv->devstatus = lpc214x_usbcmd(CMD_USB_DEV_GETSTATUS, 0);
  return;

errout:
  up_usbuninitialize();
}

/*******************************************************************************
 * Name: up_usbuninitialize
 *******************************************************************************/

void up_usbuninitialize(void)
{
  struct lpc214x_usbdev_s *priv = &g_usbdev;
  uint32 reg;
  irqstate_t flags;

  usbtrace(TRACE_DEVUNINIT, 0);
  if (priv->driver)
    {
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_DRIVERREGISTERED), 0);
      usbdev_unregister(priv->driver);
    }

  /* Disconnect device */

  flags = irqsave();
  lpc214x_pullup(&priv->usbdev, FALSE);
  priv->usbdev.speed = USB_SPEED_UNKNOWN;
  lpc214x_usbcmd(CMD_USB_DEV_CONFIG, 0);

  /* Disable and detach IRQs */

  up_disable_irq(LPC214X_USB_IRQ);
  irq_detach(LPC214X_USB_IRQ);

  /* Turn off USB power and clocking */

  reg = lpc214x_getreg(LPC214X_PCON_PCONP);
  reg &= ~LPC214X_PCONP_PCUSB;
  lpc214x_putreg(reg, LPC214X_PCON_PCONP);
  irqrestore(flags);
}

/*******************************************************************************
 * 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)
{
  int ret;

  usbtrace(TRACE_DEVREGISTER, 0);
patacongo's avatar
patacongo committed

#ifdef CONFIG_DEBUG
patacongo's avatar
patacongo committed
  if (!driver || !driver->ops->bind || !driver->ops->unbind ||
      !driver->ops->disconnect || !driver->ops->setup)
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_INVALIDPARMS), 0);
patacongo's avatar
patacongo committed
      return -EINVAL;
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_DRIVER), 0);
      return -EBUSY;
    }
#endif

  /* First hook up the driver */

  g_usbdev.driver = driver;

  /* Then bind the class driver */

  ret = CLASS_BIND(driver, &g_usbdev.usbdev);
  if (ret)
    {
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_BINDFAILED), (uint16)-ret);
      g_usbdev.driver = NULL;
    }
  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)
{
  usbtrace(TRACE_DEVUNREGISTER, 0);

#ifdef CONFIG_DEBUG
  if (driver != g_usbdev.driver)
    {
      usbtrace(TRACE_DEVERROR(LPC214X_TRACEERR_INVALIDPARMS), 0);
      return -EINVAL;
    }
#endif

  /* Unbind the class driver */

  CLASS_UNBIND(driver, &g_usbdev.usbdev);

  /* Unhook the driver */

  g_usbdev.driver = NULL;
  return OK;
}