/*
    (c) 2017 night_ghost@ykoctpa.ru
 
based on: datasheet

*/

#pragma GCC optimize ("O2")

#include "i2c.h"
#include "dma.h"
#include "systick.h"
#include "stm32f4xx_i2c.h"
#include "stm32f4xx_dma.h"



static i2c_state i2c1_state IN_CCM;
static i2c_state i2c2_state IN_CCM;

static const i2c_dev i2c_dev1 = {
    .I2Cx         = I2C1,
    .gpio_port    = &gpiob,
    .sda_pin      = 9,
    .scl_pin      = 8,
    .clk       	  = RCC_APB1Periph_I2C1,
    .gpio_af	  = GPIO_AF_I2C1,
    .ev_nvic_line = I2C1_EV_IRQn,
    .er_nvic_line = I2C1_ER_IRQn,
//    .dma          = { DMA_CR_CH1, DMA1_STREAM0, DMA1_STREAM6 }, // I2C1
    .state        = &i2c1_state,
};
/** I2C1 device */
const i2c_dev* const _I2C1 = &i2c_dev1;


static const i2c_dev i2c_dev2 = {
    .I2Cx         = I2C2,
    .gpio_port    = &gpiob,
    .sda_pin      = 11,
    .scl_pin      = 10,
    .clk       	  = RCC_APB1Periph_I2C2,
    .gpio_af	  = GPIO_AF_I2C2,
    .ev_nvic_line = I2C2_EV_IRQn,
    .er_nvic_line = I2C2_ER_IRQn,
//    .dma          = { DMA_CR_CH7, DMA1_STREAM3 /* intersects with spi2_tx */ , DMA1_STREAM7 }, // I2C2
    .state        = &i2c2_state,
};

/** I2C2 device */
const i2c_dev* const _I2C2 = &i2c_dev2;


typedef enum {TX = 0, RX = 1, TXREG = 2} I2C_Dir;

static void delay_10us(){
    hal_delay_microseconds(10);
}

uint32_t i2c_bit_time=4;

/**
 * @brief  DeInitializes peripherals used by the I2C driver.
 * @param  None
 * @retval None
 */
void i2c_lowLevel_deinit(const i2c_dev *dev){
    GPIO_InitTypeDef GPIO_InitStructure;

    /* I2C Peripheral Disable */
    I2C_Cmd(dev->I2Cx, DISABLE);

    /* I2C DeInit */
    I2C_DeInit(dev->I2Cx);

    /*!< GPIO configuration */
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; // low speed to prevent glitches

    /*!< Configure I2C pins: SCL */
    GPIO_InitStructure.GPIO_Pin = BIT(dev->scl_pin);
    GPIO_Init(dev->gpio_port->GPIOx, &GPIO_InitStructure);

    /*!< Configure I2C pins: SDA */
    GPIO_InitStructure.GPIO_Pin = BIT(dev->sda_pin);
    GPIO_Init(dev->gpio_port->GPIOx, &GPIO_InitStructure);
}

/**
 * @brief  Initializes peripherals used by the I2C driver.
 */
static inline void i2c_lowLevel_init(const i2c_dev *dev)  {
    memset(dev->state,0,sizeof(i2c_state));

    GPIO_InitTypeDef GPIO_InitStructure;

    /* Enable the i2c */
    RCC_APB1PeriphClockCmd(dev->clk, ENABLE);

    /* Reset the Peripheral */
    RCC_APB1PeriphResetCmd(dev->clk, ENABLE);
    RCC_APB1PeriphResetCmd(dev->clk, DISABLE);

    /* Enable the GPIOs for the SCL/SDA Pins */
    RCC_AHB1PeriphClockCmd(dev->gpio_port->clk, ENABLE);

    memset(dev->state,0,sizeof(i2c_state));

// common configuration
    /* common GPIO configuration */
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz; // GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;

    /* Configure SCL */
    GPIO_InitStructure.GPIO_Pin = BIT(dev->scl_pin);
    GPIO_Init(dev->gpio_port->GPIOx, &GPIO_InitStructure);

    /* Configure SDA */
    GPIO_InitStructure.GPIO_Pin = BIT(dev->sda_pin);
    GPIO_Init(dev->gpio_port->GPIOx, &GPIO_InitStructure);

    /* Connect GPIO pins to peripheral, SCL must be first! */
    gpio_set_af_mode(dev->gpio_port, dev->scl_pin, dev->gpio_af);
    gpio_set_af_mode(dev->gpio_port, dev->sda_pin, dev->gpio_af);
}

void i2c_init(const i2c_dev *dev, uint16_t address, uint32_t speed)
{

    i2c_lowLevel_init(dev); // init GPIO hardware

    i2c_bit_time = 1000000l / speed;

    I2C_InitTypeDef I2C_InitStructure;     /* I2C configuration */
    I2C_StructInit(&I2C_InitStructure);

    I2C_InitStructure.I2C_Mode                = I2C_Mode_I2C;
    I2C_InitStructure.I2C_DutyCycle           = I2C_DutyCycle_2;
    I2C_InitStructure.I2C_OwnAddress1         = address;
    I2C_InitStructure.I2C_Ack                 = I2C_Ack_Enable;
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_InitStructure.I2C_ClockSpeed          = speed;


    I2C_ITConfig(dev->I2Cx, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR, DISABLE);

    (void)dev->I2Cx->DR; 

    /* I2C Peripheral Enable */
    I2C_Cmd(dev->I2Cx, ENABLE);
    /* Apply I2C configuration after enabling it */
    I2C_Init(dev->I2Cx, &I2C_InitStructure);
}

/**
 * @brief  DeInitializes peripherals used by the I2C driver.
 * @param  None
 * @retval None
 */
void i2c_deinit(const i2c_dev *dev)
{
    i2c_lowLevel_deinit(dev);
}

static void ev_handler(const i2c_dev *dev, bool err){
    if(dev->state->handler) revo_call_handler(dev->state->handler, err);
    else { // disable interrupts
        dev->I2Cx->CR2 &= ~(I2C_CR2_ITBUFEN|I2C_CR2_ITEVTEN|I2C_CR2_ITERREN);    // Disable interrupts
    }
}

void I2C1_EV_IRQHandler(); // to avoid warnings
void I2C1_ER_IRQHandler();
void I2C2_EV_IRQHandler();
void I2C2_ER_IRQHandler();

void I2C1_EV_IRQHandler(){                // I2C1 Event                   
    ev_handler(_I2C1, false);
}
  
void I2C1_ER_IRQHandler(){                // I2C1 Error                   
    ev_handler(_I2C1, true);
}

void I2C2_EV_IRQHandler(){                // I2C2 Event                 
    ev_handler(_I2C2, false);
}

void I2C2_ER_IRQHandler(){                // I2C2 Error
    ev_handler(_I2C2, true);
}



void i2c_master_release_bus(const i2c_dev *dev) {
    gpio_write_bit(dev->gpio_port, dev->scl_pin, 1);
    gpio_write_bit(dev->gpio_port, dev->sda_pin, 1);
    gpio_set_mode(dev->gpio_port, dev->scl_pin, GPIO_OUTPUT_OD_PU);
    gpio_set_mode(dev->gpio_port, dev->sda_pin, GPIO_OUTPUT_OD_PU);
}


/**
 * @brief Reset an I2C bus.
 *
 * Reset is accomplished by clocking out pulses until any hung slaves
 * release SDA and SCL, then generating a START condition, then a STOP
 * condition.
 *
 * @param dev I2C device
 */
 
#define MAX_I2C_TIME 300 // 300ms before device turn off

bool i2c_bus_reset(const i2c_dev *dev) {

    /* Release both lines */
    i2c_master_release_bus(dev);

    uint32_t t=systick_uptime();

    /*
     * Make sure the bus is free by clocking it until any slaves release the
     * bus.
     */

again:
    /* Wait for any clock stretching to finish */
    while (!gpio_read_bit(dev->gpio_port, dev->scl_pin)) {// device can output 1 so check clock first
        if(systick_uptime()-t > MAX_I2C_TIME) return false;
        hal_yield(10);
    }
    delay_10us();	// 50kHz

    while (!gpio_read_bit(dev->gpio_port, dev->sda_pin)) {
        /* Wait for any clock stretching to finish */
        while (!gpio_read_bit(dev->gpio_port, dev->scl_pin)){
            if(systick_uptime()-t > MAX_I2C_TIME) return false;
            hal_yield(10);
        }
        delay_10us();	// 50kHz

        /* Pull low */
        gpio_write_bit(dev->gpio_port, dev->scl_pin, 0);
        delay_10us();

        /* Release high again */
        gpio_write_bit(dev->gpio_port, dev->scl_pin, 1);
        delay_10us();
    }

    /* Generate start then stop condition */
    gpio_write_bit(dev->gpio_port, dev->sda_pin, 0);
    delay_10us();
    gpio_write_bit(dev->gpio_port, dev->scl_pin, 0);
    delay_10us();
    gpio_write_bit(dev->gpio_port, dev->scl_pin, 1);
    delay_10us();
    gpio_write_bit(dev->gpio_port, dev->sda_pin, 1);
    
    uint32_t rtime = stopwatch_getticks();
    uint32_t dt    = us_ticks * 50; // 50uS

    while ((stopwatch_getticks() - rtime) < dt) {
        if (!gpio_read_bit(dev->gpio_port, dev->scl_pin))  goto again; // any SCL activity after STOP
    }

// we was generating signals on I2C bus, but BUSY flag senses it even when hardware is off
// datasheet: It indicates a communication in progress on the bus. This information is still updated when the interface is disabled (PE=0).

    dev->I2Cx->CR1 |= I2C_CR1_SWRST;           // set SoftReset for some time 
    hal_yield(0);
    dev->I2Cx->CR1 &= (uint16_t)(~I2C_CR1_SWRST); // clear SoftReset flag 
    return true;
}