#include "ltrmodule_at93c86a.h"
#include "ltrmodule.h"

#define CMD_IO_ROM                                     (LTR010CMD|0x60)
/* макс. время записи - 10 мс, скорость интерфейса 500 КСлов/c (2 мкс на слово),
 * одна команда - 32 слова => 10000 / (32 * 2) = 156.25 команд */
#define FLASH_WRITE_DUMMY_CYCLES                       157
#define FLASH_MAX_BATCH_WRDS                           33

/* команды FLASH-памяти CAT93C86 из 14 бит - старший - стартовая 1,
    далее 2-4 бита - опкод, далее для READ/WRITE/ERASE 11-битный адрес */
#define AT93C86A_FLASH_CMD_READ      (0x3000) /* Чтение              - 10 + Адрес */
#define AT93C86A_FLASH_CMD_ERASE     (0x3800) /* Стирание            - 11 + Адрес */
#define AT93C86A_FLASH_CMD_WRITE     (0x2800) /* Запись              - 01 + Адрес */
#define AT93C86A_FLASH_CMD_EWEN      (0x2600) /* Разрешение записи   - 0011 */
#define AT93C86A_FLASH_CMD_EWDS      (0x2000) /* Запрет записи       - 0000 */
#define AT93C86A_FLASH_CMD_ERAL      (0x2400) /* Clear All Addresses - 0010 */
#define AT93C86A_FLASH_CMD_WRAL      (0x2200) /* Write All Addresses - 0001 */

#define AT93C86A_FLASH_CMD_SIZE          14
#define AT93C86A_FLASH_ADDR_SIZE         11

#define AT93C86A_FLASH_SIZE            2048


static INT f_at93c86a_cmd_send(TLTR *ltr, DWORD cmd, DWORD cmd_size, INT deselect) {
    /* на каждый бит по команде => максимум можем передать 32 бита в 32 командах
       + 1 команда для снятия CS */
    DWORD wrds[FLASH_MAX_BATCH_WRDS];
    DWORD i, msk;
    for (i=0, msk = 1 << (cmd_size ? cmd_size - 1 : 0); i < cmd_size; i++, msk >>= 1)     {
        wrds[i] = CMD_IO_ROM;
        /* младший бит кода команды - состояние DI для flash */
        if (cmd & msk)
            wrds[i] |= 1;
        wrds[i] |= 0x2; /* второй бит команды - состояние линии CS
                           (1 - память выбрана) */
    }

    if (deselect) {
        wrds[cmd_size++] = CMD_IO_ROM; /* возвращаем CS в 0 */
    }

    return ltr_module_send_cmd(ltr, wrds, cmd_size);
}

static INT f_at93c86a_cmd_send_dummy_nosel(TLTR *ltr, DWORD size) {
    /* на каждый бит по команде => максимум можем передать 32 бита в 32 командах
       + 1 команда для снятия CS */
    DWORD wrds[FLASH_MAX_BATCH_WRDS];
    DWORD i;
    for (i=0; i < size; i++)     {
        wrds[i] = CMD_IO_ROM;
    }
    return ltr_module_send_cmd(ltr, wrds, size);
}



static INT f_at93c86a_flash_cmd_ack_rcv(TLTR *ltr, DWORD cmd_size, INT deselect,
                              DWORD *ack) {
    /* на каждый бит по команде => максимум можем передать 32 бита в 32 командах
       + 1 команда для снятия CS */
    DWORD wrds[sizeof(DWORD)*8+2];
    DWORD i, resp, recv_size = cmd_size;
    INT res = LTR_OK;

    if (deselect) {
        recv_size++;
    }

    res = ltr_module_recv_cmd_resp(ltr, wrds, recv_size);

    /* проверяем овет на команду */
    for (i = 0, resp = 0; (res == LTR_OK) && (i < recv_size); i++) {
        if ((wrds[i] & 0xF0FF) != CMD_IO_ROM) {
            res = LTR_ERROR_INVALID_CMD_RESPONSE;
        } else if (i < cmd_size) {
            resp<<=1;
            /* в младшем бите данных приходит состояние линии */
            if (wrds[i] & 0x10000)
                resp |= 1;
        }
    }

    if ((res == LTR_OK) && (ack != NULL)) {
        *ack = resp;
    }
    return res;
}


INT ltr_at93c86a_read(TLTR *ltr, WORD addr, BYTE *data, WORD size) {
    WORD i;
    INT res = LTR_OK;
    INT dis_res = LTR_OK;
    DWORD cmd_ack = 0;


    if ((res == LTR_OK) && (((addr + size) > AT93C86A_FLASH_SIZE) || (data == NULL)))
        res = LTR_ERROR_PARAMETERS;

    /* делаем явный сброс cs, на случай, если он был оставлен в некорректном сотоянии */
    if (res == LTR_OK)
        res = f_at93c86a_cmd_send_dummy_nosel(ltr, 1);

    if (res == LTR_OK) {
        /* Выдаем одну команду чтения с адресом */
        res = f_at93c86a_cmd_send(ltr, AT93C86A_FLASH_CMD_READ | addr,
                           AT93C86A_FLASH_CMD_SIZE, 0);
    }

    for (i = 0; (res == LTR_OK) && (i < size); i++) {
        res = f_at93c86a_cmd_send(ltr, 0, 8, 0);
    }

    /* снимаем CS не зависимо от результата чтения */
    dis_res = f_at93c86a_cmd_send(ltr, 0, 0, 1);
    if (res == LTR_OK)
        res = dis_res;

    /* принимаем ответы - сперва на команду чтения, затем на данные, затем
       на снятие select */
    if (res == LTR_OK)
        res = f_at93c86a_flash_cmd_ack_rcv(ltr, 0, 1, NULL);
    if (res==LTR_OK)
        res = f_at93c86a_flash_cmd_ack_rcv(ltr, AT93C86A_FLASH_CMD_SIZE, 0, &cmd_ack);

    for (i = 0; (res == LTR_OK) && (i < size); i++) {
        DWORD ack;
        /* читаем каждый байт последовательно, не снимая CS */
        res = f_at93c86a_flash_cmd_ack_rcv(ltr, 8, 0, &ack);
        if (res == LTR_OK) {
            data[i] = ack & 0xFF;
        }
    }

    if (res == LTR_OK)
        res = f_at93c86a_flash_cmd_ack_rcv(ltr, 0, 1, NULL);

    /* на последний бит команды чтения память должна ответить нулевым уровнем.
     * Его наличие можно использовать как признак наличия памяти */
    if ((res == LTR_OK) && ((cmd_ack & 1) != 0)) {
        res = LTR_ERROR_FLASH_NOT_PRESENT;
    }

    return res;
}


/** @todo Для больших записей/чтений рассмотреть сделать ли их по порциям
    @todo При ошибке при записи мб сделать, чтобы вычитывались все ответы на
          успешные команды (хотя скорее всего все равно в этом случае сброс нужен) */
INT ltr_at93c86a_write(TLTR *ltr, WORD addr, const BYTE *data, WORD size) {
    WORD i;
    INT res = LTR_OK;

    if ((res == LTR_OK) && (((addr + size) > AT93C86A_FLASH_SIZE) || (data == NULL)))
        res = LTR_ERROR_PARAMETERS;

    /* делаем явный сброс cs, на случай, если он был оставлен в некорректном сотоянии */
    if (res == LTR_OK)
        res = f_at93c86a_cmd_send_dummy_nosel(ltr, 1);

    if (res == LTR_OK) {
        /* Передаем команду разрешения записи в ППЗУ */
        res = f_at93c86a_cmd_send(ltr, AT93C86A_FLASH_CMD_EWEN, AT93C86A_FLASH_CMD_SIZE, 1);
    }

    if (res == LTR_OK) {
        INT dis_res = LTR_OK;
        for (i = 0; (res == LTR_OK) && (i < size); i++) {
            WORD k;
            res = f_at93c86a_cmd_send(ltr, ((DWORD)(AT93C86A_FLASH_CMD_WRITE | (addr + i)) << 8)
                                   | data[i], AT93C86A_FLASH_CMD_SIZE + 8, 1);
            for (k = 0; (k < FLASH_WRITE_DUMMY_CYCLES) && (res == LTR_OK); k++)
                res = f_at93c86a_cmd_send_dummy_nosel(ltr, 32);
            if (res == LTR_OK)
                res = dis_res;
        }

        /* запрещаем запись во FLASH */
        dis_res = f_at93c86a_cmd_send(ltr, AT93C86A_FLASH_CMD_EWDS, AT93C86A_FLASH_CMD_SIZE, 1);
        if (res == LTR_OK)
            res = dis_res;
    }

    /* принимаем ответы на все посланные выше команды */
    if (res == LTR_OK)
        res = f_at93c86a_flash_cmd_ack_rcv(ltr, 0, 1, NULL);
    if (res == LTR_OK)
        res = f_at93c86a_flash_cmd_ack_rcv(ltr, AT93C86A_FLASH_CMD_SIZE, 1, NULL);
    for (i=0; (res == LTR_OK) && (i < size); i++) {
        WORD k;
        res = f_at93c86a_flash_cmd_ack_rcv(ltr, AT93C86A_FLASH_CMD_SIZE+8, 1, NULL);
        for (k = 0; (k < FLASH_WRITE_DUMMY_CYCLES) && (res == LTR_OK); k++)
            res = f_at93c86a_flash_cmd_ack_rcv(ltr, 32, 0, NULL);
     }
     if (res == LTR_OK)
         res = f_at93c86a_flash_cmd_ack_rcv(ltr, AT93C86A_FLASH_CMD_SIZE, 1, NULL);

     return res;
}
