/**
 * \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 USBB driver: Core/Transceiver part
 *
 * This file implements a driver for the transceiver logic in the USBB
 * hardware controller. It is responsible for enabling the host- and
 * device parts of the driver depending on configuration options,
 * transceiver states and USB On-The-Go events originating from HNP
 * and SRP.
 *
 * If the driver is configured as host-only or device-only, the OTG
 * logic will be disabled. This file is still responsible for
 * detecting connection/disconnection events, and generating/detecting
 * Vbus.
 *
 * - Compiler:           IAR EWAVR32 and GNU GCC for AVR32
 * - Supported devices:  All AVR32 devices with a USBB module can be used.
 * - 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 <debug.h>
#include <irq_handler.h>
#include <physmem.h>
#include <stdbool.h>
#include <chip/clk.h>
#include <chip/memory-map.h>
#include <chip/irq-map.h>
#include <chip/portmux.h>
#include <chip/usbb.h>
#include <usb/udc.h>

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

#include "usbb_internal.h"
#include "usbb_regs.h"

/* Configuration sanity-checks */
#if defined(CONFIG_USBB_UDC) && !defined(CHIP_USBB_HAS_DEVICE)
# error USBB: This chip does not have a Device Controller
#endif
#if defined(CONFIG_USBB_HOST) && !defined(CHIP_USBB_HAS_HOST)
# error USBB: This chip does not have a Host Controller
#endif
#if defined(CONFIG_USBB_OTG) && !defined(CHIP_USBB_HAS_OTG)
# error USBB: This chip does not support USB On-The-Go
#endif

#ifndef usbb_desc_physmem_pool
# define usbb_desc_physmem_pool	hsb_sram_pool
#endif

#define USBB_DMA_DESC_ALIGN	4

struct dma_pool		usbb_desc_pool;

static inline struct usbb_udc *usbb_get_udc(struct usbb_controller *usbb)
{
#ifdef CONFIG_USBB_UDC
	return usbb->udc;
#else
	return NULL;
#endif
}

static inline struct usbb_host *usbb_get_host(struct usbb_controller *usbb)
{
#ifdef CONFIG_USBB_HOST
	return usbb->host;
#else
	return NULL;
#endif
}

static inline bool usbb_is_otg(struct usbb_controller *usbb)
{
#ifdef CONFIG_USBB_OTG
	return true;
#else
	return false;
#endif
}

static void usbb_check_vbus(struct usbb_controller *usbb)
{
	struct usbb_udc	*udcb = usbb_get_udc(usbb);

	if (usbb_read_reg(USBSTA) & USBB_USBSTA_VBUS)
		usbb_udc_vbus_on(udcb);
	else
		usbb_udc_vbus_off(udcb);
}

/**
 * \brief Enter USB device mode.
 *
 * Disable the host driver (if any), enable the UDC driver and start
 * watching the state of the Vbus line.
 */
static void usbb_enter_device_mode(struct usbb_controller *usbb)
{
	struct usbb_host	*host = usbb_get_host(usbb);
	struct usbb_udc		*udc = usbb_get_udc(usbb);
	uint32_t		usbcon;

	dbg_printf("USBB: Entering device mode...\n");

	if (usbb_host_is_enabled(host))
		usbb_host_disable(host);
	if (!usbb_udc_is_enabled(udc))
		usbb_udc_enable(udc);

	usbb_write_reg(USBSTACLR, USBB_VBUSTI);
	usbb_check_vbus(usbb);

	usbcon = usbb_read_reg(USBCON);
	usbcon |= USBB_VBUSTI;
	usbb_write_reg(USBCON, usbcon);

	dbg_printf("USBB: USBCON=%08x\n", usbb_read_reg(USBCON));
}

static void usbb_enter_host_mode(struct usbb_controller *usbb)
{
	struct usbb_host	*host = usbb_get_host(usbb);
	struct usbb_udc		*udc = usbb_get_udc(usbb);

	if (host) {
		if (usbb_udc_is_enabled(udc))
			usbb_udc_disable(udc);
		if (!usbb_host_is_enabled(host))
			usbb_host_enable(host);
	}
}

/**
 * \brief Check the state of the USB OTG ID pin.
 *
 * Check the state of the USB OTG ID pin and enable host/device
 * functionality as appropriate.
 * \param usbb The controller
 */
static void usbb_check_id(struct usbb_controller *usbb)
{
	if (usbb_read_reg(USBSTA) & USBB_USBSTA_ID)
		usbb_enter_device_mode(usbb);
	else
		usbb_enter_host_mode(usbb);
}

/**
 * \brief USBB main interrupt handler.
 *
 * This is the main interrupt handler for the USBB controller. It
 * handles OTG and Vbus events, and calls the host- or device-specific
 * interrupt handler, depending on the configuration and/or ID pin
 * state.
 *
 * @param data Data associated with this interrupt.
 */
static void usbb_interrupt(void *data)
{
	struct usbb_controller	*usbb = data;
	struct usbb_udc		*udcb;
	struct usbb_host	*hostb;
	uint32_t		usbsta;

	usbsta = usbb_read_reg(USBSTA);

	if (usbb_is_otg(usbb)) {
		if (usbsta & USBB_IDTI) {
			usbb_write_reg(USBSTACLR, USBB_IDTI);
			usbb_check_id(usbb);
		}

		/* TODO: More OTG stuff */
	}

	udcb = usbb_get_udc(usbb);
	if (usbb_udc_is_enabled(udcb)) {
		if (usbsta & USBB_VBUSTI) {
			usbb_write_reg(USBSTACLR, USBB_VBUSTI);
			usbb_check_vbus(usbb);
		}

		usbb_udc_interrupt(udcb);
	}

	hostb = usbb_get_host(usbb);
	if (usbb_host_is_enabled(hostb)) {
		/* TODO: Host */
	}
}
DEFINE_IRQ_HANDLER(usbb, usbb_interrupt, 0);

static void usbb_init_desc_pool(void)
{
	phys_addr_t	addr;
	phys_size_t	size;

	size =  round_up(sizeof(struct usbb_sw_dma_desc), USBB_DMA_DESC_ALIGN);
	size *= CONFIG_USBB_NR_DMA_DESCRIPTORS;

	addr = physmem_alloc(&usbb_desc_physmem_pool, size,
			USBB_DMA_DESC_ALIGN);
	assert(addr != PHYSMEM_ALLOC_ERR);

	dma_pool_init_coherent(&usbb_desc_pool, addr, size,
			sizeof(struct usbb_sw_dma_desc), USBB_DMA_DESC_ALIGN);
}

static struct usbb_controller	the_usbb_controller;

/**
 * \internal
 * \brief Initialize the USBB controller.
 *
 * This function will initialize and enable the USBB controller.
 * Depending on the configuration, this may involve one or more of the
 * following steps:
 *  - Initialize the host and device parts of the driver.
 *  - Start monitoring the ID pin.
 *  - Switch the controller into host or device mode.
 *  - Start monitoring the Vbus pin.
 *
 * \return A USBB controller instance.
 *
 * \todo Add support for host and OTG modes.
 */
static struct usbb_controller *usbb_init(void)
{
	struct usbb_controller	*usbb = &the_usbb_controller;
	uint32_t		usbcon;

	/*
	 * Only do the initialization once. We might get called from
	 * udc_init() as well as other initialization functions.
	 */
	if (usbb_get_host(usbb) || usbb_get_udc(usbb))
		return usbb;

	usbb_init_desc_pool();

	usbcon = USBB_USBCON_USBE | USBB_USBCON_OTGPADE;

	if (clk_enable_usbb())
		return NULL;

	setup_irq_handler(USBB_IRQ, usbb, 0, usbb);

#ifdef CONFIG_USBB_UDC
	usbb->udc = usbb_udc_init();
	if (!usbb->udc)
		goto err_udc;
#endif
#ifdef CONFIG_USBB_HOST
	usbb->host = usbb_host_init();
	if (!usbb->host)
		goto err_host;
#endif

	if (usbb_is_otg(usbb)) {
		/* Full OTG */
		usbcon |= USBB_USBCON_UIDE;
		usbb_write_reg(USBCON, usbcon);
		usbb_write_reg(USBSTACLR, USBB_IDTI);
		usbb_check_id(usbb);
		usbcon |= USBB_IDTI;
		usbb_write_reg(USBCON, usbcon);

		/* TODO: initialize HNP, SRP handling, etc. */
	} else if (usbb_get_udc(usbb)) {
		/* Device only */
		usbcon |= USBB_USBCON_UIMOD_DEVICE;
		usbb_write_reg(USBCON, usbcon);
		usbb_enter_device_mode(usbb);
	} else if (usbb_get_host(usbb)) {
		/* Host only */
		usbcon |= USBB_USBCON_UIMOD_HOST;
		usbb_write_reg(USBCON, usbcon);
		usbb_enter_host_mode(usbb);
	}

	return usbb;

#ifdef CONFIG_USBB_HOST
	usbb_host_shutdown(usbb_get_host(usbb));
err_host:
#endif
#ifdef CONFIG_USBB_UDC
	usbb_udc_shutdown(usbb_get_udc(usbb));
err_udc:
#endif
	return NULL;
}

/**
 * \brief Initialize the USB Device Controller
 *
 * This will initialize the USB Device Controller, making it ready to
 * attach to the bus. The controller won't attach automatically until
 * udc_attach() has been called as well.
 *
 * If the UDC is part of a dual-role (OTG) controller, and dual-role
 * configuration is enabled, this function will initialize the whole
 * controller if it hasn't been initialized before.
 *
 * \return The USB Device Controller object
 */
struct udc *udc_init(void)
{
	struct usbb_controller	*usbb;

	usbb = usbb_init();
	if (!usbb)
		return NULL;

	return &usbb_get_udc(usbb)->udc;
}
