#include "flash_dev_at45d011.h"

#define AT45D011_TIME_XFER             200

#define AT45D011_TIME_PROGR_MKS       15000
#define AT45D011_TIME_PROGR_ERASE_MKS 20000

#define AT45D011_TIME_ERASE_PAGE      15 /* в datasheet 10, но взят запас, т.к. на erase block реальное время больше */
#define AT45D011_TIME_ERASE_BLOCK     25 /* хотя в datasheet стоит 15, реально время выполнения может быть и 17. взято с запасом */


#define AT45D011_CMD_MEM_PAGE_READ              0x52
#define AT45D011_CMD_BUF_READ                   0x54
#define AT45D011_CMD_MEM_TO_BUF_TRANSF          0x53
#define AT45D011_CMD_MEM_TO_BUF_CMP             0x60
#define AT45D011_CMD_BUF_WRITE                  0x84
#define AT45D011_CMD_BUF_TO_MEM_WITH_ERASE      0x83
#define AT45D011_CMD_BUF_TO_MEM_WITHOUT_ERASE   0x88
#define AT45D011_CMD_PAGE_ERASE                 FLASH_CMD_CODE_ERASE_PAGE
#define AT45D011_CMD_BLOCK_ERASE                0x50
#define AT45D011_CMD_MEM_PROG                   0x82
#define AT45D011_CMD_AUTO_PAGE_REWRITE          0x58
#define AT45D011_CMD_STATUS_REG                 0x57


static const t_flash_erase_cmd f_erase_cmds_at45d011[] = {
    {AT45D011_CMD_BLOCK_ERASE,  AT45D011_BLOCK_SIZE,  AT45D011_TIME_ERASE_BLOCK, 0},
    {AT45D011_CMD_PAGE_ERASE,   AT45D011_PAGE_SIZE,   AT45D011_TIME_ERASE_PAGE,  0},
    {0,0,0,0}
};

#define AT45D011_PAGE_NUM(addr)   (addr / AT45D011_PAGE_SIZE)
#define AT45D011_PAGE_OFFS(addr)   (addr % AT45D011_PAGE_SIZE)
#define AT45D011_BLOCK_NUM(addr)   (addr / AT45D011_BLOCK_SIZE)
#define AT45D011_BLOCK_OFFS(addr)   (addr % AT45D011_BLOCK_SIZE)
#define AT45D011_CMD_PAGE_ADDR(page_num, page_offs)  (((page_num) << 9) | (page_offs))
#define AT45D011_CMD_BLOCK_ADDR(block_num) ((block_num) << 12)


t_flash_errs flash_at45d011_read(t_flash_iface *iface, unsigned addr, unsigned char *data, size_t len) {
    return flash_at45d011_read_cb(iface, addr, data, len, NULL);
}

t_flash_errs flash_at45d011_read_cb(t_flash_iface *iface, unsigned addr, unsigned char *data, size_t len, void (*cb)(void)) {
    t_flash_errs err = FLASH_ADDR_CHECK(iface, addr, len);
    while (!err && (len > 0)) {
        unsigned page_num  = AT45D011_PAGE_NUM(addr);
        unsigned page_offs = AT45D011_PAGE_OFFS(addr);
        size_t page_rd_len = AT45D011_PAGE_SIZE - page_offs;

        if (page_rd_len > len)
            page_rd_len = len;

        err = flash_exec_cmd_addr(iface, AT45D011_CMD_MEM_PAGE_READ,
                                  AT45D011_CMD_PAGE_ADDR(page_num, page_offs),
                                  4, NULL, data, page_rd_len,
                                  FLASH_FLAGS_FLUSH);
        if (!err) {
            addr += page_rd_len;
            data += page_rd_len;
            len  -= page_rd_len;
            if (cb)
                cb();
        }
    }
    return err;
}


t_flash_errs flash_at45d011_erase(t_flash_iface *iface, unsigned addr, size_t size) {
    return  flash_at45d011_erase_cb(iface, addr, size, NULL);
}

t_flash_errs flash_at45d011_erase_cb(t_flash_iface *iface, unsigned addr, size_t size, void (*cb)(void)) {
    t_flash_errs err = FLASH_ADDR_CHECK(iface, addr, size);
    while (!err && (size != 0)) {
        /* подбираем наиболее подходящую команду для стирания для данных размеров и адреса */
        const t_flash_erase_cmd *erase_cmd = iface->flash_info->func.get_erase_cmd(iface, addr, size);
        if (erase_cmd != NULL) {
            if (erase_cmd->cmd_code == AT45D011_CMD_PAGE_ERASE) {
                unsigned page_num = AT45D011_PAGE_NUM(addr);
                err = flash_exec_cmd_addr_only(iface, erase_cmd->cmd_code, AT45D011_CMD_PAGE_ADDR(page_num, 0), 0);
            } else {
                unsigned block_num = AT45D011_BLOCK_NUM(addr);
                err = flash_exec_cmd_addr_only(iface, erase_cmd->cmd_code, AT45D011_CMD_BLOCK_ADDR(block_num), 0);
            }

            if (cb)
                cb();

            if (!err)
                err = flash_wait_ready_cb(iface, erase_cmd->time, NULL, cb);

            if (!err) {
                addr += erase_cmd->size;
                size -= erase_cmd->size;
            }
        } else {
            err = FLASH_ERR_UNALINGNED_ADDR;
        }
    }

    flash_iface_flush(iface, err);

    return err;
}


t_flash_errs flash_at45d011_write(t_flash_iface *iface, unsigned addr,
                                const unsigned char *data, size_t len,
                                unsigned flags) {
    return flash_at45d011_write_cb(iface, addr, data, len, flags, NULL);
}


t_flash_errs flash_at45d011_write_cb(t_flash_iface *iface, unsigned addr, const unsigned char *data, size_t len, unsigned flags, void (*cb)(void)) {
    t_flash_errs err = FLASH_ADDR_CHECK(iface, addr, len);
    unsigned page_size = AT45D011_PAGE_SIZE;

    while (len && !err) {
        unsigned page_num = AT45D011_PAGE_NUM(addr);
        unsigned page_offs = AT45D011_PAGE_OFFS(addr);
        size_t page_wr_len = page_size - page_offs;
        int page_update = 0;

        if (page_wr_len > len)
            page_wr_len = len;
        if (page_wr_len != page_size) {
            page_update = 1;

            if (!err) {
                err = flash_exec_cmd_addr_only(iface, AT45D011_CMD_MEM_TO_BUF_TRANSF,
                                               AT45D011_CMD_PAGE_ADDR(page_num, 0), 0);
            }
            if (!err) {
                err = flash_wait_ready(iface, AT45D011_TIME_XFER, NULL);
            }

            if (cb)
                cb();
        }

        if (!err) {
            err = flash_exec_cmd_addr(iface, AT45D011_CMD_BUF_WRITE, page_offs, 0,
                                      data, NULL, page_wr_len, 0);
            if (cb)
                cb();
        }

        if (!err) {
            unsigned char cmd_code = page_update || (flags & FLASH_WR_FLAGS_WITH_ERASE) ?
                        AT45D011_CMD_BUF_TO_MEM_WITH_ERASE :
                        AT45D011_CMD_BUF_TO_MEM_WITHOUT_ERASE;

            err = flash_exec_cmd_addr_only(iface, cmd_code, AT45D011_CMD_PAGE_ADDR(page_num, 0),  0);
            if (!err) {
                err = flash_wait_ready_cb(iface, cmd_code == AT45D011_CMD_BUF_TO_MEM_WITH_ERASE ?
                                           AT45D011_TIME_PROGR_ERASE_MKS : AT45D011_TIME_PROGR_MKS, NULL, cb);
            }

            if (cb)
                cb();
        }

        if (!err && page_update)  {
            err = flash_exec_cmd_addr_only(iface, AT45D011_CMD_AUTO_PAGE_REWRITE,
                                           AT45D011_CMD_PAGE_ADDR(page_num, 0),  0);
            if (!err) {
                err = flash_wait_ready_cb(iface, AT45D011_TIME_PROGR_ERASE_MKS, NULL, cb);
            }
        }

        if (!err) {
            addr += (unsigned)page_wr_len;
            data += page_wr_len;
            len  -= page_wr_len;
        }
    }

    flash_iface_flush(iface, err);

    return err;
}



const t_flash_info flash_info_at45d011 = {
    {AT45D011_ID_MANUFACTURER, AT45D011_ID_DEVICE, AT45D011_ID_CAPACITY, 0},
    AT45D011_FLASH_SIZE,
    {AT45D011_CMD_STATUS_REG, AT45D011_STATUS_RDY, AT45D011_STATUS_RDY,0x0},
    {AT45D011_CMD_MEM_PAGE_READ, 4},
    {f_erase_cmds_at45d011, 0},
    {0, 0, 0},
    {0},
    {0,0},
    {   NULL,
        flash_at45d011_get_status,
        flash_at45d011_read,
        flash_at45d011_erase,
        flash_at45d011_get_erase_cmd,
        flash_at45d011_write,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        flash_at45d011_read_cb,
        flash_at45d011_write_cb,
        flash_at45d011_erase_cb,        
        NULL,
        NULL,
        NULL
    },
    NULL
};

