/*
 * Copyright (c) 2016, Freescale Semiconductor, Inc.
 * Copyright (c) 2016-2021, NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
#include "common.h"
#include "fsl_i2c.h"
#include "fsl_port.h"
#include "fsl_gpio.h"
#include "iic_config.h"
#include "Defines.h"
#include "Application.h"
#include "EEPROMDriver.h"
#include "PowerModes.h"
#include "AppCommon.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/
#define I2C1_GetIsrFlag         (I2C1->S & (uint8_t)kI2C_IntPendingFlag)
#define I2C1_ClrIsrFlag         (I2C1->S = (I2C1->S | I2C_S_IICIF_MASK) & ~I2C_S_ARBL_MASK)
#define I2C1_GetBusyFlag        (I2C1->S & I2C_S_BUSY_MASK)
#define I2C1_RdData             (uint8)I2C1->D
#define I2C1_SetTxMode          iosetb (I2C1->C1,I2C_C1_TX_MASK)
#define I2C1_StartSignal        iosetb (I2C1->C1,I2C_C1_MST_MASK)
#define I2C1_RepeatStartSignal  iosetb (I2C1->C1,I2C_C1_RSTA_MASK)
#define I2C1_SetNackMode        iosetb (I2C1->C1,I2C_C1_TXAK_MASK)
#define I2C1_ClrNackMode        ioclrb (I2C1->C1,I2C_C1_TXAK_MASK)
#define I2C1_SetRxMode          ioclrb (I2C1->C1,I2C_C1_TX_MASK)
#define I2C1_StopSignal         ioclrb (I2C1->C1,I2C_C1_MST_MASK)
#define I2C1_Reset              (I2C1->C1 = I2C_C1_IICEN_MASK)
#define I2C1_WrData(data)       (I2C1->D = data)
#define USE_I2C_CPU
typedef enum
{
  I2C_WRITE,
  I2C_READ
}I2C_OP;

/*******************************************************************************
* Prototypes
******************************************************************************/
volatile uint8 I2CTimeout;
uint8 I2CStuck;
#ifndef USE_I2C_CPU
volatile uint8 DMACompletionFlag;
uint8 I2CDMADirection;
#endif
/*
* --------------------------
* Function Prototypes
* --------------------------
*/
static void WaitAndClearI2CIntrpt(void);
#ifndef USE_I2C_CPU
/* DMA callback prototype                                                     */
void dma0_callback (DMA_CH_CALLBACK_TYPE type);
#endif
/*******************************************************************************
 * Code
 ******************************************************************************/
#ifndef USE_I2C_CPU
void dma0_callback (DMA_CH_CALLBACK_TYPE type)
{
  /* Disable DMA. */
  I2C1_EnableDMA(0);
  
  if(I2CDMADirection == I2C_READ)
  {
    I2C1_SetNackMode;         // NO ACK mode
    WaitAndClearI2CIntrpt();
  }
  DMACompletionFlag = 1;
}
#endif

 /*!
 * @brief Initializes I2C instance: sets the baudrate and interrupt masking.
 */
void IIC_Init(void)
{
  /* baudrate will be dependent on bus/I2C module clock 
    Meter RUN mode, Bus/I2C module clock = 11712 KHz
    Meter in battery mode, Bus/I2C module clock = 1024 KHz */
  if (SystemState.PowerMode == POWERMODE_RUN)
  {
    /* Baudrate divider = 0x23 -> 256
    Baudrate = 11993088/(1 x 256) = 46.84 kbps */
    SET_I2C_BAUD_RATE(0x2B);
  }
  else
  {
    /* Baudrate divider = 0x12 -> 64
    Baudrate = 1024000/(1 x 64) = 16 kbps */
    SET_I2C_BAUD_RATE(0x2B);
  }
  I2C1->C1 = (I2C_C1_IICIE_MASK | I2C_C1_IICEN_MASK);
  
#ifndef USE_I2C_CPU
  /* Install DMA0 callback and initialize DMA0 channel                        */
  DMA_InstallCallback (DMA0, PRI_LVL3, dma0_callback);
#endif
}

 /*!
 * @brief Implements a flow control mechanism as needed in I2C protocol, with timeout.
 */
static void WaitAndClearI2CIntrpt(void)
{
  if (I2CStuck == TRUE)
  {
    return;
  }
  I2CTimeout = 3;
  while (I2C1_GetIsrFlag == FALSE) // wait until one byte transfer completion
  {
    if (I2CTimeout == 0)
    {
      I2CStuck = TRUE;
      break;
    }
  }
  I2C1_ClrIsrFlag;                  /* clear IIC interrupt flag */
  return;
}

 /*!
 * @brief Implements I2C read transaction to device.
 */
void IICReadData(uint32 Address, uint8 *Buffer, uint16 NoOfBytes)
{
  uint16 i;
  uint8 j = 0;
  
  /* for Address 0 - 0x1FFFF */
  if ((Address>=0x10000) && (Address <= 0x1FFFF))
  {
    /* 0010b - Select 1st EEPROM, set A16 bit */
    j = 2;
  }
  /* for Address 0x20000 - 0x3FFFF */
  else if ((Address>=0x20000) && (Address <= 0x2FFFF))
  {
    /* 0100b - Select 2nd EEPROM */
    j = 4;
  }    
  else if ((Address>=0x30000) && (Address <= 0x3FFFF))
  {
      /* 0110b - and set A16 bit */
      j = 6; 
  }
  
  /****************************** READING SECTION *****************************/
  if (I2C1_GetIsrFlag == TRUE)
  {
    I2C1_ClrIsrFlag;                  /* clear IIC interrupt flag */
  }
  if (I2C1_GetBusyFlag)
  {
    I2C1->C1 = 0;
    I2C1->C1 = (I2C_C1_IICIE_MASK | I2C_C1_IICEN_MASK);
    i = I2C1_RdData; // Dummy read
    I2C1->S = 0;
    I2C1->C1 = 0;
    I2C1->C1 = (I2C_C1_IICIE_MASK | I2C_C1_IICEN_MASK);
  }
  
  I2C1_SetTxMode;     // setting in Tx mode
  I2C1_StartSignal; 	// send start condition
  
  I2C1_WrData(0xA0 | j);
  WaitAndClearI2CIntrpt();
  
  // if the slave device is already busy (due to some previous write operation), it won't ack
  while (I2C1->S & I2C_S_RXAK_MASK)
  {
    I2C1_RepeatStartSignal;
    SDK_DelayAtLeastUs(166, SYSTEM_CLOCK);
    I2C1_WrData(0xA0 | j);
    WaitAndClearI2CIntrpt();
  }
  
  i = 2; // Bytes in address
  while(i)
  {
    i--;
    I2C1_WrData(Address >> (i * 8)); // memory address, beginning with the MSB first
    WaitAndClearI2CIntrpt();
  }
  
  I2C1_RepeatStartSignal;			// resend start
  SDK_DelayAtLeastUs(166, SYSTEM_CLOCK);
  I2C1_WrData(0xA1 | j);          // device id to read
  WaitAndClearI2CIntrpt();
  
  if (NoOfBytes > 1)
  {
    I2C1_ClrNackMode;         // ACK mode

#ifndef USE_I2C_CPU  
    DMACompletionFlag = 0;
    I2CDMADirection = I2C_READ;
    I2C1_EnableDMA(1);
    DMA_Init (DMA0,DMA_CH_HWTRG_P2M_CS_CONFIG(DMA_REQ_I2C1,DMA_SIZE8BIT,
                                            &I2C1_D,(uint8 *)Buffer,(NoOfBytes-1)));
#endif
    I2C1_SetRxMode; // setting in Rx mode
    i = I2C1_RdData; 		// dummy read to start
    
#ifdef USE_I2C_CPU  
    for(i = 0; i < NoOfBytes-1; i++)
    {
      WaitAndClearI2CIntrpt();
      Buffer[i] = I2C1_RdData;			// read the received data
    }
    
    I2C1_SetNackMode;			// send NO ACK for last byte
    WaitAndClearI2CIntrpt(); 
#else
    while(!DMACompletionFlag)
    {
      /* Wait for DMA transfer done */
    }
#endif
  }
  else
  {
    I2C1_SetRxMode; // setting in Rx mode
    i = I2C1_RdData; 		// dummy read to start
    I2C1_SetNackMode;         // NO ACK mode
    WaitAndClearI2CIntrpt();
  }
   
  Buffer[NoOfBytes-1] = I2C1_RdData;		// read the received data
  I2C1_ClrIsrFlag;			// clear the completion transfer flag
  I2C1_StopSignal;			// generate stop condition
  if (I2CStuck == TRUE)
  {
    I2CModuleInit();
  }
  return;
}

 /*!
 * @brief Implements write transaction to device.
 */
void IICWriteData(uint32 Address, uint8 *Buffer, uint16 NoOfBytes)
{
  uint16 i;
  uint8 j = 0;
  
  /* for Address 0 - 0x1FFFF */
  if ((Address>=0x10000) && (Address <= 0x1FFFF))
  {
    /* 0010b - Select 1st EEPROM, set A16 bit */
    j = 2;
  }
  /* for Address 0x20000 - 0x3FFFF */
  else if ((Address>=0x20000) && (Address <= 0x2FFFF))
  {
    /* 0100b - Select 2nd EEPROM */
    j = 4;
  }    
  else if ((Address>=0x30000) && (Address <= 0x3FFFF))
  {
      /* 0110b - and set A16 bit */
      j = 6; 
  }
  
  /****************************** WRITING SECTION *****************************/
  if (I2C1_GetIsrFlag == TRUE)
  {
    I2C1_ClrIsrFlag;                  /* clear IIC interrupt flag */
  }
  if (I2C1_GetBusyFlag)
  {
    I2C1->C1 = 0;
    I2C1->C1 = (I2C_C1_IICIE_MASK | I2C_C1_IICEN_MASK);
    i = I2C1_RdData; // Dummy read
    I2C1->S = 0;
    I2C1->C1 = 0;
    I2C1->C1 = (I2C_C1_IICIE_MASK | I2C_C1_IICEN_MASK);
  }
  
  I2C1_SetTxMode;     // setting in Tx mode
  I2C1_StartSignal; 	// send start condition
  
  I2C1_WrData(0xA0 | j);
  WaitAndClearI2CIntrpt();
  
  // if the slave device is already busy (due to some previous write operation), it won't ack
  while (I2C1->S & I2C_S_RXAK_MASK)
  {
    I2C1_RepeatStartSignal;
    SDK_DelayAtLeastUs(166, SYSTEM_CLOCK);
    I2C1_WrData(0xA0 | j);
    WaitAndClearI2CIntrpt();
  }
  
  i = 2; // Bytes in address
  while(i)
  {
    i--;
    I2C1_WrData(Address >> (i * 8)); // memory address, beginning with the MSB first
    WaitAndClearI2CIntrpt();
  }
  
#ifdef USE_I2C_CPU  
  for (i = 0; i < NoOfBytes; i++)
  {
    I2C1_WrData(Buffer[i]);			// Send the data
    WaitAndClearI2CIntrpt();
  }
#else
  if(NoOfBytes > 1)
  {
    DMACompletionFlag = 0;
    I2CDMADirection = I2C_WRITE;
    I2C1_EnableDMA(1);
    DMA_Init (DMA0,DMA_CH_HWTRG_M2P_CS_CONFIG(DMA_REQ_I2C1,(uint8 *)&Buffer[1],
                                              DMA_SIZE8BIT,&I2C1_D,(NoOfBytes-1)));
    
    /* Send the first data, this will HW trigger DMA for the leftover (NoOfBytes-1) bytes */
    I2C1_WrData(Buffer[0]);
    WaitAndClearI2CIntrpt();
    
    while(!DMACompletionFlag)
    {
      /* Wait for DMA transfer done */
    }
  }
  else
  {
    I2C1_WrData(Buffer[0]);			// Send the data
    WaitAndClearI2CIntrpt();
  }
#endif
  
  I2C1_StopSignal;			// generate stop condition
  
  if (I2CStuck == TRUE)
  {
    I2CModuleInit();
  }
  return;
}

/*!
 * @brief Intializes I2C-device function driver.
 */
void I2CModuleInit(void)
{
  /*
  Baud Rate of I2C should be 100 kHz
  For that the minimum value of SCR fiels is BusFreqMin/100Khz= 1Mhz/100Khz=10
  Maximum value                             BusFreqMax/100Khz=24Mhz/100Khz=240
  
  Enable the I2C Clock
  I2C Module (PTF5,PTF6)
  Configuring the Pins as I2C
  */
  CLOCK_EnableClock(kCLOCK_I2c1);
  I2CStop( );
  const port_pin_config_t i2c_sda_port_config = {/* Internal pull-up resistor is enabled */
    kPORT_PullDisable,
    /* Fast slew rate is configured */
    kPORT_SlowSlewRate,
    /* Open drain is disabled */
    kPORT_OpenDrainEnable,
    /* Pin is configured as PTA4 */
    kPORT_MuxAlt3,
    /* Pin Control Register fields [15:0] are not locked */
    kPORT_UnlockRegister};
  
  const port_pin_config_t i2c_scl_port_config = {/* Internal pull-up resistor is enabled */
    kPORT_PullDisable,
    /* Fast slew rate is configured */
    kPORT_SlowSlewRate,
    /* Open drain is disabled */
    kPORT_OpenDrainEnable,
    /* Pin is configured as PTA4 */
    kPORT_MuxAlt3,
    /* Pin Control Register fields [15:0] are not locked */
    kPORT_UnlockRegister};
  
  PORT_SetPinConfig(SCL_PORT, SCL_PIN, &i2c_sda_port_config);
  PORT_SetPinConfig(SDA_PORT, SDA_PIN, &i2c_scl_port_config);
  I2C1->C1 = 0;
  SDK_DelayAtLeastUs(166, SYSTEM_CLOCK);
  I2C1_Reset;
  IIC_Init();
}

 /*!
 * @brief Implements a bit-banging method of sending a stop signal to I2C device.
 */
void  I2CStop(void)
{
  uint8 i;
  const port_pin_config_t i2c_sda_port_config = {/* Internal pull-up resistor is enabled */
    kPORT_PullDisable,
    /* Fast slew rate is configured */
    kPORT_SlowSlewRate,
    /* Open drain is disabled */
    kPORT_OpenDrainDisable,
    /* Pin is configured as PTA4 */
    kPORT_MuxAsGpio,
    /* Pin Control Register fields [15:0] are not locked */
    kPORT_UnlockRegister};
  
  /* Define the init structure for the output LED pin */
  gpio_pin_config_t i2c_sda_gpio_config = {
    kGPIO_DigitalOutput,
    1U,
  };
  
  const port_pin_config_t i2c_scl_port_config = {/* Internal pull-up resistor is enabled */
    kPORT_PullDisable,
    /* Fast slew rate is configured */
    kPORT_SlowSlewRate,
    /* Open drain is disabled */
    kPORT_OpenDrainDisable,
    /* Pin is configured as PTA4 */
    kPORT_MuxAsGpio,
    /* Pin Control Register fields [15:0] are not locked */
    kPORT_UnlockRegister};
  
  /* Define the init structure for the output LED pin */
  gpio_pin_config_t i2c_scl_gpio_config = {
    kGPIO_DigitalOutput,
    1U,
  };
  
  PORT_SetPinConfig(PORTF, 5U, &i2c_sda_port_config);
  GPIO_PinInit(GPIOF, 5U, &i2c_sda_gpio_config);
  PORT_SetPinConfig(PORTF, 6U, &i2c_scl_port_config);
  GPIO_PinInit(GPIOF, 6U, &i2c_scl_gpio_config);
  
  for (i = 0; i < 9; i++)
  {
    CLR_SCL;
    SDK_DelayAtLeastUs(166, SYSTEM_CLOCK);
    CLR_SDA;
    SDK_DelayAtLeastUs(166, SYSTEM_CLOCK);
    SET_SCL;
    SDK_DelayAtLeastUs(166, SYSTEM_CLOCK);
    SET_SDA;
    SDK_DelayAtLeastUs(166, SYSTEM_CLOCK);
  }
}
