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

#include "fpga_cmn.h"
#include "lmcal.h"
#include "dbg_printf.h"
#include "ltimer.h"
#include "lta_private.h"
#include "self_diag.h"


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

#define FPGA_REG_INVALID_VALUE      (0xFFFFFFFF)

// максимальное число циклов ожидания при чтении регистров ПЛИС
#define FPGA_CMD_REGS_MAX_WAIT_CYCLES    (5U)

// Канал USART для обмена с ПЛИС
#define FPGA_CMD_USART_CHANNEL      (LMCAL_UART_CHANNEL0)

#define FPGA_CMD_REGS_BIT_START     (1UL << 8)
#define FPGA_CMD_REGS_BIT_WR        (1UL << 6)

#define FPGA_CMD_ADDR_H_MASK        (0x003F) // 6 бит
#define FPGA_CMD_ADDR_L_MASK        (0x00FF) // 8 бит

#define FPGA_CMD_ACK_RCV_MASK       (0x01FC) // 8..2 = 1 или (1_1111_1100)
#define FPGA_CMD_RCV_MASK           (0x100) 

#define FPGA_CMD_ACK_RESP_MASK      (0x03)

#define FPGA_CMD_DUMMY_DATA         (0x00)


// Время для ожидания инициализации ПЛИС в мс
#define FPGA_INIT_DELAY             (500)

// Кол-во байт для чтения
#define FPGA_RX_BYTE_QTY            (4U)


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

// Типы АСК
typedef enum
{
    FPGA_CMD_RESP_NACK = 0x00,     // Отрицательный АСК
    FPGA_CMD_RESP_WAIT = 0x01,     // Ожидание операции
    FPGA_CMD_RESP_ACK  = 0x02,     // Положительный АСК
    FPGA_CMD_RESP_ERR  = 0x03,     // Ошибка
} fpga_cmd_resp_t;



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

// Версия проекта
u32 fpga_prj_ver = FPGA_REG_INVALID_VALUE;

// Название проекта (идентификатор)
u32 fpga_prj_name = FPGA_REG_INVALID_VALUE;

// Идентификатор установленной ПЛИС
u32 fpga_id = FPGA_REG_INVALID_VALUE;


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

static bool b_init_done = false;

#if (DBG_ON)
static const char s_fpga_cmd_err[FPGA_CMD_ERR_QTY][20] = 
{
    "OK\0",
    "SOFT ERR\0",
    "BAD WORD FORMAT\0",
    "GET NACK\0",
    "WAIT EXPIRED\0",
    "REG_ERR\0",
};
#endif

// Код последней ошибки
static fpga_cmd_err_t e_last_err = FPGA_CMD_ERR_OK;



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

#if (DBG_ON)
// Получить код ошибки модуля в текстовом варианте
static const char* f_get_cmd_err(fpga_cmd_err_t err)
{
    return s_fpga_cmd_err[err]; 
}
#endif

//-----------------------------------------------------------------------------------------------------------
// @brief      Выполнение записи и чтения одного слова по USART (в блокирующем режиме).
// @param[in]  tx_value - значение для отправки
// @retval     Прочитанное значения
//-----------------------------------------------------------------------------------------------------------
static u16 f_uart_send_recv(const u16 tx_value)
{
    u16 rx_value;

    lmcal_uart_write(FPGA_CMD_USART_CHANNEL, tx_value);
    rx_value = lmcal_uart_read(FPGA_CMD_USART_CHANNEL);
    
    return rx_value;
}



static fpga_cmd_err_t f_read_reg(const u16 addr, u32* const value)
{
    fpga_cmd_err_t res = FPGA_CMD_ERR_OK;
    u16 dummy = 0U;
    u32 rcv = 0U;
    fpga_cmd_resp_t ack = 0U;
    u8 retry_cnt = 0U;

    // Проверка вх. переменных
    if (NULL == value)
    {
        return FPGA_CMD_ERR_SOFT_ERR;
    }

    // слово 1. Маркер начала пакета, резерв, признак-чтение, адрес(13..8).
    f_uart_send_recv(FPGA_CMD_REGS_BIT_START | ((addr >> 8U) & FPGA_CMD_ADDR_H_MASK));

    // слово 2. 0, адрес (7..0)
    f_uart_send_recv(addr & FPGA_CMD_ADDR_L_MASK);

    // слово 3. Произвольные данные. Ожидание АСК
    do 
    {
        dummy = f_uart_send_recv(FPGA_CMD_DUMMY_DATA);

        if ((dummy & FPGA_CMD_ACK_RCV_MASK) != FPGA_CMD_ACK_RCV_MASK) 
        {
            // Принятый пакет должен выглядеть так = 1_1111_11хх
            return FPGA_CMD_ERR_WORD_ERR;
        }
        ack = dummy & FPGA_CMD_ACK_RESP_MASK;
    } 
    while(ack == FPGA_CMD_RESP_WAIT && (++retry_cnt != FPGA_CMD_REGS_MAX_WAIT_CYCLES));
    
    // слова 4-7. Произвольные данные. Чтение данных
    for(u8 nCnt = 0U; nCnt < FPGA_RX_BYTE_QTY; nCnt++) 
    {
        dummy = f_uart_send_recv(FPGA_CMD_DUMMY_DATA);

        if ((dummy & FPGA_CMD_RCV_MASK) != false)
        {
            // Если в ответе старший бит = 1, то это ошибка формата
            return FPGA_CMD_ERR_WORD_ERR;
        }

        rcv <<= 8;
        rcv |= dummy;
    }

    // Проверка ошибки
    switch(ack)
    {
        // Ошибка обмена
        case(FPGA_CMD_RESP_NACK):
            res = FPGA_CMD_ERR_NACK;
        break;

        case(FPGA_CMD_RESP_WAIT):
            res = FPGA_CMD_ERR_NWAIT;
        break;

        case(FPGA_CMD_RESP_ERR):
            res = FPGA_CMD_ERR_ERROR;
        break;

        // Получили корректный АСК
        case(FPGA_CMD_RESP_ACK):
            *value = rcv;
        break;

        default:
        break;
    }

    return res;
}



fpga_cmd_err_t f_write_reg(const u16 addr, u32 const value)
{
    fpga_cmd_err_t res = FPGA_CMD_ERR_OK;
    u16 dummy = 0U;
    fpga_cmd_resp_t ack = 0U;
    u8 retry_cnt = 0U;

    // слово 1. Маркер начала пакета, резерв, признак-запись, адрес(13..8).
    f_uart_send_recv(FPGA_CMD_REGS_BIT_START | FPGA_CMD_REGS_BIT_WR | ((addr >> 8U) & FPGA_CMD_ADDR_H_MASK));

    // слово 2. 0, адрес (7..0)
    f_uart_send_recv(addr & FPGA_CMD_ADDR_L_MASK);

    // слова 3-6. Данные.
    f_uart_send_recv(value >> 24U);
    f_uart_send_recv((value >> 16U) & 0xFF);
    f_uart_send_recv((value >> 8U) & 0xFF);
    f_uart_send_recv(value & 0xFF);

    // слово 7. Произвольные данные. Чтение АСК
    do {
        dummy = f_uart_send_recv(FPGA_CMD_DUMMY_DATA);

        if ((dummy & FPGA_CMD_ACK_RCV_MASK) != FPGA_CMD_ACK_RCV_MASK) 
        {
            return FPGA_CMD_ERR_WORD_ERR;
        }
        ack = dummy & FPGA_CMD_ACK_RESP_MASK;
    } 
    while(ack == FPGA_CMD_RESP_WAIT && (++retry_cnt != FPGA_CMD_REGS_MAX_WAIT_CYCLES));

    // Проверка ошибки
    switch(ack)
    {
        // Ошибка обмена
        case(FPGA_CMD_RESP_NACK):
            res = FPGA_CMD_ERR_NACK;
        break;

        case(FPGA_CMD_RESP_WAIT):
            res = FPGA_CMD_ERR_NWAIT;
        break;

        case(FPGA_CMD_RESP_ERR):
            res = FPGA_CMD_ERR_ERROR;
        break;

        // Получили корректный АСК
        case(FPGA_CMD_RESP_ACK):
        break;

        default:
        break;
    }

    return res;
}



//-----------------------------------------------------------------------------------------------------------
// @brief      Проверка инициализации ПЛИС (в течение таймаута)
// @param[in]  
// @retval     Результат операции
//-----------------------------------------------------------------------------------------------------------
static fun_res_t f_is_fpga_init(void)
{
    fun_res_t err = ERR_OK;
    t_ltimer tmr;
    fpga_cmd_err_t sts;

    ltimer_set_ms(&tmr, FPGA_INIT_DELAY); 
    
    do
    {
        sts = f_read_reg(FPGA_REG_COM_ADDR_PRJ_NAME, &fpga_prj_name);
    } 
    while((sts != FPGA_CMD_ERR_OK) && (!ltimer_expired(&tmr)));


    // Проверка результата
    if (sts == FPGA_CMD_ERR_OK)
    {
        if (fpga_prj_name != FPGA_CUR_PRJ_NAME)
        {
            err = ERR_NOT_IMPLEMENTED;
            sdiag_set_err(SD_ERR_FPGA_PRJ_NAME);
            DBG_LOGE("Invalid project name!\n");
            L_GOTO_EXIT;
        }
    }
    else
    {
        err = ERR_TIMEOUT_EXPIRED;
        sdiag_set_err(SD_ERR_FPGA_NOT_LOAD);
        sdiag_set_err(SD_ERR_FPGA_CMD_INT);
        DBG_LOGE("error: %s, cmd error: %s \n", get_err_name(err), f_get_cmd_err(sts));
        L_GOTO_EXIT;
    }

    // Читаем остальные регистры    
    // Константные регистры
    sts |= f_read_reg(FPGA_REG_COM_ADDR_PRJ_VER, &fpga_prj_ver);
    sts |= f_read_reg(FPGA_REG_COM_ADDR_FPGA_ID, &fpga_id);
    
    // Если во время обмена была ошибка, то не продолжаем работу!
    if (sts != FPGA_CMD_ERR_OK)
    {
        err = ERR_RUNTIME_ERROR;
        sdiag_set_err(SD_ERR_FPGA_CMD_INT);
        DBG_LOGE("error: %s, cmd error: %s \n", get_err_name(err), f_get_cmd_err(sts));
        L_GOTO_EXIT;
    }

    L_EXIT;
    return err;
}



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

//-----------------------------------------------------------------------------------------------------------
fun_res_t fpga_init(void)
{
    fun_res_t err = ERR_OK;
    err_or_res_t res;

    // Проверка инициализации модуля USART
    res = lmcal_uart_get_status(FPGA_CMD_USART_CHANNEL);
    if ((res < ERR_OK) || (res == CH_STS_NOT_INIT))
    {
        err = ERR_ASSERT_ERROR;
        L_GOTO_EXIT;
    }
    
    lmcal_uart_enable(FPGA_CMD_USART_CHANNEL, true);

    // Сброс ПЛИС
    fpga_reset();

    // Проверка инициализации ПЛИС (чтение константных регистров)
    err = f_is_fpga_init();
    L_CHECK_ERR(err);

    b_init_done = true;
    DBG_LOGI("FPGA has initialized! \n");
    
    // Инициализация команд TCP для текущего модуля
    extern void fpga_tcp_init(void);
    fpga_tcp_init();

    L_EXIT;
    return err;
}


//-----------------------------------------------------------------------------------------------------------
bool fpga_is_init(void)
{
    return b_init_done;
}



//-----------------------------------------------------------------------------------------------------------
void fpga_reset(void)
{
    lmcal_gpio_write_pin(FPGA_RESET_CH_GPIO, true);
    lmcal_gpio_write_pin(FPGA_RESET_CH_GPIO, false);
    lmcal_ptim_delay(1);
    lmcal_gpio_write_pin(FPGA_RESET_CH_GPIO, true);
    lmcal_ptim_delay(1);
}



//-----------------------------------------------------------------------------------------------------------
err_or_res_t fpga_get_slot_num(void)
{
    fun_res_t err = ERR_OK;
    err_or_res_t res;
    u8 addr = 0;
    fpga_cmd_err_t sts;

    #if (FPGA_DEBUG == 1)
    err = L_ASSERT(true == b_init_done);
    L_CHECK_ERR(err);
    #endif

    // addr = 0 - это крейт контроллер
    #if (FPGA_IS_BUS_CONTROLLER_MKIO == 0)

        bool b_slot_num_sts = false;
        bool b_slot_num_err = false;
        u8 n_unit_slot_num = 0U;
        u32 tmp;

        sts = f_read_reg(FPGA_REG_COM_ADDR_MKO_INFO, &tmp);
        b_slot_num_sts  = (bool)((tmp & FPGA_MKO_ADDR_STS));
        b_slot_num_err  = (bool)((tmp & FPGA_MKO_ADDR_ERR));
        n_unit_slot_num = (u8)  ((tmp & FPGA_MKO_ADDR_MASK));

        if (sts != FPGA_CMD_ERR_OK)
        {
            err = ERR_RUNTIME_ERROR;
            sdiag_set_err(SD_ERR_FPGA_CMD_INT);
            DBG_LOGE("error: %s, cmd error: %s \n", get_err_name(err), f_get_cmd_err(sts));
            L_GOTO_EXIT;
        }

        if (b_slot_num_sts == true)
        {
            // Модуль в крейте
            if (b_slot_num_err == false)
            {
                // Нет ошибки, результат 1..16
                addr = n_unit_slot_num;
            }
            else
            {
                // Ошибка
                addr = FPGA_UNIT_NUM_ERR;
                sdiag_set_err(SD_ERR_FPGA_MKO_ADDR);
                DBG_LOGE("Slot address error \n");
            }
        }
        else
        {
            // не было приема адреса
            addr = FPGA_UNIT_IS_SINGLE;
        }

    #endif

    L_EXIT;
    res = (err == ERR_OK) ? addr : err;
    return res;
}



//-----------------------------------------------------------------------------------------------------------
fun_res_t fpga_get_crait_sn(char* dst)
{
    fun_res_t err = ERR_OK;

    #if (FPGA_DEBUG == 1)
    err = L_ASSERT(true == b_init_done);
    L_CHECK_ERR(err);
    #endif

    #warning "TODO - fpga_get_crait_sn"
    L_UNUSED(dst);
    // Сделать запрос к регистру ПЛИС, выдать значение SN по переданному указателю
    L_GOTO_EXIT;
    
    // sdiag_set_err(SD_ERR_FPGA_CMD_INT);

    L_EXIT;
    return err;
}


//-----------------------------------------------------------------------------------------------------------
fun_res_t fpga_read_reg(u16 addr, u32* const value)
{
    fun_res_t err = ERR_OK;
    fpga_cmd_err_t res = FPGA_CMD_ERR_OK;
    e_last_err = FPGA_CMD_ERR_OK;
    
    #if (FPGA_DEBUG == 1)
        err = L_ASSERT(true == b_init_done);
        L_CHECK_ERR(err);
        err = L_ASSERT(NULL != value);
        L_CHECK_ERR(err);
    #endif
    
    res = f_read_reg(addr, value);

    if (res != FPGA_CMD_ERR_OK)
    {
        e_last_err = res;
        err = ERR_RUNTIME_ERROR;
        DBG_LOGE("read addr (0x%04X), cmd error: %s \n", addr, f_get_cmd_err(res));
    }

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



//-----------------------------------------------------------------------------------------------------------
fun_res_t fpga_write_reg(u16 addr, u32 value)
{
    fun_res_t err = ERR_OK;
    fpga_cmd_err_t res = FPGA_CMD_ERR_OK;
    e_last_err = FPGA_CMD_ERR_OK;

    #if (FPGA_DEBUG == 1)
        err = L_ASSERT(true == b_init_done);
        L_CHECK_ERR(err);
    #endif
    
    res = f_write_reg(addr, value);

    if (res != FPGA_CMD_ERR_OK)
    {
        e_last_err = res;
        err = ERR_RUNTIME_ERROR;
        DBG_LOGE("write addr (0x%04X), cmd error: %s \n", addr, f_get_cmd_err(res));
    }

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



//-----------------------------------------------------------------------------------------------------------
fpga_cmd_err_t fpga_get_last_err(void)
{
    return e_last_err;
}



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