/*
   Description:
   This is a example code for Sandbox Electronics' I2C/SPI to UART bridge module.
   You can get one of those products on
   http://sandboxelectronics.com

   Version:
   V0.1

   Release Date:
   2014-02-16

   Author:
   Tiequan Shao          info@sandboxelectronics.com

   Lisence:
   CC BY-NC-SA 3.0

   Please keep the above information when you use this code in your project.
 */


// #define SC16IS750_DEBUG_PRINT
#include <SC16IS752.h>
#ifndef DISABLE_SC16IS752_SPI
#include <SPI.h>
#endif
#include <Wire.h>


#ifdef __AVR__
 # define WIRE Wire
#elif defined(ESP8266) || defined(ESP32) // ESP8266/ESP32
 # define WIRE Wire
#else // Arduino Due
 # define WIRE Wire1
#endif // ifdef __AVR__


SC16IS752::SC16IS752(uint8_t prtcl, uint8_t addr_sspin) : initialized(false)
{
  protocol = prtcl;

  if (protocol == SC16IS750_PROTOCOL_I2C) {
    // Datasheet uses extra read/write bit to describe I2C address.
    // Actual address in communication has one bit shifted.
    if ((addr_sspin >= 0x48) && (addr_sspin <= 0x57)) {
      device_address_sspin = addr_sspin;
    } else {
      device_address_sspin = (addr_sspin >> 1);
    }
#ifndef DISABLE_SC16IS752_SPI
  } else {
    device_address_sspin = addr_sspin;
#endif
  }
  peek_flag[SC16IS752_CHANNEL_A] = 0;
  peek_flag[SC16IS752_CHANNEL_B] = 0;

  //	timeout = 1000;
}

void SC16IS752::begin(uint32_t baud_A, uint32_t baud_B)
{
  Initialize(); // Force initialize, since we're initializing both channels at once
  beginA(baud_A);
  beginB(baud_B);
}

void SC16IS752::beginA(uint32_t baud_A)
{
  if (!initialized) {
    Initialize();
  }
  FIFOEnable(SC16IS752_CHANNEL_A, 1);
  SetBaudrate(SC16IS752_CHANNEL_A, baud_A);
  SetLine(SC16IS752_CHANNEL_A, 8, 0, 1);
}

void SC16IS752::beginB(uint32_t baud_B)
{
  if (!initialized) {
    Initialize();
  }
  FIFOEnable(SC16IS752_CHANNEL_B, 1);
  SetBaudrate(SC16IS752_CHANNEL_B, baud_B);
  SetLine(SC16IS752_CHANNEL_B, 8, 0, 1);
}

int SC16IS752::available(uint8_t channel)
{
  return FIFOAvailableData(channel);
}

int SC16IS752::read(uint8_t channel)
{
  if (peek_flag[channel] == 0) {
    return ReadByte(channel);
  }
  peek_flag[channel] = 0;
  return peek_buf[channel];
}

size_t SC16IS752::write(uint8_t channel, uint8_t val)
{
  WriteByte(channel, val);
  return 1;
}

void SC16IS752::pinMode(uint8_t pin, uint8_t i_o)
{
  GPIOSetPinMode(pin, i_o);
}

void SC16IS752::digitalWrite(uint8_t pin, uint8_t value)
{
  GPIOSetPinState(pin, value);
}

uint8_t SC16IS752::digitalRead(uint8_t pin)
{
  return GPIOGetPinState(pin);
}

uint8_t SC16IS752::ReadRegister(uint8_t channel, uint8_t reg_addr)
{
  uint8_t result = 0;

  if (protocol == SC16IS750_PROTOCOL_I2C) { // register read operation via I2C
    WIRE.beginTransmission(device_address_sspin);
    WIRE.write((reg_addr << 3 | channel << 1));
    WIRE.endTransmission(0);
    WIRE.requestFrom(device_address_sspin, (uint8_t)1);
    result = WIRE.read();
#ifndef DISABLE_SC16IS752_SPI
  } else if (protocol == SC16IS750_PROTOCOL_SPI) { // register read operation via SPI
    ::digitalWrite(device_address_sspin, LOW);
    delayMicroseconds(10);
    SPI.transfer(0x80 | ((reg_addr << 3 | channel << 1)));
    result = SPI.transfer(0xff);
    delayMicroseconds(10);
    ::digitalWrite(device_address_sspin, HIGH);
#endif
  }

#ifdef  SC16IS750_DEBUG_PRINT
  Serial.print("ReadRegister channel=");
  Serial.print(channel,                        HEX);
  Serial.print(" reg_addr=");
  Serial.print((reg_addr << 3 | channel << 1), HEX);
  Serial.print(" result=");
  Serial.println(result, HEX);
#endif // ifdef  SC16IS750_DEBUG_PRINT
  return result;
}

void SC16IS752::WriteRegister(uint8_t channel, uint8_t reg_addr, uint8_t val)
{
#ifdef  SC16IS750_DEBUG_PRINT
  Serial.print("WriteRegister channel=");
  Serial.print(channel,                        HEX);
  Serial.print(" reg_addr=");
  Serial.print((reg_addr << 3 | channel << 1), HEX);
  Serial.print(" val=");
  Serial.println(val, HEX);
#endif // ifdef  SC16IS750_DEBUG_PRINT

  if (protocol == SC16IS750_PROTOCOL_I2C) { // register read operation via I2C
    WIRE.beginTransmission(device_address_sspin);
    WIRE.write((reg_addr << 3 | channel << 1));
    WIRE.write(val);
    WIRE.endTransmission(1);
#ifndef DISABLE_SC16IS752_SPI
  } else {
    ::digitalWrite(device_address_sspin, LOW);
    delayMicroseconds(10);
    SPI.transfer((reg_addr << 3 | channel << 1));
    SPI.transfer(val);
    delayMicroseconds(10);
    ::digitalWrite(device_address_sspin, HIGH);
#endif
  }
}

void SC16IS752::Initialize()
{
  if (protocol == SC16IS750_PROTOCOL_I2C) {
    WIRE.begin();
#ifndef DISABLE_SC16IS752_SPI
  } else {
    ::pinMode(device_address_sspin, OUTPUT);
    ::digitalWrite(device_address_sspin, HIGH);
    SPI.setDataMode(SPI_MODE0);
    SPI.setClockDivider(SPI_CLOCK_DIV4);
    SPI.setBitOrder(MSBFIRST);
    SPI.begin();

    // SPI.setClockDivider(32);
#endif
  }
  ResetDevice();
  initialized = true;
}

int16_t SC16IS752::SetBaudrate(uint8_t channel, uint32_t baudrate) // return error of baudrate parts per thousand
{
  uint16_t divisor;
  uint8_t  prescaler;
  uint32_t actual_baudrate;
  int16_t  error;
  uint8_t  temp_lcr;

  if ((ReadRegister(channel, SC16IS750_REG_MCR) & 0x80) == 0) { // if prescaler==1
    prescaler = 1;
  } else {
    prescaler = 4;
  }

  divisor = (SC16IS750_CRYSTCAL_FREQ / prescaler) / (baudrate * 16);

  temp_lcr  = ReadRegister(channel, SC16IS750_REG_LCR);
  temp_lcr |= 0x80;
  WriteRegister(channel, SC16IS750_REG_LCR, temp_lcr);

  // write to DLL
  WriteRegister(channel, SC16IS750_REG_DLL,        (uint8_t)divisor);

  // write to DLH
  WriteRegister(channel, SC16IS750_REG_DLH,        (uint8_t)(divisor >> 8));
  temp_lcr &= 0x7F;
  WriteRegister(channel, SC16IS750_REG_LCR, temp_lcr);


  actual_baudrate = (SC16IS750_CRYSTCAL_FREQ / prescaler) / (16 * divisor);
  error           = ((float)actual_baudrate - baudrate) * 1000 / baudrate;
#ifdef  SC16IS750_DEBUG_PRINT
  Serial.print("Desired baudrate: ");
  Serial.println(baudrate, DEC);
  Serial.print("Calculated divisor: ");
  Serial.println(divisor, DEC);
  Serial.print("Actual baudrate: ");
  Serial.println(actual_baudrate, DEC);
  Serial.print("Baudrate error: ");
  Serial.println(error, DEC);
#endif // ifdef  SC16IS750_DEBUG_PRINT

  return error;
}

void SC16IS752::SetLine(uint8_t channel, uint8_t data_length, uint8_t parity_select, uint8_t stop_length)
{
  uint8_t temp_lcr;

  temp_lcr  = ReadRegister(channel, SC16IS750_REG_LCR);
  temp_lcr &= 0xC0; // Clear the lower six bit of LCR (LCR[0] to LCR[5]
#ifdef  SC16IS750_DEBUG_PRINT
  Serial.print("LCR Register:0x");
  Serial.println(temp_lcr, DEC);
#endif // ifdef  SC16IS750_DEBUG_PRINT

  switch (data_length) { // data length settings
    case 5:
      break;
    case 6:
      temp_lcr |= 0x01;
      break;
    case 7:
      temp_lcr |= 0x02;
      break;
    case 8:
      temp_lcr |= 0x03;
      break;
    default:
      temp_lcr |= 0x03;
      break;
  }

  if (stop_length == 2) {
    temp_lcr |= 0x04;
  }

  switch (parity_select) { // parity selection length settings
    case 0:                // no parity
      break;
    case 1:                // odd parity
      temp_lcr |= 0x08;
      break;
    case 2:                // even parity
      temp_lcr |= 0x18;
      break;
    case 3:                // force '1' parity
      temp_lcr |= 0x03;
      break;
    case 4:                // force '0' parity
      break;
    default:
      break;
  }

  WriteRegister(channel, SC16IS750_REG_LCR, temp_lcr);
}

void SC16IS752::GPIOSetPinMode(uint8_t pin_number, uint8_t i_o)
{
  uint8_t temp_iodir;

  temp_iodir = ReadRegister(SC16IS752_CHANNEL_BOTH, SC16IS750_REG_IODIR);

  if (i_o == OUTPUT) {
    temp_iodir |= (0x01 << pin_number);
  } else {
    temp_iodir &= (uint8_t) ~(0x01 << pin_number);
  }

  WriteRegister(SC16IS752_CHANNEL_BOTH, SC16IS750_REG_IODIR, temp_iodir);
}

void SC16IS752::GPIOSetPinState(uint8_t pin_number, uint8_t pin_state)
{
  uint8_t temp_iostate;

  temp_iostate = ReadRegister(SC16IS752_CHANNEL_BOTH, SC16IS750_REG_IOSTATE);

  if (pin_state == 1) {
    temp_iostate |= (0x01 << pin_number);
  } else {
    temp_iostate &= (uint8_t) ~(0x01 << pin_number);
  }

  WriteRegister(SC16IS752_CHANNEL_BOTH, SC16IS750_REG_IOSTATE, temp_iostate);
}

uint8_t SC16IS752::GPIOGetPinState(uint8_t pin_number)
{
  uint8_t temp_iostate;

  temp_iostate = ReadRegister(SC16IS752_CHANNEL_BOTH, SC16IS750_REG_IOSTATE);

  if ((temp_iostate & (0x01 << pin_number)) == 0) {
    return 0;
  }
  return 1;
}

uint8_t SC16IS752::GPIOGetPortState(void)
{
  return ReadRegister(SC16IS752_CHANNEL_BOTH, SC16IS750_REG_IOSTATE);
}

void SC16IS752::GPIOSetPortMode(uint8_t port_io)
{
  WriteRegister(SC16IS752_CHANNEL_BOTH, SC16IS750_REG_IODIR, port_io);
}

void SC16IS752::GPIOSetPortState(uint8_t port_state)
{
  WriteRegister(SC16IS752_CHANNEL_BOTH, SC16IS750_REG_IOSTATE, port_state);
}

void SC16IS752::SetPinInterrupt(uint8_t io_int_ena)
{
  WriteRegister(SC16IS752_CHANNEL_BOTH, SC16IS750_REG_IOINTENA, io_int_ena);
}

void SC16IS752::ResetDevice()
{
  uint8_t reg;

  reg  = ReadRegister(SC16IS752_CHANNEL_BOTH, SC16IS750_REG_IOCONTROL);
  reg |= 0x08;
  WriteRegister(SC16IS752_CHANNEL_BOTH, SC16IS750_REG_IOCONTROL, reg);
}

void SC16IS752::ModemPin(uint8_t gpio) // gpio == 0, gpio[7:4] are modem pins, gpio == 1 gpio[7:4] are gpios
{
  uint8_t temp_iocontrol;

  temp_iocontrol = ReadRegister(SC16IS752_CHANNEL_BOTH, SC16IS750_REG_IOCONTROL);

  if (gpio == 0) {
    temp_iocontrol |= 0x02;
  } else {
    temp_iocontrol &= 0xFD;
  }
  WriteRegister(SC16IS752_CHANNEL_BOTH, SC16IS750_REG_IOCONTROL, temp_iocontrol);
}

void SC16IS752::GPIOLatch(uint8_t latch)
{
  uint8_t temp_iocontrol;

  temp_iocontrol = ReadRegister(SC16IS752_CHANNEL_BOTH, SC16IS750_REG_IOCONTROL);

  if (latch == 0) {
    temp_iocontrol &= 0xFE;
  } else {
    temp_iocontrol |= 0x01;
  }
  WriteRegister(SC16IS752_CHANNEL_BOTH, SC16IS750_REG_IOCONTROL, temp_iocontrol);
}

void SC16IS752::InterruptControl(uint8_t channel, uint8_t int_ena)
{
  WriteRegister(channel, SC16IS750_REG_IER, int_ena);
}

uint8_t SC16IS752::InterruptPendingTest(uint8_t channel)
{
  return ReadRegister(channel, SC16IS750_REG_IIR) & 0x01;
}

void SC16IS752::__isr(uint8_t channel)
{
  uint8_t irq_src;

  irq_src  = ReadRegister(channel, SC16IS750_REG_IIR);
  irq_src  = (irq_src >> 1);
  irq_src &= 0x3F;

  switch (irq_src) {
    case 0x06: // Receiver Line Status Error
      break;
    case 0x0c: // Receiver time-out interrupt
      break;
    case 0x04: // RHR interrupt
      break;
    case 0x02: // THR interrupt
      break;
    case 0x00: // modem interrupt;
      break;
    case 0x30: // input pin change of state
      break;
    case 0x10: // XOFF
      break;
    case 0x20: // CTS,RTS
      break;
    default:
      break;
  }
}

void SC16IS752::FIFOEnable(uint8_t channel, uint8_t fifo_enable)
{
  uint8_t temp_fcr;

  temp_fcr = ReadRegister(channel, SC16IS750_REG_FCR);

  if (fifo_enable == 0) {
    temp_fcr &= 0xFE;
  } else {
    temp_fcr |= 0x01;
  }
  WriteRegister(channel, SC16IS750_REG_FCR, temp_fcr);
}

void SC16IS752::FIFOReset(uint8_t channel, uint8_t rx_fifo)
{
  uint8_t temp_fcr;

  temp_fcr = ReadRegister(channel, SC16IS750_REG_FCR);

  if (rx_fifo == 0) {
    temp_fcr |= 0x04;
  } else {
    temp_fcr |= 0x02;
  }
  WriteRegister(channel, SC16IS750_REG_FCR, temp_fcr);
}

void SC16IS752::FIFOSetTriggerLevel(uint8_t channel, uint8_t rx_fifo, uint8_t length)
{
  uint8_t temp_reg;

  temp_reg  = ReadRegister(channel, SC16IS750_REG_MCR);
  temp_reg |= 0x04;
  WriteRegister(channel, SC16IS750_REG_MCR, temp_reg);        // SET MCR[2] to '1' to use TLR register or trigger level control in FCR
                                                              // register

  temp_reg = ReadRegister(channel, SC16IS750_REG_EFR);
  WriteRegister(channel, SC16IS750_REG_EFR, temp_reg | 0x10); // set ERF[4] to '1' to use the  enhanced features

  if (rx_fifo == 0) {
    WriteRegister(channel, SC16IS750_REG_TLR, length << 4);   // Tx FIFO trigger level setting
  } else {
    WriteRegister(channel, SC16IS750_REG_TLR, length);        // Rx FIFO Trigger level setting
  }
  WriteRegister(channel, SC16IS750_REG_EFR, temp_reg);        // restore EFR register
}

uint8_t SC16IS752::FIFOAvailableData(uint8_t channel)
{
#ifdef  SC16IS750_DEBUG_PRINT
  Serial.print("=====Available data:");
  Serial.println(ReadRegister(channel, SC16IS750_REG_RXLVL), DEC);
#endif // ifdef  SC16IS750_DEBUG_PRINT
  if (fifo_available[channel] == 0) {
    fifo_available[channel] = ReadRegister(channel, SC16IS750_REG_RXLVL);
  }
  return fifo_available[channel];
  //    return ReadRegister(channel, SC16IS750_REG_LSR) & 0x01;
}

uint8_t SC16IS752::FIFOAvailableSpace(uint8_t channel)
{
  return ReadRegister(channel, SC16IS750_REG_TXLVL);
}

void SC16IS752::WriteByte(uint8_t channel, uint8_t val)
{
  uint8_t tmp_lsr;

  /*   while ( FIFOAvailableSpace(channel) == 0 ){
   #ifdef  SC16IS750_DEBUG_PRINT
                 Serial.println("No available space");
   #endif

         };

   #ifdef  SC16IS750_DEBUG_PRINT
     Serial.println("++++++++++++Data sent");
   #endif
     WriteRegister(SC16IS750_REG_THR,val);
   */
  do {
    tmp_lsr = ReadRegister(channel, SC16IS750_REG_LSR);
  } while ((tmp_lsr & 0x20) == 0);

  WriteRegister(channel, SC16IS750_REG_THR, val);
}

int SC16IS752::ReadByte(uint8_t channel)
{
  volatile uint8_t val;

  if (FIFOAvailableData(channel) == 0) {
#ifdef  SC16IS750_DEBUG_PRINT
    Serial.println("No data available");
#endif // ifdef  SC16IS750_DEBUG_PRINT
    return -1;
  } else {
#ifdef  SC16IS750_DEBUG_PRINT
    Serial.println("***********Data available***********");
#endif // ifdef  SC16IS750_DEBUG_PRINT
    if (fifo_available[channel] > 0) {
      --fifo_available[channel];
    }
    val = ReadRegister(channel, SC16IS750_REG_RHR);
    return val;
  }
}

void SC16IS752::EnableTransmit(uint8_t channel, uint8_t tx_enable)
{
  uint8_t temp_efcr;

  temp_efcr = ReadRegister(channel, SC16IS750_REG_EFCR);

  if (tx_enable == 0) {
    temp_efcr |= 0x04;
  } else {
    temp_efcr &= 0xFB;
  }
  WriteRegister(channel, SC16IS750_REG_EFCR, temp_efcr);
}

uint8_t SC16IS752::ping()
{
  WriteRegister(SC16IS752_CHANNEL_A, SC16IS750_REG_SPR, 0x55);

  if (ReadRegister(SC16IS752_CHANNEL_A, SC16IS750_REG_SPR) != 0x55) {
    return 0;
  }

  WriteRegister(SC16IS752_CHANNEL_A, SC16IS750_REG_SPR, 0xAA);

  if (ReadRegister(SC16IS752_CHANNEL_A, SC16IS750_REG_SPR) != 0xAA) {
    return 0;
  }

  WriteRegister(SC16IS752_CHANNEL_B, SC16IS750_REG_SPR, 0x55);

  if (ReadRegister(SC16IS752_CHANNEL_B, SC16IS750_REG_SPR) != 0x55) {
    return 0;
  }

  WriteRegister(SC16IS752_CHANNEL_B, SC16IS750_REG_SPR, 0xAA);

  if (ReadRegister(SC16IS752_CHANNEL_B, SC16IS750_REG_SPR) != 0xAA) {
    return 0;
  }

  return 1;
}

/*
   void SC16IS752::setTimeout(uint32_t time_out)
   {
        timeout = time_out;
   }

   size_t SC16IS752::readBytes(char *buffer, size_t length)
   {
        size_t count=0;
        int16_t tmp;

        while (count < length) {
                tmp = readwithtimeout();
                if (tmp < 0) {
                        break;
                }
 * buffer++ = (char)tmp;
                count++;
        }

        return count;
   }

   int16_t SC16IS752::readwithtimeout()
   {
   int16_t tmp;
   uint32_t time_stamp;
   time_stamp = millis();
   do {
    tmp = read();
    if (tmp >= 0) return tmp;
   } while(millis() - time_stamp < timeout);
   return -1;     // -1 indicates timeout
   }
 */
void SC16IS752::flush(uint8_t channel)
{
  uint8_t tmp_lsr;

  do {
    tmp_lsr = ReadRegister(channel, SC16IS750_REG_LSR);
  } while ((tmp_lsr & 0x20) == 0);
}

int SC16IS752::peek(uint8_t channel)
{
  if (peek_flag[channel] == 0) {
    peek_buf[channel] = ReadByte(channel);

    if (peek_buf[channel] >= 0) {
      peek_flag[channel] = 1;
    }
  }

  return peek_buf[channel];
}
