#include "l502api_private.h"
#include "ltimer.h"
#include "l502_fpga_regs.h"
#include <stdlib.h>
#include <string.h>

#define BF_LDR_HDR_SIZE  (16)
#define BF_LDR_HDRSGN    (0xAD)

#define BF_LDR_HDRPOS_SGN  (3)

#define BF_LDR_FLAG_SAVE      (0x0010) //не используется
#define BF_LDR_FLAG_AUX       (0x0020) //не используется
#define BF_LDR_FLAG_FILL      (0x0100)
#define BF_LDR_FLAG_QUICKBOOT (0x0200) //не используется
#define BF_LDR_FLAG_CALLBACK  (0x0400) //не используется
#define BF_LDR_FLAG_INIT      (0x0800) //не используется
#define BF_LDR_FLAG_IGNORE    (0x1000)
#define BF_LDR_FLAG_INDIRECT  (0x2000) //не используется
#define BF_LDR_FLAG_FIRST     (0x4000)
#define BF_LDR_FLAG_FINAL     (0x8000)

#define L502_BF_WAIT_LOAD_RDY_TOUT  500

#define LDR_BUFF_SIZE 4096

#define BF_CHECK_ADDR(addr)  (((addr) < 0xFFA0C000) && ((addr)>= 0xFFA0000)) || \
    (((addr) < 0xFF908000) && ((addr) >=0xFF900000)) || \
    (((addr) < 0xFF808000) && ((addr) >=0xFF800000)) || \
    (((addr) < 0x2000000)) ? 0 : X502_ERR_BF_INVALID_ADDR

#define BF_CHECK_ADDR_SIZE(addr, size) BF_CHECK_ADDR(addr) ? X502_ERR_BF_INVALID_ADDR : \
    BF_CHECK_ADDR(addr+size*4-1) ? X502_ERR_BF_INVALID_ADDR : 0


typedef struct st_bf_ldr_pkt {
    uint8_t res;
    uint8_t dma_mode;
    uint16_t flags;
    uint32_t addr;
    uint32_t size;
    uint32_t arg;
} t_bf_ldr_pkt;

/* Разбираем заголовок блока LDR-формата из буфера размером BF_LDR_HDR_SIZE
   и сохраняем параметры в структуре pkt */
int32_t f_parse_ldr_hdr(const uint8_t *hdr, t_bf_ldr_pkt *pkt) {
    int32_t err = X502_ERR_OK;
    uint32_t* pdw_buff = (uint32_t*)hdr;
    uint8_t xor_ch = 0;
    int i;
    for (i=0; i < BF_LDR_HDR_SIZE; i++) {
        xor_ch ^= hdr[i];
    }

    if ((xor_ch!=0) || (hdr[BF_LDR_HDRPOS_SGN] != BF_LDR_HDRSGN)) {
        err = X502_ERR_LDR_FILE_FORMAT;
    } else {
        pkt->res = 0;
        pkt->dma_mode = pdw_buff[0]&0xF;
        pkt->flags = pdw_buff[0]&0xFFF0;
        pkt->addr = pdw_buff[1];
        pkt->size = pdw_buff[2];
        pkt->arg = pdw_buff[3];

        if ((pkt->flags & BF_LDR_FLAG_INIT) && (pkt->flags & BF_LDR_FLAG_FILL)) {
            err = X502_ERR_LDR_FILE_FORMAT;
        } else if (pkt->flags & (BF_LDR_FLAG_CALLBACK | BF_LDR_FLAG_INDIRECT | BF_LDR_FLAG_INIT)) {
            err = X502_ERR_LDR_FILE_UNSUP_FEATURE;
        } else if ((pkt->flags & BF_LDR_FLAG_INIT) && (pkt->addr != 0xFFA00000)) {
            err = X502_ERR_LDR_FILE_UNSUP_STARTUP_ADDR;
        }
    }
    return err;
}

static int32_t f_bf_wait_cmd_done(t_x502_hnd hnd) {
    int32_t err = X502_ERR_OK;
    t_ltimer tmr;
    uint32_t status;
    ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(X502_BF_REQ_TOUT));
    do {
        err = l502_port_fpga_reg_read(hnd, L502_REGS_BF_STATUS, &status);
    } while ((status &  L502_REGBIT_BF_STATUS_BUSY_Msk) &&
             (err == X502_ERR_OK) && !ltimer_expired(&tmr));

    if (!err && (status & L502_REGBIT_BF_STATUS_BUSY_Msk))
        err = X502_ERR_BF_REQ_TIMEOUT;
    return err;
}



int32_t l502_iface_bf_mem_block_rd(t_x502_hnd hnd, uint32_t addr, uint32_t *block, uint32_t size){
    uint32_t i;
    int32_t err = f_bf_wait_cmd_done(hnd);
    /* записываем переметры передачи - размер и адрес в памяти BlackFin */
    if (err == X502_ERR_OK)
        err = l502_port_fpga_reg_write(hnd,  L502_REGS_BF_REQ_SIZE, size);
    if (err == X502_ERR_OK)
        err = l502_port_fpga_reg_write(hnd, L502_REGS_BF_REQ_ADDR, addr);
    /* даем команду на запис */
    if (err == X502_ERR_OK)
        err = l502_port_fpga_reg_write(hnd, L502_REGS_BF_CMD, L502_BF_CMD_READ);

    /* ждем, пока операция не будет завершена */
    if (err == X502_ERR_OK)
        err = f_bf_wait_cmd_done(hnd);

    /* записываем блок данных в буфер ПЛИС */
    for (i=0; (i < size) && (err == X502_ERR_OK); i++) {
        err = l502_port_fpga_reg_read(hnd, L502_REGS_BF_REQ_DATA+i, &block[i]);
    }
    return err;
}

int32_t l502_iface_bf_mem_block_wr(t_x502_hnd hnd, uint32_t addr, const uint32_t *block, uint32_t size) {
    uint32_t i;
    int32_t err = f_bf_wait_cmd_done(hnd);
    if (err == X502_ERR_OK) {
        /* записываем блок данных в буфер ПЛИС */
        for (i=0; (i < size) && (err == X502_ERR_OK); i++) {
            err = l502_port_fpga_reg_write(hnd, L502_REGS_BF_REQ_DATA+i, block[i]);
        }

        /* записываем переметры передачи - размер и адрес в памяти BlackFin */
        if (err == X502_ERR_OK)
            err = l502_port_fpga_reg_write(hnd,  L502_REGS_BF_REQ_SIZE, size);
        if (err == X502_ERR_OK)
            err = l502_port_fpga_reg_write(hnd, L502_REGS_BF_REQ_ADDR, addr);
        /* даем команду на запис */
        if (err == X502_ERR_OK)
            err = l502_port_fpga_reg_write(hnd, L502_REGS_BF_CMD, L502_BF_CMD_WRITE);

        /* ждем, пока операция не будет завершена */
        if (err == X502_ERR_OK)
            err = f_bf_wait_cmd_done(hnd);
    }
    return err;
}


static int32_t f_bf_mem_wr(t_x502_hnd hnd, uint32_t addr, const uint32_t* regs, uint32_t size) {
    int32_t err = X502_ERR_OK;

    /* данные записываем блоками по L502_BF_REQ_DATA_SIZE */
    while ((err == X502_ERR_OK) && size) {
        int put_size = (size < hnd->iface_hnd->bf_mem_block_size) ? size :
                                         hnd->iface_hnd->bf_mem_block_size;
        err = hnd->iface_hnd->bf_mem_block_wr(hnd, addr, regs, put_size);
        if (!err) {
            size -= put_size;
            regs += put_size;
            addr += put_size*4;
        }
    }
    return err;
}

int32_t l502_iface_bf_firm_load(t_x502_hnd hnd, const char *filename) {
    int32_t err = X502_ERR_OK;
    FILE* ldr_file=fopen(filename, "rb");
    if (ldr_file==NULL) {
        err = X502_ERR_LDR_FILE_OPEN;
    } else {
        int32_t next_err = X502_ERR_OK;
        uint32_t *ldr_buff = NULL;
        ldr_buff = malloc(LDR_BUFF_SIZE);
        if (ldr_buff == NULL)
            err = X502_ERR_MEMORY_ALLOC;

        if (err == X502_ERR_OK) {
            int rd_size = 0;
            int stop = 0;
            uint32_t reg;
            uint8_t hdr[BF_LDR_HDR_SIZE];
            t_ltimer tmr;

            //uint32_t* pdw = (uint32_t*)ldr_buff;
            t_bf_ldr_pkt pkt, pkt_next;
            uint32_t bf_val = 0;
            memset(&pkt_next, 0, sizeof(pkt_next));

            l502_port_fpga_reg_read(hnd, L502_REGS_BF_CTL, &bf_val);
            l502_port_fpga_reg_write(hnd, L502_REGS_BF_CTL, L502_REGBIT_BF_CTL_DSP_MODE_Msk
                            | (bf_val & 0xF00)); //set rst
            SLEEP_MS(1);
            l502_port_fpga_reg_write(hnd, L502_REGS_BF_CTL, L502_REGBIT_BF_CTL_DSP_MODE_Msk |
                            L502_REGBIT_BF_CTL_BF_RESET_Msk | (bf_val & 0xF00));  //release rst

            ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(L502_BF_WAIT_LOAD_RDY_TOUT));
            do {
                l502_port_fpga_reg_read(hnd, L502_REGS_BF_CTL, &reg);
                if ((reg & L502_REGBIT_BF_CTL_HOST_WAIT_Msk) && ltimer_expired(&tmr))
                    err = X502_ERR_BF_LOAD_RDY_TOUT;
            } while ((err == X502_ERR_OK) && (reg & L502_REGBIT_BF_CTL_HOST_WAIT_Msk));

            if (err == X502_ERR_OK) {
                err = fread(hdr, 1, BF_LDR_HDR_SIZE, ldr_file) == BF_LDR_HDR_SIZE ?
                      f_parse_ldr_hdr(hdr, &pkt) : X502_ERR_LDR_FILE_READ;
            }

            while ((err == X502_ERR_OK) && !stop) {
                if (next_err != X502_ERR_OK) {
                    err = next_err;
                } else if (((pkt.flags & BF_LDR_FLAG_FILL) == 0) && (pkt.size != 0)) {
                    int r_size = (pkt.size > LDR_BUFF_SIZE) ? LDR_BUFF_SIZE : pkt.size;

                    rd_size = (int)fread(ldr_buff, 1, r_size, ldr_file);
                    if (rd_size!=r_size)
                        err = X502_ERR_LDR_FILE_READ;
                }
                if (err == X502_ERR_OK) {
                    if (pkt.size > LDR_BUFF_SIZE) {
                        pkt_next = pkt;
                        pkt_next.addr += LDR_BUFF_SIZE;
                        pkt_next.size -= LDR_BUFF_SIZE;
                        pkt.size = LDR_BUFF_SIZE;
                    } else {
                        next_err = fread(hdr, 1, BF_LDR_HDR_SIZE, ldr_file) == BF_LDR_HDR_SIZE ?
                                  f_parse_ldr_hdr(hdr, &pkt_next) : X502_ERR_LDR_FILE_READ;
                        if (next_err != X502_ERR_OK) {
                            pkt_next.size = 0;
                        }
                    }

                    if (pkt.size!=0) {
                        uint32_t size = ((pkt.size+31)/(32))*8;
                        if (pkt.flags & BF_LDR_FLAG_FILL) {
                            uint32_t i;
                            for (i=0; i < size; i++)
                                ldr_buff[i] = pkt.arg;
                        }

                        if ((pkt.flags & BF_LDR_FLAG_FINAL)
                            || ((pkt_next.flags & BF_LDR_FLAG_FINAL) && (pkt_next.size==0))) {
                            uint32_t buf_pos = 0;
                            err = BF_CHECK_ADDR_SIZE(pkt.addr, size);

                            if ((err == X502_ERR_OK) && (size > 8)) {
                                err = f_bf_mem_wr(hnd, pkt.addr, ldr_buff, size-8);
                                pkt.addr+=4*(size-8);                                
                                buf_pos = size-8;
                                size = 8;
                            }

                            if (err == X502_ERR_OK)
                                err = l502_port_fpga_reg_write(hnd, L502_REGS_BF_CMD, L502_BF_CMD_HIRQ);
                            if (err == X502_ERR_OK)
                                err = f_bf_mem_wr(hnd, pkt.addr, &ldr_buff[buf_pos], size);
                            stop=1;

                            if (err == X502_ERR_OK) {
                                err = l502_port_fpga_reg_write(hnd, L502_REGS_BF_CTL, L502_REGBIT_BF_CTL_DSP_MODE_Msk |
                                            L502_REGBIT_BF_CTL_BF_RESET_Msk);
                            }
                        } else if (!(pkt.flags & BF_LDR_FLAG_IGNORE)) {
                            err = BF_CHECK_ADDR_SIZE(pkt.addr, size);
                            if (!err)
                                err = f_bf_mem_wr(hnd, pkt.addr, ldr_buff, size);
                        }
                    }
                    pkt = pkt_next;
                }
            }
        }
        free(ldr_buff);

        fclose(ldr_file);
    }
    return err;
}
