#ifndef FLASH_H
#define FLASH_H

#include <stdlib.h>
#include "flash_errs.h"

#ifdef __cplusplus
extern "C" {
#endif


/** код для обозначания, что данная flash-память вообще не возвращает даже длину
   расширенной информации id */
#define FLASH_ID_EDI_LEN_NOT_SUPPORTED    0xFF
/** максимальный размер расширения id, поддерживаемый в данной библиотеке */
#define FLASH_ID_EDI_LEN_MAX             8

/** Коды команд, общих для большого числа вариантов памяти */
typedef enum {
    FLASH_CMD_CODE_WRITE_STATUS             = 0x01,
    FLASH_CMD_CODE_PAGE_PROGRAM             = 0x02,
    FLASH_CMD_CODE_READ                     = 0x03,
    FLASH_CMD_CODE_WRITE_DISABLE            = 0x04,
    FLASH_CMD_CODE_READ_STATUS              = 0x05,
    FLASH_CMD_CODE_WRITE_ENABLE             = 0x06,
    FLASH_CMD_CODE_FAST_READ                = 0x0B,
    FLASH_CMD_CODE_ERASE_4K                 = 0x20,
    FLASH_CMD_CODE_ERASE_32K                = 0x52,
    FLASH_CMD_CODE_READ_SFDP                = 0x5A, /* Read Serial Flash Discoverable Parameters */
    FLASH_CMD_CODE_ERASE_PAGE               = 0x81,
    FLASH_CMD_CODE_READ_JEDEC_ID            = 0x9F,
    FLASH_CMD_CODE_ERASE_CHIP               = 0xC7,
    FLASH_CMD_CODE_ERASE_64K                = 0xD8
} t_flash_gen_cmds;

typedef enum {
    FLASH_FLAGS_FLUSH       = 0x01,
} t_flash_flags;

typedef enum {
    FLASH_WR_FLAGS_WITH_ERASE  = 0x01
} t_flash_wr_flags;

typedef enum {
    FLASH_ERASE_CMD_FLAG_NOADDR    = 1 << 0
} t_flash_erase_cmd_flags;


/** Флаги для выполнения операции set_status_ex */
typedef enum {
    FLASH_SET_STATUS_FLAG_VOLATILE = 1 << 0  /**< Запись в volatile версию статусного регистра,
                                                  если flash поддерживает одновременно volatile и non-volatile
                                                  вариант */
} t_flash_set_status_flags;

struct st_flash_iface;


/** Описание команды стирания */
typedef struct {
    unsigned char  cmd_code; /**< Код команды */
    unsigned size;     /**< Размер блока, который стирается */
    unsigned time;    /**< Время выполнения команды в мс */
    unsigned flags;   /**< Флаги выполнения команды */
} t_flash_erase_cmd;

/** Описание области внутри памяти */
typedef struct {
    unsigned addr;     /**< Адрес начала региона */
    unsigned size;     /**< Размер региона */
} t_flash_region;




typedef t_flash_errs (*t_flash_iface_flush)(struct st_flash_iface *iface);
typedef t_flash_errs (*t_flash_iface_wait)(struct st_flash_iface *iface, unsigned tout_mks);

typedef t_flash_errs (*t_flash_exec_cmd)(struct st_flash_iface *iface, const unsigned char *cmd, size_t cmd_size,
                                         const unsigned char *snd_data, size_t snd_size,
                                         unsigned char *resp, size_t resp_size, int flags);



typedef t_flash_errs (*t_flash_dev_init)(struct st_flash_iface *iface);
typedef t_flash_errs (*t_flash_dev_erase)(struct st_flash_iface *iface, unsigned addr, size_t len);
typedef t_flash_errs (*t_flash_dev_erase_cb)(struct st_flash_iface *iface, unsigned addr, size_t len, void (*cb)(void));
typedef const t_flash_erase_cmd* (*t_flash_get_erase_cmd)(struct st_flash_iface *iface, unsigned addr, size_t size);
typedef t_flash_errs (*t_flash_dev_write)(struct st_flash_iface *iface, unsigned addr,
                                          const unsigned char *data, size_t len,
                                          unsigned flags);
typedef t_flash_errs (*t_flash_dev_write_cb)(struct st_flash_iface *iface, unsigned addr,
                                          const unsigned char *data, size_t len,
                                          unsigned flags, void (*cb)(void));
typedef t_flash_errs (*t_flash_dev_read)(struct st_flash_iface *iface, unsigned addr,
                                         unsigned char* data, size_t len);
typedef t_flash_errs (*t_flash_dev_read_cb)(struct st_flash_iface *iface, unsigned addr,
                                         unsigned char* data, size_t len, void (*cb)(void));
typedef t_flash_errs (*t_flash_dev_get_status)(struct st_flash_iface *iface, unsigned char *status);
typedef t_flash_errs (*t_flash_dev_write_enable)(struct st_flash_iface *iface);

typedef t_flash_errs (*t_flash_dev_read_uid)(struct st_flash_iface *iface, unsigned char *uid);

typedef t_flash_errs (*t_flash_dev_get_status_ex)(struct st_flash_iface *iface, unsigned *status);
typedef t_flash_errs (*t_flash_dev_set_status_ex)(struct st_flash_iface *iface, unsigned status, unsigned flags);

/* Разерешение механизма защиты секторов памяти. Если механизм всегда разрешен,
 * то функция должна просто вернуть FLASH_ERR_OK */
typedef t_flash_errs (*t_flash_dev_lock_enable)(struct st_flash_iface *iface);
/* Запрещает запись в указанную область. Если функция завершается успешно, то вся область с addr размером size
 * защищена от записи (при этом может быть защищена область большего размера, если
 * переданная область не кратна блокам разрешения/запрещения записи) */
typedef t_flash_errs (*t_flash_dev_lock)(struct st_flash_iface *iface, unsigned addr, unsigned size);
/* Разрешает запись в указанную область. Если функция завершается успешно, то вся область с addr размером size
 * доступна для записи (при этом может быть разрешена область большего размера, если
 * переданная область не кратна блокам разрешения/запрещения записи) */
typedef t_flash_errs (*t_flash_dev_unlock)(struct st_flash_iface *iface, unsigned addr, unsigned size);
/* Функция проверяет, защищен доступ к указанной области или нет.
 * сheck_ok возвращает результат проверки (1 - успешно, 0 - нет), в то время как сама функция возвращает
 * ошибки выполнянеия операции.
 * Если locked == 0, то проверка успешна, если вся область доступна для записи
 * Если locked == 1, то проверка успешна, если вся область защищена от записи
 * (это не взаимоисключающие условия, т.к. может быть часть области защищено, а часть нет */
typedef t_flash_errs (*t_flash_dev_lock_check)(struct st_flash_iface *iface, unsigned addr, unsigned size, int locked, int *check_ok);







typedef struct {
    unsigned char manufacturer;
    unsigned char memory;
    unsigned char capacity;
    unsigned char edi_len;
} t_flash_jedec_id;

typedef struct {
    t_flash_jedec_id id;
    unsigned char edi[FLASH_ID_EDI_LEN_MAX];
} t_flash_jedec_id_ext;


/** Описание flash-памяти */
typedef struct {
    t_flash_jedec_id id; /**< Идентификатор памят */
    unsigned total_size; /**< Полный размер памяти */
    struct {
        unsigned char cmd_code; /**< Код команды для чтения статуса */
        unsigned char rdy_msk; /**< Маска для выделения бита готовности из статуса */
        unsigned char rdy_val; /**< Значение, которое при наложении rdy_msk означает, что
                           готова к выполнению следующей операции */
        unsigned char err_msk; /**< Маска битов статуса, 1-ое значение которых означает ошибку */
    } status;

    struct {
        unsigned char cmd_code; /**< Код команды чтения */
        unsigned char dummy_bytes; /**< Количество пустых циклов чтения после команды */
    } read;

    /** Параметры для операции стирания содержимого */
    struct {
        /** Массив команд стирания в порядке возрастания размера стираемого блока.
         *  Должен оканчиваться элементом с кодом команды 0.
         *  Используется в стандартной реализации get_erase_cmd. При нестандартной
         *  реализации этой функции данное поле может быть равно NULL */
        const t_flash_erase_cmd *cmds;
        unsigned char cmd_code_wr_en; /**< Код команды для разрешения записи (0 - если не требуется) */
    } erase;

    /** Параметры для записи содержимого в память */
    struct {
        unsigned prog_size; /**< Размер записи одного блока */
        unsigned char cmd_code; /**< Код команды для записи блока */
        unsigned prog_tout_mks; /**< Время ожидания заверешния записи блока */
    } progr;

    /** Уникальный идентификатор UID */
    struct {
        int bitlen; /**< Размер UID в битах, 0 - если не поддерживается */
    } uid;

    /** Extended status */
    struct {
        unsigned char len;   /**< Длина расширенного статуса в байтах */
        unsigned valid_mask; /**< Маска действительных битов в статусе */
        unsigned nv_wr_tout; /**< Время записи non-volatile стутса */
    } status_ex;

    struct {
        t_flash_dev_init            init;
        t_flash_dev_get_status      get_status;
        t_flash_dev_read            read;
        t_flash_dev_erase           erase;
        t_flash_get_erase_cmd       get_erase_cmd;
        t_flash_dev_write           write;
        t_flash_dev_write_enable    write_enable;
        t_flash_dev_lock_enable     lock_enable;
        t_flash_dev_lock            lock;
        t_flash_dev_unlock          unlock;
        t_flash_dev_lock_check      lock_check;
        /* варианты функций с периодическим вызовом cb для возможности сбросить wdt и т.п. */
        t_flash_dev_read_cb         read_cb;
        t_flash_dev_write_cb        write_cb;
        t_flash_dev_erase_cb        erase_cb;

        t_flash_dev_read_uid        read_uid;
        t_flash_dev_get_status_ex   get_status_ex;
        t_flash_dev_set_status_ex   set_status_ex;
    } func;

    const void *devspec; /**< Специфическая для данного устройства информация, не доступная явно, но используемая в функциях */
} t_flash_info;


/** описание интерфейса для передачи команд flash-памяти */
typedef struct st_flash_iface {
    const t_flash_info *flash_info; /**< Информация о подключенной flash-памяти */
    void *port_data;                /**< Непрозрачная структура с параметрами порта */
    const void *internal_data;            /**< Непрозрачная указатель, используемый в реализациях flash (не должен изменяться портом) */
    unsigned max_transf_size;       /**< Максимальный размер передваемой команды
                                         по интерфейсу (0 - если не ограничен) */
    unsigned port_exec_tout_ms;        /**< Таймаут для выполнение обмена, вносимый портом  */
    unsigned wait_treshold_us;     /**< Имеет значение при действительной функции wiat.
                                         Если время ожидания меньше wait_treshold_mks,
                                         то для ожидания завершения операции используется wiat,
                                         иначе - используется опрос статуса памяти */
    t_flash_exec_cmd       exec_cmd; /**< Выполнение команды */
    t_flash_iface_flush    flush;    /**< Если не NULL, то вызывается в конце операций и
                                          в ней нобходимо принять все непринятые слова,
                                          чтобы можно было выполнять другие операции,
                                          не связанные с этой памятью */
    t_flash_iface_wait     wait;     /**  Если не NULL, то используется для
                                          ожидания завершения операции вместо
                                          опроса статуса, если время операции
                                          меньше wait_treshold_mks */
} t_flash_iface;



#define FLASH_ADDR_CHECK(iface, addr, len) ((((addr) + (len)) > iface->flash_info->total_size) \
    ? FLASH_ERR_ADDR_OUT_OF_MEM : 0)

t_flash_errs flash_read_jedec_id(t_flash_iface *iface, t_flash_jedec_id *id);


t_flash_errs flash_read_jedec_id_ext(t_flash_iface *iface, t_flash_jedec_id_ext *id, unsigned edi_max_len);

/***************************************************************************//**
    Назначение указанной flash-памяти интерфейсу. Функция читает jedec-id памяти
    и проверяет, соответствует ли этот id указанному интерфейсу. Если соответствует,
    то сохраняет информацию о памяти в интерфейсе и после этого можно использовать
    данную flash-память. Так как предполагается, что при этой функции точно известно,
    какая flash-память установлена, то данная функция может выполнить специфические
    для данной flash-памяти операции перед чтением id, если может потребоваться
    вывести flash из какого-либо режима для корректного чтения id
    @param[in,out]  iface       Интерфейс к flash-памяти.
    @param[in]      info        Структура с информацией о flash-памяти, которая
                                должна быть доступна через указанный интерфейс.
    @param[out]     rd_id       Прочитанный идентификатор flash-памяти (позволяет его узнать
                                в том числе, если он не соответствует одной из найденных
                                памятей).
    @return                     Код ошибки
*******************************************************************************/
t_flash_errs flash_set(t_flash_iface *iface, const t_flash_info *info, t_flash_jedec_id_ext *rd_id);

/***************************************************************************//**
    Назначение интерфейсу одной flash-памяти из указанного списка.
    Функция читает jedec-id памяти и выбирает по id память, которая ему соответсвтует.
    Так как предполагается, что при этой функции точно не известно,
    какая flash-память установлена, то данная функция не выполняет никаких действий
    до чтения id
    @param[in,out]  iface       Интерфейс к flash-памяти.
    @param[in]      info_list   Массив с указателями на структура с информацией о
                                модулях flash-памяти, из которых нужно определить,
                                какая flash-память подключена к интерфейсу.
    @param[in]      info_cnt    Количество элементов в массиве info_list.
    @param[out]     rd_id       Прочитанный идентификатор flash-памяти (позволяет его узнать
                                в том числе, если он не соответствует одной из найденных
                                памятей).
    @return                     Код ошибки
*******************************************************************************/
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);
/***************************************************************************//**
    Проверка, соответствует ли указанный id заданной flash-памяти.
    @param[in]      info        Структура с информацией о flash-памяти
    @param[in]      id          Структура с jedec-id
    @return                     Код ошибки (0 - если соответствует)
*******************************************************************************/
t_flash_errs flash_id_cmp(const t_flash_info *info, const t_flash_jedec_id *id);

/* Стандартные реализации функций, которые могут быть использованы в тех устройствах,
   где алгоритм не отличается от общего */
t_flash_errs flash_generic_read(t_flash_iface *iface, unsigned addr, unsigned char* data, size_t len);
t_flash_errs flash_generic_erase_cb(t_flash_iface *iface, unsigned addr, size_t size, void (*cb)(void));
t_flash_errs flash_generic_erase(t_flash_iface *iface, unsigned addr, size_t size);
t_flash_errs flash_generic_write(t_flash_iface *iface, unsigned addr, const unsigned char* data, size_t len, unsigned flags);
t_flash_errs flash_generic_get_status(t_flash_iface *iface, unsigned char *status);
t_flash_errs flash_generic_write_enable(t_flash_iface *iface);
const t_flash_erase_cmd* flash_generic_get_erase_cmd(t_flash_iface *iface, unsigned addr, size_t size);

int flash_check_erase_cmd(const t_flash_erase_cmd* cmd, unsigned addr, size_t size);

/* макросы для вызова реализаций функций для указанного интерфейса и устройства */
#define flash_read(iface, addr, data, len)                  (iface)->flash_info->func.read(iface, addr, data, len)
#define flash_write(iface, addr, data, len, flags)          (iface)->flash_info->func.write(iface, addr, data, len, flags)
#define flash_erase(iface, addr, len)                       (iface)->flash_info->func.erase(iface, addr, len)
#define flash_get_status(iface, status)                     (iface)->flash_info->func.get_status(iface, status)
#define flash_write_enable(iface)                           (iface)->flash_info->func.write_enable(iface)
#define flash_lock_enable(iface)                            (iface)->flash_info->func.lock_enable(iface)
#define flash_lock(iface, addr, size)                       (iface)->flash_info->func.lock(iface, addr, size)
#define flash_unlock(iface, addr, size)                     (iface)->flash_info->func.unlock(iface, addr, size)
#define flash_lock_check(iface, addr, size, locked, check_ok) \
        (iface)->flash_info->func.lock_check(iface, addr, size, locked, check_ok)

#define flash_read_cb(iface, addr, data, len, cb)           (iface)->flash_info->func.read_cb(iface, addr, data, len, cb)
#define flash_write_cb(iface, addr, data, len, flags, cb)   (iface)->flash_info->func.write_cb(iface, addr, data, len, flags, cb)
#define flash_erase_cb(iface, addr, len, cb)                (iface)->flash_info->func.erase_cb(iface, addr, len, cb)
#define flash_read_uid(iface, uid)                          (iface)->flash_info->func.read_uid(iface, uid)

#define flash_get_status_ex(iface, pstatus)                 (iface)->flash_info->func.get_status_ex(iface, pstatus)
#define flash_set_status_ex(iface, status, flags)           (iface)->flash_info->func.set_status_ex(iface, status, flags)


unsigned flash_get_min_erase_size(const t_flash_info *flash);




#define flash_exec_cmd(iface, cmd, cmd_size, snd_data, snd_size, resp, resp_size, flags) iface->exec_cmd(iface, cmd, cmd_size, snd_data, snd_size, resp, resp_size, flags)
#define flash_exec_cmd_only(hnd, cmd, cmd_size, flags) flash_exec_cmd(hnd, cmd, cmd_size, NULL, 0, NULL, 0, flags)

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);
#define flash_exec_cmd_addr_only(iface, cmd_code, addr, flags)       flash_exec_cmd_addr(iface, cmd_code, addr, 0, NULL, NULL, 0, flags)
#define flash_exec_cmd_read_sfdp(iface, addr, data, size, flags)     flash_exec_cmd_addr(iface, FLASH_CMD_CODE_READ_SFDP, addr, 1, NULL, data, size, flags)

#define flash_wait_ready(iface, tout, status) flash_wait_ready_mks(iface, tout*1000, status)
#define flash_wait_ready_mks(iface, tout, status) flash_wait_ready_mks_cb(iface, tout, status, NULL)
#define flash_wait_ready_cb(iface, tout, status, cb) flash_wait_ready_mks_cb(iface, tout*1000, status, cb)
t_flash_errs flash_wait_ready_mks_cb(t_flash_iface *iface, unsigned tout_mks, unsigned char *last_status, void (*cb)(void));



#define flash_iface_flush(iface, cur_err) do { \
        if ((iface)->flush != NULL) { \
            t_flash_errs flush_err = (iface)->flush(iface); \
            if (!cur_err) \
                cur_err = flush_err; \
        } \
    } while(0)

#ifdef __cplusplus
}
#endif

#endif // FLASH_H
