#include "main.h"

/**
 * @brief Check I2C for errors.
 * @retval I2C return code
 */
int8_t i2c_check_err(void) {
  int8_t r = I2C_RET_OK;

  if ((I2C1->ISR & I2C_ISR_NACKF) != 0) {
  /* device not present */
    r = I2C_RET_NACK;
  } else if ((I2C1->ISR & (I2C_ISR_ARLO | I2C_ISR_BERR)) != 0) {
  /* other error */
    r = I2C_RET_ERR;
  }

  if (r != I2C_RET_OK) {
  /* restart I2C and clear flags */
    I2C1->CR1 &= ~I2C_CR1_PE;
    while ((I2C1->CR1 & I2C_CR1_PE) != 0) {};
    I2C1->CR1 |= I2C_CR1_PE;
  }

  return r;
}

/**
 * @brief Read len bytes from I2C bus to data by reg_addr.
 * @retval I2C return code
 */
int8_t user_i2c_read(const uint8_t id, const uint8_t reg_addr, uint8_t *data, const uint16_t len) {
  int8_t r = I2C_RET_OK;

  Flag.I2C_RX_End = 0;
  Flag.I2C_RX_Err = 0;
  Flag.I2C_TX_Err = 0;

  /* wait for i2c */
  while ( I2C1->ISR & I2C_ISR_BUSY ) { __NOP(); };

  /* prepare i2c for sending reg addr */
  I2C1->CR2 &= ~( I2C_CR2_SADD | I2C_CR2_NBYTES | I2C_CR2_RD_WRN);
  I2C1->CR2 |= ( id | 1 << I2C_CR2_NBYTES_Pos );
  /* gen START */
  I2C1->CR2 |= ( I2C_CR2_START );

  /* wait for byte request or any error */
  while ((I2C1->ISR & (I2C_ISR_ARLO | I2C_ISR_BERR | I2C_ISR_NACKF | I2C_ISR_TXE)) == 0) { __NOP(); };

  if ((I2C2->ISR & I2C_ISR_TXE) != 0) {
  /* device ok, send reg addr */
    I2C1->TXDR = reg_addr;
  } else {
    r = i2c_check_err();
    if (r != I2C_RET_OK) {
      Flag.I2C_TX_Err = 1;
      return r;
    }
  }

  /* wait for i2c or any error */
  while (((I2C1->ISR & I2C_ISR_BUSY) != 0) && ((I2C1->ISR & (I2C_ISR_ARLO | I2C_ISR_BERR | I2C_ISR_NACKF)) == 0)) { __NOP(); };
  r = i2c_check_err();
  if (r != I2C_RET_OK) {
    Flag.I2C_TX_Err = 1;
    return r;
  }

  /* prepare dma channel for receiving data */
  DMA1_Channel2->CMAR = (uint32_t)data;
  DMA1_Channel2->CPAR = (uint32_t)&(I2C1->RXDR);
  DMA1_Channel2->CNDTR = len;
  DMA1_Channel2->CCR |= DMA_CCR_EN;

  /* prepare i2c for receiving data */
  I2C1->CR2 &= ~( I2C_CR2_SADD | I2C_CR2_NBYTES | I2C_CR2_RD_WRN);
  I2C1->CR2 |= ( id | len << I2C_CR2_NBYTES_Pos | I2C_CR2_RD_WRN);
  /* launch receiving */
  I2C1->CR1 |= ( I2C_CR1_RXDMAEN );
  I2C1->CR2 |= ( I2C_CR2_START );

  /* wait for receiving data */
  while ((Flag.I2C_RX_End == 0) && (Flag.I2C_RX_Err == 0)) { __NOP(); };

  return r;
}

/**
 * @brief Write len bytes to I2C bus from data by reg_addr.
 * @retval I2C return code
 */
int8_t user_i2c_write(const uint8_t id, const uint8_t reg_addr, uint8_t *data, const uint16_t len) {
  int8_t r = I2C_RET_OK;

  Flag.I2C_TX_End = 0;
  Flag.I2C_TX_Err = 0;

  DMA1_Channel3->CMAR = (uint32_t)data;
  DMA1_Channel3->CPAR = (uint32_t)&(I2C1->TXDR);
  DMA1_Channel3->CNDTR = len;

  while ( I2C1->ISR & I2C_ISR_BUSY ) {};

  I2C1->CR2 &= ~( I2C_CR2_SADD | I2C_CR2_NBYTES | I2C_CR2_RD_WRN);
  I2C1->CR2 |= ( id | (len + 1) << I2C_CR2_NBYTES_Pos );
  I2C1->CR2 |= ( I2C_CR2_START );

  while ((I2C1->ISR & (I2C_ISR_ARLO | I2C_ISR_BERR | I2C_ISR_NACKF | I2C_ISR_TXE)) == 0) { __NOP(); };
  if ((I2C2->ISR & I2C_ISR_TXE) != 0) {
    I2C1->TXDR = reg_addr;
  } else {
    r = i2c_check_err();
    if (r != I2C_RET_OK) {
      Flag.I2C_TX_Err = 1;
      return r;
    }
  }

  DMA1_Channel3->CCR |= DMA_CCR_EN;
  I2C1->CR1 |= ( I2C_CR1_TXDMAEN );

  return r;
}