#include "sst25.h"
#include "byteblaster.h"
#include "timer.h"

#define MF_ID_SST            0xBF /**< SST Manufacturer ID - читается с флешки при RDID */
#define DEV_ID_SST25VF016B   0x41 /**< Device ID - читается с флешки при RDID */


/** команды флеш памяти */
typedef enum
{
    SST25_OPCODE_READ       = 0x03, /**< чтение на частоте до 25МГц */
    SST25_OPCODE_READ_HS    = 0x0B, /**< чтение на частоте до 80МГц */
    SST25_OPCODE_ERASE_4    = 0x20, /**< стирание сектора на 4КБ */
    SST25_OPCODE_ERASE_32   = 0x52, /**< стирание 32КБ */
    SST25_OPCODE_ERASE_64   = 0xD8, /**< стирание 64КБ */
    SST25_OPCODE_ERASE_CHIP = 0x60, /**< стирание всего чика */
    SST25_OPCODE_BYTE_PROGR = 0x02, /**< запись одного байта */
    SST25_OPCODE_AAI_PORGR  = 0xAD, /**< запись слова с автоувеличением адреса */
    SST25_OPCODE_RDSR       = 0x05, /**< чтение статусного регистра */
    SST25_OPCODE_EWSR       = 0x50, /**< разрешение записи статусного регистра */
    SST25_OPCODE_WRSR       = 0x01, /**< запись статусного регистра */
    SST25_OPCODE_WREN       = 0x06, /**< разрешение записи во флеш */
    SST25_OPCODE_WRDI       = 0x04, /**< запрещение записи */
    SST25_OPCODE_RDID       = 0x90, /**< чтение ID */
    SST25_OPCODE_JEDEC_ID   = 0x9F, /**< чтение JEDEC ID */
    SST25_OPCODE_EBSY       = 0x70, /**< разрешение выдачи на SO BYSY */
    SST25_OPCODE_DBSY       = 0x80  /**< запрет выдачи на SO BYSY */
} t_sst25_opcodes;


/** Запись байта вместе с чтением по SPI-интрфейсу через байтбластер */
static int f_rw_spi(int port, int byte)
{
    int res = 0;
    int i;
    for (i=0; i <8; i++)
    {
        int bit = byte&(1<<(7-i)) ? BB_BIT_TDI : 0;
        byteblaster_write(port, bit);
        bit = byteblaster_write(port, bit | BB_BIT_TCK);
        if (bit)
            res |= (1<<(7-i));
    }
    return res;
}




static void f_sst25_enable(int port)
{
    byteblaster_write(port, 0);
}

static void f_sst25_disable(int port)
{
    byteblaster_write(port, BB_BIT_TMS);
}

static void f_sst25_read_id(int port, int* mnf_id, int* dev_id)
{
    int tst = 0;
    f_rw_spi(port, SST25_OPCODE_RDID);
    f_rw_spi(port, 0);
    f_rw_spi(port, 0);
    f_rw_spi(port, 0);
    *mnf_id = f_rw_spi(port, 0);

    f_rw_spi(port, SST25_OPCODE_RDID);
    f_rw_spi(port, 0);
    f_rw_spi(port, 0);
    f_rw_spi(port, 1);
    *dev_id = f_rw_spi(port, 0);
    
    f_sst25_disable(port);
}

static void f_sst25_write_enable(int port)
{
    f_rw_spi(port, SST25_OPCODE_WREN);
    f_sst25_disable(port);
}

static void f_sst25_write_disable(int port)
{
    f_rw_spi(port, SST25_OPCODE_WRDI);
    f_sst25_disable(port);
}

static int f_sst25_read_status(int port)
{
    int status = 0;
    f_rw_spi(port, SST25_OPCODE_RDSR);
    status = f_rw_spi(port, 0);
    f_sst25_disable(port);
    return status;
}

static void f_sst25_write_status(int port, int status)
{
    f_rw_spi(port, SST25_OPCODE_EWSR);
    f_sst25_disable(port);

    f_rw_spi(port, SST25_OPCODE_WRSR);
    f_rw_spi(port, status);
    f_sst25_disable(port);
}


static void f_sst25_read(int port, int addr, char* buf, int size)
{
    int i;
    f_rw_spi(port, SST25_OPCODE_READ);
    f_rw_spi(port, (addr & 0xFF0000)>>16);
    f_rw_spi(port, (addr & 0xFF00)>>8);
    f_rw_spi(port, addr & 0xFF);
    for (i=0; i < size; i++)
        buf[i] = f_rw_spi(port,0);
    f_sst25_disable(port);
}



static int f_sst25_wt_rdy(int port)
{
    int err = 0;
    int status = f_sst25_read_status(port);
    t_timer tmr;
    timer_set(&tmr, 3000*CLOCK_CONF_SECOND/1000);
    while ((status & SST25_STATUS_BUSY) & !timer_expired(&tmr))
    {
        status = f_sst25_read_status(port);
    }

    if (status & SST25_STATUS_BUSY)
        err = SST25_ERR_WRITE_TOUT;
    return err;
}

static int f_sst25_check_aai(int port)
{
    int err = 0;
    int status = f_sst25_read_status(port);
    if ((status & (SST25_STATUS_AAI | SST25_STATUS_WEL))!=
        (SST25_STATUS_AAI | SST25_STATUS_WEL))
    {
        err = SST25_ERR_INVALID_WRITE_STATUS;
    }
    else if (status & SST25_STATUS_BUSY)
        err = f_sst25_wt_rdy(port);
    return err;
}


static int f_sst25_write_ai_first(int port, int addr, int byte0, int byte1)
{
    f_sst25_write_enable(port);

    f_rw_spi(port, SST25_OPCODE_AAI_PORGR);
    f_rw_spi(port, (addr & 0xFF0000)>>16);
    f_rw_spi(port, (addr & 0xFF00)>>8);
    f_rw_spi(port, addr & 0xFF);
    f_rw_spi(port, byte0);
    f_rw_spi(port, byte1);
    f_sst25_disable(port);

    return f_sst25_check_aai(port);    
}


static int f_sst25_write_ai_next(int port, int byte0, int byte1)
{
    f_rw_spi(port, SST25_OPCODE_AAI_PORGR);
    f_rw_spi(port, byte0);
    f_rw_spi(port, byte1);
    f_sst25_disable(port);

    return f_sst25_check_aai(port);
}


static int f_sst25_write_ai_stop(int port)
{
    f_sst25_write_disable(port);

    return f_sst25_wt_rdy(port);
}


static int f_sst25_erase(int port, int addr, int cmd)
{
    f_sst25_write_enable(port);

    f_rw_spi(port, cmd);
    f_rw_spi(port, (addr & 0xFF0000)>>16);
    f_rw_spi(port, (addr & 0xFF00)>>8);
    f_rw_spi(port, addr & 0xFF);
    f_sst25_disable(port);

    return f_sst25_wt_rdy(port);
}



    







/** Инициализация работы с флешкой - разрешается ByteBlaster, заполняются поля описателя,
    читается ID флешки и проверяется статус 
    @param[out] hnd   Описатель, который заполняется при успешном завершении
    @param[in]  port  Aдрес начало области в пространстве IO для порта 
    @return         Код ошибки  */
int sst25_init(t_sst25* hnd, int port)
{
    int err = 0;
    if (hnd)
    {
        hnd->lpt_io = port;



        /* разрешаем выходной регистр ByteBlaster и устанавливаем начальные неактивные уровни */
        byteblaster_init(port);

        hnd->status = f_sst25_read_status(hnd->lpt_io);

        /* если была незконченная операция AAI - останавливаем ее */
        if (hnd->status & (SST25_STATUS_AAI | SST25_STATUS_WEL))
        {
            f_sst25_write_ai_stop(hnd->lpt_io);
        }

        /* читаем ID памяти, чтобы убедиться, что это то что нужно */
        f_sst25_read_id(hnd->lpt_io, &hnd->mf_id, &hnd->dev_id);

        if (hnd->mf_id != MF_ID_SST)
            err = SST25_ERR_UNSUP_MF_ID;
        else if (hnd->dev_id != DEV_ID_SST25VF016B)
            err = SST25_ERR_UNSUP_DEV_ID;

        if (!err)
            hnd->status = f_sst25_read_status(hnd->lpt_io);

        /* если была незконченная операция AAI - останавливаем ее */
        if (hnd->status & (SST25_STATUS_AAI | SST25_STATUS_WEL))
        {
            f_sst25_write_ai_stop(hnd->lpt_io);
        }
    }
    else
    {
        err = SST25_ERR_INVALID_HANDLE;
    }
    return err;
}


int sst25_set_protection(t_sst25* hnd, int protect_code)
{
    int err = 0;
    if (hnd)
    {
        int status = f_sst25_read_status(hnd->lpt_io);
        
        status &= ~(SST25_STATUS_BP0 | SST25_STATUS_BP1 | SST25_STATUS_BP2 | SST25_STATUS_BP3);
        protect_code &= 0xF;
        status |= (protect_code << 2);
        
        f_sst25_write_status(hnd->lpt_io, status);

    }
    else err = SST25_ERR_INVALID_HANDLE;
    return err;
}

int sst25_read(t_sst25* hnd, int addr, char* buf, int size)
{
    int err =0;
    if (hnd)
    {
        f_sst25_read(hnd->lpt_io, addr, buf, size);
    }
    else err = SST25_ERR_INVALID_HANDLE;
    return err;
}


int sst25_write(t_sst25* hnd, int addr, const char* buf, int size)
{
    int err =0;
    if (hnd)
    {
        int stoperr = 0;
        err = sst25_write_start(hnd, addr, buf, size);

        stoperr = sst25_write_stop(hnd);
        if (!err)
            err = stoperr;
    }
    else err = SST25_ERR_INVALID_HANDLE;
    return err;
}

int sst25_write_start(t_sst25* hnd, int addr, const char* buf, int size)
{
    int err =0;
    if (hnd)
    {
        int i, status;
            
        status = f_sst25_read_status(hnd->lpt_io);
        /* если была незконченная операция AAI - останавливаем ее */
        if (hnd->status & (SST25_STATUS_AAI | SST25_STATUS_WEL))
        {
            f_sst25_write_ai_stop(hnd->lpt_io);
        }


        err = f_sst25_write_ai_first(hnd->lpt_io, addr, buf[0], buf[1]);

        for (i=2; ((i+1) < size) && !err; i+=2)
            err = f_sst25_write_ai_next(hnd->lpt_io, buf[i], buf[i+1]);
    }
    else err = SST25_ERR_INVALID_HANDLE;
    return err;
}


int sst25_write_next(t_sst25* hnd, const char* buf, int size)
{
    int err=0, i=0;
    if (hnd)
    {
        for (i=0; ((i+1) < size) && !err; i+=2)
            err = f_sst25_write_ai_next(hnd->lpt_io, buf[i], buf[i+1]);
    }
    else err = SST25_ERR_INVALID_HANDLE;
    return err;
}
int sst25_write_stop(t_sst25* hnd)
{
    return hnd ? f_sst25_write_ai_stop(hnd->lpt_io) : SST25_ERR_INVALID_HANDLE;
}




int sst25_erase_chip(t_sst25* hnd)
{
    int err = 0;
    if (hnd)
    {
        f_sst25_write_enable(hnd->lpt_io);

        f_rw_spi(hnd->lpt_io, SST25_OPCODE_ERASE_CHIP);
        f_sst25_disable(hnd->lpt_io);

        err = f_sst25_wt_rdy(hnd->lpt_io);
    } else err = SST25_ERR_INVALID_HANDLE;
    return err;
}

int sst25_erase_4k(t_sst25* hnd, int addr)
{
    int err = 0;
    if (hnd)
    {
        err = f_sst25_erase(hnd->lpt_io, addr, SST25_OPCODE_ERASE_4);
    } else err = SST25_ERR_INVALID_HANDLE;
    return err;
}

int sst25_erase_32k(t_sst25* hnd, int addr)
{
    int err = 0;
    if (hnd)
    {
        err = f_sst25_erase(hnd->lpt_io, addr, SST25_OPCODE_ERASE_32);
    } else err = SST25_ERR_INVALID_HANDLE;
    return err;
}

int sst25_erase_64k(t_sst25* hnd, int addr)
{
    int err = 0;
    if (hnd)
    {
        err = f_sst25_erase(hnd->lpt_io, addr, SST25_OPCODE_ERASE_64);
    } else err = SST25_ERR_INVALID_HANDLE;
    return err;
}




void sst25_close(t_sst25* hnd)
{
    f_sst25_disable(hnd->lpt_io);
    byteblaster_close(hnd->lpt_io);
}



static const char* f_errs[] = 
{
    "Success",
    "Invalid SST25 handle",
    "Unsupported manufacturer ID",
    "Unsupported device ID",
    "Invalid write status",
    "Write timeout expired"
};

const char* sst25_get_err_string(int err)
{
    if ((err <= 0) && (err > -(int)(sizeof(f_errs)/sizeof(f_errs[0]))))
        return f_errs[-err];
    else
        return "Unknown error code";
}