/*
 * Copyright (c) 2021 ARM Limited. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the License); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an AS IS BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * ----------------------------------------------------------------------
 *
 * $Date:        1. March 2021
 * $Revision:    V1.0.0
 *
 * Project:      CMSIS-DAP Source
 * Title:        UART.c CMSIS-DAP UART
 *
 *---------------------------------------------------------------------------*/

#include "DAP_config.h"
#include "DAP.h"

#if (DAP_UART != 0)

#ifdef DAP_FW_V1
#error "UART Communication Port not supported in DAP V1!"
#endif

#include "Driver_USART.h"

#include "cmsis_os2.h"
#include <string.h>

#define UART_RX_BLOCK_SIZE    32U   /* Uart Rx Block Size (must be 2^n) */

// USART Driver
#define _USART_Driver_(n)  Driver_USART##n
#define  USART_Driver_(n) _USART_Driver_(n)
extern ARM_DRIVER_USART    USART_Driver_(DAP_UART_DRIVER);
#define pUSART           (&USART_Driver_(DAP_UART_DRIVER))

// UART Configuration
#if (DAP_UART_USB_COM_PORT != 0)
static uint8_t  UartTransport = DAP_UART_TRANSPORT_USB_COM_PORT;
#else
static uint8_t  UartTransport = DAP_UART_TRANSPORT_NONE;
#endif

// UART Flags
static uint8_t  UartConfigured = 0U;
static uint8_t  UartReceiveEnabled = 0U;
static uint8_t  UartTransmitEnabled = 0U;
static uint8_t  UartTransmitActive = 0U;

// UART TX Buffer
static uint8_t  UartTxBuf[DAP_UART_TX_BUFFER_SIZE];
static volatile uint32_t UartTxIndexI = 0U;
static volatile uint32_t UartTxIndexO = 0U;

// UART RX Buffer
static uint8_t  UartRxBuf[DAP_UART_RX_BUFFER_SIZE];
static volatile uint32_t UartRxIndexI = 0U;
static volatile uint32_t UartRxIndexO = 0U;

// Uart Errors
static volatile uint8_t  UartErrorRxDataLost = 0U;
static volatile uint8_t  UartErrorFraming = 0U;
static volatile uint8_t  UartErrorParity = 0U;

// UART Transmit
static uint32_t UartTxNum = 0U;

// Function prototypes
static uint8_t  UART_Init (void);
static void     UART_Uninit (void);
static uint8_t  UART_Get_Status (void);
static uint8_t  UART_Receive_Enable (void);
static uint8_t  UART_Transmit_Enable (void);
static void     UART_Receive_Disable (void);
static void     UART_Transmit_Disable (void);
static void     UART_Receive_Flush (void);
static void     UART_Transmit_Flush (void);
static void     UART_Receive (void);
static void     UART_Transmit (void);


// USART Driver Callback function
//   event: event mask
static void USART_Callback (uint32_t event) {
  if (event &  ARM_USART_EVENT_SEND_COMPLETE) {
    UartTxIndexO += UartTxNum;
    UartTransmitActive = 0U;
    UART_Transmit();
  }
  if (event &  ARM_USART_EVENT_RECEIVE_COMPLETE) {
    UartRxIndexI += UART_RX_BLOCK_SIZE;
    UART_Receive();
  }
  if (event &  ARM_USART_EVENT_RX_OVERFLOW) {
    UartErrorRxDataLost = 1U;
  }
  if (event &  ARM_USART_EVENT_RX_FRAMING_ERROR) {
    UartErrorFraming = 1U;
  }
  if (event &  ARM_USART_EVENT_RX_PARITY_ERROR) {
    UartErrorParity = 1U;
  }
}

// Init UART
//   return: DAP_OK or DAP_ERROR
static uint8_t UART_Init (void) {
  int32_t status;
  uint8_t ret = DAP_ERROR;

  UartConfigured = 0U;
  UartReceiveEnabled = 0U;
  UartTransmitEnabled = 0U;
  UartTransmitActive = 0U;
  UartErrorRxDataLost = 0U;
  UartErrorFraming = 0U;
  UartErrorParity = 0U;
  UartTxIndexI = 0U;
  UartTxIndexO = 0U;
  UartRxIndexI = 0U;
  UartRxIndexO = 0U;
  UartTxNum = 0U;

  status = pUSART->Initialize(USART_Callback);
  if (status == ARM_DRIVER_OK) {
    status = pUSART->PowerControl(ARM_POWER_FULL);
  }
  if (status == ARM_DRIVER_OK) {
    ret = DAP_OK;
  }

  return (ret);
}

// Un-Init UART
static void UART_Uninit (void) {
  UartConfigured = 0U;

  pUSART->PowerControl(ARM_POWER_OFF);
  pUSART->Uninitialize();
}

// Get UART Status
//   return: status
static uint8_t UART_Get_Status (void) {
  uint8_t status = 0U;

  if (UartReceiveEnabled != 0U) {
    status |= DAP_UART_STATUS_RX_ENABLED;
  }
  if (UartErrorRxDataLost != 0U) {
    UartErrorRxDataLost = 0U;
    status |= DAP_UART_STATUS_RX_DATA_LOST;
  }
  if (UartErrorFraming != 0U) {
    UartErrorFraming = 0U;
    status |= DAP_UART_STATUS_FRAMING_ERROR;
  }
  if (UartErrorParity != 0U) {
    UartErrorParity = 0U;
    status |= DAP_UART_STATUS_PARITY_ERROR;
  }
  if (UartTransmitEnabled != 0U) {
    status |= DAP_UART_STATUS_TX_ENABLED;
  }

  return (status);
}

// Enable UART Receive
//   return: DAP_OK or DAP_ERROR
static uint8_t UART_Receive_Enable (void) {
  int32_t status;
  uint8_t ret = DAP_ERROR;

  if (UartReceiveEnabled == 0U) {
    // Flush Buffers
    UartRxIndexI = 0U;
    UartRxIndexO = 0U;

    UART_Receive();
    status = pUSART->Control(ARM_USART_CONTROL_RX, 1U);
    if (status == ARM_DRIVER_OK) {
      UartReceiveEnabled = 1U;
      ret = DAP_OK;
    }
  } else {
    ret = DAP_OK;
  }

  return (ret);
}

// Enable UART Transmit
//   return: DAP_OK or DAP_ERROR
static uint8_t UART_Transmit_Enable (void) {
  int32_t status;
  uint8_t ret = DAP_ERROR;

  if (UartTransmitEnabled == 0U) {
    // Flush Buffers
    UartTransmitActive = 0U;
    UartTxIndexI = 0U;
    UartTxIndexO = 0U;
    UartTxNum = 0U;

    status = pUSART->Control(ARM_USART_CONTROL_TX, 1U);
    if (status == ARM_DRIVER_OK) {
      UartTransmitEnabled = 1U;
      ret = DAP_OK;
    }
  } else {
    ret = DAP_OK;
  }

  return (ret);
}

// Disable UART Receive
static void UART_Receive_Disable (void) {
  if (UartReceiveEnabled != 0U) {
    pUSART->Control(ARM_USART_CONTROL_RX, 0U);
    pUSART->Control(ARM_USART_ABORT_RECEIVE, 0U);
    UartReceiveEnabled = 0U;
  }
}

// Disable UART Transmit
static void UART_Transmit_Disable (void) {
  if (UartTransmitEnabled != 0U) {
    pUSART->Control(ARM_USART_ABORT_SEND, 0U);
    pUSART->Control(ARM_USART_CONTROL_TX, 0U);
    UartTransmitActive = 0U;
    UartTransmitEnabled = 0U;
  }
}

// Flush UART Receive buffer
static void UART_Receive_Flush (void) {
  pUSART->Control(ARM_USART_ABORT_RECEIVE, 0U);
  UartRxIndexI = 0U;
  UartRxIndexO = 0U;
  if (UartReceiveEnabled != 0U) {
    UART_Receive();
  }
}

// Flush UART Transmit buffer
static void UART_Transmit_Flush (void) {
  pUSART->Control(ARM_USART_ABORT_SEND, 0U);
  UartTransmitActive = 0U;
  UartTxIndexI = 0U;
  UartTxIndexO = 0U;
  UartTxNum = 0U;
}

// Receive data from target via UART
static void UART_Receive (void) {
  uint32_t index;

  index = UartRxIndexI & (DAP_UART_RX_BUFFER_SIZE - 1U);
  pUSART->Receive(&UartRxBuf[index], UART_RX_BLOCK_SIZE);
}

// Transmit available data to target via UART
static void UART_Transmit (void) {
  uint32_t count;
  uint32_t index;

  count = UartTxIndexI - UartTxIndexO;
  index = UartTxIndexO & (DAP_UART_TX_BUFFER_SIZE - 1U);

  if (count != 0U) {
    if ((index + count) <= DAP_UART_TX_BUFFER_SIZE) {
      UartTxNum = count;
    } else {
      UartTxNum = DAP_UART_TX_BUFFER_SIZE - index;
    }
    UartTransmitActive = 1U;
    pUSART->Send(&UartTxBuf[index], UartTxNum);
  }
}

// Process UART Transport command and prepare response
//   request:  pointer to request data
//   response: pointer to response data
//   return:   number of bytes in response (lower 16 bits)
//             number of bytes in request (upper 16 bits)
uint32_t UART_Transport (const uint8_t *request, uint8_t *response) {
  uint8_t  transport;
  uint8_t  ret = DAP_ERROR;

  transport = *request;
  switch (transport) {
    case DAP_UART_TRANSPORT_NONE:
      switch (UartTransport) {
        case DAP_UART_TRANSPORT_NONE:
          ret = DAP_OK;
          break;
        case DAP_UART_TRANSPORT_USB_COM_PORT:
#if (DAP_UART_USB_COM_PORT != 0)
          USB_COM_PORT_Activate(0U);
          UartTransport = DAP_UART_TRANSPORT_NONE;
          ret = DAP_OK;
#endif
          break;
        case DAP_UART_TRANSPORT_DAP_COMMAND:
          UART_Receive_Disable();
          UART_Transmit_Disable();
          UART_Uninit();
          UartTransport = DAP_UART_TRANSPORT_NONE;
          ret= DAP_OK;
          break;
      }
      break;
    case DAP_UART_TRANSPORT_USB_COM_PORT:
      switch (UartTransport) {
        case DAP_UART_TRANSPORT_NONE:
#if (DAP_UART_USB_COM_PORT != 0)
          if (USB_COM_PORT_Activate(1U) == 0U) {
            UartTransport = DAP_UART_TRANSPORT_USB_COM_PORT;
            ret = DAP_OK;
          }
#endif
          break;
        case DAP_UART_TRANSPORT_USB_COM_PORT:
          ret = DAP_OK;
          break;
        case DAP_UART_TRANSPORT_DAP_COMMAND:
          UART_Receive_Disable();
          UART_Transmit_Disable();
          UART_Uninit();
          UartTransport = DAP_UART_TRANSPORT_NONE;
#if (DAP_UART_USB_COM_PORT != 0)
          if (USB_COM_PORT_Activate(1U) == 0U) {
            UartTransport = DAP_UART_TRANSPORT_USB_COM_PORT;
            ret = DAP_OK;
          }
#endif
          break;
      }
      break;
    case DAP_UART_TRANSPORT_DAP_COMMAND:
      switch (UartTransport) {
        case DAP_UART_TRANSPORT_NONE:
          ret = UART_Init();
          if (ret == DAP_OK) {
            UartTransport = DAP_UART_TRANSPORT_DAP_COMMAND;
          }
          break;
        case DAP_UART_TRANSPORT_USB_COM_PORT:
#if (DAP_UART_USB_COM_PORT != 0)
          USB_COM_PORT_Activate(0U);
          UartTransport = DAP_UART_TRANSPORT_NONE;
#endif
          ret = UART_Init();
          if (ret == DAP_OK) {
            UartTransport = DAP_UART_TRANSPORT_DAP_COMMAND;
          }
          break;
        case DAP_UART_TRANSPORT_DAP_COMMAND:
          ret = DAP_OK;
          break;
      }
      break;
    default:
      break;
  }

  *response = ret;

  return ((1U << 16) | 1U);
}

// Process UART Configure command and prepare response
//   request:  pointer to request data
//   response: pointer to response data
//   return:   number of bytes in response (lower 16 bits)
//             number of bytes in request (upper 16 bits)
uint32_t UART_Configure (const uint8_t *request, uint8_t *response) {
  uint8_t  control, status;
  uint32_t baudrate;
  int32_t  result;

  if (UartTransport != DAP_UART_TRANSPORT_DAP_COMMAND) {
    status = DAP_UART_CFG_ERROR_DATA_BITS |
             DAP_UART_CFG_ERROR_PARITY    |
             DAP_UART_CFG_ERROR_STOP_BITS;
    baudrate = 0U;  // baudrate error
  } else {

    status   = 0U;
    control  = *request;
    baudrate = (uint32_t)(*(request+1) <<  0) |
               (uint32_t)(*(request+2) <<  8) |
               (uint32_t)(*(request+3) << 16) |
               (uint32_t)(*(request+4) << 24);

    result = pUSART->Control(control |
                             ARM_USART_MODE_ASYNCHRONOUS |
                             ARM_USART_FLOW_CONTROL_NONE,
                             baudrate);
    if (result == ARM_DRIVER_OK) {
      UartConfigured = 1U;
    } else {
      UartConfigured = 0U;
      switch (result) {
        case ARM_USART_ERROR_BAUDRATE:
          status = 0U;
          baudrate = 0U;
          break;
        case ARM_USART_ERROR_DATA_BITS:
          status = DAP_UART_CFG_ERROR_DATA_BITS;
          break;
        case ARM_USART_ERROR_PARITY:
          status = DAP_UART_CFG_ERROR_PARITY;
          break;
        case ARM_USART_ERROR_STOP_BITS:
          status = DAP_UART_CFG_ERROR_STOP_BITS;
          break;
        default:
          status = DAP_UART_CFG_ERROR_DATA_BITS |
                   DAP_UART_CFG_ERROR_PARITY    |
                   DAP_UART_CFG_ERROR_STOP_BITS;
          baudrate = 0U;
          break;
      }
    }
  }

  *response++ = status;
  *response++ = (uint8_t)(baudrate >>  0);
  *response++ = (uint8_t)(baudrate >>  8);
  *response++ = (uint8_t)(baudrate >> 16);
  *response   = (uint8_t)(baudrate >> 24);

  return ((5U << 16) | 5U);
}

// Process UART Control command and prepare response
//   request:  pointer to request data
//   response: pointer to response data
//   return:   number of bytes in response (lower 16 bits)
//             number of bytes in request (upper 16 bits)
uint32_t UART_Control (const uint8_t *request, uint8_t *response) {
  uint8_t control;
  uint8_t result;
  uint8_t ret = DAP_OK;

  if (UartTransport != DAP_UART_TRANSPORT_DAP_COMMAND) {
    ret = DAP_ERROR;
  } else {
 
    control = *request;

    if ((control & DAP_UART_CONTROL_RX_DISABLE) != 0U) {
      // Receive disable
      UART_Receive_Disable();
    } else if ((control & DAP_UART_CONTROL_RX_ENABLE) != 0U) {
      // Receive enable
      if (UartConfigured != 0U) {
        result = UART_Receive_Enable();
        if (result != DAP_OK) {
          ret = DAP_ERROR;
        }
      } else {
        ret = DAP_ERROR;
      }
    }
    if ((control & DAP_UART_CONTROL_RX_BUF_FLUSH) != 0U) {
      UART_Receive_Flush();
    }

    if ((control & DAP_UART_CONTROL_TX_DISABLE) != 0U) {
      // Transmit disable
      UART_Transmit_Disable();
    } else if ((control & DAP_UART_CONTROL_TX_ENABLE) != 0U) {
      // Transmit enable
      if (UartConfigured != 0U) {
        result = UART_Transmit_Enable();
        if (result != DAP_OK) {
          ret = DAP_ERROR;
        }
      } else {
        ret = DAP_ERROR;
      }
    } 
    if ((control & DAP_UART_CONTROL_TX_BUF_FLUSH) != 0U) {
      UART_Transmit_Flush();
    }
  }

  *response = ret;

  return ((1U << 16) | 1U);
}

// Process UART Status command and prepare response
//   response: pointer to response data
//   return:   number of bytes in response (lower 16 bits)
//             number of bytes in request (upper 16 bits)
uint32_t UART_Status (uint8_t *response) {
  uint32_t rx_cnt, tx_cnt;
  uint32_t cnt;
  uint8_t  status;

  if ((UartTransport != DAP_UART_TRANSPORT_DAP_COMMAND) ||
      (UartConfigured == 0U)) {
    rx_cnt = 0U;
    tx_cnt = 0U;
    status = 0U;
  } else {

    rx_cnt  = UartRxIndexI - UartRxIndexO;
    rx_cnt += pUSART->GetRxCount();
    if (rx_cnt > (DAP_UART_RX_BUFFER_SIZE - (UART_RX_BLOCK_SIZE*2))) {
      // Overflow
      UartErrorRxDataLost = 1U;
      rx_cnt = (DAP_UART_RX_BUFFER_SIZE - (UART_RX_BLOCK_SIZE*2));
      UartRxIndexO = UartRxIndexI - rx_cnt;
    }

    tx_cnt = UartTxIndexI - UartTxIndexO;
    cnt = pUSART->GetTxCount();
    if (UartTransmitActive != 0U) {
      tx_cnt -= cnt;
    }

    status = UART_Get_Status();
  }

  *response++ = status;
  *response++ = (uint8_t)(rx_cnt >>  0);
  *response++ = (uint8_t)(rx_cnt >>  8);
  *response++ = (uint8_t)(rx_cnt >> 16);
  *response++ = (uint8_t)(rx_cnt >> 24);
  *response++ = (uint8_t)(tx_cnt >>  0);
  *response++ = (uint8_t)(tx_cnt >>  8);
  *response++ = (uint8_t)(tx_cnt >> 16);
  *response   = (uint8_t)(tx_cnt >> 24);

  return ((0U << 16) | 9U);
}

// Process UART Transfer command and prepare response
//   request:  pointer to request data
//   response: pointer to response data
//   return:   number of bytes in response (lower 16 bits)
//             number of bytes in request (upper 16 bits)
uint32_t UART_Transfer (const uint8_t *request, uint8_t *response) {
  uint32_t rx_cnt, tx_cnt;
  uint32_t rx_num, tx_num;
  uint8_t *rx_data;
  const
  uint8_t *tx_data;
  uint32_t num;
  uint32_t index;
  uint8_t  status;

  if (UartTransport != DAP_UART_TRANSPORT_DAP_COMMAND) {
    status = 0U;
    rx_cnt = 0U;
    tx_cnt = 0U;
  } else {

    // RX Data
    rx_cnt = ((uint32_t)(*(request+0) << 0)  |
              (uint32_t)(*(request+1) << 8));

    if (rx_cnt > (DAP_PACKET_SIZE - 6U)) {
      rx_cnt = (DAP_PACKET_SIZE - 6U);
    }
    rx_num  = UartRxIndexI - UartRxIndexO;
    rx_num += pUSART->GetRxCount();
    if (rx_num > (DAP_UART_RX_BUFFER_SIZE - (UART_RX_BLOCK_SIZE*2))) {
      // Overflow
      UartErrorRxDataLost = 1U;
      rx_num = (DAP_UART_RX_BUFFER_SIZE - (UART_RX_BLOCK_SIZE*2));
      UartRxIndexO = UartRxIndexI - rx_num;
    }
    if (rx_cnt > rx_num) {
      rx_cnt = rx_num;
    }

    rx_data = (response+5);
    index = UartRxIndexO & (DAP_UART_RX_BUFFER_SIZE - 1U);
    if ((index + rx_cnt) <= DAP_UART_RX_BUFFER_SIZE) {
      memcpy( rx_data,      &UartRxBuf[index], rx_cnt);
    } else {
      num = DAP_UART_RX_BUFFER_SIZE - index;
      memcpy( rx_data,      &UartRxBuf[index], num);
      memcpy(&rx_data[num], &UartRxBuf[0],     rx_cnt - num);
    }
    UartRxIndexO += rx_cnt;

    // TX Data
    tx_cnt  = ((uint32_t)(*(request+2) << 0) |
               (uint32_t)(*(request+3) << 8));
    tx_data =              (request+4);

    if (tx_cnt > (DAP_PACKET_SIZE - 5U)) {
      tx_cnt = (DAP_PACKET_SIZE - 5U);
    }
    tx_num = UartTxIndexI - UartTxIndexO;
    num = pUSART->GetTxCount();
    if (UartTransmitActive != 0U) {
      tx_num -= num;
    }
    if (tx_cnt > (DAP_UART_TX_BUFFER_SIZE - tx_num)) {
      tx_cnt = (DAP_UART_TX_BUFFER_SIZE - tx_num);
    }

    index = UartTxIndexI & (DAP_UART_TX_BUFFER_SIZE - 1U);
    if ((index + tx_cnt) <= DAP_UART_TX_BUFFER_SIZE) {
      memcpy(&UartTxBuf[index],  tx_data,      tx_cnt);
    } else {
      num = DAP_UART_TX_BUFFER_SIZE - index;
      memcpy(&UartTxBuf[index],  tx_data,      num);
      memcpy(&UartTxBuf[0],     &tx_data[num], tx_cnt - num);
    }
    UartTxIndexI += tx_cnt;

    if (UartTransmitActive == 0U) {
      UART_Transmit();
    }

    status = UART_Get_Status();
  }

  *response++ = status;
  *response++ = (uint8_t)(tx_cnt >> 0);
  *response++ = (uint8_t)(tx_cnt >> 8);
  *response++ = (uint8_t)(rx_cnt >> 0);
  *response   = (uint8_t)(rx_cnt >> 8);

  return (((4U + tx_cnt) << 16) | (5U + rx_cnt));
}

#endif /* DAP_UART */