/****************************************************************************
 * drivers/usbdev/composite.c
 *
 *   Copyright (C) 2012 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * 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 <stdint.h>
#include <string.h>
#include <errno.h>
#include <debug.h>

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

#include "composite.h"

#ifdef CONFIG_USBDEV_COMPOSITE

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

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

/* This structure describes the internal state of the driver */

struct composite_dev_s
{
  FAR struct usbdev_s         *usbdev;   /* usbdev driver pointer */
  uint8_t                      config;   /* Configuration number */
  FAR struct usbdev_req_s     *ctrlreq;  /* Allocated control request */
  struct usbdevclass_driver_s *dev1;     /* Device 1 class object */
  struct usbdevclass_driver_s *dev2;     /* Device 2 class object */
};

/* The internal version of the class driver */

struct composite_driver_s
{
  struct usbdevclass_driver_s  drvr;
  FAR struct composite_dev_s  *dev;
};

/* This is what is allocated */

struct composite_alloc_s
{
  struct composite_dev_s       dev;
  struct composite_driver_s    drvr;
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/
/* USB helps ****************************************************************/

static void    composite_ep0incomplete(FAR struct usbdev_ep_s *ep,
                 FAR struct usbdev_req_s *req);
static int     composite_classsetup(FAR struct composite_dev_s *priv,
                 FAR struct usbdev_s *dev,
                 FAR const struct usb_ctrlreq_s *ctrl, FAR uint8_t *dataout,
                 size_t outlen);
static struct usbdev_req_s *composite_allocreq(FAR struct usbdev_ep_s *ep,
                 uint16_t len);
static void    composite_freereq(FAR struct usbdev_ep_s *ep,
                 FAR struct usbdev_req_s *req);

/* USB class device ********************************************************/

static int     composite_bind(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev);
static void    composite_unbind(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev);
static int     composite_setup(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev,
                 FAR const struct usb_ctrlreq_s *ctrl, FAR uint8_t *dataout,
                 size_t outlen);
static void    composite_disconnect(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev);
static void    composite_suspend(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev);
static void    composite_resume(FAR struct usbdevclass_driver_s *driver,
                 FAR struct usbdev_s *dev);

/****************************************************************************
 * Private Data
 ****************************************************************************/
/* USB class device *********************************************************/

static const struct usbdevclass_driverops_s g_driverops =
{
  composite_bind,       /* bind */
  composite_unbind,     /* unbind */
  composite_setup,      /* setup */
  composite_disconnect, /* disconnect */
  composite_suspend,    /* suspend */
  composite_resume,     /* resume */
};

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

const char g_compvendorstr[]  = CONFIG_COMPOSITE_VENDORSTR;
const char g_compproductstr[] = CONFIG_COMPOSITE_PRODUCTSTR;
const char g_compserialstr[]  = CONFIG_COMPOSITE_SERIALSTR;

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

/****************************************************************************
 * Name: composite_ep0incomplete
 *
 * Description:
 *   Handle completion of the composite driver's EP0 control operations
 *
 ****************************************************************************/

static void composite_ep0incomplete(FAR struct usbdev_ep_s *ep,
                                    FAR struct usbdev_req_s *req)
{
  /* Just check the result of the transfer */

  if (req->result || req->xfrd != req->len)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_REQRESULT), (uint16_t)-req->result);
    }
}

/****************************************************************************
 * Name: composite_classsetup
 *
 * Description:
 *   Forward a setup command to the appropriate component device
 *
 ****************************************************************************/

static int composite_classsetup(FAR struct composite_dev_s *priv,
                                FAR struct usbdev_s *dev,
                                FAR const struct usb_ctrlreq_s *ctrl,
                                FAR uint8_t *dataout, size_t outlen)
{
  uint16_t index;
  uint8_t  interface;
  int ret = -EOPNOTSUPP;

  index     = GETUINT16(ctrl->index);
  interface = (uint8_t)(index & 0xff);

  if (interface >= DEV1_FIRSTINTERFACE && interface < (DEV1_FIRSTINTERFACE + DEV1_NINTERFACES))
    {
      ret = CLASS_SETUP(priv->dev1, dev, ctrl, dataout, outlen);
    }
  else if (interface >= DEV2_FIRSTINTERFACE && interface < (DEV2_FIRSTINTERFACE + DEV2_NINTERFACES))
    {
      ret = CLASS_SETUP(priv->dev2, dev, ctrl, dataout, outlen);
    }

  return ret;
}

/****************************************************************************
 * Name: composite_allocreq
 *
 * Description:
 *   Allocate a request instance along with its buffer
 *
 ****************************************************************************/

static struct usbdev_req_s *composite_allocreq(FAR struct usbdev_ep_s *ep,
                                               uint16_t len)
{
  FAR struct usbdev_req_s *req;

  req = EP_ALLOCREQ(ep);
  if (req != NULL)
    {
      req->len = len;
      req->buf = EP_ALLOCBUFFER(ep, len);
      if (!req->buf)
        {
          EP_FREEREQ(ep, req);
          req = NULL;
        }
    }
  return req;
}

/****************************************************************************
 * Name: composite_freereq
 *
 * Description:
 *   Free a request instance along with its buffer
 *
 ****************************************************************************/

static void composite_freereq(FAR struct usbdev_ep_s *ep,
                              FAR struct usbdev_req_s *req)
{
  if (ep != NULL && req != NULL)
    {
      if (req->buf != NULL)
        {
          EP_FREEBUFFER(ep, req->buf);
        }
      EP_FREEREQ(ep, req);
    }
}

/****************************************************************************
 * USB Class Driver Methods
 ****************************************************************************/

/****************************************************************************
 * Name: composite_bind
 *
 * Description:
 *   Invoked when the driver is bound to a USB device driver
 *
 ****************************************************************************/

static int composite_bind(FAR struct usbdevclass_driver_s *driver,
                          FAR struct usbdev_s *dev)
{
  FAR struct composite_dev_s *priv = ((FAR struct composite_driver_s*)driver)->dev;
  int ret;

  usbtrace(TRACE_CLASSBIND, 0);

  /* Bind the structures */

  priv->usbdev   = dev;

  /* Save the reference to our private data structure in EP0 so that it
   * can be recovered in ep0 completion events.
   */

  dev->ep0->priv = priv;

  /* Preallocate one control request */

  priv->ctrlreq = composite_allocreq(dev->ep0, COMPOSITE_CFGDESCSIZE);
  if (priv->ctrlreq == NULL)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_ALLOCCTRLREQ), 0);
      ret = -ENOMEM;
      goto errout;
    }

  /* Initialize the pre-allocated control request */

  priv->ctrlreq->callback = composite_ep0incomplete;

  /* Then bind each of the constituent class drivers */

  ret = CLASS_BIND(priv->dev1, dev);
  if (ret < 0)
    {
      goto errout;
    }

  ret = CLASS_BIND(priv->dev2, dev);
  if (ret < 0)
    {
      goto errout;
    }

  /* Report if we are selfpowered */

#ifdef CONFIG_USBDEV_SELFPOWERED
  DEV_SETSELFPOWERED(dev);
#endif

  /* And pull-up the data line for the soft connect function */

  DEV_CONNECT(dev);
  return OK;

errout:
  composite_unbind(driver, dev);
  return ret;
}

/****************************************************************************
 * Name: composite_unbind
 *
 * Description:
 *    Invoked when the driver is unbound from a USB device driver
 *
 ****************************************************************************/

static void composite_unbind(FAR struct usbdevclass_driver_s *driver,
                             FAR struct usbdev_s *dev)
{
  FAR struct composite_dev_s *priv;
  irqstate_t flags;

  usbtrace(TRACE_CLASSUNBIND, 0);

#ifdef CONFIG_DEBUG
  if (!driver || !dev || !dev->ep0)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_INVALIDARG), 0);
      return;
     }
#endif

  /* Extract reference to private data */

  priv = ((FAR struct composite_driver_s*)driver)->dev;

#ifdef CONFIG_DEBUG
  if (!priv)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_EP0NOTBOUND), 0);
      return;
    }
#endif

  /* Make sure that we are not already unbound */

  if (priv != NULL)
    {
      /* Unbind the constituent class drivers */

      flags = irqsave();
      CLASS_UNBIND(priv->dev1, dev);
      CLASS_UNBIND(priv->dev2, dev);

      /* Free the pre-allocated control request */

      priv->config = COMPOSITE_CONFIGIDNONE;
      if (priv->ctrlreq != NULL)
        {
          composite_freereq(dev->ep0, priv->ctrlreq);
          priv->ctrlreq = NULL;
        }
      irqrestore(flags);
    }
}

/****************************************************************************
 * Name: composite_setup
 *
 * Description:
 *   Invoked for ep0 control requests.  This function probably executes
 *   in the context of an interrupt handler.
 *
 ****************************************************************************/

static int composite_setup(FAR struct usbdevclass_driver_s *driver,
                           FAR struct usbdev_s *dev,
                           FAR const struct usb_ctrlreq_s *ctrl,
                           FAR uint8_t *dataout, size_t outlen)
{
  FAR struct composite_dev_s *priv;
  FAR struct usbdev_req_s *ctrlreq;
  uint16_t value;
  uint16_t index;
  uint16_t len;
  bool dispatched = false;
  int ret = -EOPNOTSUPP;

#ifdef CONFIG_DEBUG
  if (!driver || !dev || !dev->ep0 || !ctrl)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_SETUPINVALIDARGS), 0);
      return -EIO;
     }
#endif

  /* Extract a reference to private data */

  usbtrace(TRACE_CLASSSETUP, ctrl->req);
  priv = ((FAR struct composite_driver_s*)driver)->dev;

#ifdef CONFIG_DEBUG
  if (!priv)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_EP0NOTBOUND2), 0);
      return -ENODEV;
    }
#endif
  ctrlreq   = priv->ctrlreq;

  /* Extract the little-endian 16-bit values to host order */

  value     = GETUINT16(ctrl->value);
  index     = GETUINT16(ctrl->index);
  len       = GETUINT16(ctrl->len);

  uvdbg("type=%02x req=%02x value=%04x index=%04x len=%04x\n",
        ctrl->type, ctrl->req, value, index, len);
  UNUSED(index);

  if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_STANDARD)
    {
      /**********************************************************************
       * Standard Requests
       **********************************************************************/

      switch (ctrl->req)
        {
        case USB_REQ_GETDESCRIPTOR:
          {
            /* The value field specifies the descriptor type in the MS byte and the
             * descriptor index in the LS byte (order is little endian)
             */

            switch (ctrl->value[1])
              {
              case USB_DESC_TYPE_DEVICE:
                {
                  ret = USB_SIZEOF_DEVDESC;
                  memcpy(ctrlreq->buf, composite_getdevdesc(), ret);
                }
                break;

#ifdef CONFIG_USBDEV_DUALSPEED
              case USB_DESC_TYPE_DEVICEQUALIFIER:
                {
                  ret = USB_SIZEOF_QUALDESC;
                  memcpy(ctrlreq->buf, composite_getqualdesc(), ret);
                }
                break;

              case USB_DESC_TYPE_OTHERSPEEDCONFIG:
#endif

              case USB_DESC_TYPE_CONFIG:
                {
#ifdef CONFIG_USBDEV_DUALSPEED
                  ret = composite_mkcfgdesc(ctrlreq->buf, dev->speed,
                                            ctrl->value[1]);
#else
                  ret = composite_mkcfgdesc(ctrlreq->buf);
#endif
                }
                break;

              case USB_DESC_TYPE_STRING:
                {
                  /* value == string index. Zero is the language ID. */

                  uint8_t strid = ctrl->value[0];
                  FAR struct usb_strdesc_s *buf = (FAR struct usb_strdesc_s *)ctrlreq->buf;

                  if (strid <= COMPOSITE_NSTRIDS)
                    {
                      ret = composite_mkstrdesc(strid, buf);
                    }
#if DEV1_NSTRIDS > 0
                  else if (strid <= DEV1_STRIDBASE + DEV1_NSTRIDS)
                    {
                      ret = DEV1_MKSTRDESC(strid, buf);
                    }
#endif
#if DEV2_NSTRIDS > 0
                  else if (strid <= DEV2_STRIDBASE + DEV2_NSTRIDS)
                    {
                      ret = DEV2_MKSTRDESC(strid, buf);
                    }
#endif
                }
                break;

              default:
                {
                  usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_GETUNKNOWNDESC), value);
                }
                break;
              }
          }
          break;

        case USB_REQ_SETCONFIGURATION:
          {
            if (ctrl->type == 0)
              {
                /* Save the configuration and inform the constituent classes */

                ret = CLASS_SETUP(priv->dev1, dev, ctrl, dataout, outlen);
                dispatched = true;

                if (ret >= 0)
                  {
                    ret = CLASS_SETUP(priv->dev2, dev, ctrl, dataout, outlen);
                    if (ret >= 0)
                      {
                        priv->config = value;
                      }
                  }
              }
          }
          break;

        case USB_REQ_GETCONFIGURATION:
          {
            if (ctrl->type == USB_DIR_IN)
              {
                ctrlreq->buf[0] = priv->config;
                ret = 1;
              }
          }
          break;

        case USB_REQ_SETINTERFACE:
          {
            if (ctrl->type == USB_REQ_RECIPIENT_INTERFACE &&
                priv->config == COMPOSITE_CONFIGID)
              {
                ret = composite_classsetup(priv, dev, ctrl, dataout, outlen);
                dispatched = true;
              }
          }
          break;

        case USB_REQ_GETINTERFACE:
          {
            if (ctrl->type == (USB_DIR_IN|USB_REQ_RECIPIENT_INTERFACE) &&
                priv->config == COMPOSITE_CONFIGIDNONE)
              {
                ret = composite_classsetup(priv, dev, ctrl, dataout, outlen);
                dispatched = true;
              }
           }
           break;

        default:
          usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_UNSUPPORTEDSTDREQ), ctrl->req);
          break;
        }
    }
  else
    {
      uint8_t recipient;

      /**********************************************************************
       * Non-Standard Class Requests
       **********************************************************************/

       /* Class implementations should handle there own interface and endpoint
        * requests.
        */

       recipient = ctrl->type & USB_REQ_RECIPIENT_MASK;
       if (recipient == USB_REQ_RECIPIENT_INTERFACE || recipient == USB_REQ_RECIPIENT_ENDPOINT)
         {
           ret = composite_classsetup(priv, dev, ctrl, dataout, outlen);
           dispatched = true;
         }
    }


  /* Respond to the setup command if (1) data was returned, and (2) the request was
   * NOT successfully dispatched to the component class driver.  On an error return
   * value (ret < 0), the USB driver will stall EP0.
   */

  if (ret >= 0 && !dispatched)
    {
      /* Setup the request */

      ctrlreq->len   = MIN(len, ret);
      ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT;

      /* And submit the request to the USB controller driver */

      ret = EP_SUBMIT(dev->ep0, ctrlreq);
      if (ret < 0)
        {
          usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_EPRESPQ), (uint16_t)-ret);
          ctrlreq->result = OK;
          composite_ep0incomplete(dev->ep0, ctrlreq);
        }
    }

  return ret;
}

/****************************************************************************
 * Name: composite_disconnect
 *
 * Description:
 *   Invoked after all transfers have been stopped, when the host is
 *   disconnected.  This function is probably called from the context of an
 *   interrupt handler.
 *
 ****************************************************************************/

static void composite_disconnect(FAR struct usbdevclass_driver_s *driver,
                                 FAR struct usbdev_s *dev)
{
  FAR struct composite_dev_s *priv;
  irqstate_t flags;

  usbtrace(TRACE_CLASSDISCONNECT, 0);

#ifdef CONFIG_DEBUG
  if (!driver || !dev)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_INVALIDARG), 0);
      return;
     }
#endif

  /* Extract reference to private data */

  priv = ((FAR struct composite_driver_s*)driver)->dev;

#ifdef CONFIG_DEBUG
  if (!priv)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_EP0NOTBOUND), 0);
      return;
    }
#endif

  /* Reset the configuration and inform the constituent class drivers of
   * the disconnection.
   */

  flags = irqsave();
  priv->config = COMPOSITE_CONFIGIDNONE;
  CLASS_DISCONNECT(priv->dev1, dev);
  CLASS_DISCONNECT(priv->dev2, dev);
  irqrestore(flags);

  /* Perform the soft connect function so that we will we can be
   * re-enumerated.
   */

  DEV_CONNECT(dev);
}

/****************************************************************************
 * Name: composite_suspend
 *
 * Description:
 *   Invoked on a USB suspend event.
 *
 ****************************************************************************/

static void composite_suspend(FAR struct usbdevclass_driver_s *driver,
                              FAR struct usbdev_s *dev)
{
  FAR struct composite_dev_s *priv;
  irqstate_t flags;

  usbtrace(TRACE_CLASSSUSPEND, 0);

#ifdef CONFIG_DEBUG
  if (!dev)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_INVALIDARG), 0);
      return;
     }
#endif

  /* Extract reference to private data */

  priv = ((FAR struct composite_driver_s*)driver)->dev;

#ifdef CONFIG_DEBUG
  if (!priv)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_EP0NOTBOUND), 0);
      return;
    }
#endif

  /* Forward the suspend event to the constituent devices */

  flags = irqsave();
  CLASS_SUSPEND(priv->dev1, priv->usbdev);
  CLASS_SUSPEND(priv->dev2, priv->usbdev);
  irqrestore(flags);
}

/****************************************************************************
 * Name: composite_resume
 *
 * Description:
 *   Invoked on a USB resume event.
 *
 ****************************************************************************/

static void composite_resume(FAR struct usbdevclass_driver_s *driver,
                             FAR struct usbdev_s *dev)
{
  FAR struct composite_dev_s *priv = NULL;
  irqstate_t flags;

#ifdef CONFIG_DEBUG
  if (!dev)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_INVALIDARG), 0);
      return;
     }
#endif

  /* Extract reference to private data */

  priv = ((FAR struct composite_driver_s*)driver)->dev;

#ifdef CONFIG_DEBUG
  if (!priv)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_EP0NOTBOUND), 0);
      return;
    }
#endif

  /* Forward the resume event to the constituent devices */

  flags = irqsave();
  CLASS_RESUME(priv->dev1, priv->usbdev);
  CLASS_RESUME(priv->dev2, priv->usbdev);
  irqrestore(flags);
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/
/****************************************************************************
 * Name: composite_initialize
 *
 * Description:
 *   Register USB composite device as configured.  This function will call
 *   board-specific implementations in order to obtain the class objects for
 *   each of the members of the composite (see board_mscclassobject(),
 *   board_cdcclassobjec(), ...)
 *
 * Input Parameter:
 *   None
 *
 * Returned Value:
 *   A non-NULL "handle" is returned on success.  This handle may be used
 *   later with composite_uninitialize() in order to removed the composite
 *   device.  This handle is the (untyped) internal representation of the
 *   the class driver instance.
 *
 *   NULL is returned on any failure.
 *
 ****************************************************************************/

FAR void *composite_initialize(void)
{
  FAR struct composite_alloc_s *alloc;
  FAR struct composite_dev_s *priv;
  FAR struct composite_driver_s *drvr;
  int ret;

  /* Allocate the structures needed */

  alloc = (FAR struct composite_alloc_s*)kmm_malloc(sizeof(struct composite_alloc_s));
  if (!alloc)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_ALLOCDEVSTRUCT), 0);
      return NULL;
    }

  /* Convenience pointers into the allocated blob */

  priv = &alloc->dev;
  drvr = &alloc->drvr;

  /* Initialize the USB composite driver structure */

  memset(priv, 0, sizeof(struct composite_dev_s));

  /* Get the constitueat class driver objects */

  ret = DEV1_CLASSOBJECT(&priv->dev1);
  if (ret < 0)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_CLASSOBJECT), (uint16_t)-ret);
      goto errout_with_alloc;
    }

  ret = DEV2_CLASSOBJECT(&priv->dev2);
  if (ret < 0)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_CLASSOBJECT), (uint16_t)-ret);
      goto errout_with_alloc;
    }

  /* Initialize the USB class driver structure */

#ifdef CONFIG_USBDEV_DUALSPEED
  drvr->drvr.speed         = USB_SPEED_HIGH;
#else
  drvr->drvr.speed         = USB_SPEED_FULL;
#endif
  drvr->drvr.ops           = &g_driverops;
  drvr->dev                = priv;

  /* Register the USB composite class driver */

  ret = usbdev_register(&drvr->drvr);
  if (ret)
    {
      usbtrace(TRACE_CLSERROR(USBCOMPOSITE_TRACEERR_DEVREGISTER), (uint16_t)-ret);
      goto errout_with_alloc;
    }

  return (FAR void *)alloc;

errout_with_alloc:
  kmm_free(alloc);
  return NULL;
}

/****************************************************************************
 * Name: composite_uninitialize
 *
 * Description:
 *   Un-initialize the USB composite driver.  The handle is the USB composite
 *   class' device object as was returned by composite_initialize().  This
 *   function will call  board-specific implementations in order to free the
 *   class objects for each of the members of the composite (see
 *   board_mscuninitialize(), board_cdcuninitialize(), ...)
 *
 * Input Parameters:
 *   handle - The handle returned by a previous call to composite_initialize().
 *
 * Returned Value:
 *   None
 *
 ***************************************************************************/

void composite_uninitialize(FAR void *handle)
{
  FAR struct composite_alloc_s *alloc = (FAR struct composite_alloc_s *)handle;
  FAR struct composite_dev_s *priv;

  DEBUGASSERT(alloc != NULL);

  /* First phase uninitialization each of the member classes */

  priv = &alloc->dev;

  DEV1_UNINITIALIZE(priv->dev1);
  DEV2_UNINITIALIZE(priv->dev2);

  /* Then unregister and destroy the composite class */

  usbdev_unregister(&alloc->drvr.drvr);

  /* Free any resources used by the composite driver */
  /* None */

  /* Second phase uninitialization:  Clean up all memory resources */

  DEV1_UNINITIALIZE(priv->dev1);
  DEV2_UNINITIALIZE(priv->dev2);

  /* Then free the composite driver state structure itself */

  kmm_free(priv);
}

/****************************************************************************
 * Name: composite_ep0submit
 *
 * Description:
 *   Members of the composite cannot send on EP0 directly because EP0 is
 *   is "owned" by the composite device.  Instead, when configured as members
 *   of a composite device, those classes should call this method so that
 *   the composite device can send on EP0 onbehalf of the class.
 *
 ****************************************************************************/

int composite_ep0submit(FAR struct usbdevclass_driver_s *driver,
                        FAR struct usbdev_s *dev,
                        FAR struct usbdev_req_s *ctrlreq)
{
  /* This function is not really necessary in the current design.  However,
   * keeping this will provide us a little flexibility in the future if
   * it becomes necessary to manage the completion callbacks.
   */

   return EP_SUBMIT(dev->ep0, ctrlreq);
}

#endif /* CONFIG_USBDEV_COMPOSITE */
