//------------------------------------------------------------------------------
// Copyright (c) 2012 by Silicon Laboratories. 
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Silicon Laboratories End User 
// License Agreement which accompanies this distribution, and is available at
// http://developer.silabs.com/legal/version/v10/License_Agreement_v10.htm
// Original content and implementation provided by Silicon Laboratories.
//------------------------------------------------------------------------------
// library
#include <si32UsbCoreComponent.h>
#include <si32RunLoopObject.h>
// hal
#include <si32_device.h>
#include <SI32_CLKCTRL_A_Type.h>
#include <SI32_USB_A_Type.h>
#include <SI32_USBEP_A_Type.h>
// application
#include "myApplication.h"
#include "myUsbAudioDevice.h"
#include "myUSB0.h"


//------------------------------------------------------------------------------
void myUSB0_initialize_memory(void)
{
  si32LogPrologue();

   extern void si32Usb_initialize_memory(void);


   // Initialize usb component memory (initializes library objects & buffers for basic USB devices)
   si32Usb_initialize_memory();

  // (optionally) Initialize additional endpoints here, if used.
  // Note:  There must be 5 UsbEpIoObjects - even if only using the default
  _si32UsbIoObject_initialize_endpoint(_si32UsbIoObjects[1], 1, -1, -1);
  _si32UsbIoObject_initialize_endpoint(_si32UsbIoObjects[2], 2, -1, -1);
  _si32UsbIoObject_initialize_endpoint(_si32UsbIoObjects[3], 3, -1, -1);
  _si32UsbIoObject_initialize_endpoint(_si32UsbIoObjects[4], 4, -1, -1);

  si32LogEpilogue();
}

//------------------------------------------------------------------------------
si32UsbDeviceObject           * myUsbDevice;

//------------------------------------------------------------------------------
#define SI32_USB_A_CLKSEL_TEST_MASK 0x00000080

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
bool SI32_USB_A_verify_clock_is_running(SI32_USB_A_Type * usb)
{
  usb->CLKSEL.U32 |= SI32_USB_A_CLKSEL_TEST_MASK;
  int loop=100;

  while ( (usb->CLKSEL.U32 & SI32_USB_A_CLKSEL_TEST_MASK) )
  {
    if ( 0==loop--)
    {
      printf("USB Clock Not Running! clksel = %02x\n", usb->CLKSEL.U32);
      return false;
    }
  }
  printf("USB Clock is running.  clksel = %02x\n", usb->CLKSEL.U32);
  return true;
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
volatile bool g_bPauseOnUSBOSCEnable=false;
volatile bool g_bEnableUsbAsXbarSrc=false;
void USB_enable_usb_oscillator(void)
{
  // Initialize USB clock to use 48Mhz Oscillator
  _SI32_USB_A_enable_usb_oscillator (SI32_USB_0); // Clear and Set don't seem to work
  SI32_USB_A_verify_clock_is_running(SI32_USB_0);
  /*
   {
   SI32_USB_0->OSCCONTROL.U32 &= ~SI32_USB_A_OSCCONTROL_OSCEN_MASK;
   SI32_USB_0->OSCCONTROL.U32 |= SI32_USB_A_OSCCONTROL_OSCEN_ENABLED_U32 ;
   }
   */

  _SI32_USB_A_select_usb_clock_48mhz (SI32_USB_0);

  if ( g_bEnableUsbAsXbarSrc )
  {
    // enable ahb source as usb oscillator
    _SI32_CLKCTRL_A_select_ahb_source_usb_oscillator(SI32_CLKCTRL_0);

    // Enable AHB/16 output on PB0.0
    // SI32_PBCFG_0->XBAR0H.U32 =  SI32_SIM3U1XX_PBCFG_A_XBAR0H_XBAR0EN_MASK      | SI32_SIM3U1XX_PBCFG_A_XBAR0H_AHBEN_MASK;
    // SI32_PBCFG_0->XBAR0H.U32 = /**/SI32_SIM3U1XX_PBCFG_A_XBAR0H_XBAR0E_MASK/**/   | SI32_SIM3U1XX_PBCFG_A_XBAR0H_AHBEN_MASK;
    // SI32_PBSTD_0->PBOUTMD_SET = 1;

    while (g_bPauseOnUSBOSCEnable);
  }
}


//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

void USB_initialize ()
{
  printf("USB_initialize\n");

  // Initialize USB clock to use 48Mhz Oscillator

#if 0 // this functionality moved to myApplication.c
  _SI32_USB_A_enable_usb_oscillator (SI32_USB_0);
  _SI32_USB_A_select_usb_clock_48mhz (SI32_USB_0);
#endif

  SI32_USB_A_verify_clock_is_running(SI32_USB_0);

  // _SI32_USB_A_set_usb_clock_pll(SI32_USB_0);

  // Perform asynchronous reset of the USB module
  _SI32_USB_A_reset_module (SI32_USB_0);

  // Enable Endpoint 0 interrupts
  _SI32_USB_A_write_cmint (SI32_USB_0, 0x00000000);
  _SI32_USB_A_write_ioint (SI32_USB_0, 0x00000000);
  _SI32_USB_A_enable_ep0_interrupt (SI32_USB_0);

  // Enable Reset, Resume, Suspend interrupts
  _SI32_USB_A_enable_suspend_interrupt (SI32_USB_0);
  _SI32_USB_A_enable_resume_interrupt (SI32_USB_0);
  _SI32_USB_A_enable_reset_interrupt (SI32_USB_0);
  _SI32_USB_A_enable_start_of_frame_interrupt (SI32_USB_0);

  // Enable Transceiver, fullspeed
  _SI32_USB_A_write_tcontrol (SI32_USB_0, 0x00);
  _SI32_USB_A_select_transceiver_full_speed (SI32_USB_0);
  _SI32_USB_A_enable_transceiver (SI32_USB_0);
  // _SI32_USB_A_enable_internal_pull_up (SI32_USB_0);

  // Enable clock recovery, single-step mode disabled
  _SI32_USB_A_enable_clock_recovery (SI32_USB_0);
  _SI32_USB_A_select_clock_recovery_mode_full_speed (SI32_USB_0);
  _SI32_USB_A_select_clock_recovery_normal_cal  (SI32_USB_0);
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void USB_connect()
{
  _SI32_USB_A_enable_internal_pull_up (SI32_USB_0);
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void USB_disconnect()
{
  _SI32_USB_A_disable_internal_pull_up (SI32_USB_0);
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void USB_start()
{
  // Try to enable EP0 interrupt, where it may have been ommitted accidentally...
  SI32_USB_0->CMINTEPE.U32 |= (5<<16) ;

  // Enable USB interrupts
  NVIC_EnableIRQ (USB0_IRQn);

  // Uninhibit the module once all initialization is complete
  _SI32_USB_A_enable_module (SI32_USB_0);
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void USB_stop()
{
  // Enable USB interrupts
  NVIC_DisableIRQ(USB0_IRQn);

  // Inhibit USB
  _SI32_USB_A_disable_module(SI32_USB_0);
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void USB_shutdown()
{
  // Reset the block
  _SI32_USB_A_reset_module (SI32_USB_0);
}



#if 1
//------------------------------------------------------------------------------
// Store middleware usb device object
void USB0_IRQHandler(void)
{
  // Route the IRQ to app-specific usb device object
  si32UsbDeviceObject_IRQHandler(myUsbDevice);
  return;
}
#else
//------------------------------------------------------------------------------
void usb_dsr(obj* obj, void* context)
{
  // si32UsbDeviceObject * device =
  si32UsbDeviceObject_IRQHandler((si32UsbDeviceObject *) obj);
  NVIC_EnableIRQ (USB0_IRQn);
}

//------------------------------------------------------------------------------
void USB0_IRQHandler(void)
{
  // Route the IRQ to app-specific usb device object
  // si32UsbDeviceObject_IRQHandler(myUsbDevice);
  NVIC_DisableIRQ (USB0_IRQn);
  si32RunLoopObject_post_work_request(si32GetCurrentRunLoop(),
                                      SI32_RUN_LOOP_PRIORITY_HIGH,
                                      usb_dsr,
                                      myUsbDevice,
                                      NULL);
  return;
}
#endif

//------------------------------------------------------------------------------
void usb_device_state_change(uint32_t from, uint32_t to)
{
  printf("USB DEVICE STATE:  %d->%d\n", from, to);
}

//------------------------------------------------------------------------------
//
extern void usb_device_state_change(uint32_t old, uint32_t new);
void myUsbDevice_poller(obj* obj, void* context)
{
  si32UsbDeviceObject * pDevice = (si32UsbDeviceObject *) obj;
  static uint8_t last = 0xff;
  if ( last ^ pDevice->usbDeviceObject.State )
  {
    uint32_t save_last = last;
    last=pDevice->usbDeviceObject.State;
    usb_device_state_change(save_last, last);
  }

  // reschedule this poller
  si32RunLoopObject_post_work_request(si32GetCurrentRunLoop(),
                                        1, // lo prio
                                        myUsbDevice_poller,
                                        obj,
                                        NULL);

}


//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void myUsb0_main(si32UsbDeviceObject * pDevice)
{
  /* Specify the device object instance */
  myUsbDevice=pDevice;

#if 0 // this functionality is in myPB.c
  /* ----------------------------------- */
  /* Enable access to the USB peripheral */
  /* ----------------------------------- */
  //printf("Enabling apb to all modules\n");
  //SI32_CLKCTRL_A_enable_apb_to_all_modules(SI32_CLKCTRL_0);
  //printf("Enabling ahb to all modules\n\n");
  //SI32_CLKCTRL_A_enable_ahb_to_all_modules(SI32_CLKCTRL_0);
#endif

  /* --------------------- */
  /* Initialize USB Module */
  /* --------------------- */
  USB_initialize ();

  /* -------------------- */
  /* Set custom parameters */
  /* -------------------- */
  /* test setting ahb source to internal usb oscillator */
  // SI32_SIM3U1XX_CLKCTRL_A_set_ahb_source_usb0_oscillator(SI32_CLKCTRL_0);
  // SystemCoreClock = 48000000;
  // SysTick_Config(SystemCoreClock/1000);

  /* -------------------- */
  /* Start the USB Module */
  /* -------------------- */
  USB_start();

  /* ------------------------------------------ */
  /* Start a poller to monitor usb device state */
  /* ------------------------------------------ */
  si32RunLoopObject_post_work_request(si32GetCurrentRunLoop(),
                                        SI32_RUN_LOOP_PRIORITY_LOW,
                                        myUsbDevice_poller,
                                        myUsbDevice,
                                        NULL);

  /* ------------------ */
  /* Connect to the USB */
  /* ------------------ */
  USB_connect();

}

//------------------------------------------------------------------------------
void myUSB0_enter_default_mode()
{
  // nothing to do
}

//------------------------------------------------------------------------------
void myUSB0_enter_active_mode()
{
  myUsbAudioDevice_main();
}

//-eof--------------------------------------------------------------------------
