#include "drivers/i2c/i2c.h"
#include "stm32l1xx_hal.h"
#include "stm32l1xx_hal_i2c.h"
#include "core/gpio.h"
#include "core/main.h"
#include "core/csect.h"
#include "core/config_pins.h"

I2C_HandleTypeDef i2c_handle = {
   #if CONFIG_PERIF_TSENSI2C == CONFIG_PERIF_I2C1
   .Instance = I2C1
   #elif CONFIG_PERIF_TSENSI2C == CONFIG_PERIF_I2C2
   .Instance = I2C2
   #else
   #error Invalid I2C specified in 'config_pins.h': see 'CONFIG_PERIF_TSENSI2C' 
   #endif
};

static bool I2C_Init( eI2CPinMode_t );
static HAL_StatusTypeDef I2C_Master_Transmit(  uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
static HAL_StatusTypeDef I2C_Master_Receive( uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
static void I2C_DeInit( eI2CPinMode_t );

const I2C_Handle_t I2C_Handle = {
   .Init = I2C_Init,
   .Transmit = I2C_Master_Transmit,
   .Receive = I2C_Master_Receive,
   .DeInit = I2C_DeInit,
};

// HAL Callback: initialize I2C GPIO, CLOCK, NVIC...etc

void HAL_I2C_MspInit( I2C_HandleTypeDef * hi2c )
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    if( hi2c->Instance == I2C1 )
    {
       __HAL_RCC_I2C1_CLK_ENABLE();
    }

    if( hi2c->Instance == I2C2 )
    {
       __HAL_RCC_I2C2_CLK_ENABLE();
    }
    
    // Perform periferal reset
    MODIFY_REG(hi2c->Instance->CR1, (0), (I2C_CR1_SWRST));
    MODIFY_REG(hi2c->Instance->CR1, (I2C_CR1_SWRST), (0));

    if( hi2c->Instance == I2C1 )
    {
      __HAL_RCC_I2C1_FORCE_RESET();
      __HAL_RCC_I2C1_RELEASE_RESET();
      HAL_NVIC_SetPriority( I2C1_EV_IRQn, I2C_INT_PRIORITY, 0);
      HAL_NVIC_EnableIRQ( I2C1_EV_IRQn );
    }

    if( hi2c->Instance == I2C2 )
    {
      __HAL_RCC_I2C2_FORCE_RESET();
      __HAL_RCC_I2C2_RELEASE_RESET();
      HAL_NVIC_SetPriority( I2C2_EV_IRQn, I2C_INT_PRIORITY, 0);
      HAL_NVIC_EnableIRQ( I2C2_EV_IRQn );
    }

    // Configure the pin muxing - setup I2C pins
    GPIO_InitStruct.Pin = CONFIG_PIN__TSENSI2C__DA | CONFIG_PIN__TSENSI2C__CL;  
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; 

    if( hi2c->Instance == I2C1 )
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;

    if( hi2c->Instance == I2C2 )
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C2;

    HAL_GPIO_Init(CONFIG_PORT__TSENSI2C__DA_CL, &GPIO_InitStruct);    
}

// HAL Callback: deinitialize I2C GPIO, CLOCK, NVIC...etc
void HAL_I2C_MspDeInit( I2C_HandleTypeDef * hi2c )
{
    if( hi2c->Instance == I2C1 ) 
    {
       HAL_NVIC_DisableIRQ( I2C1_EV_IRQn );
       __HAL_RCC_I2C1_CLK_DISABLE();
    }

    if( hi2c->Instance == I2C2 ) 
    {
       HAL_NVIC_DisableIRQ( I2C2_EV_IRQn );
       __HAL_RCC_I2C2_CLK_DISABLE();
    }
}

static bool I2C_Init( eI2CPinMode_t pinmode )
{
    bool ret = false;

    i2c_handle.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    i2c_handle.Init.ClockSpeed = 100000;
    i2c_handle.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    i2c_handle.Init.DutyCycle = I2C_DUTYCYCLE_2;
    i2c_handle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    i2c_handle.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
    i2c_handle.Init.OwnAddress1 = 0;
    i2c_handle.Init.OwnAddress2 = 0;

    DI();

    // Initialize I2C-bus
    if( HAL_OK == HAL_I2C_Init( &i2c_handle ) )
    {
        ret = true;
    }

    EI();

    if( !ret )
    {
        // ERROR, deinitialize
        I2C_DeInit( pinmode );              
    }

    return ret;
}

static void I2C_DeInit( eI2CPinMode_t pinmode )
{    
    {   // I2C Pin Muxing
        GPIO_InitTypeDef GPIO_InitStruct = {0};

        // Deinitialize I2C pins
        GPIO_InitStruct.Pin = CONFIG_PIN__TSENSI2C__DA | CONFIG_PIN__TSENSI2C__CL;  
        
        switch( pinmode )
        {
            case eI2CPinMode_KeepPullUp:
            GPIO_InitStruct.Mode = GPIO_MODE_INPUT; //
            GPIO_InitStruct.Pull = GPIO_PULLUP;     // required to guaranee the correct level
            break;

            case eI2CPinMode_KeepPullDown:
            GPIO_InitStruct.Mode = GPIO_MODE_INPUT; //
            GPIO_InitStruct.Pull = GPIO_PULLDOWN;     // required to guaranee the correct level
            break;

            default:
            GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
            GPIO_InitStruct.Pull = GPIO_NOPULL;          
        }
        
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; 
        HAL_GPIO_Init(CONFIG_PORT__TSENSI2C__DA_CL, &GPIO_InitStruct);
    }

    DI();
    // Deinitialize I2C-bus
    HAL_I2C_DeInit( &i2c_handle );    
    EI();    
}


static HAL_StatusTypeDef I2C_Master_Transmit(  uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
     return HAL_I2C_Master_Transmit( &i2c_handle, DevAddress, pData, Size, Timeout );
}

static HAL_StatusTypeDef I2C_Master_Receive( uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
     return HAL_I2C_Master_Receive( &i2c_handle, DevAddress, pData, Size, Timeout );
}
