/****************************************************************************
 *
 * Copyright 2016 Samsung Electronics All Rights Reserved.
 *
 * Licensed 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.
 *
 ****************************************************************************/
/****************************************************************************
 * 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 <tinyara/config.h>

#include <sys/types.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <debug.h>

#include <tinyara/arch.h>
#include <tinyara/kmalloc.h>
#include <tinyara/usb/usb.h>
#include <tinyara/usb/usbdev.h>
#include <tinyara/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 */
