/**
 * \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 library functions.
 *
 * This file contains various utility functions for use by UDC
 * 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 <byteorder.h>
#include <debug.h>
#include <usb/function.h>
#include <usb/udc.h>
#include <usb/usb_protocol.h>

static int udc_lib_standard_request(struct udc *udc, struct usb_setup_req *req)
{
	uint16_t	value = le16_to_cpu(req->wValue);
	uint16_t	index = le16_to_cpu(req->wIndex);
	uint16_t	len = le16_to_cpu(req->wLength);
	uint8_t		byte;

	switch (req->bRequest) {
	case USB_REQ_GET_STATUS: {
		le16_t	status = LE16(0);
		int	ret;

		if (len != sizeof(status) || usb_req_is_out(req))
			return -1;

		switch (usb_req_recipient(req)) {
		case USB_RECIP_DEVICE:
			/*
			 * Assuming that we are self-powered, although
			 * we really don't know that...
			 */
			status = LE16(0x0001);
			break;
		case USB_RECIP_ENDPOINT:
			ret = udc_ep_is_halted(udc, index & USB_EP_ADDR_MASK);
			if (ret < 0)
				return -1;

			status = cpu_to_le16(ret);
			break;
		default:
			break;
		}

		udc_ep0_write_sync(udc, &status, sizeof(status));
		udc_ep0_expect_status(udc);
		break;
	}

	case USB_REQ_CLEAR_FEATURE:
		if (len != 0 || usb_req_is_in(req))
			return -1;

		if (usb_req_recipient(req) == USB_RECIP_DEVICE
				&& value == USB_DEV_FEATURE_REMOTE_WAKEUP) {
			/* do nothing */
		} else if (usb_req_recipient(req) == USB_RECIP_ENDPOINT
				&& value == USB_EP_FEATURE_HALT) {
			if (udc_ep_clear_halt(udc, index & USB_EP_ADDR_MASK))
				return -1;
		} else {
			return -1;
		}

		udc_ep0_send_status(udc);
		break;

	case USB_REQ_SET_FEATURE:
		if (len != 0 || usb_req_is_in(req))
			return -1;
		if (udc_is_high_speed(udc)
				&& usb_req_recipient(req) == USB_RECIP_DEVICE
				&& value == USB_DEV_FEATURE_TEST_MODE) {
			if (index & 0xff)
				return -1;

			/*
			 * Unconfigure the device, terminating all
			 * ongoing requests.
			 */
			usb_func_set_configuration(udc, 0);

			if (udc_enter_test_mode(udc, index >> 8))
				return -1;
		} else if (usb_req_recipient(req) == USB_RECIP_ENDPOINT
				&& value == USB_EP_FEATURE_HALT) {
			if (udc_ep_set_halt(udc, index & USB_EP_ADDR_MASK))
				return -1;
		} else {
			return -1;
		}

		udc_ep0_send_status(udc);
		break;

	case USB_REQ_SET_ADDRESS:
		if (len != 0 || usb_req_is_in(req) || value > 127)
			return -1;

		/*
		 * The address isn't actually changed until the status
		 * stage is complete. Make sure we don't handle any
		 * SETUP packets until then.
		 */
		udc_set_address(udc, value);
		udc_ep0_send_status(udc);
		break;

	case USB_REQ_GET_DESCRIPTOR:
		if (usb_req_is_out(req))
			return -1;

		if (usb_func_get_descriptor(udc, value, index, len) < 0)
			return -1;
		break;

	case USB_REQ_SET_DESCRIPTOR:
		/* Not supported (defined as optional by the USB 2.0 spec) */
		return -1;

	case USB_REQ_GET_CONFIGURATION:
		if (len != sizeof(byte) || usb_req_is_out(req))
			return -1;

		byte = 0;
		if (udc->config)
			byte = udc->config->bConfigurationValue;

		udc_ep0_write_sync(udc, &byte, sizeof(byte));
		udc_ep0_expect_status(udc);
		break;

	case USB_REQ_SET_CONFIGURATION:
		if (len != 0 || usb_req_is_in(req) || !udc->address)
			return -1;

		if (usb_func_set_configuration(udc, value) < 0)
			return -1;

		udc_ep0_send_status(udc);
		break;

	case USB_REQ_GET_INTERFACE:
		if (len != 1 || usb_req_is_out(req) || !udc->config)
			return -1;

		if (usb_func_get_interface(udc, index) < 0)
			return -1;
		break;

	case USB_REQ_SET_INTERFACE:
		if (len != 0 || usb_req_is_in(req) || !udc->config)
			return -1;

		if (usb_func_set_interface(udc, index, value) < 0)
			return -1;

		udc_ep0_send_status(udc);
		break;

	default:
		return -1;
	}

	return 0;
}

/**
 * \brief Handle a USB SETUP request.
 *
 * This function parses a USB SETUP request and submits an appropriate
 * response back to the host or, in the case of SETUP OUT requests
 * with data, sets up a buffer for receiving the data payload.
 *
 * Standard requests defined by the USB 2.0 standard are handled
 * internaly, while class- and vendor-specific requests are passed on
 * to the function driver.
 *
 * @param udc USB Device Controller instance.
 * @param req The raw USB SETUP request.
 *
 * @return 0 if successful or a negative value if a STALL handshake
 *	should be sent as the response.
 */
int udc_lib_process_setup_request(struct udc *udc, struct usb_setup_req *req)
{
	dbg_printf("req %02x: t%02x v%04x i%04x l%04x\n",
		   req->bRequest, req->bmRequestType,
		   le16_to_cpu(req->wValue), le16_to_cpu(req->wIndex),
		   le16_to_cpu(req->wLength));

	if (usb_req_type(req) == USB_REQTYPE_STANDARD)
		return udc_lib_standard_request(udc, req);
	else
		return usb_func_process_setup_request(udc, req);
}
