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

#include "lmcal_cfg.h"
#include "lmcal_cpu.h"
#include "lmcal_flash.h"
#include "lmcal_flash_port.h"

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

#define FLASH_CH_CONFIG_FILL(nChNum)              \
{                                                 \
    .enable   = LMCAL_FLASH_CH##nChNum##_ENABLE,  \
    .addr     = LMCAL_FLASH_CH##nChNum##_ADDR,    \
    .size     = LMCAL_FLASH_CH##nChNum##_SIZE     \
}

//******************************************* Проверка параметров *******************************************


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

// Конфигурация канала
typedef struct
{
    bool enable;
    u32  addr;
    u32  size;
} flash_ch_config_t;


typedef enum
{
    FLASH_STATE_IDLE = 0,
    FLASH_STATE_WRITE,
    FLASH_STATE_ERASE
    // Операция чтения может быть "прервана" записью или стиранием 
} flash_ch_state_t;

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


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

#if (LMCAL_DEBUG == 1)
static bool b_init_done = false;
#endif

static const flash_ch_config_t flash_st_ch_config[LMCAL_FLASH_CHANNEL_QTY] =
{ 
    FLASH_CH_CONFIG_FILL(0),
    FLASH_CH_CONFIG_FILL(1),
    FLASH_CH_CONFIG_FILL(2),
    FLASH_CH_CONFIG_FILL(3),
    FLASH_CH_CONFIG_FILL(4),
};

static flash_ch_state_t flash_current_state = FLASH_STATE_IDLE;


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

#if (LMCAL_DEBUG == 1)
static fun_res_t f_check_channel(lmcal_flash_channel_t channel)
{
    fun_res_t err = ERR_OK;

    if (channel > LMCAL_FLASH_CHANNEL_QTY)
    {
        err = ERR_INVALID_ARGUMENT;
    }

    return err;
}
#endif


static fun_res_t f_is_range_correct(lmcal_flash_channel_t channel, u32 offset, u32 size)
{
    fun_res_t err = ERR_OK;
    
    // Проверка выхода за диапазон канала
    if ((size == 0) || 
        (((offset + size) > (flash_st_ch_config[channel].size))))
    {
        err = ERR_INVALID_ARGUMENT;
    }

    return err;
}



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



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

fun_res_t lmcal_flash_init(void)
{
    fun_res_t err = ERR_OK;

    #if (LMCAL_DEBUG == 1)
        err = L_ASSERT(false == b_init_done);
        L_CHECK_ERR(err);
    #endif
    
    err = lmcal_flash_port_init();
    L_CHECK_ERR(err);

    #if (LMCAL_DEBUG == 1)
    b_init_done = true;
    #endif

    L_EXIT;
    return err;
}


fun_res_t lmcal_flash_deinit(void)
{
    fun_res_t err = ERR_OK;

    #if (LMCAL_DEBUG == 1)
        err = L_ASSERT(true == b_init_done);
        L_CHECK_ERR(err);
    #endif
    
    err = lmcal_flash_port_deinit();
    L_CHECK_ERR(err);
    
    #if (LMCAL_DEBUG == 1)
    b_init_done = false;
    #endif

    L_EXIT;
    return err;
}


err_or_res_t lmcal_flash_get_status(void)
{
    err_or_res_t res = ERR_OK;
    fun_res_t err = ERR_OK;
    lmcal_ch_sts_t sts = CH_STS_IDLE;
    
    #if (LMCAL_DEBUG == 1)
        err = L_ASSERT(true == b_init_done);
        L_CHECK_ERR(err);
    #endif
    
    if (flash_current_state != FLASH_STATE_IDLE)
    {
        sts = CH_STS_BUSY;
    }

    #if (LMCAL_DEBUG == 1)
    L_EXIT;
    #endif

    res = (err_or_res_t)((err == ERR_OK) ? (sts) : (err));
    return res;
}



fun_res_t lmcal_flash_set_callback(lmcal_flash_channel_t channel, 
                                   lmcal_mem_callback_type_t type,
                                   void_fun_t function)
{
    fun_res_t err = ERR_OK;

    #if (LMCAL_DEBUG == 1)
        err = L_ASSERT(true == b_init_done);
        L_CHECK_ERR(err);
        err = f_check_channel(channel);
        L_CHECK_ERR(err);
        err = L_ASSERT(true == flash_st_ch_config[channel].enable);
        L_CHECK_ERR(err);
    #endif
    
    // Может быть полезно при асинхронной работе с Flash
    #warning "TODO lmcal_flash_set_callback()"
    L_UNUSED(type);
    L_UNUSED(function);
    L_UNUSED(channel);

    #if (LMCAL_DEBUG == 1)
    L_EXIT;
    #endif
    return err;
}


fun_res_t lmcal_flash_write(lmcal_flash_channel_t channel, 
                            u32 offset, 
                            u8* src, 
                            u32 size, 
                            void* param)
{
    fun_res_t err = ERR_OK;

    #if (LMCAL_DEBUG == 1)
        err = L_ASSERT(true == b_init_done);
        L_CHECK_ERR(err);
        err = f_check_channel(channel);
        L_CHECK_ERR(err);
        err = L_ASSERT(true == flash_st_ch_config[channel].enable);
        L_CHECK_ERR(err);
        err = L_ASSERT(NULL != src);
        L_CHECK_ERR(err);
    #endif

    err = f_is_range_correct(channel, offset, size);
    L_CHECK_ERR(err);
    
    
    lmcal_cpu_interrupt_enable(false);
    
    if (FLASH_STATE_IDLE == flash_current_state)
    {
        flash_current_state = FLASH_STATE_WRITE;
        
        err = lmcal_flash_port_write((flash_st_ch_config[channel].addr + offset),
                                 src, size, param);
        
        flash_current_state = FLASH_STATE_IDLE;
        
        L_CHECK_RT_ERR(err);
    }
    else
    {
        // Драйвер занят
        err = ERR_RUNTIME_ERROR;
    }

    L_RT_EXIT;
    lmcal_cpu_interrupt_enable(true);
    
    L_EXIT;
    return err;
}


fun_res_t lmcal_flash_read(lmcal_flash_channel_t channel, 
                          u32 offset, 
                          u8* dst, 
                          u32 size, 
                          void* param)
{
    fun_res_t err = ERR_OK;

    #if (LMCAL_DEBUG == 1)
        err = L_ASSERT(true == b_init_done);
        L_CHECK_ERR(err);
        err = f_check_channel(channel);
        L_CHECK_ERR(err);
        err = L_ASSERT(true == flash_st_ch_config[channel].enable);
        L_CHECK_ERR(err);        
        err = L_ASSERT(NULL != dst);
        L_CHECK_ERR(err);
    #endif

    err = f_is_range_correct(channel, offset, size);
    L_CHECK_ERR(err);
    

    err = lmcal_flash_port_read((flash_st_ch_config[channel].addr + offset),
                                 dst, size, param);
    
    #if (LMCAL_DEBUG == 1)
    L_EXIT;
    #endif
    return err;
}


fun_res_t lmcal_flash_erase(lmcal_flash_channel_t channel, 
                            u32 offset, 
                            u32 size, 
                            void* param)
{
    fun_res_t err = ERR_OK;

    #if (LMCAL_DEBUG == 1)
        err = L_ASSERT(true == b_init_done);
        L_CHECK_ERR(err);
        err = f_check_channel(channel);
        L_CHECK_ERR(err);
        err = L_ASSERT(true == flash_st_ch_config[channel].enable);
        L_CHECK_ERR(err);        
    #endif

    err = f_is_range_correct(channel, offset, size);    
    L_CHECK_ERR(err);
    
    lmcal_cpu_interrupt_enable(false);

    if (FLASH_STATE_IDLE == flash_current_state)
    {
        flash_current_state = FLASH_STATE_ERASE;

        err = lmcal_flash_port_erase((flash_st_ch_config[channel].addr + offset),
                                     size, param);
        
        flash_current_state = FLASH_STATE_IDLE;

        L_CHECK_RT_ERR(err);
    }
    else
    {
        // Драйвер занят
        err = ERR_RUNTIME_ERROR;
    }

    L_RT_EXIT;
    lmcal_cpu_interrupt_enable(true);
    
    L_EXIT;
    return err;
}

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