/** @file ltrmodule_cyclone.c

    Вспомогательный файл, использующийся библиотеками модулей, для которых
    нужно загружать прошивку ПЛИС Cyclone III по LTR-интерфейсу.

    Данный файл содержит реализацию функций для работы с прошивкой Cyclone III.
    Данный файл включается автоматически в проект, если установлена
    переменная cmake LTR_MODULE_USE_CYCLONE */

#include "ltrmodule.h"
#include "ltrmodule_cyclone.h"


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* коды команд LTR */
#define CMD_PS_CNTRL                     (LTR010CMD_INSTR | 0x20)
#define CMD_PS_DATA                      (LTR010CMD_INSTR | 0x21)
#define CMD_EN_CYCLON                    (LTR010CMD_INSTR | 0x22)

#define CMD_RESP_PS                      (LTR010CMD_INSTR | 0x20)

/* биты в командах */
#define BIT_PS_NCONFIG          0x0001
#define BIT_PS_NSTATUS          0x0002

#define BIT_PSRESP_NCONFIG      0x0001
#define BIT_PSRESP_NSTATUS      0x0002
#define BIT_PSRESP_CONF_DONE    0x0004



/* количество попыток проверить готовность ПЛИС к работе, после которого случай,
 * если ПЛИС не готов, считается ошибкой */
#define NSTATUS_WAIT_WR_CNT  100

/* размер блока, который используется при записи прошивки в модуль */
#define FPGA_FIRM_WR_BLOCK_SIZE (2*1024)


static INT f_send_recv(TLTR *hnd, DWORD* cmd, DWORD resp, DWORD size)
{
    INT err = LTR_OK;
    DWORD i;

    err = ltr_module_send_cmd(hnd, cmd, size);
    if (err==LTR_OK)
        err = ltr_module_recv_cmd_resp(hnd, cmd, size);

    for (i=0; (err==LTR_OK) && (i < size); i++)
    {
        if ((cmd[i] & 0xF0FF) != (resp & 0xF0FF))
            err = LTR_ERROR_INVALID_CMD_RESPONSE;
    }
    return err;
}



/**************************************************************************
    Вспомогательная функция
    Ожидание наличия положительного бита NSTATUS от ПЛИС
    Параметры:
       hnd       - (in) описатель канала связи с модулем
    Возвращаемое значение:
       код ошибки
 **************************************************************************/
static INT f_check_fpga_nstatus(TLTR *hnd)
{
    int status = 0;
    int i = 0;
    INT res = LTR_OK;


    while (!status && (i < NSTATUS_WAIT_WR_CNT) && (res == LTR_OK))
    {
        DWORD cmd = LTR_MODULE_MAKE_CMD(CMD_PS_CNTRL, BIT_PS_NCONFIG);
        res = f_send_recv(hnd, &cmd, CMD_RESP_PS, 1);
        if (res == LTR_OK)
        {
            status = (cmd >> 16) & BIT_PSRESP_NSTATUS;
        }
        i++;
    }

    if ((res== LTR_OK) && !status)
    {
        res = LTR_ERROR_FPGA_LOAD_READY_TOUT;
    }

    return res;
}



/**************************************************************************
    Вспомогательная функция
    Записывает блок данных прошивки ПЛИС
    Параметры:
       hnd       - (in) описатель канала связи с модулем
       data      - (in) массив данных с прошивкой
       size      - (in) размер данных (в словах uint16_t)
       conf_done - (out) данные возвращаемого слова (последнего ответа)
                         для определения CONF_DONE
    Возвращаемое значение:
       код ошибки
 **************************************************************************/
static INT f_write_fpga_firm_data(TLTR *hnd, WORD* data, int size, int* conf_done)
{
    DWORD* cmd = malloc(2*sizeof(cmd[0]) * size);
    INT i, res = LTR_OK;

    if (cmd==NULL)
        res = LTR_ERROR_MEMORY_ALLOC;
    else
    {
        for (i=0; i < size; i++)
        {
            //реверсируем биты с LSB на MSB
            data[i] = ((data[i] & 0x0001) << 15) | ((data[i] & 0x0002) << 13)
                | ((data[i] & 0x0004) << 11) | ((data[i] & 0x0008) <<  9)
                | ((data[i] & 0x0010) <<  7) | ((data[i] & 0x0020) <<  5)
                | ((data[i] & 0x0040) <<  3) | ((data[i] & 0x0080) <<  1)
                | ((data[i] & 0x0100) >>  1) | ((data[i] & 0x0200) >>  3)
                | ((data[i] & 0x0400) >>  5) | ((data[i] & 0x0800) >>  7)
                | ((data[i] & 0x1000) >>  9) | ((data[i] & 0x2000) >> 11)
                | ((data[i] & 0x4000) >> 13) | ((data[i] & 0x8000) >> 15);
            //составляем команду
            cmd[2*i+1] = LTR_MODULE_MAKE_CMD(CMD_PS_DATA, data[i]&0xFF);
            cmd[2*i] = LTR_MODULE_MAKE_CMD(CMD_PS_DATA, (data[i]>>8)&0xFF);
        }

        res = f_send_recv(hnd, cmd, CMD_RESP_PS, 2*size);
        if ((res==LTR_OK) && (conf_done!=NULL))
        {
            *conf_done = (cmd[2*size-1] >> 16);
        }
    }
    free(cmd);

    return res;
}



INT ltr_module_fpga_enable(TLTR *hnd, INT en)
{
    DWORD cmd = LTR_MODULE_MAKE_CMD(CMD_EN_CYCLON, en ? 1 : 0);
    return en ? f_send_recv(hnd, &cmd, cmd, 1) : ltr_module_send_cmd(hnd, &cmd, 1);
}

INT ltr_module_fpga_is_loaded(TLTR *hnd)
{
    INT res = LTR_IsOpened(hnd);
    if (res==LTR_OK)
    {
        DWORD cmd = LTR_MODULE_MAKE_CMD(CMD_PS_CNTRL, BIT_PS_NCONFIG);
        res = f_send_recv(hnd, &cmd, CMD_RESP_PS, 1);
        if (res == LTR_OK)
        {
            if (!((cmd >> 16) & BIT_PSRESP_CONF_DONE))
                res = LTR_ERROR_FPGA_IS_NOT_LOADED;
        }
    }
    return res;
}

INT ltr_module_fpga_load(TLTR* hnd, const char* filename, HINSTANCE hInst, const char *defname, t_ltr_fpga_load_cb cb, void *cb_data, void *hltr)
{
    FILE* f = NULL;
    INT err= LTR_IsOpened(hnd);
    DWORD total_size = 0;
    // нулевой указатель или пустая строка соответствует встроенной прошивке
    INT internal_name = (filename == NULL) || (filename[0]=='\0') || (filename[0]=='\n') || (filename[0]=='\r');
#ifdef _WIN32
    const BYTE* res_data = NULL;
#endif

    if (internal_name)
    {
#ifndef _WIN32
        /* для ОС, отличных от Windows, используем файл из места, указанного
            как путь установки при сборке */
        filename = defname;
#else    
        /* Для Windows загружаем файл из ресурса библиотеки */
        HRSRC res_info = FindResource(hInst, defname, RT_RCDATA);
        HGLOBAL rhnd = NULL;
        if (res_info!=NULL)
        {
            rhnd = LoadResource(hInst, res_info);
            if (rhnd!=NULL)
            {
                res_data = LockResource(rhnd);
                if (res_data)
                    total_size = SizeofResource(hInst, res_info);
            }
        }

        if (res_data==NULL)
            err = LTR_ERROR_FIRM_FILE_OPEN;
#endif
    }

    /* если данные из файла, то открываем файл и определяем размер */
    if ((err==LTR_OK)
#ifdef _WIN32
            && (res_data==NULL)
#endif
            )
    {
        f = fopen(filename, "rb");
        if (f==NULL)
            err = LTR_ERROR_FIRM_FILE_OPEN;
        else
        {
            /* определяем полный разер файла */
            fseek(f, 0, SEEK_END);
            total_size = ftell(f);
            fseek(f, 0, SEEK_SET);

        }
    }

    if (err==LTR_OK)
    {
        if (cb)
            cb(cb_data, hltr, 0, total_size);
    }

    if (err==LTR_OK)
    {
        /* запрещаем работу ПЛИС (чтобы он не мог испортить наши команды
         * программирования */
        err = ltr_module_fpga_enable(hnd, 0);
    }

    if (err==LTR_OK)
    {
        //перепад NCONFIG - с 0 на 1 - признак начала прошивки
        DWORD cmd[3];
        cmd[0] = LTR_MODULE_MAKE_CMD(CMD_PS_CNTRL, 0);
        cmd[1] = LTR_MODULE_MAKE_CMD(CMD_PS_CNTRL, 0);
        cmd[2] = LTR_MODULE_MAKE_CMD(CMD_PS_CNTRL, BIT_PS_NCONFIG);

        err = f_send_recv(hnd, cmd, CMD_RESP_PS, sizeof(cmd)/sizeof(cmd[0]));
        if (err==LTR_OK)
            err = f_check_fpga_nstatus(hnd);

    }

    if (err==LTR_OK)
    {
        BYTE data[FPGA_FIRM_WR_BLOCK_SIZE];
        DWORD rd_cnt = FPGA_FIRM_WR_BLOCK_SIZE;
        DWORD done_cnt = 0;
        int conf_done = 0;

        //читаем данные из файла или ресурса и пишем в ПЛИС блоками
        while ((rd_cnt == FPGA_FIRM_WR_BLOCK_SIZE) && (err==LTR_OK))
        {

#ifdef _WIN32
            if (res_data!=NULL)
            {
                DWORD rem_size = total_size-done_cnt;
                if (rem_size < rd_cnt)
                    rd_cnt = rem_size;
                memcpy(data, &res_data[done_cnt], rd_cnt);
            }
            else
#endif
            {
                rd_cnt = (DWORD)fread(data, 1, FPGA_FIRM_WR_BLOCK_SIZE, f);
            }
            if (rd_cnt > 0)
            {
                /* так как записываем 16-битными словами, то для нечетного числа
                 * байт добавляем 0xFF в конец */
                if (rd_cnt&1)
                {
                    data[rd_cnt]=0xFF;
                }

                err = f_write_fpga_firm_data(hnd, (WORD*)data, (rd_cnt+1)/2, &conf_done);
                if (err==LTR_OK)
                {
                    done_cnt+=rd_cnt;
                    if (cb && (done_cnt!=total_size))
                        cb(cb_data, hltr, done_cnt, total_size);
                }
            }
        }

        /* после прошивки записываем дополнительные 0xFF'ы, чтобы ПЛИС создать
           клоки для перевода ПЛИС в рабочее состояние */
        if (!err)
        {
            int send_cnt;
            memset(data, 0xFF, FPGA_FIRM_WR_BLOCK_SIZE);
            for (send_cnt=0; !(conf_done& BIT_PSRESP_CONF_DONE) && (err==LTR_OK) && (send_cnt < 10); send_cnt++)
            {
                err = f_write_fpga_firm_data(hnd, (WORD*)data, FPGA_FIRM_WR_BLOCK_SIZE/2, &conf_done);
            }
            if (conf_done&BIT_PSRESP_CONF_DONE)
                err = f_write_fpga_firm_data(hnd,(WORD*)data, 2, &conf_done);
            else
                err = LTR_ERROR_FPGA_LOAD_DONE_TOUT;
        }

        if (cb && (err==LTR_OK))
            cb(cb_data, hltr, total_size, total_size);
    }

    if (f!=NULL)
        fclose(f);

    return err;
}
