#include "flash.h"
#include "ltimer.h"


#define FLASH_FILL_CMD_WITH_ADDR(cmd_buf, cmd_code, addr) do {\
    cmd_buf[0] = cmd_code; \
    cmd_buf[1] = (addr >> 16) & 0xFF; \
    cmd_buf[2] = (addr >> 8) & 0xFF; \
    cmd_buf[3] = addr & 0xFF; \
    } while(0)


int flash_check_erase_cmd(const t_flash_erase_cmd *cmd, unsigned addr, size_t size) {
    return ((addr % cmd->size) == 0) && (size >= cmd->size);
}


/* получить подходящую команду для стирания по адресу и размеру */
const t_flash_erase_cmd *flash_generic_get_erase_cmd(t_flash_iface *iface,
                                                     unsigned addr, size_t size) {
    const t_flash_erase_cmd *fnd_cmd = NULL;
    const t_flash_erase_cmd *cmd;

    for (cmd = iface->flash_info->erase.cmds; !fnd_cmd && (cmd->cmd_code != 0); ++cmd) {
        if (flash_check_erase_cmd(cmd, addr, size))
            fnd_cmd = cmd;
    }
    return fnd_cmd;
}

unsigned flash_get_min_erase_size(const t_flash_info *flash) {
    unsigned size = 0;
    const t_flash_erase_cmd *cmd;

    for (cmd = flash->erase.cmds; cmd->cmd_code != 0; ++cmd) {
        if ((size == 0) || (size > cmd->size))
            size = cmd->size;
    }
    return size;
}


t_flash_errs flash_wait_ready_mks_cb(t_flash_iface *iface, unsigned tout_us, unsigned char *last_status, void (*cb)(void)) {
    t_flash_errs err = FLASH_ERR_OK;
    int rdy = 0;
    t_ltimer tmr;

    if ((iface->wait != NULL) && (tout_us < iface->wait_treshold_us)) {
        err = iface->wait(iface, tout_us);
    } else {
        ltimer_set_ms(&tmr, (tout_us + 999)/1000 + iface->port_exec_tout_ms);

        while (!rdy && (err == FLASH_ERR_OK)) {
            unsigned char status;
            /* делаем проверку таймаута до статуса, а не после,
             * иначе мы можем выйти раньше таймаута на время выполнения
             * операции получения статуса */
            int expired = ltimer_expired(&tmr);
            err = flash_generic_get_status(iface, &status);
            if (cb)
                cb();

            if (!err) {
                if (status & iface->flash_info->status.err_msk) {
                    err = FLASH_ERR_OP_FAILED;
                } else if ((status & iface->flash_info->status.rdy_msk)
                           ==iface->flash_info->status.rdy_val) {
                    rdy = 1;
                } else if (expired) {
                    err = FLASH_ERR_WAIT_RDY_TOUT;
                }

                if (last_status != NULL)
                    *last_status = status;
            }
        }
    }

    return err;
}




t_flash_errs flash_read_jedec_id(t_flash_iface *iface, t_flash_jedec_id *id) {
    unsigned char cmd = FLASH_CMD_CODE_READ_JEDEC_ID;
    return flash_exec_cmd(iface, &cmd, 1, NULL, 0, (unsigned char*)id, 4, FLASH_FLAGS_FLUSH);
}

t_flash_errs flash_read_jedec_id_ext(t_flash_iface *iface, t_flash_jedec_id_ext *id, unsigned edi_max_len) {
    unsigned char cmd = FLASH_CMD_CODE_READ_JEDEC_ID;
    return flash_exec_cmd(iface, &cmd, 1, NULL, 0, (unsigned char*)id, 4 + edi_max_len, FLASH_FLAGS_FLUSH);
}

t_flash_errs flash_id_cmp(const t_flash_info *info, const t_flash_jedec_id *id) {
    return ((id->manufacturer != info->id.manufacturer) ||
            (id->memory != info->id.memory) ||
            (id->capacity != info->id.capacity) ||
            ((info->id.edi_len != FLASH_ID_EDI_LEN_NOT_SUPPORTED) &&
                (id->edi_len != info->id.edi_len))) ? FLASH_ERR_INVALID_DEVICE : 0;

}

t_flash_errs flash_set(t_flash_iface *iface, const t_flash_info *info, t_flash_jedec_id_ext *rd_id) {
    t_flash_errs err = 0;

    iface->flash_info = info;
    if  (info->func.init != NULL) {
        err = info->func.init(iface);
    }

    if (!err) {
        err = flash_set_from_list(iface, &info, 1, rd_id);
    }
    return err;
}

t_flash_errs flash_set_from_list(t_flash_iface *iface, const t_flash_info **info_list, size_t info_cnt, t_flash_jedec_id_ext *rd_id) {
    t_flash_errs err = 0;
    t_flash_jedec_id_ext eid;

    if (!err) {
        err = flash_read_jedec_id_ext(iface, &eid, FLASH_ID_EDI_LEN_MAX);
    }

    if (!err) {
        if (rd_id != NULL) {
            *rd_id = eid;
        }

        if ((eid.id.manufacturer == 0xFF) && (eid.id.memory == 0xFF) && (eid.id.capacity == 0xFF)) {
            err = FLASH_ERR_NO_DEVICE;
        } else {
            int fnd = 0;
            size_t info_idx;
            for (info_idx = 0; !fnd && (info_idx < info_cnt); ++info_idx) {
                if (flash_id_cmp(info_list[info_idx], &eid.id) == 0) {
                    iface->flash_info = info_list[info_idx];
                    fnd = 1;
                }
            }

            if (!fnd) {
                err = FLASH_ERR_INVALID_DEVICE;
            }
        }
    }
    return err;
}




t_flash_errs flash_generic_write_enable(t_flash_iface *iface) {
    t_flash_errs err = 0;
    if (iface->flash_info->erase.cmd_code_wr_en) {
        err = flash_exec_cmd_only(iface, &iface->flash_info->erase.cmd_code_wr_en, 1, 0);
    }
    return err;
}


t_flash_errs flash_generic_read(t_flash_iface *iface, unsigned addr, unsigned char *data, size_t len) {
    t_flash_errs err = FLASH_ADDR_CHECK(iface, addr, len);
    if (!err) {
        err = flash_exec_cmd_addr(iface, iface->flash_info->read.cmd_code, addr, iface->flash_info->read.dummy_bytes,
                               NULL, data, len, FLASH_FLAGS_FLUSH);
    }
    return err;
}


t_flash_errs flash_generic_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) {
            err = flash_write_enable(iface);

            if (!err) {
                if (erase_cmd->flags & FLASH_ERASE_CMD_FLAG_NOADDR) {
                    err = flash_exec_cmd_only(iface, &erase_cmd->cmd_code, 1, 0);
                } else {
                    err = flash_exec_cmd_addr_only(iface, erase_cmd->cmd_code, addr, 0);
                }
            }

            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_generic_erase(t_flash_iface *iface, unsigned addr, size_t size) {
    return flash_generic_erase_cb(iface, addr, size, NULL);
}

t_flash_errs flash_generic_get_status(t_flash_iface *iface, unsigned char *status) {
    return flash_exec_cmd(iface, &iface->flash_info->status.cmd_code, 1, NULL,0, status,1, FLASH_FLAGS_FLUSH);
}


t_flash_errs flash_generic_write(t_flash_iface *iface, unsigned addr, const unsigned char *data, size_t len, unsigned flags) {
    t_flash_errs err = FLASH_ADDR_CHECK(iface, addr, len);

    while (len && !err) {
        size_t page_offs = addr & (iface->flash_info->progr.prog_size-1);
        size_t page_len  = iface->flash_info->progr.prog_size - page_offs;
        if (page_len > len)
            page_len = len;

        err = flash_write_enable(iface);

        if (!err) {
            err = flash_exec_cmd_addr(iface, iface->flash_info->progr.cmd_code, addr, 0,
                                      data, NULL, page_len, 0);
        }

        if (!err) {
            /* ожидание готовности к памяти к записи */
            err = flash_wait_ready_mks(iface, iface->flash_info->progr.prog_tout_mks, NULL);
        }

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

    flash_iface_flush(iface, err);

    return err;
}


t_flash_errs flash_exec_cmd_addr(t_flash_iface *iface, unsigned char cmd_code, unsigned addr, unsigned dummy_wrds,
                                 const unsigned char *snd_data, unsigned char *rcv_data, size_t size, int flags) {
    t_flash_errs err = FLASH_ERR_OK;
    int max_block = 0;
    unsigned char cmd[16];

    if (dummy_wrds > (sizeof(cmd) - 4)) {
        err = FLASH_ERR_INVALID_PARAMS;
    } else if (iface->max_transf_size != 0) {
        max_block =  iface->max_transf_size - 4 - dummy_wrds;
        if (max_block <= 0)
            err = FLASH_ERR_IFACE_PARAMS;
    }



    if (!err) {
        unsigned cmd_size = 4;
        unsigned rem_dummy_wrds = dummy_wrds;
        while (rem_dummy_wrds > 0) {
            cmd[cmd_size++] = 0xFF;
            rem_dummy_wrds--;
        }

        do {
            int block_size = (int)size;
            if ((max_block > 0) && (block_size > max_block))
                block_size = max_block;

            FLASH_FILL_CMD_WITH_ADDR(cmd, cmd_code, addr);


            err = flash_exec_cmd(iface, cmd, cmd_size,
                                 snd_data, (snd_data == NULL) ? 0 : block_size,
                                 rcv_data, (rcv_data == NULL) ? 0 : block_size, 0);
            if (!err) {
                addr += block_size;
                size -= block_size;
                if (snd_data != NULL)
                    snd_data += block_size;
                if (rcv_data != NULL)
                    rcv_data += block_size;
            }
        } while ((size != 0) && !err);
    }

    if (flags & FLASH_FLAGS_FLUSH) {
        flash_iface_flush(iface, err);
    }

    return err;
}




