/*
 *   Copyright (C) 2017 by Wojciech Krutnik N0CALL
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "Config.h"
#include "Globals.h"
#include "RingBuff.h"

#include "SerialPort.h"

/*
Pin definitions (configuration in IOSTM_CMSIS.c):

- Host communication:
USART1 - TXD PA9  - RXD PA10

*/


#if defined(STM32F1)

// BaudRate calculator macro
// source: STM32 HAL Library (stm32f1xx_hal_usart.h)
#define USART_DIV(__PCLK__, __BAUD__)     (((__PCLK__)*25)/(4*(__BAUD__)))
#define USART_DIVMANT(__PCLK__, __BAUD__) (USART_DIV((__PCLK__), (__BAUD__))/100)
#define USART_DIVFRAQ(__PCLK__, __BAUD__) (((USART_DIV((__PCLK__), (__BAUD__)) - (USART_DIVMANT((__PCLK__), (__BAUD__)) * 100)) * 16 + 50) / 100)
#define USART_BRR(__PCLK__, __BAUD__)     ((USART_DIVMANT((__PCLK__), (__BAUD__)) << 4)|(USART_DIVFRAQ((__PCLK__), (__BAUD__)) & 0x0F))

#define USART_BUFFER_SIZE 256U
DECLARE_RINGBUFFER_TYPE(USARTBuffer, USART_BUFFER_SIZE);

/* ************* USART1 ***************** */
static volatile USARTBuffer_t txBuffer1={.size=USART_BUFFER_SIZE};
static volatile USARTBuffer_t rxBuffer1={.size=USART_BUFFER_SIZE};


extern "C" {
  bitband_t txe = (bitband_t)BITBAND_PERIPH(&USART1->SR, USART_SR_TXE_Pos);
  bitband_t rxne = (bitband_t)BITBAND_PERIPH(&USART1->SR, USART_SR_RXNE_Pos);
  bitband_t txeie = (bitband_t)BITBAND_PERIPH(&USART1->CR1, USART_CR1_TXEIE_Pos);
  
  RAMFUNC void USART1_IRQHandler()
  {
    /* Transmitting data */
    if(*txe){
      if(!(RINGBUFF_EMPTY(txBuffer1))){
        USART1->DR = RINGBUFF_READ(txBuffer1);
      }else{  /* Buffer empty */
        *txeie = 0;  /* Don't send further data */
      }
    }

    /* Receiving data */
    if(*rxne){
      RINGBUFF_WRITE(rxBuffer1, USART1->DR);
    }
  }
}


void USART1Init(int speed)
{
  RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
  
  USART1->BRR = USART_BRR(72000000UL, speed);
  
  USART1->CR1 = USART_CR1_UE | USART_CR1_TE |
                USART_CR1_RE | USART_CR1_RXNEIE;  // Enable USART and RX interrupt
  NVIC_EnableIRQ(USART1_IRQn);
}

RAMFUNC void USART1TxData(const uint8_t* data, uint16_t length)
{
  NVIC_DisableIRQ(USART1_IRQn);
  
  /* Check remaining space in buffer */
  if(RINGBUFF_COUNT(txBuffer1) + length > RINGBUFF_SIZE(txBuffer1)){
    NVIC_EnableIRQ(USART1_IRQn);
    return;
  }
  
  /* Append data to buffer  */
  while(length--){
    RINGBUFF_WRITE(txBuffer1, *(data++));
  }

  /* Start sending data */
  USART1->CR1 |= USART_CR1_TXEIE;

  NVIC_EnableIRQ(USART1_IRQn);
}


/////////////////////////////////////////////////////////////////

void CSerialPort::beginInt(uint8_t n, int speed)
{
  switch (n) {
    case 1U:
      USART1Init(speed);
      break;
  default:
    break;
  }
}

int CSerialPort::availableInt(uint8_t n)
{
  switch (n) {
    case 1U:
      return !RINGBUFF_EMPTY(rxBuffer1);
    default:
      return false;
  }
}

uint8_t CSerialPort::readInt(uint8_t n)
{
  switch (n) {
    case 1U:
      return RINGBUFF_READ(rxBuffer1);
    default:
      return 0U;
  }
}

void CSerialPort::writeInt(uint8_t n, const uint8_t* data, uint16_t length, bool flush)
{
  bitband_t tc = (bitband_t)BITBAND_PERIPH(&USART1->SR, USART_SR_TC_Pos);
  
  switch (n) {
    case 1U:
      USART1TxData(data, length);
      *tc = 0;
      
      if (flush) {
        while (!RINGBUFF_EMPTY(txBuffer1) || !tc)
          ;
      }
      break;
    default:
      break;
  }
}

#endif
