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

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

#include "ltimer.h"
#include "dbg_printf.h"

#include "gd32f4xx.h"
#include "gd32f4xx_rcu.h"
#include "gd32f4xx_gpio.h"
#include "gd32f4xx_sdio.h"


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

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


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

// Выбор режима обмена
#define LMCAL_SDIO_MODE_POLLING     0
#define LMCAL_SDIO_MODE_DMA         1

// #define SDIO_DBG

// Настройка частоты
#if (LMCAL_SDIO_CLK == 12)
    #define LMCAL_SDIO_BYPASS   (SDIO_CLOCKBYPASS_DISABLE)
    #define LMCAL_CLK_DIV       (2) // 48 / 2 + (2)

#elif (LMCAL_SDIO_CLK == 24)
    #define LMCAL_SDIO_BYPASS   (SDIO_CLOCKBYPASS_DISABLE)
    #define LMCAL_CLK_DIV       (0) // 48 / 2 + (0)

#elif (LMCAL_SDIO_CLK == 48)
    #define LMCAL_SDIO_BYPASS   (SDIO_CLOCKBYPASS_ENABLE)
    #define LMCAL_CLK_DIV       (0)
#else
    #error "invalid SDIO clock config"
#endif

// Альтернативные функций GPIO
#define LMCAL_SPIO_AF_GPIO      (GPIO_AF_12)

// Коды команд
#define SDIO_CMD_READ_SINGLE_BLOCK    ((uint8_t)17)  /* CMD17, READ_SINGLE_BLOCK */
#define SDIO_CMD_WRITE_BLOCK          ((uint8_t)24)  /* CMD24, WRITE_BLOCK */

/* DSM data timeout */
#define SDIO_DATATIMEOUT              ((uint32_t)0xFFFFFFFF)    

#define SDIO_FIFO_ADDR                ((uint32_t)0x40012C80)    /* address of SDIO_FIFO */
#define SDIO_FIFOHALF_WORDS           ((uint32_t)0x00000008)    /* words of FIFO half full/empty */    
#define SDIO_FIFOHALF_BYTES           ((uint32_t)0x00000020)    /* bytes of FIFO half full/empty */
#define SDIO_MASK_INTC_FLAGS          ((uint32_t)0x00C007FF)    /* mask flags of SDIO_INTC */

#define SDIO_ERROR_MASK               (SDIO_FLAG_CCRCERR  | SDIO_FLAG_DTCRCERR | \
                                       SDIO_FLAG_CMDTMOUT | SDIO_FLAG_DTTMOUT | \
                                       SDIO_FLAG_TXURE    | SDIO_FLAG_RXORE | SDIO_FLAG_STBITE)

// Тип операции
#define SDIO_OP_WRITE                 (1)
#define SDIO_OP_READ                  (2)


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

// Сервисные переменные
sdio_ch_service_t lmcal_sdio_port_service_var[LMCAL_SDIO_CHANNEL_QTY];

// Глобальный флаг занятости SDIO
bool volatile lmcal_sdio_port_service_busy = false;

// Текущий лог.канал SDIO
extern u32 lmcal_sdio_port_service_ch = 0;



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

static u32 lmcal_sdio_port_service_op = 0;



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

static void f_config_gpio(u32 port_num,  u32 pin_num, u32 pull, u32 alt_fun)
{
    gpio_af_set(port_num, alt_fun, pin_num);
    gpio_mode_set(port_num, GPIO_MODE_AF, pull, pin_num);
    gpio_output_options_set(port_num, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, pin_num);
}


static void f_gpio_init(void)
{
    // CK - Pull none
    f_config_gpio(LMCAL_SDIO_CK_PORT, LMCAL_SDIO_CK_PIN, GPIO_PUPD_NONE, LMCAL_SPIO_AF_GPIO);

    // CMD, D0..7 - pull_up
    f_config_gpio(LMCAL_SDIO_CMD_PORT, LMCAL_SDIO_CMD_PIN, GPIO_PUPD_PULLUP, LMCAL_SPIO_AF_GPIO);
    f_config_gpio(LMCAL_SDIO_D0_PORT, LMCAL_SDIO_D0_PIN, GPIO_PUPD_PULLUP, LMCAL_SPIO_AF_GPIO);
    f_config_gpio(LMCAL_SDIO_D1_PORT, LMCAL_SDIO_D1_PIN, GPIO_PUPD_PULLUP, LMCAL_SPIO_AF_GPIO);
    f_config_gpio(LMCAL_SDIO_D2_PORT, LMCAL_SDIO_D2_PIN, GPIO_PUPD_PULLUP, LMCAL_SPIO_AF_GPIO);
    f_config_gpio(LMCAL_SDIO_D3_PORT, LMCAL_SDIO_D3_PIN, GPIO_PUPD_PULLUP, LMCAL_SPIO_AF_GPIO);
    f_config_gpio(LMCAL_SDIO_D4_PORT, LMCAL_SDIO_D4_PIN, GPIO_PUPD_PULLUP, LMCAL_SPIO_AF_GPIO);
    f_config_gpio(LMCAL_SDIO_D5_PORT, LMCAL_SDIO_D5_PIN, GPIO_PUPD_PULLUP, LMCAL_SPIO_AF_GPIO);
    f_config_gpio(LMCAL_SDIO_D6_PORT, LMCAL_SDIO_D6_PIN, GPIO_PUPD_PULLUP, LMCAL_SPIO_AF_GPIO);
    f_config_gpio(LMCAL_SDIO_D7_PORT, LMCAL_SDIO_D7_PIN, GPIO_PUPD_PULLUP, LMCAL_SPIO_AF_GPIO);
}



// Ожидание ответа на команду. Возвращает true, если ответ пришел
static fun_res_t f_wait_response(void)
{
    fun_res_t err = ERR_RUNTIME_ERROR;
    t_ltimer tmr;
    uint32_t reg_status = 0;

    ltimer_set_ms(&tmr, 100);

    while (ltimer_expired(&tmr) == false)
    {
        reg_status = SDIO_STAT;
        // Проверка флагов:
        // - пришел ответ (ошибка crc7)
        // - пришел ответ (ок)
        // - прошел таймаут ответа (прошло 64 SDIO_CLK clock periods)
        if (false != (reg_status & (SDIO_FLAG_CCRCERR | SDIO_FLAG_CMDTMOUT | SDIO_FLAG_CMDRECV)))
        {
            if (reg_status & SDIO_FLAG_CCRCERR)
            {
                // CRC error
                DBG_LOGE("CMD CRC error\n");
            }
            if (reg_status & SDIO_FLAG_CMDTMOUT)
            {
                // Таймаут
                DBG_LOGE("CMD Timeout error\n");
            }
            if (reg_status & SDIO_FLAG_CMDRECV)
            {
                err = ERR_OK;
            }

            break;
        }
    }

    return err;
}


#if (LMCAL_SDIO_MODE_POLLING == 1)

static fun_res_t f_write_data_polling(u32* data, u16 size)
{
    fun_res_t err = ERR_RUNTIME_ERROR;
    u32 status = 0;
    u16 buf_ptr = 0;
    u16 size_remain = size;

    while (size_remain)
    {
        // Выполняем если нет ошибок
        // while(!sdio_flag_get(SDIO_FLAG_DTCRCERR | SDIO_FLAG_DTTMOUT | SDIO_FLAG_TXURE | SDIO_FLAG_STBITE)) // | SDIO_FLAG_DTBLKEND
        
        // Выполняем в любом случае
        while(1)
        {
            // Если фифо не полон
            if(RESET == sdio_flag_get(SDIO_FLAG_TFF)) 
            {
                sdio_data_write(data[buf_ptr]);
                buf_ptr++;
                size_remain--;
                break;
            }
        }
  
        /* whether some error occurs and return it */
        if(RESET != sdio_flag_get(SDIO_FLAG_DTCRCERR)) 
        {
            DBG_LOGE("SD_DATA_CRC_ERROR\n");
            status = SDIO_FLAG_DTCRCERR;
            sdio_flag_clear(SDIO_FLAG_DTCRCERR);
            #ifndef SDIO_DBG
            break;
            #endif
        } 
        else if(RESET != sdio_flag_get(SDIO_FLAG_DTTMOUT)) 
        {
            DBG_LOGE("SD_DATA_TIMEOUT\n");
            status = SDIO_FLAG_DTTMOUT;
            sdio_flag_clear(SDIO_FLAG_DTTMOUT);
            #ifndef SDIO_DBG
            break;
            #endif
        } 
        else if(RESET != sdio_flag_get(SDIO_FLAG_TXURE)) 
        {
            DBG_LOGE("SD_TX_UNDERRUN_ERROR\n");
            status = SDIO_FLAG_TXURE;
            sdio_flag_clear(SDIO_FLAG_TXURE);
            #ifndef SDIO_DBG
            break;
            #endif
        } 
        else if(RESET != sdio_flag_get(SDIO_FLAG_STBITE)) 
        {
            DBG_LOGE("SD_START_BIT_ERROR\n");
            status = SDIO_FLAG_STBITE;
            sdio_flag_clear(SDIO_FLAG_STBITE);
            #ifndef SDIO_DBG
            break;
            #endif
        }
    } // while

    if ((size_remain == 0) && (status == 0))
    {
        err = ERR_OK;
    }

    return err;
}



// Аргументы - указатель на буфер приемника, количество слов (32 бита)
static fun_res_t f_read_data_polling(u32* data, u16 size)
{
    fun_res_t err = ERR_RUNTIME_ERROR;
    u32 status = 0;
    u16 buf_ptr = 0;
    u16 size_remain = size;

    while (size_remain)
    {
        // Выполняем если нет ошибок
        // while(!sdio_flag_get(SDIO_FLAG_DTCRCERR | SDIO_FLAG_DTTMOUT | SDIO_FLAG_RXORE | SDIO_FLAG_STBITE)) // | SDIO_FLAG_DTBLKEND
        
        // Выполняем в любом случае
        while(1)
        {
            // Если фифо не пусто - читаем
            if(RESET == sdio_flag_get(SDIO_FLAG_RFE)) 
            {
                data[buf_ptr] = sdio_data_read();
                buf_ptr++;
                size_remain--;
                break;
            }
        }

        /* whether some error occurs and return it */
        if(RESET != sdio_flag_get(SDIO_FLAG_DTCRCERR)) 
        {
            DBG_LOGE("SDIO_FLAG_DTCRCERR\n");
            status = SDIO_FLAG_DTCRCERR;
            sdio_flag_clear(SDIO_FLAG_DTCRCERR);
            #ifndef SDIO_DBG
            break;
            #endif
        } 
        else if(RESET != sdio_flag_get(SDIO_FLAG_DTTMOUT)) 
        {
            DBG_LOGE("SDIO_FLAG_DTTMOUT\n");
            status = SDIO_FLAG_DTTMOUT;
            sdio_flag_clear(SDIO_FLAG_DTTMOUT);
            #ifndef SDIO_DBG
            break;
            #endif
        } 
        else if(RESET != sdio_flag_get(SDIO_FLAG_RXORE)) 
        {
            DBG_LOGE("SDIO_FLAG_RXORE\n");
            status = SDIO_FLAG_RXORE;
            sdio_flag_clear(SDIO_FLAG_RXORE);
            #ifndef SDIO_DBG
            break;
            #endif
        } 
        else if(RESET != sdio_flag_get(SDIO_FLAG_STBITE)) 
        {
            DBG_LOGE("SDIO_FLAG_STBITE\n");
            status = SDIO_FLAG_STBITE;
            sdio_flag_clear(SDIO_FLAG_STBITE);
            #ifndef SDIO_DBG
            break;
            #endif
        }
    }
    
    if ((size_remain == 0) && (status == 0))
    {
        err = ERR_OK;
    }

    return err;
}

#endif



#if (LMCAL_SDIO_MODE_DMA == 1)
void f_dma_transfer_config(uint32_t *srcbuf, uint32_t direction)
{
    dma_multi_data_parameter_struct dma_struct;

    /* clear all the interrupt flags */
    dma_flag_clear(DMA1, DMA_CH3, DMA_FLAG_FEE);
    dma_flag_clear(DMA1, DMA_CH3, DMA_FLAG_SDE);
    dma_flag_clear(DMA1, DMA_CH3, DMA_FLAG_TAE);
    dma_flag_clear(DMA1, DMA_CH3, DMA_FLAG_HTF);
    dma_flag_clear(DMA1, DMA_CH3, DMA_FLAG_FTF);
    dma_channel_disable(DMA1, DMA_CH3);
    dma_deinit(DMA1, DMA_CH3);

    /* configure the DMA1 channel 3 */
    dma_struct.periph_addr = (uint32_t)SDIO_FIFO_ADDR;
    dma_struct.memory0_addr = (uint32_t)srcbuf;
    dma_struct.direction = direction;
    //! Когда включен режим контроля потока - не нужно указывать размер
    dma_struct.number = 0;
    dma_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
    dma_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
    dma_struct.periph_width = DMA_PERIPH_WIDTH_32BIT;
    dma_struct.memory_width = DMA_MEMORY_WIDTH_32BIT;
    dma_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
    
    //! наверное поэтому передает минимум - 4 слова по 4 байта (16 байт)
    dma_struct.periph_burst_width = DMA_PERIPH_BURST_SINGLE; // DMA_PERIPH_BURST_4_BEAT
    dma_struct.memory_burst_width = DMA_PERIPH_BURST_SINGLE;
    dma_struct.critical_value = DMA_FIFO_4_WORD;
    //!
    dma_struct.circular_mode = DMA_CIRCULAR_MODE_DISABLE;

    dma_multi_data_mode_init(DMA1, DMA_CH3, &dma_struct);

    dma_flow_controller_config(DMA1, DMA_CH3, DMA_FLOW_CONTROLLER_PERI);
    dma_channel_subperipheral_select(DMA1, DMA_CH3, DMA_SUBPERI4);
}
#endif



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

void SDIO_IRQHandler(void) 
{
    // Проверка ошибки
    uint32_t status_reg = SDIO_STAT;
    
    if (status_reg & SDIO_ERROR_MASK)
    {
        // Если установлен флаг, то статус драйвера = ошибка, 
        // Это может сломать логику и застопорить интерфейс
        // lmcal_sdio_port_service_var[channel].error = true;
        DBG_LOGE("SDIO_error= %x\n", status_reg);
    }

    /* disable all the interrupts */
    sdio_interrupt_disable(SDIO_INT_DTCRCERR | SDIO_INT_DTTMOUT | SDIO_INT_DTEND | SDIO_INT_STBITE |
                            SDIO_INT_TFH | SDIO_INT_RFH | SDIO_INT_TXURE | SDIO_INT_RXORE);

    // Сброс флага занятости
    lmcal_sdio_port_service_busy = false;

    // Вызов колбека
    switch(lmcal_sdio_port_service_op)
    {
        case(SDIO_OP_WRITE):
            if (NULL != lmcal_sdio_port_service_var[lmcal_sdio_port_service_ch].wr_callback)
            {
                lmcal_sdio_port_service_var[lmcal_sdio_port_service_ch].wr_callback();
            }
        break;

        case(SDIO_OP_READ):
            if (NULL != lmcal_sdio_port_service_var[lmcal_sdio_port_service_ch].rd_callback)
            {
                lmcal_sdio_port_service_var[lmcal_sdio_port_service_ch].rd_callback();
            }
        break;

        default:
        break;
    }

    //! test
    // lmcal_gpio_write_pin(LMCAL_GPIO_CHANNEL9, true);
}


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


fun_res_t lmcal_sdio_port_init(void)
{
    fun_res_t err = ERR_OK;
    
    // Включение тактирования
    rcu_periph_clock_enable(RCU_GPIOD);
    rcu_periph_clock_enable(RCU_GPIOC);
    rcu_periph_clock_enable(RCU_GPIOB);

    rcu_periph_clock_enable(RCU_SDIO);

    #if (LMCAL_SDIO_MODE_POLLING == 1)
    #elif (LMCAL_SDIO_MODE_DMA)
    rcu_periph_clock_enable(RCU_DMA1);
    rcu_periph_clock_enable(RCU_DMA0);
    #endif

    // Настройка GPIO
    f_gpio_init();
    
    // Sdio
    sdio_deinit();
    sdio_clock_config(SDIO_SDIOCLKEDGE_RISING, LMCAL_SDIO_BYPASS, SDIO_CLOCKPWRSAVE_DISABLE, LMCAL_CLK_DIV);
    sdio_bus_mode_set(SDIO_BUSMODE_8BIT);
    // предотвращает TX/RX underrun/overrun error
    sdio_hardware_clock_enable();

    sdio_power_state_set(SDIO_POWER_ON);
    sdio_clock_enable();

    #if (LMCAL_SDIO_MODE_POLLING == 1)
    #elif (LMCAL_SDIO_MODE_DMA)
        NVIC_EnableIRQ(SDIO_IRQn);
    #endif

    return err;
}



fun_res_t lmcal_sdio_port_deinit(void)
{
    fun_res_t err = ERR_OK;
    
    sdio_deinit();

    rcu_periph_clock_disable(RCU_SDIO);
    rcu_periph_clock_disable(RCU_DMA1);
    rcu_periph_clock_disable(RCU_DMA0);

    return err;
}



fun_res_t lmcal_sdio_port_send_cmd(u32 cmd, u32 arg, void* param)
{
    fun_res_t err = ERR_OK;
    L_UNUSED(param);

    // Отправка команды (передаем адрес)
    sdio_command_response_config(cmd, arg, SDIO_RESPONSETYPE_SHORT);
    sdio_wait_type_set(SDIO_WAITTYPE_NO);
    sdio_csm_enable();
    
    // Ждем ответа
    err = f_wait_response();
    L_CHECK_ERR(err);

    L_EXIT;
    return err;
}



fun_res_t lmcal_sdio_port_write_buf(u32 addr, u32* data, u16 size, u32 blk_size, u32 mode, void* param)
{
    fun_res_t err = ERR_OK;
    L_UNUSED(param);
    L_UNUSED(mode);

    lmcal_sdio_port_service_op = SDIO_OP_WRITE;

    sdio_csm_disable();
    sdio_dsm_disable();

    #if (LMCAL_SDIO_MODE_POLLING == 1)
    #elif (LMCAL_SDIO_MODE_DMA)
        sdio_dma_disable();
        DMA_INTC0(DMA1) = DMA_FLAG_ADD(DMA_INTF_FTFIF, DMA_CH3) | 
                          DMA_FLAG_ADD(DMA_INTF_HTFIF, DMA_CH3) |
                          DMA_FLAG_ADD(DMA_INTF_SDEIF, DMA_CH3) | 
                          DMA_FLAG_ADD(DMA_INTF_TAEIF, DMA_CH3);
    #endif
    
    // Сброс флагов
    SDIO_INTC = 0xFFFFFFFF;

    // Отправка команды (передаем адрес)
    sdio_command_response_config(SDIO_CMD_WRITE_BLOCK, addr, SDIO_RESPONSETYPE_SHORT);
    sdio_wait_type_set(SDIO_WAITTYPE_NO);
    sdio_csm_enable();
    
    // Настройка передачи
    sdio_data_transfer_config(SDIO_TRANSMODE_BLOCK, SDIO_TRANSDIRECTION_TOCARD);
    
    //! Исправлено из-за путаницы с кол-вом передаваемых байт
    sdio_data_config(SDIO_DATATIMEOUT, size * sizeof(uint32_t), blk_size);
    // sdio_data_config(SDIO_DATATIMEOUT, size, blk_size);

    #if (LMCAL_SDIO_MODE_POLLING == 1)
    #elif (LMCAL_SDIO_MODE_DMA)
        // Пока ответ не пришел - готовим буфер для передачи
        f_dma_transfer_config(data, DMA_MEMORY_TO_PERIPH);
        sdio_dma_enable();
        dma_channel_enable(DMA1, DMA_CH3);
    #endif

    // Ждем ответа
    err = f_wait_response();
    L_CHECK_ERR(err);

    // Запуск автомата передачи
    sdio_dsm_enable();

    #if (LMCAL_SDIO_MODE_POLLING == 1)
        err = f_write_data_polling(data, size);
        L_CHECK_ERR(err);
    #elif (LMCAL_SDIO_MODE_DMA == 1)
        sdio_interrupt_enable(SDIO_INT_DTCRCERR | SDIO_INT_DTTMOUT | SDIO_INT_TXURE | SDIO_INT_DTEND | SDIO_INT_STBITE);
    #endif

    L_EXIT;
    return err;
}



fun_res_t lmcal_sdio_port_read_buf(u32 addr, u32* data, u16 size, u32 blk_size, u32 mode, void* param)
{
    fun_res_t err = ERR_OK;
    L_UNUSED(param);
    L_UNUSED(mode);

    lmcal_sdio_port_service_op = SDIO_OP_READ;

    sdio_csm_disable();
    sdio_dsm_disable();

    // Сброс флагов
    SDIO_INTC = 0xFFFFFFFF;
    
    // Настройка приема
    sdio_data_transfer_config(SDIO_TRANSMODE_BLOCK, SDIO_TRANSDIRECTION_TOSDIO);
    
    //! Исправлено из-за путаницы с кол-вом передаваемых байт
    sdio_data_config(SDIO_DATATIMEOUT, size * sizeof(uint32_t), blk_size);
    // sdio_data_config(SDIO_DATATIMEOUT, size, blk_size);
    sdio_dsm_enable();

    #if (LMCAL_SDIO_MODE_POLLING == 1)

    #elif (LMCAL_SDIO_MODE_DMA)
        sdio_dma_disable();
        DMA_INTC0(DMA1) = DMA_FLAG_ADD(DMA_INTF_FTFIF, DMA_CH3) | 
                          DMA_FLAG_ADD(DMA_INTF_HTFIF, DMA_CH3) |
                          DMA_FLAG_ADD(DMA_INTF_SDEIF, DMA_CH3) | 
                          DMA_FLAG_ADD(DMA_INTF_TAEIF, DMA_CH3);
        
        sdio_dma_enable();
        f_dma_transfer_config(data, DMA_PERIPH_TO_MEMORY);
        dma_channel_enable(DMA1, DMA_CH3);
    #endif

    // Отправка команды (передаем адрес)
    sdio_command_response_config(SDIO_CMD_READ_SINGLE_BLOCK, addr, SDIO_RESPONSETYPE_SHORT);
    sdio_wait_type_set(SDIO_WAITTYPE_NO);
    sdio_csm_enable();
    
    // Ждем ответа
    err = f_wait_response();
    L_CHECK_ERR(err);

    #if (LMCAL_SDIO_MODE_POLLING == 1)
    
    err = f_read_data_polling(data, size);
    L_CHECK_ERR(err);
    
    #elif (LMCAL_SDIO_MODE_DMA == 1)
        sdio_interrupt_enable(SDIO_INT_CCRCERR | SDIO_INT_DTTMOUT | SDIO_INT_RXORE | SDIO_INT_DTEND | SDIO_INT_STBITE);
    #endif

    L_EXIT;
    return err;
}


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