/* *****************************************************************************
 * File:      i2c.c
 * Date:      6 Jan 2025
 * Author:    Andrew Levido
 *
 * Copyright 2025 Imbrius Pty Ltd - All Rights Reserved
 *
 ******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#include "i2c_driver.h"

/* Defines -------------------------------------------------------------------*/
#define I2C_BYTE_TIMEOUT_MS     1      // 400Khz x 9 bits per byte = 22.6us

/* Typedefs & Enumerations ---------------------------------------------------*/

/* Global Variables ----------------------------------------------------------*/

/* Private Functions ---------------------------------------------------------*/
i2c_result_t i2c_start_transaction(uint8_t device, uint32_t request, uint16_t size)
{
  uint32_t timeout = I2C_BYTE_TIMEOUT_MS;

  /* Initialise transaction */
  LL_I2C_HandleTransfer(I2C1, device, LL_I2C_ADDRSLAVE_7BIT, size, 
                      LL_I2C_MODE_SOFTEND, request);

 /* Wait for TXIS or RXNE - error on NACK or timeout */
  while (!LL_I2C_IsActiveFlag_TXIS(I2C1) && !LL_I2C_IsActiveFlag_RXNE(I2C1)) {
    if (LL_I2C_IsActiveFlag_NACK(I2C1)) {
      LL_I2C_ClearFlag_NACK(I2C1);
      return I2C_ERR_NACK;
    }
    if (LL_SYSTICK_IsActiveCounterFlag()) {
      if (timeout-- == 0) {
        return I2C_ERR_TIMEOUT;
      }
    }
  }
  return(I2C_OK);
}

i2c_result_t i2c_send_byte(uint8_t byte)
{
  uint32_t timeout = I2C_BYTE_TIMEOUT_MS;

  /* Send byte */
  LL_I2C_TransmitData8(I2C1, byte);

  /* Wait for TXIS or TC - error on NACK or timeout */
  while (!LL_I2C_IsActiveFlag_TXIS(I2C1) && !LL_I2C_IsActiveFlag_TC(I2C1)) {
    if (LL_I2C_IsActiveFlag_NACK(I2C1)) {
      LL_I2C_ClearFlag_NACK(I2C1);
      return I2C_ERR_NACK;
    }
    if (LL_SYSTICK_IsActiveCounterFlag()) {
      if (timeout-- == 0) {
        return I2C_ERR_TIMEOUT;
      }
    }
  }
  return(I2C_OK);
}

i2c_result_t i2c_read_byte(uint8_t * byte)
{
  uint32_t timeout = I2C_BYTE_TIMEOUT_MS;

  /* Wait for RXNE or TC - error on timeout */
  while (!LL_I2C_IsActiveFlag_RXNE(I2C1) && !LL_I2C_IsActiveFlag_TC(I2C1)) {
    if (LL_SYSTICK_IsActiveCounterFlag()) {
      if (timeout-- == 0) {
        return I2C_ERR_TIMEOUT;
      }
    }
  }
  /* Get byte */
  *byte = LL_I2C_ReceiveData8(I2C1);

  return(I2C_OK);
}

/* Public Functions ----------------------------------------------------------*/


i2c_result_t i2c_write(uint8_t device, uint8_t reg, uint8_t * data, 
                      uint8_t data_len)                 
{
  i2c_result_t result;

  if(device > 0x7f) { return(I2C_ERR_PARAM); }
  if(data_len == 255) { return(I2C_ERR_PARAM); }
  
  __disable_irq();
  /* Initialise write transaction (send start & device address) */
  LL_I2C_Enable(I2C1);
  result = i2c_start_transaction(device << 1, LL_I2C_GENERATE_START_WRITE, data_len + 1);
  if(result != I2C_OK) { 
    LL_I2C_GenerateStopCondition(I2C1);
    LL_I2C_Disable(I2C1);
    __enable_irq();
    return(result); 
  }
  
  /* Send reg address */
  result = i2c_send_byte(reg);
  if(result != I2C_OK) { 
    LL_I2C_GenerateStopCondition(I2C1);
    LL_I2C_Disable(I2C1);
    __enable_irq();
    return(result); 
  }

  /* send data bytes */
  if(data_len != 0) {
    while(data_len--) {
      result = i2c_send_byte(*data++);
      if(result != I2C_OK) { break; }
    }
  }
  /* send stop condition to end */
  LL_I2C_GenerateStopCondition(I2C1);
  LL_I2C_Disable(I2C1);
  __enable_irq();
  return(result);
}

i2c_result_t i2c_read(uint8_t device, uint8_t reg, uint8_t * data, 
                      uint8_t data_len, bool skip_reg)
{
  i2c_result_t result;

  if(device > 0x7f) { return(I2C_ERR_PARAM); }
  if(data_len == 255) { return(I2C_ERR_PARAM); }
  LL_I2C_Enable(I2C1);

  __disable_irq();
  /* If we are not skipping the register write */
  if(skip_reg == false) {
    /* Initialise write transaction (send start & device address) */
    result = i2c_start_transaction(device << 1, LL_I2C_GENERATE_START_WRITE, 1);
    if(result != I2C_OK) { 
      LL_I2C_GenerateStopCondition(I2C1);
      LL_I2C_Disable(I2C1);
      __enable_irq();
      return(result);
    }
    /* Send reg address */
    result = i2c_send_byte(reg);
    if(result != I2C_OK) { 
      LL_I2C_GenerateStopCondition(I2C1);
      LL_I2C_Disable(I2C1);
      __enable_irq();
      return(result); 
    }
  }

  /* Initialise read transaction (send start/restart & device address) */
  result = i2c_start_transaction(device << 1, LL_I2C_GENERATE_START_READ, data_len);
  if(result != I2C_OK) { 
    LL_I2C_GenerateStopCondition(I2C1);
    LL_I2C_Disable(I2C1);
    __enable_irq();
    return(result); 
  }
  
  /* recieve data bytes */
  if(data_len != 0) {
    while(data_len--) {
      result = i2c_read_byte(data++);
      if(result != I2C_OK) { break; }
    }
  }
  LL_I2C_GenerateStopCondition(I2C1);
  LL_I2C_Disable(I2C1);
  __enable_irq();
  return(I2C_OK);
}


/* Interrupt Service Routines ------------------------------------------------*/


/* End i2c */