#include "l502api_private.h"
#include "ltimer.h"
#include "l502_fpga_regs.h"

#define L502_EEPROM_BIG_SECTOR_SIZE    (64*1024)
#define L502_EEPROM_SMALL_SECTOR_SIZE  (4*1024)

#define L502_FLASH_WRITE_TOUT  500
#define L502_FLASH_ERASE_TOUT  500

/** биты регистра статуса */
typedef enum {
    SST25_STATUS_BUSY   = 0x01,
    SST25_STATUS_WEL    = 0x02,
    SST25_STATUS_BP0    = 0x04,
    SST25_STATUS_BP1    = 0x08,
    SST25_STATUS_BP2    = 0x10,
    SST25_STATUS_BP3    = 0x20,
    SST25_STATUS_AAI    = 0x40,
    SST25_STATUS_BPL    = 0x80
} t_sst25_status_bits;

static uint8_t f_prot_bits[] = {
    SST25_STATUS_BP2 | SST25_STATUS_BP1 | SST25_STATUS_BP0,
    SST25_STATUS_BP2 | SST25_STATUS_BP0,
    SST25_STATUS_BP2,
    SST25_STATUS_BP0,
    0
};



static LINLINE int32_t f_eeprom_rd_status(t_x502_hnd hnd, uint8_t* stat) {
    uint32_t val;
    int32_t err = l502_port_fpga_reg_read(hnd, L502_REGS_EEPROM_RD_STATUS, &val);
    if (err == X502_ERR_OK)
        *stat = (val>>24)&0xFF;
    return err;
}

static LINLINE int32_t f_eeprom_wr_status(t_x502_hnd hnd, uint8_t stat) {
    int32_t err = l502_port_fpga_reg_write(hnd, L502_REGS_EEPROM_WR_STATUS_EN, 1);
    if (err == X502_ERR_OK) {
        err = l502_port_fpga_reg_write(hnd, L502_REGS_EEPROM_WR_STATUS, stat);
    }
    return err;
}


static int32_t f_eeprom_wr_byte(t_x502_hnd hnd, uint32_t addr, uint8_t val) {
    int32_t err = X502_ERR_OK;
    t_ltimer tmr;
    uint8_t stat = SST25_STATUS_BUSY;

    /* разрешаем запись в EEPROM */
    err = l502_port_fpga_reg_write(hnd, L502_REGS_EEPROM_WR_EN, 1);
    if (err == X502_ERR_OK) {
        err = l502_port_fpga_reg_write(hnd, L502_REGS_EEPROM_WR_BYTE, ((addr & 0xFFFFFF) << 8) | val);
        if (err != X502_ERR_OK)
            l502_port_fpga_reg_write(hnd, L502_REGS_EEPROM_WR_DIS, 1);
    }

    if (err == X502_ERR_OK)
        ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(L502_FLASH_WRITE_TOUT));

    /* Ожидаем завершения записи */
    while ((err == X502_ERR_OK) && (stat & SST25_STATUS_BUSY) && !ltimer_expired(&tmr)) {
        err = f_eeprom_rd_status(hnd, &stat);
    }

    if ((err == X502_ERR_OK) && (stat & SST25_STATUS_BUSY)) {
        err = X502_ERR_FLASH_WRITE_TOUT;
    }
    return err;
}





int32_t l502_iface_flash_rd(t_x502_hnd hnd, uint32_t addr, uint8_t* data, uint32_t size) {
    int32_t err = X502_ERR_OK;
    uint32_t val;

    err = l502_port_fpga_reg_write(hnd, L502_REGS_EEPROM_SET_RD_ADDR, (addr & 0xFFFFFF) << 8);
    if (err == X502_ERR_OK)
        err = l502_port_fpga_reg_read(hnd, L502_REGS_EEPROM_RD_DWORD, &val);
    if (err == X502_ERR_OK) {
        unsigned int i;
        for (i=0; (i < sizeof(val)) && size; i++, size--) {
            *data++ = val & 0xFF;
            val >>= 8;
        }
    }
    return err;
}

int32_t l502_iface_flash_wr(t_x502_hnd hnd, uint32_t addr, const uint8_t* data, uint32_t size) {
    uint32_t i;
    int32_t err = X502_ERR_OK;
    for (i=0; (i < size) && (err == X502_ERR_OK); i++) {
        err = f_eeprom_wr_byte(hnd, addr+i, data[i]);
    }
    return err;
}

int32_t l502_iface_flash_erase(t_x502_hnd hnd, uint32_t addr, uint32_t size) {
    int32_t err = X502_ERR_OK;
    if (((addr & (L502_EEPROM_SMALL_SECTOR_SIZE-1)) ||
             (size & (L502_EEPROM_SMALL_SECTOR_SIZE-1)))) {
        err = X502_ERR_FLASH_SECTOR_BOUNDARY;
    }

    while((size != 0) && (err == X502_ERR_OK)) {
        uint32_t er_size;
        /* разрешаем запись в EEPROM */
        err = l502_port_fpga_reg_write(hnd, L502_REGS_EEPROM_WR_EN, 1);
        if (err == X502_ERR_OK) {
            uint8_t stat = SST25_STATUS_BUSY;
            t_ltimer tmr;
            /* проверяем - можем ли стереть целиком большой сектор или
               придется писать в мелкий */
            if ((size >= L502_EEPROM_BIG_SECTOR_SIZE) &&
                                !(size & (L502_EEPROM_BIG_SECTOR_SIZE-1))) {
                er_size = L502_EEPROM_BIG_SECTOR_SIZE;
                err = l502_port_fpga_reg_write(hnd, L502_REGS_EEPROM_ERASE_64K, addr<<8);
            } else {
                er_size = L502_EEPROM_SMALL_SECTOR_SIZE;
                err = l502_port_fpga_reg_write(hnd, L502_REGS_EEPROM_ERASE_4K, addr<<8);
            }

            if (err == X502_ERR_OK)
                ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(L502_FLASH_ERASE_TOUT));

            /* ожидаем завершения стирания */
            while ((err == X502_ERR_OK) && (stat & SST25_STATUS_BUSY) && !ltimer_expired(&tmr)) {
                err = f_eeprom_rd_status(hnd, &stat);
            }

            if ((err == X502_ERR_OK) && (stat & SST25_STATUS_BUSY)) {
                err = X502_ERR_FLASH_ERASE_TOUT;
            }

            /* запрещаем запись, если произошла ошибка. при успешном стирании
               запись будут запрещена атоматически */
            if (err != X502_ERR_OK)
                l502_port_fpga_reg_write(hnd, L502_REGS_EEPROM_WR_EN, 0);

            if (err == X502_ERR_OK) {
                addr += er_size;
                size -= er_size;
            }
        }
    }
    return err;
}

int32_t l502_iface_flash_set_prot(t_x502_hnd hnd, uint32_t prot, const uint8_t* prot_data, uint32_t size) {
    int32_t err = X502_ERR_OK;
    uint16_t prot_code = 0;
    if (size == 2) {
        prot_code = ((uint16_t)prot_data[1] << 8) | prot_data[0];
    }

    err = l502_port_fpga_reg_write(hnd, L502_REGS_EEPROM_WR_STATUS_EN, 1);
    if ((err == X502_ERR_OK) && (prot_code != 0))
        err = l502_port_fpga_reg_write(hnd, L502_REGS_EEPROM_HARD_WR_STATUS_EN, prot_code);
    if (err == X502_ERR_OK)
       err = l502_port_fpga_reg_write(hnd, L502_REGS_EEPROM_WR_STATUS,  f_prot_bits[prot]);
    return err;
}
