/**************************************************************************************************
  Filename:       _hal_uart_usb.c
  Revised:        $Date: 2012-03-27 14:53:26 -0700 (Tue, 27 Mar 2012) $
  Revision:       $Revision: 29910 $

  Description: This file contains the interface to the H/W UART driver by USB.


  Copyright 2009-2012 Texas Instruments Incorporated. All rights reserved.

  IMPORTANT: Your use of this Software is limited to those specific rights
  granted under the terms of a software license agreement between the user
  who downloaded the software, his/her employer (which must be your employer)
  and Texas Instruments Incorporated (the "License").  You may not use this
  Software unless you agree to abide by the terms of the License. The License
  limits your use, and you acknowledge, that the Software may not be modified,
  copied or distributed unless embedded on a Texas Instruments microcontroller
  or used solely and exclusively in conjunction with a Texas Instruments radio
  frequency transceiver, which is integrated into your product.  Other than for
  the foregoing purpose, you may not use, reproduce, copy, prepare derivative
  works of, modify, distribute, perform, display or sell this Software and/or
  its documentation for any purpose.

  YOU FURTHER ACKNOWLEDGE AND AGREE THAT THE SOFTWARE AND DOCUMENTATION ARE
  PROVIDED �AS IS� WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED,
  INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY, TITLE,
  NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL
  TEXAS INSTRUMENTS OR ITS LICENSORS BE LIABLE OR OBLIGATED UNDER CONTRACT,
  NEGLIGENCE, STRICT LIABILITY, CONTRIBUTION, BREACH OF WARRANTY, OR OTHER
  LEGAL EQUITABLE THEORY ANY DIRECT OR INDIRECT DAMAGES OR EXPENSES
  INCLUDING BUT NOT LIMITED TO ANY INCIDENTAL, SPECIAL, INDIRECT, PUNITIVE
  OR CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF PROCUREMENT
  OF SUBSTITUTE GOODS, TECHNOLOGY, SERVICES, OR ANY CLAIMS BY THIRD PARTIES
  (INCLUDING BUT NOT LIMITED TO ANY DEFENSE THEREOF), OR OTHER SIMILAR COSTS.

  Should you have any questions regarding your right to use this Software,
  contact Texas Instruments Incorporated at www.TI.com.
**************************************************************************************************/

/*********************************************************************
 * INCLUDES
 */

#include "hal_uart.h"
#if defined MT_TASK
#include "MT_UART.h"
#endif

#include "usb_board_cfg.h"
#include "usb_cdc.h"
#include "usb_cdc_hooks.h"
#include "usb_firmware_library_config.h"
#include "usb_firmware_library_headers.h"

/*********************************************************************
 * MACROS
 */

#if !defined HAL_UART_BAUD_RATE
#define HAL_UART_BAUD_RATE  115200
#endif

// The timeout tick is at 32-kHz, so multiply msecs by 33.
#define HAL_UART_MSECS_TO_TICKS    33

#if defined MT_TASK
#define HAL_UART_USB_HIGH          MT_UART_DEFAULT_THRESHOLD
#define HAL_UART_USB_IDLE         (MT_UART_DEFAULT_IDLE_TIMEOUT * HAL_UART_MSECS_TO_TICKS)
#define HAL_UART_USB_RX_MAX        MT_UART_DEFAULT_MAX_RX_BUFF
#else
#if !defined HAL_UART_USB_HIGH
#define HAL_UART_USB_HIGH         (HAL_UART_USB_RX_MAX / 2 - 16)
#endif
#if !defined HAL_UART_USB_IDLE
#define HAL_UART_USB_IDLE         (6 * HAL_UART_MSECS_TO_TICKS)
#endif
#if !defined HAL_UART_USB_RX_MAX
#define HAL_UART_USB_RX_MAX        128
#endif
#endif
// Max USB packet size, per specification; see also usb_cdc_descriptor.s51
#if !defined HAL_UART_USB_TX_MAX
#define HAL_UART_USB_TX_MAX        64
#endif

/***********************************************************************************
 * EXTERNAL VARIABLES
 */

/***********************************************************************************
 * GLOBAL VARIABLES
 */


/***********************************************************************************
 * LOCAL DATA
 */

static uint8 halUartRxH, halUartRxT, halUartRxQ[256];
static uint8 halUartTxH, halUartTxT, halUartTxQ[256];

#if !defined HAL_SB_BOOT_CODE
static uint8 rxTick;
static uint8 rxShdw;
static uint8 txMT;
static halUARTCBack_t uartCB;
#endif

/***********************************************************************************
 * LOCAL FUNCTIONS
 */

static void HalUARTInitUSB(void);
static void HalUARTPollUSB(void);
static void halUartPollEvt(void);
static void halUartPollRx(void);
static void halUartPollTx(void);

/******************************************************************************
 * FUNCTIONS
 */

/***********************************************************************************
* @fn           usbUartInitUSB
*
* @brief        USB UART init function.
*               - Set initial line decoding to 8/NONE/1.
*               - Initialise the USB Firmware Library and the USB controller.
*
* @param        none
*
* @return       none
*/
void HalUARTInitUSB(void)
{
  // Set default line coding.
  currentLineCoding.dteRate = HAL_UART_BAUD_RATE;
  currentLineCoding.charFormat = CDC_CHAR_FORMAT_1_STOP_BIT;
  currentLineCoding.parityType = CDC_PARITY_TYPE_NONE;
  currentLineCoding.dataBits = 8;

  // Init USB library
  usbfwInit();

  // Initialize the USB interrupt handler with bit mask containing all processed USBIRQ events
  usbirqInit(0xFFFF);

#if defined HAL_SB_BOOT_CODE
  HAL_USB_PULLUP_ENABLE();  // Enable pullup on D+.
#else
  txMT = TRUE;
#endif
}

/******************************************************************************
 * @fn      HalUARTUnInitUSB
 *
 * @brief   UnInitialize the USB.
 *
 * @param   none
 *
 * @return  none
 *****************************************************************************/
static void HalUARTUnInitUSB(void)
{
  P2IEN = 0;
  IEN2 = 0;
  HAL_USB_PULLUP_DISABLE();
  HAL_USB_DISABLE;
}

#if !defined HAL_SB_BOOT_CODE
/******************************************************************************
 * @fn      HalUARTOpenUSB
 *
 * @brief   Open a port according tp the configuration specified by parameter.
 *
 * @param   config - contains configuration information
 *
 * @return  none
 *****************************************************************************/
static void HalUARTOpenUSB(halUARTCfg_t *config)
{
  // Synchronize initial value to any value between 0 and 255
  halUartRxH = halUartRxT;

  uartCB = config->callBackFunc;
  (void)config;

  HAL_USB_PULLUP_ENABLE();  // Enable pullup on D+.
}
#endif

/***********************************************************************************
* @fn           HalUartPollUSB
*
* @brief        The USB UART main task function. Should be called from the OSAL main loop.
*
* @param        none
*
* @return       none
*/
void HalUARTPollUSB(void)
{
#if defined HAL_SB_BOOT_CODE
  while (USBIF)  usbirqHandler();
#endif
  halUartPollEvt();
  halUartPollRx();
  halUartPollTx();
}

uint8 HalUARTRx(uint8 *buf, uint8 max);
uint8 HalUARTRx(uint8 *buf, uint8 max)
{
  uint8 cnt = 0;

  while ((halUartRxH != halUartRxT) && (cnt < max))
  {
    *buf++ = halUartRxQ[halUartRxH++];
    cnt++;
  }

  return cnt;
}

void HalUARTTx(uint8 *buf, uint8 cnt);
void HalUARTTx(uint8 *buf, uint8 cnt)
{
  while (cnt--)
  {
    halUartTxQ[halUartTxT++] = *buf++;
  }
#if !defined HAL_SB_BOOT_CODE
  txMT = FALSE;
#endif
}

/**************************************************************************************************
 * @fn      HalUARTRxAvailUSB()
 *
 * @brief   Calculate Rx Buffer length - the number of bytes in the buffer.
 *
 * @param   none
 *
 * @return  length of current Rx Buffer
 **************************************************************************************************/
static uint16 HalUARTRxAvailUSB(void)
{
  return ((halUartRxT >= halUartRxH)?
          halUartRxT - halUartRxH : sizeof(halUartRxQ) - halUartRxH + halUartRxT);
}

/***********************************************************************************
* @fn           halUartPollEvt
*
* @brief        Poll for USB events which are not directly related to the UART.
*
* @param        none
*
* @return       none
*/
static void halUartPollEvt(void)
{
  // Handle reset signaling on the bus
  if (USBIRQ_GET_EVENT_MASK() & USBIRQ_EVENT_RESET)
  {
    USBIRQ_CLEAR_EVENTS(USBIRQ_EVENT_RESET);
    usbfwResetHandler();
  }

  // Handle packets on EP0
  if (USBIRQ_GET_EVENT_MASK() & USBIRQ_EVENT_SETUP)
  {
    USBIRQ_CLEAR_EVENTS(USBIRQ_EVENT_SETUP);
    usbfwSetupHandler();
  }

  // Handle USB suspend
  if (USBIRQ_GET_EVENT_MASK() & USBIRQ_EVENT_SUSPEND)
  {
    // Clear USB suspend interrupt
    USBIRQ_CLEAR_EVENTS(USBIRQ_EVENT_SUSPEND);

#if HAL_UART_USB_SUSPEND
    // Take the chip into PM1 until a USB resume is deteceted.
    usbsuspEnter();
#endif

    // Running again; first clear RESUME interrupt
    USBIRQ_CLEAR_EVENTS(USBIRQ_EVENT_RESUME);
  }
}

/***********************************************************************************
* @fn           halUartPollRx
*
* @brief        Poll for data from USB.
*
* @param        none
*
* @return       none
*/
static void halUartPollRx(void)
{
  uint8 cnt;
  uint8 ep = USBFW_GET_SELECTED_ENDPOINT();
  USBFW_SELECT_ENDPOINT(4);

  // If the OUT endpoint has received a complete packet.
  if (USBFW_OUT_ENDPOINT_DISARMED())
  {
    halIntState_t intState;

    HAL_ENTER_CRITICAL_SECTION(intState);
    // Get length of USB packet, this operation must not be interrupted.
    cnt = USBFW_GET_OUT_ENDPOINT_COUNT_LOW();
    cnt += USBFW_GET_OUT_ENDPOINT_COUNT_HIGH() >> 8;
    HAL_EXIT_CRITICAL_SECTION(intState);

    while (cnt--)
    {
      halUartRxQ[halUartRxT++] = USBF4;
    }
    USBFW_ARM_OUT_ENDPOINT();

#if !defined HAL_SB_BOOT_CODE
    // If the USB has transferred in more Rx bytes, reset the Rx idle timer.

    // Re-sync the shadow on any 1st byte(s) received.
    if (rxTick == 0)
    {
      rxShdw = ST0;
    }
    rxTick = HAL_UART_USB_IDLE;
#endif
  }
#if !defined HAL_SB_BOOT_CODE
  else if (rxTick)
  {
    // Use the LSB of the sleep timer (ST0 must be read first anyway).
    uint8 decr = ST0 - rxShdw;

    if (rxTick > decr)
    {
      rxTick -= decr;
      rxShdw = ST0;
    }
    else
    {
      rxTick = 0;
    }
  }

  {
    uint8 evt = 0;
    cnt = halUartRxT - halUartRxH;

    if (cnt >= HAL_UART_USB_HIGH)
    {
      evt = HAL_UART_RX_ABOUT_FULL;
    }
    else if (cnt && !rxTick)
    {
      evt = HAL_UART_RX_TIMEOUT;
    }

    if (evt && (NULL != uartCB))
    {
      uartCB(0, evt);
    }
  }
#endif

  USBFW_SELECT_ENDPOINT(ep);
}

/***********************************************************************************
* @fn           halUartPollTx
*
* @brief        Poll for data to USB.
*
* @param        none
*
* @return       none
*/
static void halUartPollTx(void)
{
  uint8 ep = USBFW_GET_SELECTED_ENDPOINT();
  USBFW_SELECT_ENDPOINT(4);

  // If the IN endpoint is ready to accept data.
  if (USBFW_IN_ENDPOINT_DISARMED())
  {
    if (halUartTxT == halUartTxH)
    {
#if !defined HAL_SB_BOOT_CODE
      if (!txMT)
      {
        txMT = TRUE;
        uartCB(0, HAL_UART_TX_EMPTY);
      }
#endif
    }
    else
    {
      uint8 max = HAL_UART_USB_TX_MAX;

      do
      {
        USBF4 = halUartTxQ[halUartTxH++];
      } while ((halUartTxH != halUartTxT) && (0 != --max));

      USBFW_ARM_IN_ENDPOINT();
    }
  }

  USBFW_SELECT_ENDPOINT(ep);
}

/******************************************************************************
******************************************************************************/