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

#include "lmcal_cfg.h"
#include "lmcal_sdio.h"
#include "lmcal_gpio.h"
#include "lmcal_sdio_port.h"

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

// Макрос, заполняющий структуру конфиграции
#define SDIO_CH_CONFIG_FILL(nChNum)                         \
{                                                           \
    .enable      = (bool)LMCAL_SDIO_CH##nChNum##_ENABLE,    \
    .blk_size    = (u32) LMCAL_SDIO_CH##nChNum##_SIZE,      \
    .async_mode  = (u32) LMCAL_SDIO_CH##nChNum##_ASYNC_MODE \
}


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

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

// Конфигурация канала
typedef struct
{
    bool enable;
    u32  blk_size;
    u32  async_mode;
} sdio_ch_config_t;


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

extern sdio_ch_service_t lmcal_sdio_port_service_var[LMCAL_SDIO_CHANNEL_QTY];

extern volatile bool lmcal_sdio_port_service_busy;

extern u32 lmcal_sdio_port_service_ch;


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

// Массив конфигурации каналов
static const sdio_ch_config_t sdio_st_ch_config[LMCAL_SDIO_CHANNEL_QTY] =
{
    SDIO_CH_CONFIG_FILL(0),
    SDIO_CH_CONFIG_FILL(1),
};

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

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

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

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

    return err;
}
#endif



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

fun_res_t lmcal_sdio_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_sdio_port_init();
    L_CHECK_ERR(err);

    // Инициализация всех включенных каналов
    for (u8 cnt = 0U; cnt < LMCAL_SDIO_CHANNEL_QTY; cnt++)
    {
        if (sdio_st_ch_config[cnt].enable == true)
        {
            // Сброс сервисного массива
            lmcal_sdio_port_service_var[cnt].init = true;
            lmcal_sdio_port_service_var[cnt].error = false;
            lmcal_sdio_port_service_var[cnt].wr_callback = NULL;
            lmcal_sdio_port_service_var[cnt].rd_callback = NULL;
            lmcal_sdio_port_service_var[cnt].err_callback = NULL;
        }
    }
    lmcal_sdio_port_service_busy = false;

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

    L_EXIT;
    return err;
}



fun_res_t lmcal_sdio_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_sdio_port_deinit();
    L_CHECK_ERR(err);

    // Выполняем деинициализацию только включенных каналов
    for (u8 cnt = 0U; cnt < LMCAL_SDIO_CHANNEL_QTY; cnt++)
    {
        if (sdio_st_ch_config[cnt].enable == true)
        {
            // Сброс сервисного массива
            lmcal_sdio_port_service_var[cnt].init = false;
        }
    }

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

    L_EXIT;
    return err;
}



err_or_res_t lmcal_sdio_get_status(lmcal_sdio_channel_t channel)
{
    err_or_res_t res = ERR_OK;
    fun_res_t err = ERR_OK;
    lmcal_ch_sts_t sts = 0;

    #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 == sdio_st_ch_config[channel].enable);
        L_CHECK_ERR(err);
    #endif

    // Проверка инициализации канала
    sts = (lmcal_sdio_port_service_var[channel].init == false) ? (CH_STS_NOT_INIT) : (0);
    L_CHECK_VAL(sts);

    sts = (lmcal_sdio_port_service_var[channel].error == true) ? (CH_STS_ERROR) : (0);
    L_CHECK_VAL(sts);

    sts = (lmcal_sdio_port_service_busy == true) ? (CH_STS_BUSY) : (0);
    L_CHECK_VAL(sts);
    
    #if (LMCAL_DEBUG == 1)
    L_EXIT;
    #endif
    res = (err_or_res_t)((err == ERR_OK) ? (sts) : (err));
    return res;
}



fun_res_t lmcal_sdio_set_callback(lmcal_sdio_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 == sdio_st_ch_config[channel].enable);
        L_CHECK_ERR(err);
    #endif

    switch(type)
    {
        case(CB_MEM_WR_DONE_CALLBACK):
            lmcal_sdio_port_service_var[channel].wr_callback = function;
        break;
        case(CB_MEM_RD_DONE_CALLBACK):
            lmcal_sdio_port_service_var[channel].rd_callback = function;
        break;
        case(CB_MEM_ERROR_CALLBACK):
            lmcal_sdio_port_service_var[channel].err_callback = function;
        break;

        default:
            err = ERR_INVALID_ARGUMENT;
        break;
    }
    
    #if (LMCAL_DEBUG == 1)
    L_EXIT;
    #endif

    return err;
}



err_or_res_t lmcal_send_cmd(lmcal_sdio_channel_t channel, u32 cmd, u32 arg, void* param)
{
    err_or_res_t res = ERR_OK;
    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 == sdio_st_ch_config[channel].enable);
        L_CHECK_ERR(err);
    #endif

    if (lmcal_sdio_port_service_busy == false)
    {
        lmcal_sdio_port_service_busy = true;

        err = lmcal_sdio_port_send_cmd(cmd, arg, param);

        lmcal_sdio_port_service_busy = false;
    }
    else
    {
        err = ERR_RUNTIME_ERROR;
    }
    
    #if (LMCAL_DEBUG == 1)
    L_EXIT;
    #endif
    return res;
}



fun_res_t lmcal_sdio_write_buf(lmcal_sdio_channel_t channel, u32 addr, u32* data, u16 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 == sdio_st_ch_config[channel].enable);
        L_CHECK_ERR(err);
        err = L_ASSERT(data != NULL);
        L_CHECK_ERR(err);
        err = L_ASSERT(size > 0);
        L_CHECK_ERR(err);
    #endif

    if (lmcal_sdio_port_service_busy == false)
    {
        lmcal_sdio_port_service_busy = true;
        lmcal_sdio_port_service_ch = channel;

        err = lmcal_sdio_port_write_buf(addr,
                                        data, 
                                        size,
                                        sdio_st_ch_config[channel].blk_size, 
                                        sdio_st_ch_config[channel].async_mode,
                                        param);

        if ((sdio_st_ch_config[channel].async_mode == 0) || (err != ERR_OK))
        {
            // Сброс флага при синхронном режиме
            lmcal_sdio_port_service_busy = false;
        }
        else 
        {
            // сброс флага должен быть выполнен в прерывании
        }
    }
    else
    {
        err = ERR_RUNTIME_ERROR;
    }
    
    #if (LMCAL_DEBUG == 1)
    L_EXIT;
    #endif
    return err;
}



err_or_res_t lmcal_sdio_read_buf(lmcal_sdio_channel_t channel, u32 addr, u32* data, u16 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 == sdio_st_ch_config[channel].enable);
        L_CHECK_ERR(err);
        err = L_ASSERT(data != NULL);
        L_CHECK_ERR(err);
        err = L_ASSERT(size > 0);
        L_CHECK_ERR(err);
    #endif

    if (lmcal_sdio_port_service_busy == false)
    {
        lmcal_sdio_port_service_busy = true;
        lmcal_sdio_port_service_ch = channel;

        err = lmcal_sdio_port_read_buf(addr,
                                       data, 
                                       size,
                                       sdio_st_ch_config[channel].blk_size,
                                       sdio_st_ch_config[channel].async_mode,
                                       param);

        if ((sdio_st_ch_config[channel].async_mode == 0) || (err != ERR_OK))
        {
            // Сброс флага при синхронном режиме
            lmcal_sdio_port_service_busy = false;
        }
        else 
        {
            // сброс флага должен быть выполнен в прерывании
        }
    }
    else
    {
        err = ERR_RUNTIME_ERROR;
    }
    
    #if (LMCAL_DEBUG == 1)
    L_EXIT;
    #endif
    return err;
}



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