/* ----------------------------------------------------------------------------
 *         ATMEL Microcontroller Software Support
 * ----------------------------------------------------------------------------
 * Copyright (c) 2009, 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:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: 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
 * 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 "board.h"
#include "CDCDSerialDriver.h"
#include "usb_debug_output.h"
#include "CDCDSerialDriverDescriptors.h"

/*----------------------------------------------------------------------------
 *         VBus monitoring
 *----------------------------------------------------------------------------*/

/** VBus pin instance. */
static const Pin pinVbus = PIN_USB_VBUS ;

/**
 * Handles interrupts coming from PIO controllers.
 */
static void ISR_Vbus( const Pin *pPin )
{
    /* Check current level on VBus */
    if ( PIO_Get( &pinVbus ) )
    {
//        TRACE_INFO( "VBUS connect\n" ) ;
        USBD_Connect() ;
    }
    else
    {
//        TRACE_INFO( "VBUS disconnect\n" ) ;
        USBD_Disconnect() ;
    }
}

/**
 * Configures the VBus pin to trigger an interrupt when the level on that pin
 * changes.
 */
static void _VBus_Configure( void )
{
//    TRACE_INFO( "VBus configuration\n" ) ;

    /* Configure PIO */
    PIO_Configure( &pinVbus, 1 ) ;
    PIO_ConfigureIt( &pinVbus, ISR_Vbus ) ;
    PIO_EnableIt( &pinVbus ) ;

    /* Check current level on VBus */
    if ( PIO_Get( &pinVbus ) )
    {
        /* if VBUS present, force the connect */
//        TRACE_INFO( "connect\n" ) ;
        USBD_Connect() ;
    }
    else
    {
        USBD_Disconnect() ;
    }
}

/*-----------------------------------------------------------------------------
 *         Callback re-implementation
 *-----------------------------------------------------------------------------*/

/**
 * Invoked after the USB driver has been initialized. By default, configures
 * the UDP/UDPHS interrupt.
 */
extern void USBDCallbacks_Initialized( void )
{
    NVIC_EnableIRQ( UDP_IRQn ) ;
}

/**
 * Invoked when a new SETUP request is received from the host. Forwards the
 * request to the Mass Storage device driver handler function.
 * \param request  Pointer to a USBGenericRequest instance.
 */
extern void USBDCallbacks_RequestReceived( const USBGenericRequest *request )
{
    CDCDSerialDriver_RequestHandler( request ) ;
}

/**
 * \brief Configure 48MHz Clock for USB
 */
static void _ConfigureUsbClock( void )
{
    /* Enable PLLB for USB */
    PMC->CKGR_PLLBR = (CKGR_PLLBR_DIVB & ( 1<< 0)) | (CKGR_PLLBR_MULB & ( 7<<16)) | (CKGR_PLLBR_PLLBCOUNT) ;
    while ( (PMC->PMC_SR & PMC_SR_LOCKB) == 0 ) ;

    /* USB Clock uses PLLB */
    PMC->PMC_USB = (PMC_USB_USBDIV & (1<<8))    /* /2   */
                 | (PMC_USB_USBS & 1)           /* PLLB */
                 ;
}

/**
 * \brief Outputs a character on the UART.
 *
 * \param c  Character to output.
 *
 * \return The character that was output.
 */
extern signed int putchar( signed int c )
{
    static uint8_t _aucBuffer[256] ;
    static uint32_t _dwBufferUsed=0 ;

//    _aucBuffer[_dwBufferUsed++]=(uint8_t)c ;
//
//    // Flush the buffer to USB if needed
//    if ( (_dwBufferUsed == 255) || (c == '\n') )
//    {
//        // Test if device is configured
//        if ( USBD_GetState() == USBD_STATE_CONFIGURED )
//        {
//            CDCDSerialDriver_Write( _aucBuffer, _dwBufferUsed, NULL, NULL ) ;
//        }
//        _dwBufferUsed=0 ;
//    }

    return c ;
}

extern uint32_t USB_DebugOutput_Initialize( void )
{
    _ConfigureUsbClock() ;

    /* CDC serial driver initialization */
    CDCDSerialDriver_Initialize( &cdcdSerialDriverDescriptors ) ;

    _VBus_Configure() ;

    return 0 ;
}
