//******************************************** Включаемые файлы *********************************************

#include "lmcal_cfg.h"
#include "lmcal_cpu.h"
#include "lmcal_spi_port.h"

#include "at32f403a_407_conf.h"

//******************************************* Локальные константы *******************************************

// Активное состояние CS
#define SPI_CS_ACTIVE       (0)
// Пассивное состояние CS
#define SPI_CS_PASSIVE      (1)

#define LMCAL_SPI_TX_DONE_IRQ       (1)


//******************************************** Определения типов ********************************************



//************************************ Глобальные (публичные) переменные ************************************

spi_ch_service_t lmcal_spi_port_service_var[LMCAL_SPI_CHANNEL_QTY];


//************************************* Локальные (приватные) переменные ************************************


//***********************************************************************************************************
//******************************** Определения локальных (приватных) функций ********************************
//***********************************************************************************************************


static fun_res_t f_enable_port_clk(u32 port_num, bool state)
{
    fun_res_t err = ERR_OK;

    if ((gpio_type*)port_num == GPIOA) 
    {
        crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, state);
    } 
    else if((gpio_type*)port_num == GPIOB) 
    {
        crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, state);
    }
    else if((gpio_type*)port_num == GPIOC) 
    {
        crm_periph_clock_enable(CRM_GPIOC_PERIPH_CLOCK, state);
    }
    else if((gpio_type*)port_num == GPIOD) 
    {
        crm_periph_clock_enable(CRM_GPIOD_PERIPH_CLOCK, state);
    }
    else if((gpio_type*)port_num == GPIOE) 
    {
        crm_periph_clock_enable(CRM_GPIOE_PERIPH_CLOCK, state);
    }
    else if (port_num == LMCAL_SPI_PORT_NONE)
    {
        
    }
    else 
    {
        err = ERR_INVALID_ARGUMENT;
    }

    return err;
}


static fun_res_t f_enable_spi_clk(u32 spi_num, bool state)
{
    fun_res_t err = ERR_OK;

    if((spi_type*)spi_num == LMCAL_SPI0) 
    {
        crm_periph_clock_enable(CRM_SPI1_PERIPH_CLOCK, state);
    }
    else if((spi_type*)spi_num == LMCAL_SPI1) 
    {
        crm_periph_clock_enable(CRM_SPI2_PERIPH_CLOCK, state);
    }
    else if((spi_type*)spi_num == LMCAL_SPI2) 
    {
        crm_periph_clock_enable(CRM_SPI3_PERIPH_CLOCK, state);
    }
    else if((spi_type*)spi_num == LMCAL_SPI3) 
    {
        crm_periph_clock_enable(CRM_SPI4_PERIPH_CLOCK, state);
    }
    else 
    {
        err = ERR_INVALID_ARGUMENT;
    }

    return err;
}


// Включить\отключить тактирование всей необходимой периферии
static fun_res_t f_enable_clk(u32 mosi_port, 
                              u32 miso_port,
                              u32 clk_port,
                              u32 cs_port,
                              u32 spi_num,
                              bool state)
{
    fun_res_t err = ERR_OK;
        
    err = f_enable_port_clk(mosi_port, state);
    L_CHECK_ERR(err);
    err = f_enable_port_clk(miso_port, state);
    L_CHECK_ERR(err);
    err = f_enable_port_clk(clk_port, state);
    L_CHECK_ERR(err);
    err = f_enable_port_clk(cs_port, state);
    L_CHECK_ERR(err);

    err = f_enable_spi_clk(spi_num, state);
    L_CHECK_ERR(err);

    L_EXIT;
    return err;
}


// Установить состояние CS
static void f_set_cs(u32 cs_port, u32 cs_pin, bool state)
{
    gpio_bits_write((gpio_type*)cs_port, cs_pin, state);
}



static fun_res_t f_gpio_init(u32 spi_num, u32 mode,
                             u32 mosi_port, u32 mosi_pin,
                             u32 miso_port, u32 miso_pin,
                             u32 clk_port,  u32 clk_pin,
                             u32 cs_port,   u32 cs_pin)
{
    fun_res_t err = ERR_OK;
    L_UNUSED(spi_num);

    gpio_init_type stGPIO_InitStruct;
    
    // Предварительная инициализация структуры настроек порта
    gpio_default_para_init(&stGPIO_InitStruct);
    
    // Настройка MOSI
    stGPIO_InitStruct.gpio_out_type       = GPIO_OUTPUT_PUSH_PULL;
    stGPIO_InitStruct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
    stGPIO_InitStruct.gpio_pull           = GPIO_PULL_NONE;
    if(LMCAL_SPI_MODE_MASTER == mode)
    {
        stGPIO_InitStruct.gpio_mode = GPIO_MODE_MUX;
    }
    else
    {
        stGPIO_InitStruct.gpio_mode = GPIO_MODE_INPUT;
    }
    stGPIO_InitStruct.gpio_pins = mosi_pin;
    gpio_init((gpio_type*)mosi_port, &stGPIO_InitStruct);

    // Настройка MISO
    stGPIO_InitStruct.gpio_pull = GPIO_PULL_NONE;
    if(LMCAL_SPI_MODE_MASTER == mode)
    {
        stGPIO_InitStruct.gpio_mode = GPIO_MODE_INPUT;
    }
    else
    {
        stGPIO_InitStruct.gpio_mode = GPIO_MODE_MUX;
    }
    stGPIO_InitStruct.gpio_pins = miso_pin;
    gpio_init((gpio_type*)miso_port, &stGPIO_InitStruct);
    

    // Настройка CLK
    stGPIO_InitStruct.gpio_pull = GPIO_PULL_NONE;
    if(LMCAL_SPI_MODE_MASTER == mode)
    {
        stGPIO_InitStruct.gpio_mode = GPIO_MODE_MUX;
    }
    else
    {
        stGPIO_InitStruct.gpio_mode = GPIO_MODE_INPUT;
    }
    stGPIO_InitStruct.gpio_pins = clk_pin;
    gpio_init((gpio_type*)clk_port, &stGPIO_InitStruct);

    // Настройка CS
    if ((cs_port != LMCAL_SPI_PIN_NONE) && (cs_pin != LMCAL_SPI_PIN_NONE))
    {  
        stGPIO_InitStruct.gpio_pull = GPIO_PULL_NONE;
        if(LMCAL_SPI_MODE_MASTER == mode)
        {
            stGPIO_InitStruct.gpio_mode = GPIO_MODE_OUTPUT;
        }
        else
        {
            stGPIO_InitStruct.gpio_mode = GPIO_MODE_INPUT;
        }
        stGPIO_InitStruct.gpio_pins = cs_pin;
        gpio_init((gpio_type*)cs_port, &stGPIO_InitStruct);

        // Установка значения CS по умолчанию
        f_set_cs(cs_port, cs_pin, SPI_CS_PASSIVE);
    }

    return err;
}



static void f_nvic_irq_enable(u32 spi_num, bool state)
{
    if (state == true)
    {
        if (SPI1 == (spi_type*)spi_num)
        {
            NVIC_EnableIRQ(SPI1_IRQn);
        }
        else if (SPI2 == (spi_type*)spi_num)
        {
            NVIC_EnableIRQ(SPI2_I2S2EXT_IRQn);
        }
        else if (SPI3 == (spi_type*)spi_num)
        {
            NVIC_EnableIRQ(SPI3_I2S3EXT_IRQn);
        }
        else if (SPI4 == (spi_type*)spi_num)
        {
            NVIC_EnableIRQ(SPI4_IRQn);
        }
        else
        {
            // Ничего не делаем
        }
    }
    else
    {
        if (SPI1 == (spi_type*)spi_num)
        {
            NVIC_DisableIRQ(SPI1_IRQn);
        }
        else if (SPI2 == (spi_type*)spi_num)
        {
            NVIC_DisableIRQ(SPI2_I2S2EXT_IRQn);
        }
        else if (SPI3 == (spi_type*)spi_num)
        {
            NVIC_DisableIRQ(SPI3_I2S3EXT_IRQn);
        }
        else if (SPI4 == (spi_type*)spi_num)
        {
            NVIC_DisableIRQ(SPI4_IRQn);
        }
        else
        {
            // Ничего не делаем
        }
    }
}



static spi_mclk_freq_div_type f_get_freq_div(u32 spi_num, u32 baudrate)
{
    spi_mclk_freq_div_type res = SPI_MCLK_DIV_1024;
    u32 cur_freq = 120000000UL;

    // SPI1 - APB2
    // SPI2,3,4 - APB1
    if (spi_num == (u32)LMCAL_SPI0)
    {
        lmcal_cpu_get_bus_frequency(LMCAL_CPU_BUS2, &cur_freq);
    }
    else
    {
        lmcal_cpu_get_bus_frequency(LMCAL_CPU_BUS1, &cur_freq);
    }

    // Округленеие до большего делителя (меньшей частоты)
    u32 cur_div = cur_freq / baudrate;
    
    if ((cur_div >= 1024) || (cur_div > 512))
    {
        res = SPI_MCLK_DIV_1024;
    } 
    else if ((cur_div == 512) || (cur_div > 256))
    {
        res = SPI_MCLK_DIV_512;
    }
    else if ((cur_div == 256) || (cur_div > 128))
    {
        res = SPI_MCLK_DIV_256;
    }
    else if ((cur_div == 128) || (cur_div > 64))
    {
        res = SPI_MCLK_DIV_128;
    }
    else if ((cur_div == 64) || (cur_div > 32))
    {
        res = SPI_MCLK_DIV_64;
    }
    else if ((cur_div == 32) || (cur_div > 16))
    {
        res = SPI_MCLK_DIV_32;
    }
    else if ((cur_div == 16) || (cur_div > 8))
    {
        res = SPI_MCLK_DIV_16;
    }
    else if ((cur_div == 8) || (cur_div > 4))
    {
        res = SPI_MCLK_DIV_8;
    }
    else if ((cur_div == 4) || (cur_div > 2))
    {
        res = SPI_MCLK_DIV_4;
    }
    else
    {
        res = SPI_MCLK_DIV_2;
    }

    return res;
}



static fun_res_t f_spi_init(u32 spi_num, u32 async_mode, u32 mode, u32 baudrate, u32 frame_size, u32 bit_order, u32 clk_pol, u32 clk_pha)
{
    fun_res_t err = ERR_OK;
    spi_init_type stSPI_InitStruct;

    spi_default_para_init(&stSPI_InitStruct);

    // Тип обмена - дуплекс
    stSPI_InitStruct.transmission_mode = SPI_TRANSMIT_FULL_DUPLEX;
    // Режим
    stSPI_InitStruct.master_slave_mode = mode;
    // Порядок бит
    stSPI_InitStruct.first_bit_transmission = bit_order;
    // Размер сообщения
    stSPI_InitStruct.frame_bit_num = frame_size;
    // CLK полярность
    stSPI_InitStruct.clock_polarity = clk_pol;
    // CLK фаза
    stSPI_InitStruct.clock_phase = clk_pha;
    // Программный режим CS
    stSPI_InitStruct.cs_mode_selection = SPI_CS_SOFTWARE_MODE;
    
    // Делитель частоты SPI
    stSPI_InitStruct.mclk_freq_division = f_get_freq_div(spi_num, baudrate);
    
    spi_init((spi_type*)spi_num, &stSPI_InitStruct);

    if (async_mode == 1)
    {
        // Включение прерываний
        f_nvic_irq_enable(spi_num, true);
        //! поддерживается только режим мастера, прерывание только по приему
        spi_i2s_interrupt_enable((spi_type*)spi_num, SPI_I2S_RDBF_INT, TRUE);
    }

    return err;
}



static void f_irq_process(u32 spi_num, u8 irq_mode)
{
    u8 cur_channel = 0;

    // Поиск канала
    for (u8 i = LMCAL_SPI_CHANNEL0; i < LMCAL_SPI_CHANNEL_QTY; i++)
    {
        // Должен совпасть номер SPI и канал должен быть занят
        if ((spi_num == lmcal_spi_port_service_var[i].spi_num) && 
            (true == lmcal_spi_port_service_var[i].busy))
        {
            cur_channel = i;
            break;
        }
    }

    // Сброс флага и вызов колбека
    // todo - проверка аппаратной ошибки и выставление флага error
    if (irq_mode == LMCAL_SPI_TX_DONE_IRQ)
    {
        lmcal_spi_port_service_var[cur_channel].busy = false;

        // Сброс SC
        if ((lmcal_spi_port_service_var[cur_channel].cs_port != LMCAL_SPI_PIN_NONE) && 
            (lmcal_spi_port_service_var[cur_channel].cs_pin != LMCAL_SPI_PIN_NONE))
        {
            f_set_cs(lmcal_spi_port_service_var[cur_channel].cs_port, 
                     lmcal_spi_port_service_var[cur_channel].cs_pin, 
                     SPI_CS_PASSIVE);
        }

        if (lmcal_spi_port_service_var[cur_channel].tx_callback != NULL)
        {
            lmcal_spi_port_service_var[cur_channel].tx_callback();
        }
    }
}



//***********************************************************************************************************
//******************************** Определения процедур обработки прерываний ********************************
//***********************************************************************************************************

void SPI1_IRQHandler(void)
{
    // Буфер приемника полон (конец передачи)
    if(spi_i2s_flag_get(SPI1, SPI_I2S_RDBF_FLAG) != RESET)
    {
        f_irq_process((u32)SPI1, LMCAL_SPI_TX_DONE_IRQ);
    }
    else
    {
        // Неподдерживаемое прерывание
    }
}


void SPI2_I2S2EXT_IRQHandler(void)
{
    // Буфер приемника полон (конец передачи)
    if(spi_i2s_flag_get(SPI2, SPI_I2S_RDBF_FLAG) != RESET)
    {
        f_irq_process((u32)SPI2, LMCAL_SPI_TX_DONE_IRQ);
    }
    else
    {
        // Неподдерживаемое прерывание
    }
}


void SPI3_I2S3EXT_IRQHandler(void)
{
    // Буфер приемника полон (конец передачи)
    if(spi_i2s_flag_get(SPI3, SPI_I2S_RDBF_FLAG) != RESET)
    {
        f_irq_process((u32)SPI3, LMCAL_SPI_TX_DONE_IRQ);
    }
    else
    {
        // Неподдерживаемое прерывание
    }
}


void SPI4_IRQHandler(void)
{
    // Буфер приемника полон (конец передачи)
    if(spi_i2s_flag_get(SPI4, SPI_I2S_RDBF_FLAG) != RESET)
    {
        f_irq_process((u32)SPI4, LMCAL_SPI_TX_DONE_IRQ);
    }
    else
    {
        // Неподдерживаемое прерывание
    }
}



//***********************************************************************************************************
//******************************* Определения глобальных (публичных) функций ********************************
//***********************************************************************************************************

fun_res_t lmcal_spi_port_init(u8 param)
{
    fun_res_t err = ERR_OK;
    L_UNUSED(param);
    // Опциональная функция

    return err;
}



fun_res_t lmcal_spi_port_init_periph(u32 spi_num,
                                     u32 async_mode,
                                     u32 mode,
                                     u32 baudrate,
                                     u32 frame_size,
                                     u32 bit_order,
                                     u32 clk_pol,
                                     u32 clk_pha,
                                     u32 mosi_port,
                                     u32 mosi_pin,
                                     u32 miso_port,
                                     u32 miso_pin,
                                     u32 clk_port,
                                     u32 clk_pin,
                                     u32 cs_port,
                                     u32 cs_pin)
{
    fun_res_t err = ERR_OK;
    
    err = f_enable_clk(mosi_port, miso_port, clk_port, cs_port, spi_num, true);
    L_CHECK_ERR(err);

    err = f_gpio_init(spi_num,
                      mode,
                      mosi_port,  mosi_pin,
                      miso_port,  miso_pin,
                      clk_port,   clk_pin,
                      cs_port,    cs_pin);
    L_CHECK_ERR(err);

    err = f_spi_init(spi_num, async_mode, mode, baudrate, frame_size, bit_order, clk_pol, clk_pha);
    L_CHECK_ERR(err);

    L_EXIT;
    if (err != ERR_OK)
    {
        spi_i2s_reset((spi_type*)spi_num);
    }
    return err;
}



fun_res_t lmcal_spi_port_deinit(u8 param)
{
    fun_res_t err = ERR_OK;
    L_UNUSED(param);
    // Опциональная функция

    return err;
}



fun_res_t lmcal_spi_port_deinit_periph(u32 spi_num,
                                       u32 mosi_port,
                                       u32 mosi_pin,
                                       u32 miso_port,
                                       u32 miso_pin,
                                       u32 clk_port,
                                       u32 clk_pin,
                                       u32 cs_port,
                                       u32 cs_pin)
{
    fun_res_t err = ERR_OK;
    
    spi_i2s_reset((spi_type*)spi_num);

    // Добавить деинициализацию пинов
    #warning "TODO lmcal_spi_port_deinit_periph()"

    L_UNUSED(mosi_port);
    L_UNUSED(mosi_pin);
    L_UNUSED(miso_port);
    L_UNUSED(miso_pin);
    L_UNUSED(clk_port);
    L_UNUSED(clk_pin);
    L_UNUSED(cs_port);
    L_UNUSED(cs_pin);

    return err;
}



fun_res_t lmcal_spi_port_get_status(u32 spi_num, u32 mode, lmcal_ch_sts_t* sts)
{
    fun_res_t err = ERR_OK;
    L_UNUSED(mode);

    if (spi_i2s_flag_get((spi_type*)spi_num, SPI_I2S_BF_FLAG) != SET)
    {
        // Буфер для передачи пуст
        // Передачи нет или она происходит в данный момент
        *sts = CH_STS_IDLE;
    }
    else
    {
        *sts = CH_STS_BUSY;
    }

    return err;
}



fun_res_t lmcal_spi_port_enable(u32 spi_num, bool new_state)
{
    fun_res_t err = ERR_OK;
    
    spi_enable((spi_type*)spi_num, new_state);

    return err;
}



fun_res_t lmcal_spi_port_write(u32 spi_num, u32 async_mode, u32 mode, u32 cs_port, u32 cs_pin, u32 data)
{
    fun_res_t err = ERR_OK;

    err = (mode == LMCAL_SPI_MODE_MASTER) ? (ERR_OK) : (ERR_NOT_IMPLEMENTED);
    L_CHECK_ERR(err);

    // Поддержка только режима мастера

    // Установка CS
    if ((cs_port != LMCAL_SPI_PIN_NONE) && (cs_pin != LMCAL_SPI_PIN_NONE))
    {
        f_set_cs(cs_port, cs_pin, SPI_CS_ACTIVE);
    }

    if (async_mode == 0)
    {
        // Синхронный режим
        spi_i2s_data_transmit((spi_type*)spi_num, (u16)data);
        while(spi_i2s_flag_get((spi_type*)spi_num, SPI_I2S_RDBF_FLAG) == RESET); // SPI_I2S_TDBE_FLAG

        // Сброс SC
        if ((cs_port != LMCAL_SPI_PIN_NONE) && (cs_pin != LMCAL_SPI_PIN_NONE))
        {
            f_set_cs(cs_port, cs_pin, SPI_CS_PASSIVE);
        }
    }
    else
    {
        while(spi_i2s_flag_get((spi_type*)spi_num, SPI_I2S_TDBE_FLAG) == RESET);
        spi_i2s_data_transmit((spi_type*)spi_num, (u16)data);
    }

    L_EXIT;
    return err;
}


fun_res_t lmcal_spi_port_read(u32 spi_num, u32 async_mode, u32 mode, u32* data)
{
    fun_res_t err = ERR_OK;
    
    err = (mode == LMCAL_SPI_MODE_MASTER) ? (ERR_OK) : (ERR_NOT_IMPLEMENTED);
    L_CHECK_ERR(err);

    if (async_mode == 0)
    {
        // Синхронный режим
        while(spi_i2s_flag_get((spi_type*)spi_num, SPI_I2S_RDBF_FLAG) == RESET);
        *data = spi_i2s_data_receive((spi_type*)spi_num);
    }
    else
    {
        *data = spi_i2s_data_receive((spi_type*)spi_num);
    }

    L_EXIT;
    return err;
}


fun_res_t lmcal_spi_port_write_buf(u32 spi_num, u32 async_mode, u32 mode, u8* data, u16 size)
{
    fun_res_t err = ERR_NOT_IMPLEMENTED;

    #warning "TODO lmcal_spi_port_write_buf()"

    L_UNUSED(spi_num);
    L_UNUSED(async_mode);
    L_UNUSED(mode);
    L_UNUSED(data);
    L_UNUSED(size);

    return err;
}


fun_res_t lmcal_spi_port_read_buf(u32 spi_num, u32 async_mode, u32 mode, u8* data, u16 size)
{
    fun_res_t err = ERR_NOT_IMPLEMENTED;

    #warning "TODO lmcal_spi_port_read_buf()"

    L_UNUSED(spi_num);
    L_UNUSED(async_mode);
    L_UNUSED(mode);
    L_UNUSED(data);
    L_UNUSED(size);

    return err;
}


//*********************************************** КОНЕЦ ФАЙЛА ***********************************************
