/**
  ******************************************************************************
  * @file    usbd_cdc_if.c
  * @author  MCD Application Team
  * @brief   Generic media access Layer.
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2015 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                      www.st.com/SLA0044
  *
  ******************************************************************************
  */

#ifdef USBCON
#ifdef USBD_USE_CDC

/* Includes ------------------------------------------------------------------*/
#include "usbd_desc.h"
#include "usbd_cdc_if.h"
#include "bootloader.h"

#ifdef USE_USB_HS
  #define CDC_MAX_PACKET_SIZE USB_OTG_HS_MAX_PACKET_SIZE
#elif defined(USB_OTG_FS) || defined(USB_OTG_FS_MAX_PACKET_SIZE)
  #define CDC_MAX_PACKET_SIZE USB_OTG_FS_MAX_PACKET_SIZE
#else /* USB */
  #define CDC_MAX_PACKET_SIZE USB_MAX_EP0_SIZE
#endif

/*
 * The value USB_CDC_TRANSMIT_TIMEOUT is defined in terms of HAL_GetTick() units.
 * Typically it is 1ms value. The timeout determines when we would consider the
 * host "too slow" and threat the USB CDC port as disconnected.
 */
#ifndef USB_CDC_TRANSMIT_TIMEOUT
  #define USB_CDC_TRANSMIT_TIMEOUT 3
#endif

/* USBD_CDC Private Variables */
/* USB Device Core CDC handle declaration */
USBD_HandleTypeDef hUSBD_Device_CDC;

static bool CDC_initialized = false;
static bool CDC_DTR_enabled = true;

/* Received Data over USB are stored in this buffer       */
CDC_TransmitQueue_TypeDef TransmitQueue;
CDC_ReceiveQueue_TypeDef ReceiveQueue;
__IO bool dtrState = false; /* lineState */
__IO bool rtsState = false;
__IO bool receivePended = true;
static uint32_t transmitStart = 0;

#ifdef DTR_TOGGLING_SEQ
  /* DTR toggling sequence management */
  extern void dtr_togglingHook(uint8_t *buf, uint32_t *len);
  uint8_t dtr_toggling = 0;
#endif

/** USBD_CDC Private Function Prototypes */

static int8_t USBD_CDC_Init(void);
static int8_t USBD_CDC_DeInit(void);
static int8_t USBD_CDC_Control(uint8_t cmd, uint8_t *pbuf, uint16_t length);
static int8_t USBD_CDC_Receive(uint8_t *pbuf, uint32_t *Len);
static int8_t USBD_CDC_TransmitCplt(uint8_t *pbuf, uint32_t *Len, uint8_t epnum);

USBD_CDC_ItfTypeDef USBD_CDC_fops = {
  USBD_CDC_Init,
  USBD_CDC_DeInit,
  USBD_CDC_Control,
  USBD_CDC_Receive,
  USBD_CDC_TransmitCplt
};

USBD_CDC_LineCodingTypeDef linecoding = {
  115200, /* baud rate*/
  0x00,   /* stop bits-1*/
  0x00,   /* parity - none*/
  0x08    /* nb. of bits 8*/
};

/* Private functions ---------------------------------------------------------*/

/**
  * @brief  USBD_CDC_Init
  *         Initializes the CDC media low layer
  * @param  None
  * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t USBD_CDC_Init(void)
{
  /* Set Application Buffers */
  CDC_TransmitQueue_Init(&TransmitQueue);
  CDC_ReceiveQueue_Init(&ReceiveQueue);
  receivePended = true;
  USBD_CDC_SetRxBuffer(&hUSBD_Device_CDC, CDC_ReceiveQueue_ReserveBlock(&ReceiveQueue));

  return ((int8_t)USBD_OK);
}

/**
  * @brief  USBD_CDC_DeInit
  *         DeInitializes the CDC media low layer
  * @param  None
  * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t USBD_CDC_DeInit(void)
{
  return ((int8_t)USBD_OK);
}


/**
  * @brief  USBD_CDC_Control
  *         Manage the CDC class requests
  * @param  cmd: Command code
  * @param  pbuf: Buffer containing command data (request parameters)
  * @param  length: Number of data to be sent (in bytes)
  * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t USBD_CDC_Control(uint8_t cmd, uint8_t *pbuf, uint16_t length)
{
  UNUSED(length);
  switch (cmd) {
    case CDC_SEND_ENCAPSULATED_COMMAND:

      break;

    case CDC_GET_ENCAPSULATED_RESPONSE:

      break;

    case CDC_SET_COMM_FEATURE:

      break;

    case CDC_GET_COMM_FEATURE:

      break;

    case CDC_CLEAR_COMM_FEATURE:

      break;

    /*******************************************************************************/
    /* Line Coding Structure                                                       */
    /*-----------------------------------------------------------------------------*/
    /* Offset | Field       | Size | Value  | Description                          */
    /* 0      | dwDTERate   |   4  | Number |Data terminal rate, in bits per second*/
    /* 4      | bCharFormat |   1  | Number | Stop bits                            */
    /*                                        0 - 1 Stop bit                       */
    /*                                        1 - 1.5 Stop bits                    */
    /*                                        2 - 2 Stop bits                      */
    /* 5      | bParityType |  1   | Number | Parity                               */
    /*                                        0 - None                             */
    /*                                        1 - Odd                              */
    /*                                        2 - Even                             */
    /*                                        3 - Mark                             */
    /*                                        4 - Space                            */
    /* 6      | bDataBits  |   1   | Number Data bits (5, 6, 7, 8 or 16).          */
    /*******************************************************************************/
    case CDC_SET_LINE_CODING:
      linecoding.bitrate    = (uint32_t)(pbuf[0] | (pbuf[1] << 8) | \
                                         (pbuf[2] << 16) | (pbuf[3] << 24));
      linecoding.format     = pbuf[4];
      linecoding.paritytype = pbuf[5];
      linecoding.datatype   = pbuf[6];
      break;

    case CDC_GET_LINE_CODING:
      pbuf[0] = (uint8_t)(linecoding.bitrate);
      pbuf[1] = (uint8_t)(linecoding.bitrate >> 8);
      pbuf[2] = (uint8_t)(linecoding.bitrate >> 16);
      pbuf[3] = (uint8_t)(linecoding.bitrate >> 24);
      pbuf[4] = linecoding.format;
      pbuf[5] = linecoding.paritytype;
      pbuf[6] = linecoding.datatype;
      break;

    case CDC_SET_CONTROL_LINE_STATE:
      // Check DTR state
      dtrState = (CDC_DTR_enabled) ? (((USBD_SetupReqTypedef *)pbuf)->wValue & CLS_DTR) : true;

      if (dtrState) { // Reset the transmit timeout when the port is connected
        transmitStart = 0;
      }
      rtsState = (((USBD_SetupReqTypedef *)pbuf)->wValue & CLS_RTS);
#ifdef DTR_TOGGLING_SEQ
      dtr_toggling++; /* Count DTR toggling */
#endif
      break;

    case CDC_SEND_BREAK:

      break;

    default:
      break;
  }

  return ((int8_t)USBD_OK);
}

/**
  * @brief  USBD_CDC_Receive
  *         Data received over USB OUT endpoint are sent over CDC interface
  *         through this function.
  *
  *         @note
  *         This function will issue a NAK packet on any OUT packet received on
  *         USB endpoint until exiting this function. If you exit this function
  *         before transfer is complete on CDC interface (ie. using DMA controller)
  *         it will result in receiving more data while previous ones are still
  *         not sent.
  *
  * @param  Buf: Buffer of data to be received
  * @param  Len: Number of data received (in bytes)
  * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t USBD_CDC_Receive(uint8_t *Buf, uint32_t *Len)
{
#ifdef DTR_TOGGLING_SEQ
  if (dtr_toggling > 3) {
    dtr_togglingHook(Buf, Len);
    dtr_toggling = 0;
  }
#else
  UNUSED(Buf);
#endif
  /* It always contains required amount of free space for writing */
  CDC_ReceiveQueue_CommitBlock(&ReceiveQueue, (uint16_t)(*Len));
  receivePended = false;
  /* If enough space in the queue for a full buffer then continue receive */
  if (!CDC_resume_receive()) {
    USBD_CDC_ClearBuffer(&hUSBD_Device_CDC);
  }
  return ((int8_t)USBD_OK);
}


/**
  * @brief  USBD_CDC_TransmitCplt
  *         Data transmitted callback
  *
  *         @note
  *         This function is IN transfer complete callback used to inform user that
  *         the submitted Data is successfully sent over USB.
  *
  * @param  Buf: Buffer of data to be received
  * @param  Len: Number of data received (in bytes)
  * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t USBD_CDC_TransmitCplt(uint8_t *Buf, uint32_t *Len, uint8_t epnum)
{
  UNUSED(Buf);
  UNUSED(Len);
  UNUSED(epnum);
  transmitStart = 0;
  CDC_TransmitQueue_CommitRead(&TransmitQueue);
  CDC_continue_transmit();
  return ((int8_t)USBD_OK);
}

void CDC_init(void)
{
  if (!CDC_initialized) {
    /* Init Device Library */
    if (USBD_Init(&hUSBD_Device_CDC, &USBD_Desc, 0) == USBD_OK) {
      /* Add Supported Class */
      if (USBD_RegisterClass(&hUSBD_Device_CDC, USBD_CDC_CLASS) == USBD_OK) {
        /* Add CDC Interface Class */
        if (USBD_CDC_RegisterInterface(&hUSBD_Device_CDC, &USBD_CDC_fops) == USBD_OK) {
          /* Start Device Process */
          USBD_Start(&hUSBD_Device_CDC);
          CDC_initialized = true;
        }
      }
    }
  }
}

void CDC_deInit(void)
{
  if (CDC_initialized) {
    USBD_Stop(&hUSBD_Device_CDC);
    USBD_CDC_DeInit();
    USBD_DeInit(&hUSBD_Device_CDC);
    CDC_initialized = false;
  }
}

bool CDC_connected()
{
  /* Save the transmitStart value in a local variable to avoid twice reading - fix #478 */
  uint32_t transmitTime = transmitStart;
  if (transmitTime) {
    transmitTime = HAL_GetTick() - transmitTime;
  }
  return ((hUSBD_Device_CDC.dev_state == USBD_STATE_CONFIGURED)
          && (transmitTime < USB_CDC_TRANSMIT_TIMEOUT)
          && dtrState);
}

void CDC_continue_transmit(void)
{
  uint16_t size;
  uint8_t *buffer;
  USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef *) hUSBD_Device_CDC.pClassData;
  /*
   * TS: This method can be called both in the main thread
   * (via USBSerial::write) and in the IRQ stream (via USBD_CDC_TransmistCplt),
   * BUT the main thread cannot pass this condition while waiting for a IRQ!
   * This is not possible because TxState is not zero while waiting for data
   * transfer ending! The IRQ thread is uninterrupted, since its priority
   * is higher than that of the main thread. So this method is thread safe.
   */
  if (hcdc->TxState == 0U) {
    buffer = CDC_TransmitQueue_ReadBlock(&TransmitQueue, &size);
    if (size > 0) {
      transmitStart = HAL_GetTick();
      USBD_CDC_SetTxBuffer(&hUSBD_Device_CDC, buffer, size);
      /*
       * size never exceed PMA buffer and USBD_CDC_TransmitPacket make full
       * copy of block in PMA, so no need to worry about buffer damage
       */
      USBD_CDC_TransmitPacket(&hUSBD_Device_CDC);
    }
  }
}

bool CDC_resume_receive(void)
{
  /*
   * TS: main and IRQ threads can't pass it at same time, because
   * IRQ may occur only if receivePended is true. So it is thread-safe!
   */
  if (!receivePended) {
    uint8_t *block = CDC_ReceiveQueue_ReserveBlock(&ReceiveQueue);
    if (block != NULL) {
      receivePended = true;
      /* Set new buffer */
      USBD_CDC_SetRxBuffer(&hUSBD_Device_CDC, block);
      USBD_CDC_ReceivePacket(&hUSBD_Device_CDC);
      return true;
    }
  }
  return false;
}

void CDC_enableDTR(bool enable)
{
  CDC_DTR_enabled = enable;
}

#endif /* USBD_USE_CDC */
#endif /* USBCON */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

