/**
 * \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 Device Controller test function driver.
 *
 * This function driver does not do anything useful, but it can be
 * used to test the USB device stack.
 *
 * - 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 <byteorder.h>
#include <debug.h>
#include <dma.h>
#include <malloc.h>
#include <string.h>
#include <usb/function_core.h>
#include <usb/request.h>
#include <usb/udc.h>
#include <usb/usb_protocol.h>
#include <usb/udctest.h>

#include <app/config_usb.h>

#define UDCTEST_NR_BULK_BUFFERS	2
#define UDCTEST_BUF_SIZE	1024
#define UDCTEST_FS_BULK_EP_SIZE	64

struct udctest_bulk_iface_block {
	struct usb_interface_descriptor iface;
	struct usb_endpoint_descriptor in_ep;
	struct usb_endpoint_descriptor out_ep;
};

struct udctest {
	struct usb_func_iface	iface;
	usb_ep_id_t		bulk_in_ep;
	usb_ep_id_t		bulk_out_ep;
	struct usb_request	bulk_req[UDCTEST_NR_BULK_BUFFERS];
};

static inline struct udctest *udctest_of(struct usb_func_iface *iface)
{
	return container_of(iface, struct udctest, iface);
}

static const struct udctest_bulk_iface_block udctest_fs_bulk_iface = {
	.iface = {
		.bLength = sizeof(struct usb_interface_descriptor),
		.bDescriptorType	= USB_DT_INTERFACE,
		.bInterfaceNumber	= CONFIG_UDCTEST_INTERFACE_ID,
		.bAlternateSetting	= 0,
		.bNumEndpoints		= 2,
		.bInterfaceClass	= 0xff,
		.bInterfaceSubClass	= 0x00,
		.bInterfaceProtocol	= 0xff,
	},
	.in_ep = {
		.bLength = sizeof(struct usb_endpoint_descriptor),
		.bDescriptorType	= USB_DT_ENDPOINT,
		.bEndpointAddress	= USB_DIR_IN
			| CONFIG_UDCTEST_BULK_IN_EP,
		.bmAttributes		= USB_EP_XFER_BULK,
		.wMaxPacketSize		= LE16(UDCTEST_FS_BULK_EP_SIZE),
	},
	.out_ep = {
		.bLength = sizeof(struct usb_endpoint_descriptor),
		.bDescriptorType	= USB_DT_ENDPOINT,
		.bEndpointAddress	= USB_DIR_OUT
			| CONFIG_UDCTEST_BULK_OUT_EP,
		.bmAttributes		= USB_EP_XFER_BULK,
		.wMaxPacketSize		= LE16(UDCTEST_FS_BULK_EP_SIZE),
	},
};

static const struct udctest_bulk_iface_block udctest_hs_bulk_iface = {
	.iface = {
		.bLength = sizeof(struct usb_interface_descriptor),
		.bDescriptorType	= USB_DT_INTERFACE,
		.bInterfaceNumber	= CONFIG_UDCTEST_INTERFACE_ID,
		.bAlternateSetting	= 0,
		.bNumEndpoints		= 2,
		.bInterfaceClass	= 0xff,
		.bInterfaceSubClass	= 0x00,
		.bInterfaceProtocol	= 0xff,
	},
	.in_ep = {
		.bLength = sizeof(struct usb_endpoint_descriptor),
		.bDescriptorType	= USB_DT_ENDPOINT,
		.bEndpointAddress	= USB_DIR_IN
			| CONFIG_UDCTEST_BULK_IN_EP,
		.bmAttributes		= USB_EP_XFER_BULK,
		.wMaxPacketSize		= LE16(512),
	},
	.out_ep = {
		.bLength = sizeof(struct usb_endpoint_descriptor),
		.bDescriptorType	= USB_DT_ENDPOINT,
		.bEndpointAddress	= USB_DIR_OUT
			| CONFIG_UDCTEST_BULK_OUT_EP,
		.bmAttributes		= USB_EP_XFER_BULK,
		.wMaxPacketSize		= LE16(512),
	},
};

static struct udctest_descriptor udctest_desc;
static uint8_t udctest_data1[62];	/* unaligned start, aligned end */
static uint8_t udctest_data2[1];	/* aligned start, unaligned end */
static uint8_t udctest_data3[64];	/* unaligned start, unaligned end */

static void udctest_in_req_done(struct udc *udc, struct usb_request *req);

static void udctest_out_req_done(struct udc *udc, struct usb_request *req)
{
	struct udctest	*test = req->context;
	struct buffer	*buf;

	dbg_printf("udctest: OUT req done: %zu bytes status %d\n",
			req->bytes_xfered, req->status);

	/* Don't submit any buffers when the endpoint is unavailable */
	if (!test->bulk_in_ep)
		return;

	buf = usb_req_get_first_buffer(req);
	buf->len = req->bytes_xfered;
	req->bytes_xfered = 0;
	req->req_done = udctest_in_req_done;
	udc_ep_submit_in_req(udc, test->bulk_in_ep, req);
}

static void udctest_in_req_done(struct udc *udc, struct usb_request *req)
{
	struct udctest	*test = req->context;
	struct buffer	*buf;

	dbg_printf("udctest: IN req done: %zu bytes status %d\n",
			req->bytes_xfered, req->status);

	/* Don't submit any buffers when the endpoint is unavailable */
	if (!test->bulk_out_ep)
		return;

	buf = usb_req_get_first_buffer(req);
	buf->len = UDCTEST_BUF_SIZE;
	req->bytes_xfered = 0;
	req->req_done = udctest_out_req_done;
	udc_ep_submit_out_req(udc, test->bulk_out_ep, req);
}

static void udctest_ctrl_in_done(struct udc *udc, struct usb_request *req)
{
	dbg_printf("udctest: Control IN request done\n");

	usb_req_free_all(req);
	udc_ep0_expect_status(udc);
}

static int udctest_iface_enable(struct udc *udc, struct usb_func_iface *iface)
{
	const struct udctest_bulk_iface_block *desc;
	struct udctest		*test = udctest_of(iface);
	struct usb_request	*req;
	struct buffer		*buf;
	unsigned int		i = 0;

	switch (udc->speed) {
	case USB_SPEED_FULL:
		desc = &udctest_fs_bulk_iface;
		break;
	case USB_SPEED_HIGH:
		desc = &udctest_hs_bulk_iface;
		break;
	default:
		return -1;
	}

	test->bulk_in_ep = udc_ep_create(udc, &desc->in_ep,
			CONFIG_UDCTEST_NR_BANKS);
	test->bulk_out_ep = udc_ep_create(udc, &desc->out_ep,
			CONFIG_UDCTEST_NR_BANKS);
	if (test->bulk_in_ep < 0 || test->bulk_out_ep < 0)
		goto fail;

	for (i = 0; i < UDCTEST_NR_BULK_BUFFERS; i++) {
		void		*data;

		data = malloc(UDCTEST_BUF_SIZE);
		if (!data)
			goto fail;

		req = &test->bulk_req[i];
		usb_req_init(req);
		buf = buffer_alloc();
		if (!buf) {
			free(data);
			goto fail;
		}
		buffer_init_tx(buf, data, UDCTEST_BUF_SIZE);
		usb_req_add_buffer(req, buf);
		req->req_done = udctest_out_req_done;
		req->context = test;
	}

	for (i = 0; i < UDCTEST_NR_BULK_BUFFERS; i++)
		udc_ep_submit_out_req(udc, test->bulk_out_ep,
				&test->bulk_req[i]);

	return 0;

fail:
	dbg_printf("udctest: failed to enable interface\n");

	while (i--) {
		req = &test->bulk_req[i];
		buf = usb_req_get_first_buffer(req);
		free(buf->addr.ptr);
		buffer_free(buf);
	}

	if (test->bulk_in_ep > 0) {
		usb_ep_id_t ep = test->bulk_in_ep;
		test->bulk_in_ep = 0;
		udc_ep_destroy(udc, ep);
	}
	if (test->bulk_out_ep > 0) {
		usb_ep_id_t ep = test->bulk_out_ep;
		test->bulk_out_ep = 0;
		udc_ep_destroy(udc, ep);
	}

	return -1;
}

static void udctest_iface_disable(struct udc *udc, struct usb_func_iface *iface)
{
	struct udctest		*test = udctest_of(iface);
	struct usb_request	*req;
	struct buffer		*buf;
	usb_ep_id_t		in, out;
	unsigned int		i;

	in = test->bulk_in_ep;
	test->bulk_in_ep = 0;
	out = test->bulk_out_ep;
	test->bulk_out_ep = 0;

	if (in > 0)
		udc_ep_destroy(udc, in);
	if (out > 0)
		udc_ep_destroy(udc, out);

	for (i = 0; i < UDCTEST_NR_BULK_BUFFERS; i++) {
		req = &test->bulk_req[i];
		buf = usb_req_get_first_buffer(req);
		free(buf->addr.ptr);
		buffer_free(buf);
	}
}

static struct usb_request *usbtest_prep_test_desc(struct udctest *test,
		struct udc *udc, uint16_t value, uint16_t index, uint16_t len)
{
	static const uint8_t dt_to_len[] = {
		0, 2, 63, 64, 65, 127, 128, 129,
	};
	struct usb_request	*req;
	struct buffer		*buf;
	unsigned int		desc_len;
	unsigned int		buf_len;
	unsigned int		total_len = 0;
	uint8_t			dt = value >> 8;

	req = usb_req_alloc();
	assert(req);

	dbg_printf("udctest: get test desc %u\n", dt);

	if (len < sizeof(udctest_desc) || dt == 0
			|| dt >= ARRAY_LEN(dt_to_len))
		return NULL;

	desc_len = dt_to_len[dt];
	udctest_desc.bLength = desc_len;
	udctest_desc.bDescriptorType = dt;

	desc_len = min(len, desc_len);

	buf = buffer_alloc();
	assert(buf);
	buffer_init_tx(buf, &udctest_desc, sizeof(udctest_desc));
	usb_req_add_buffer(req, buf);
	total_len += sizeof(udctest_desc);
	if (total_len == desc_len)
		goto done;

	buf_len = min(desc_len - total_len, sizeof(udctest_data1));
	buf = buffer_alloc();
	assert(buf);
	buffer_init_tx(buf, udctest_data1, buf_len);
	usb_req_add_buffer(req, buf);
	total_len += buf_len;
	if (total_len == desc_len)
		goto done;

	buf_len = min(desc_len - total_len, sizeof(udctest_data2));
	buf = buffer_alloc();
	assert(buf);
	buffer_init_tx(buf, udctest_data2, buf_len);
	usb_req_add_buffer(req, buf);
	total_len += buf_len;
	if (total_len == desc_len)
		goto done;

	buf_len = min(desc_len - total_len, sizeof(udctest_data3));
	buf = buffer_alloc();
	assert(buf);
	buffer_init_tx(buf, udctest_data3, buf_len);
	usb_req_add_buffer(req, buf);
	total_len += buf_len;

done:
	if (total_len < len)
		set_bit(USB_REQ_SHORT_PKT, &req->flags);

	req->req_done = udctest_ctrl_in_done;
	req->context = udc;

	return req;
}

static int udctest_iface_setup(struct udc *udc, struct usb_func_iface *iface,
		struct usb_setup_req *setup)
{
	struct udctest		*test = udctest_of(iface);
	struct usb_request	*req;
	uint16_t		value = le16_to_cpu(setup->wValue);
	uint16_t		index = le16_to_cpu(setup->wIndex);
	uint16_t		len = le16_to_cpu(setup->wLength);

	if (usb_req_type(setup) != USB_REQTYPE_VENDOR)
		return -1;

	switch (setup->bRequest) {
	case USBTEST_REQ_INIT_DESC:
		if (len || usb_req_is_in(setup))
			return -1;

		memset(udctest_data1, value & 0xff, sizeof(udctest_data1));
		memset(udctest_data2, value & 0xff, sizeof(udctest_data2));
		memset(udctest_data3, value & 0xff, sizeof(udctest_data3));

		dbg_printf("udctest: INIT_DESC done, sending status\n");

		udc_ep0_send_status(udc);
		break;

	case USBTEST_REQ_GET_DESC:
		if (usb_req_is_out(setup))
			return -1;

		req = usbtest_prep_test_desc(test, udc, value, index, len);
		if (!req)
			return -1;

		dbg_printf("udctest: GET_DESC done, sending it\n");

		udc_ep0_submit_in_req(udc, req);
		break;

	default:
		return -1;
	}

	return 0;
}

static const struct usb_func_iface_ops udctest_ops = {
	.enable		= udctest_iface_enable,
	.disable	= udctest_iface_disable,
	.setup		= udctest_iface_setup,
};

static struct udctest	udctest = {
	.iface.nr_settings	= 1,
	.iface.setting[0] = {
		.ops		= &udctest_ops,
		.fs_desc	= &udctest_fs_bulk_iface.iface,
		.hs_desc	= &udctest_hs_bulk_iface.iface,
		.fs_desc_size	= sizeof(udctest_fs_bulk_iface),
		.hs_desc_size	= sizeof(udctest_hs_bulk_iface),
	},
};

struct usb_func_iface *udctest_init(void)
{
	return &udctest.iface;
}
