#include "flash_dev_at25df.h"

#define AT25DF_TWRSR_TIME               1 /* 200 ns -> 1 ms*/

#define AT25DF_CMD_SECTOR_PROTECT       0x36
#define AT25DF_CMD_SECTOR_UNPROTECT     0x39
#define AT25DF_CMD_GET_SECTOR_PROTECT   0x3C

static const t_flash_erase_cmd f_at25df41a_erase_cmds[] = {
    {FLASH_CMD_CODE_ERASE_64K,  64*1024,  950, 0},
    {FLASH_CMD_CODE_ERASE_32K,  32*1024,  600, 0},
    {FLASH_CMD_CODE_ERASE_4K,      4096,  200, 0},
    {0,0,0,0}
};

static const t_flash_erase_cmd f_at25df41b_erase_cmds[] = {
    {FLASH_CMD_CODE_ERASE_64K,  64*1024,  550, 0},
    {FLASH_CMD_CODE_ERASE_32K,  32*1024,  280, 0},
    {FLASH_CMD_CODE_ERASE_4K,      4096,   40, 0},
    {FLASH_CMD_CODE_ERASE_PAGE,     256,   15, 0},
    {0,0,0,0}
};

static const t_flash_region f_at25df_sectors[] = {
    {0x000000, 64*1024},
    {0x010000, 64*1024},
    {0x020000, 64*1024},
    {0x030000, 64*1024},
    {0x040000, 64*1024},
    {0x050000, 64*1024},
    {0x060000, 64*1024},
    {0x070000, 32*1024},
    {0x078000,  8*1024},
    {0x07A000,  8*1024},
    {0x07C000, 16*1024},
};


const t_flash_info flash_info_at25df41a =  {
    {AT25DF_ID_MANUFACTURER, AT25DF_ID_DEVICE, AT25DF41A_ID_CAPACITY, 0},
    AT25DF_FLASH_SIZE,
    {FLASH_CMD_CODE_READ_STATUS, AT25DF_STATUS_BSY, 0x0, AT25DF_STATUS_EPE},
    {FLASH_CMD_CODE_READ, 0},
    {f_at25df41a_erase_cmds, FLASH_CMD_CODE_WRITE_ENABLE},
    {0x100, FLASH_CMD_CODE_PAGE_PROGRAM, 5000},
    {0},
    {0,0,0},
    {   NULL,
        flash_at25df_get_status,
        flash_at25df_read,
        flash_at25df_erase,
        flash_at25df_get_erase_cmd,
        flash_at25df_write,
        flash_at25df_write_enable,
        NULL,
        NULL,
        NULL,
        NULL,

        NULL,
        NULL,
        NULL,

        NULL,
        NULL,
        NULL
    },
    NULL
};

const t_flash_info flash_info_at25df41b =  {
    {AT25DF_ID_MANUFACTURER, AT25DF_ID_DEVICE, AT25DF41B_ID_CAPACITY, 0},
    AT25DF_FLASH_SIZE,
    {FLASH_CMD_CODE_READ_STATUS, AT25DF_STATUS_BSY, 0x0, AT25DF_STATUS_EPE},
    {FLASH_CMD_CODE_READ, 0},
    {f_at25df41b_erase_cmds, FLASH_CMD_CODE_WRITE_ENABLE},
    {0x100, FLASH_CMD_CODE_PAGE_PROGRAM, 5000},
    {0},
    {0,0,0},
    {   NULL,
        flash_at25df_get_status,
        flash_at25df_read,
        flash_at25df_erase,
        flash_at25df_get_erase_cmd,
        flash_at25df_write,
        flash_at25df_write_enable,
        flash_at25df_lock_enable,
        flash_at25df_lock,
        flash_at25df_unlock,
        flash_at25df_lock_check,

        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL
    },
    NULL
};

static const t_flash_region *f_get_sector(unsigned addr) {
    const t_flash_region *ret = NULL;
    for (unsigned sec_num = 0; (sec_num < sizeof(f_at25df_sectors)/sizeof(f_at25df_sectors[0])) && (ret == NULL); ++sec_num) {
        const t_flash_region *test_reg = &f_at25df_sectors[sec_num];
        if ((test_reg->addr <= addr) && (addr < (test_reg->addr + test_reg->size))) {
            ret = test_reg;
        }
    }
    return ret;
}


t_flash_errs flash_at25df_set(t_flash_iface *iface) {
    const t_flash_info *flash_infos[] = {
        &flash_info_at25df41a,
        &flash_info_at25df41b
    };
    return flash_set_from_list(iface, flash_infos, sizeof(flash_infos)/sizeof(flash_infos[0]), NULL);
}


t_flash_errs flash_at25df_set_status(t_flash_iface *iface, unsigned char status) {
    t_flash_errs err = 0;
    if (!err) {
        err = flash_write_enable(iface);
    }

    if (!err) {
        unsigned char cmd = FLASH_CMD_CODE_WRITE_STATUS;
        err = flash_exec_cmd(iface, &cmd, 1, &status, 1, NULL, 0, FLASH_FLAGS_FLUSH);
    }
    if (!err) {
        err = flash_wait_ready(iface, AT25DF_TWRSR_TIME, NULL);
    }
    return err;
}


t_flash_errs flash_at25df_global_unprotect(t_flash_iface *iface) {
    return flash_at25df_set_status(iface, 0);
}

t_flash_errs flash_at25df_global_protect(t_flash_iface *iface) {
    return flash_at25df_set_status(iface, 0x3C);
}

t_flash_errs flash_at25df_sector_protect(t_flash_iface *iface, unsigned int addr) {
    t_flash_errs err = 0;
    if (!err) {
        err = flash_write_enable(iface);
    }
    if (!err) {
        err = flash_exec_cmd_addr(iface, AT25DF_CMD_SECTOR_PROTECT, addr, 0, NULL, NULL, 0, FLASH_FLAGS_FLUSH);
    }
    return err;
}

t_flash_errs flash_at25df_sector_unprotect(t_flash_iface *iface, unsigned int addr) {
    t_flash_errs err = 0;
    if (!err) {
        err = flash_write_enable(iface);
    }
    if (!err) {
        err = flash_exec_cmd_addr(iface, AT25DF_CMD_SECTOR_UNPROTECT, addr, 0, NULL, NULL, 0, FLASH_FLAGS_FLUSH);
    }
    return err;
}

t_flash_errs flash_at25df_get_sector_protection(t_flash_iface *iface, unsigned addr, int *prot) {
    unsigned char rx_byte;
    t_flash_errs err = flash_exec_cmd_addr(iface, AT25DF_CMD_GET_SECTOR_PROTECT, addr, 0, NULL, &rx_byte, 1, FLASH_FLAGS_FLUSH);
    if (!err && (prot != NULL)) {
        *prot = rx_byte == 0xFF;
    }
    return err;
}


t_flash_errs flash_at25df_lock_enable(struct st_flash_iface *iface) {
    return FLASH_ERR_OK;
}
t_flash_errs flash_at25df_lock(struct st_flash_iface *iface, unsigned addr, unsigned size) {
    t_flash_errs err = FLASH_ERR_OK;
    unsigned next_addr = addr + size;
    do {
        const t_flash_region *sec = f_get_sector(addr);
        if (sec) {
            err = flash_at25df_sector_protect(iface, sec->addr);
            if (!err) {
                addr += sec->size;
            }
        }
    } while ((addr < next_addr) && (err == FLASH_ERR_OK));
    return err;
}
t_flash_errs flash_at25df_unlock(struct st_flash_iface *iface, unsigned addr, unsigned size) {
    t_flash_errs err = FLASH_ERR_OK;
    unsigned next_addr = addr + size;
    do {
        const t_flash_region *sec = f_get_sector(addr);
        if (sec) {
            err = flash_at25df_sector_unprotect(iface, sec->addr);
            if (!err) {
                addr += sec->size;
            }
        }
    } while ((addr < next_addr) && (err == FLASH_ERR_OK));
    return err;
}
t_flash_errs flash_at25df_lock_check(struct st_flash_iface *iface, unsigned addr, unsigned size, int locked, int *check_ok) {
    t_flash_errs err = FLASH_ERR_OK;
    unsigned next_addr = addr + size;
    int ok = 1;
    do {
        const t_flash_region *sec = f_get_sector(addr);
        if (sec) {
            int prot;
            err = flash_at25df_get_sector_protection(iface, sec->addr, &prot);
            if (!err) {
                ok = prot == locked;
            }
        }
    } while ((addr < next_addr) && (err == FLASH_ERR_OK) && ok);

    if (!err && check_ok) {
        *check_ok = ok;
    }

    return err;
}
