/**
 * \file
 *
 * Copyright (C) 2009 Atmel Corporation. All rights reserved.
 *
 * \page License
 *
 * 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. The name of Atmel may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 * Atmel AVR product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL 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.
 */
/*!
 * \file
 *
 * \brief USB function driver core.
 *
 * This file implements a glue layer for easier function driver
 * implementation, and for supporting multiple configurations and/or
 * interfaces provided by separate function drivers.
 *
 * - Compiler:           IAR EWAVR32 and GNU GCC for AVR32
 * - Supported devices:  All devices with a USB Device Controller
 * - AppNote:
 *
 * \author               Atmel Corporation: http://www.atmel.com \n
 *                       Support and FAQ: http://support.atmel.no/
 *
 * \page License
 *
 * Copyright (C) 2008, Atmel Corporation All rights reserved.
 *
 * 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. The name of ATMEL may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL ``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 EXPRESSLY AND
 * SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL 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.
 */
#include <assert.h>
#include <byteorder.h>
#include <debug.h>
#include <dmapool.h>
#include <malloc.h>
#include <string.h>
#include <util.h>
#include <usb/function.h>
#include <usb/function_core.h>
#include <usb/request.h>
#include <usb/udc.h>

#include <app/config_usb.h>
#include <app/usb_strings.h>

static const struct usb_device_descriptor usb_func_device_desc = {
	.bLength		= sizeof(struct usb_device_descriptor),
	.bDescriptorType	= USB_DT_DEVICE,
	.bcdUSB			= LE16(0x0200),
	.bDeviceClass		= CONFIG_USB_FUNC_DEVICE_CLASS,
	.bDeviceSubClass	= CONFIG_USB_FUNC_DEVICE_SUBCLASS,
	.bDeviceProtocol	= CONFIG_USB_FUNC_DEVICE_PROTOCOL,
	.bMaxPacketSize0	= 64,
	.idVendor		= LE16(CONFIG_USB_FUNC_VENDOR_ID),
	.idProduct		= LE16(CONFIG_USB_FUNC_PRODUCT_ID),
	.bcdDevice		= LE16((CONFIG_USB_FUNC_MAJOR_VERSION << 8)
					| CONFIG_USB_FUNC_MINOR_VERSION),
	.iManufacturer		= USB_STRING_DEV_MANUFACTURER,
	.iProduct		= USB_STRING_DEV_PRODUCT,
	.iSerialNumber		= USB_STRING_DEV_SERIAL,
	.bNumConfigurations	= CONFIG_USB_FUNC_NR_CONFIGURATIONS,
};

#ifdef CONFIG_UDC_HIGH_SPEED
static const struct usb_device_qualifier_descriptor usb_func_device_qual = {
	.bLength	= sizeof(struct usb_device_qualifier_descriptor),
	.bDescriptorType	= USB_DT_DEVICE_QUALIFIER,
	.bcdUSB			= LE16(0x0200),
	.bDeviceClass		= CONFIG_USB_FUNC_DEVICE_CLASS,
	.bDeviceSubClass	= CONFIG_USB_FUNC_DEVICE_SUBCLASS,
	.bDeviceProtocol	= CONFIG_USB_FUNC_DEVICE_PROTOCOL,
	.bMaxPacketSize0	= 64,
	.bNumConfigurations	= CONFIG_USB_FUNC_NR_CONFIGURATIONS,
};
#endif

static struct usb_func_config usb_func_config[CONFIG_USB_FUNC_NR_CONFIGURATIONS];
static struct usb_request usb_func_control_req;
static struct buffer usb_func_desc_buf;

static struct usb_func_config *usb_func_get_config(unsigned int id)
{
	return &usb_func_config[id - 1];
}

static struct usb_func_config *usb_func_get_current_config(struct udc *udc)
{
	if (!udc->config)
		return NULL;

	return usb_func_get_config(udc->config->bConfigurationValue);
}

static unsigned int usb_func_nr_interfaces(struct usb_func_config *config)
{
	return config->desc->bNumInterfaces;
}

/**
 * \brief Add an interface to a configuration.
 *
 * This function will associate an interface with an existing
 * configuration so that it will be automatically enabled when the
 * configuration is selected, and the interface descriptor(s) will be
 * included in the configuration descriptor for this configuration.
 *
 * If no high-speed descriptors are provided, they will be assumed to
 * be the same as the full-speed descriptors.
 *
 * \param config The USB function configuration.
 * \param iface The interface to be added to \a config.
 *
 * \pre
 * - \a iface must have at least one alternate setting.
 * - \a iface must have a bInterfaceNumber less than bNumInterfaces
 *	of \a config.
 * - An interface with the same bInterfaceNumber must not have been
 *	registered before to the same configuration.
 * - All settings must have the same bInterfaceNumber.
 * - Each setting's high-speed descriptor, if present, must have
 *	the same bInterfaceNumber as the full-speed descriptor.
 */
void usb_func_add_interface(struct usb_func_config *config,
		struct usb_func_iface *iface)
{
	struct usb_func_iface_setting *setting;
	unsigned int	id = iface->setting[0].fs_desc->bInterfaceNumber;
	unsigned int	i;

	assert(iface->nr_settings > 0);
	assert(id < usb_func_nr_interfaces(config));
	assert(!config->interface[id]);

	config->interface[id] = iface;

	for (i = 0; i < iface->nr_settings; i++) {
		setting = &iface->setting[i];

		assert(setting->ops);
		assert(setting->fs_desc->bInterfaceNumber == id);

		if (!setting->hs_desc) {
			setting->hs_desc = setting->fs_desc;
			setting->hs_desc_size = setting->fs_desc_size;
		} else {
			assert(setting->hs_desc->bInterfaceNumber == id);
		}
	}

	dbg_printf("func-core: created interface %u with %zu settings\n",
			id, iface->nr_settings);
}

/**
 * \brief Add a device configuration.
 *
 * This function will add a new configuration of the device.
 *
 * \note The contents of \a desc will be altered during normal
 * operation.
 *
 * \param desc The configuration descriptor of the new configuration.
 *
 * \return An object representing the new configuration.
 *
 * \pre
 * - \a desc must have a bConfigurationValue less than or equal to
 *	#CONFIG_USB_FUNC_NR_CONFIGURATIONS.
 * - Two configurations can not have the same bConfigurationValue.
 */
struct usb_func_config *usb_func_add_config(
		struct usb_configuration_descriptor *desc)
{
	struct usb_func_config	*config;
	unsigned int		id = desc->bConfigurationValue;
	unsigned int		nr_interfaces = desc->bNumInterfaces;

	config = usb_func_get_config(id);

	assert(id <= CONFIG_USB_FUNC_NR_CONFIGURATIONS);
	assert(!config->desc);

	config->desc = desc;
	config->interface = malloc(nr_interfaces * sizeof(*config->interface));
	assert(config->interface);
	memset(config->interface, 0,
			nr_interfaces * sizeof(*config->interface));

	dbg_printf("func-core: created configuration %u with %u interfaces\n",
			id, nr_interfaces);

	return config;
}

static void usb_func_ctrl_in_done(struct udc *udc, struct usb_request *req)
{
	dbg_printf("func-core: Control IN request done\n");

	udc_ep0_expect_status(udc);
}

static void usb_func_string_desc_in_done(struct udc *udc,
		struct usb_request *req)
{
	struct buffer	*buf;

	dbg_printf("func-core: String IN descriptor done\n");

	buf = usb_req_get_first_buffer(req);
	dma_free(buf->addr.ptr, USB_MAX_DESC_SIZE);

	udc_ep0_expect_status(udc);
}

static int ascii_to_usb_str(const char *src, le16_t *dest, size_t dest_len)
{
	size_t	len = 0;
	char	c;

	while (len < (dest_len - 1)) {
		c = *src++;
		if (!c)
			break;
		*dest++ = cpu_to_le16(c);
		len += 2;
	}

	return len;
}

static int usb_func_prep_config_desc(struct udc *udc,
		struct usb_func_config *config, struct usb_request *req,
		enum usb_device_speed speed, size_t max_len)
{
	const struct usb_interface_descriptor *if_desc;
	struct usb_func_iface		*iface;
	struct usb_func_iface_setting	*setting;
	size_t				buf_len;
	size_t				total_len;
	size_t				len;
	unsigned int			i, j;

	total_len = sizeof(*config->desc);
	len = min(max_len, total_len);
	buffer_init_tx(&usb_func_desc_buf, config->desc, len);
	usb_req_add_buffer(req, &usb_func_desc_buf);

	for (i = 0; i < usb_func_nr_interfaces(config); i++) {
		iface = config->interface[i];

		for (j = 0; j < iface->nr_settings; j++) {
			setting = &iface->setting[j];

			switch (speed) {
			case USB_SPEED_FULL:
				if_desc = setting->fs_desc;
				buf_len = setting->fs_desc_size;
				break;
			case USB_SPEED_HIGH:
				if_desc = setting->hs_desc;
				buf_len = setting->hs_desc_size;
				break;
			default:
				return -1;
			}

			total_len += buf_len;
			buf_len = min(buf_len, max_len - len);
			if (buf_len == 0)
				continue;

			buffer_init_tx(&setting->desc_buf, if_desc, buf_len);
			usb_req_add_buffer(req, &setting->desc_buf);
			len += buf_len;
		}
	}

	config->desc->wTotalLength = cpu_to_le16(total_len);

	return len;
}

static int usb_func_prep_string_desc(struct udc *udc,
		struct usb_request *req, uint8_t desc_index,
		uint16_t langid, uint16_t len)
{
	struct usb_string_descriptor	*desc;
	struct buffer			*buf;
	const char			*str;
	phys_addr_t			desc_phys;
	int				str_len;

	if (desc_index >= USB_NR_STRINGS)
		return -1;
	if (desc_index != USB_STRING_LANGID && langid != USB_DEV_LANGID)
		return -1;

	str = usb_dev_string_table[desc_index];
	if (!str)
		return -1;

	desc = dma_alloc(&desc_phys, USB_MAX_DESC_SIZE);
	if (!desc)
		return -1;

	desc->bDescriptorType = USB_DT_STRING;
	if (desc_index == USB_STRING_LANGID) {
		desc->bString[0] = LE16(USB_DEV_LANGID);
		desc->bLength = 4;
	} else {
		str_len = ascii_to_usb_str(str, desc->bString,
				USB_MAX_DESC_SIZE - 2);
		desc->bLength = str_len + 2;
	}

	buf = &usb_func_desc_buf;
	buffer_init_tx_mapped(buf, desc, desc_phys, min(desc->bLength, len));
	usb_req_add_buffer(req, buf);

	req->req_done = usb_func_string_desc_in_done;

	return buf->len;
}

int usb_func_get_descriptor(struct udc *udc, uint16_t value,
		uint16_t index, uint16_t len)
{
	struct usb_func_config	*config;
	struct usb_request	*req = &usb_func_control_req;
	unsigned int		desc_index;
	unsigned int		desc_type;
	int			buf_len = -1;

	dbg_printf("func-core: get descriptor v%04x i%04x l%04x\n",
			value, index, len);

	usb_req_init(req);

	req->req_done = usb_func_ctrl_in_done;

	desc_type = value >> 8;
	desc_index = value & 0xff;

	switch (desc_type) {
	case USB_DT_DEVICE:
		buf_len = min(len, sizeof(usb_func_device_desc));
		buffer_init_tx(&usb_func_desc_buf, &usb_func_device_desc,
				buf_len);
		usb_req_add_buffer(req, &usb_func_desc_buf);
		break;

	case USB_DT_CONFIGURATION:
		if (desc_index >= CONFIG_USB_FUNC_NR_CONFIGURATIONS)
			return -1;

		config = &usb_func_config[desc_index];
		config->desc->bDescriptorType = desc_type;

		buf_len = usb_func_prep_config_desc(udc, config, req,
				udc->speed, len);
		break;

#ifdef CONFIG_UDC_HIGH_SPEED
	case USB_DT_DEVICE_QUALIFIER:
		buf_len = min(len, sizeof(usb_func_device_qual));
		buffer_init_tx(&usb_func_desc_buf, &usb_func_device_qual,
				buf_len);
		usb_req_add_buffer(req, &usb_func_desc_buf);
		break;

	case USB_DT_OTHER_SPEED_CONFIGURATION:
		if (desc_index >= CONFIG_USB_FUNC_NR_CONFIGURATIONS)
			return -1;

		config = &usb_func_config[desc_index];
		config->desc->bDescriptorType = desc_type;

		if (udc->speed == USB_SPEED_HIGH)
			buf_len = usb_func_prep_config_desc(udc, config,
					req, USB_SPEED_FULL, len);
		else
			buf_len = usb_func_prep_config_desc(udc, config,
					req, USB_SPEED_HIGH, len);
		break;
#endif

	case USB_DT_STRING:
		buf_len = usb_func_prep_string_desc(udc, req,
				desc_index, index, len);
		break;
	}

	if (buf_len < 0)
		return -1;

	if (buf_len < len)
		set_bit(USB_REQ_SHORT_PKT, &req->flags);

	udc_ep0_submit_in_req(udc, req);

	return 0;
}

static int usb_config_enable(struct udc *udc, struct usb_func_config *config)
{
	unsigned int		nr_interfaces;
	unsigned int		i;

	nr_interfaces = usb_func_nr_interfaces(config);
	for (i = 0; i < nr_interfaces; i++) {
		struct usb_func_iface	*iface;

		iface = config->interface[i];
		dbg_printf("func-core: enabling interface %u...\n", i);
		if (iface->cur_setting->ops->enable(udc, iface))
			goto fail;
	}

	return 0;

fail:
	dbg_printf("func-core: failed to enable config %u\n",
			config->desc->bConfigurationValue);
	while (i-- > 0) {
		struct usb_func_iface	*iface;

		iface = config->interface[i];
		iface->cur_setting->ops->disable(udc, iface);
	}

	return -1;
}

static void usb_config_disable(struct udc *udc, struct usb_func_config *config)
{
	unsigned int		nr_interfaces;
	unsigned int		i;

	if (!config)
		return;

	dbg_printf("func-core: disabling config %u\n",
			config->desc->bConfigurationValue);

	nr_interfaces = usb_func_nr_interfaces(config);
	for (i = 0; i < nr_interfaces; i++) {
		struct usb_func_iface	*iface;

		iface = config->interface[i];
		iface->cur_setting->ops->disable(udc, iface);
	}
}

static void usb_config_init_iface_settings(struct usb_func_config *config)
{
	unsigned int		nr_interfaces;
	unsigned int		i;

	nr_interfaces = usb_func_nr_interfaces(config);
	for (i = 0; i < nr_interfaces; i++) {
		struct usb_func_iface	*iface;

		iface = config->interface[i];
		iface->cur_setting = &iface->setting[0];
	}
}

int usb_func_set_configuration(struct udc *udc, uint16_t config_id)
{
	struct usb_func_config	*old;
	struct usb_func_config	*new;

	dbg_printf("func-core: set configuration %u\n", config_id);

	if (config_id > CONFIG_USB_FUNC_NR_CONFIGURATIONS)
		return -1;

	/* Disable the old configuration, if any */
	old = usb_func_get_current_config(udc);
	udc->config = NULL;
	if (old)
		usb_config_disable(udc, old);

	if (config_id == 0)
		return 0;

	new = usb_func_get_config(config_id);
	usb_config_init_iface_settings(new);
	udc->config = new->desc;
	if (usb_config_enable(udc, new)) {
		dbg_printf("Failed to set new configuration\n");
		if (old) {
			udc->config = old->desc;
			if (usb_config_enable(udc, old))
				udc->config = NULL;
		} else {
			udc->config = NULL;
		}
		return -1;
	}

	return 0;
}

int usb_func_get_interface(struct udc *udc, uint16_t index)
{
	struct usb_func_config		*config;
	struct usb_func_iface		*iface;
	struct usb_func_iface_setting	*setting;

	dbg_printf("func-core: get interface %u\n", index);

	config = usb_func_get_current_config(udc);
	if (!config || index >= usb_func_nr_interfaces(config))
		return -1;

	iface = config->interface[index];
	setting = iface->cur_setting;

	udc_ep0_write_sync(udc, &setting->fs_desc->bAlternateSetting, 1);
	udc_ep0_expect_status(udc);

	return 0;
}

int usb_func_set_interface(struct udc *udc, uint16_t index, uint16_t altsetting)
{
	struct usb_func_config		*config;
	struct usb_func_iface		*iface;
	struct usb_func_iface_setting	*old;
	struct usb_func_iface_setting	*new;

	dbg_printf("func-core: set interface %u altsetting %u\n",
			index, altsetting);

	config = usb_func_get_current_config(udc);
	if (!config || index >= usb_func_nr_interfaces(config))
		return -1;

	iface = config->interface[index];
	if (altsetting >= iface->nr_settings)
		return -1;

	old = iface->cur_setting;
	new = &iface->setting[altsetting];

	/*
	 * Switch interface setting. If this fails, we attempt to
	 * switch back to the old setting. If _that_ fails, there's
	 * not much we can do, as we're already going to report STALL
	 * back to the host.
	 */
	old->ops->disable(udc, iface);
	iface->cur_setting = new;
	if (new->ops->enable(udc, iface)) {
		iface->cur_setting = old;
		old->ops->enable(udc, iface);
		return -1;
	}

	return 0;
}

void usb_func_reset(struct udc *udc)
{
	dbg_printf("func-core: reset, speed=%u\n", udc->speed);

	if (udc->config)
		usb_func_set_configuration(udc, 0);
}

int usb_func_process_setup_request(struct udc *udc, struct usb_setup_req *req)
{
	struct usb_func_config		*config;
	struct usb_func_iface		*iface;
	struct usb_func_iface_setting	*setting;
	uint16_t			index = le16_to_cpu(req->wIndex);

	if (usb_req_recipient(req) != USB_RECIP_INTERFACE) {
		dbg_printf("func-core: bad request (bmRequestType: %u)\n",
				req->bmRequestType);
		return -1;
	}

	config = usb_func_get_current_config(udc);
	if (!config || index >= usb_func_nr_interfaces(config))
		return -1;

	iface = config->interface[index];
	setting = iface->cur_setting;

	return setting->ops->setup(udc, iface, req);
}
