/**
 *  Sega Saturn Cartridge Firmware Flash Utility
 *  by cafe-alpha.
 *  WWW: http://ppcenter.free.fr/pskai/
 *
 *  Made from "Sega Saturn cartridge flash tool", by Anders Montonen.
 *
 *  See LICENSE file for details.
**/

#include <stdlib.h> /* For NULL */
#include <string.h> /* For memset,memcpy */

#include "vdp2.h"
#include "flash.h"


/* ------------------------------------------------------------ */
/* SBL timer library is used in order to measure time interval. */
#include "SEGA_TIM.H"

void tmr_start(void)
{
// From sega_tim.h :
 // * NAME:    TIM_FRT_INIT            - FRT初期化 ("init free timer")
 // *
 // * PARAMETERS :
 // *      (1) Uint32 mode             - <i>   分周数 (parameter1: clock divider)
// #define TIM_B_CKS0      (0)                             /* CKS0              */
//  #define TIM_CKS_8       ( 0 << TIM_B_CKS0) -> 0             /* 8 カウント        */
//  #define TIM_CKS_32      ( 1 << TIM_B_CKS0) -> 1             /* 32カウント        */
//  #define TIM_CKS_128     ( 2 << TIM_B_CKS0) -> 2             /* 128カウント       */
//  #define TIM_CKS_OUT     ( 3 << TIM_B_CKS0) -> 3             /* 外部カウント      */


    TIM_FRT_INIT(TIM_CKS_32); // TIM_CKS_8, TIM_CKS_32 or TIM_CKS_128
    TIM_FRT_SET_16(0);
}

unsigned long tmr_tick2usec(unsigned long tick)
{
    Float32 f = TIM_FRT_CNT_TO_MCR(tick);
    return ((unsigned long)(f));
}

void tmr_tick_sleep(unsigned short tick)
{
    tmr_start();
    while(TIM_FRT_GET_16() < tick);
}

void tmr_usleep(unsigned long usec)
{
    /* Convert delay to tick value. */
    /* Note : can't sleep for delay over than 50 msec. */
    unsigned long delay_tick = delay_tick = (usec*60000) / (tmr_tick2usec(60000));

    /* Sleep at most 60000 ticks. */
    unsigned long i;
    for(i=0; i<delay_tick; i+=60000)
    {
        unsigned long s = ((i+60000) < delay_tick ? 60000 : delay_tick - i);
        tmr_tick_sleep(s);
    }
}

/* Wait delay before 2 page write, in usec unit. */
#define WAIT_DELAY (30*1000)



#define ID_ENTRY    0x9090
#define ID_EXIT     0xF0F0
#define PAGEWRITE   0xA0A0

/* First line : vendor ID strings. */
static const char NoVendorString[]      = "No Vendor ID";
static const char UnknownVendorString[] = "Unknown     ";
static const char SSTVendorString[]     = "SST         ";
static const char ATVendorString[]      = "Atmel       ";
static const char AMDVendorString[]     = "AMD         ";

/* Second line : device ID strings. */
static const char NoDeviceString[]            = "No Device ID";
static const char UnknownDeviceString[]       = "Unknown     ";
static const char SST39SF010ADeviceString[]   = "SST39SF010A ";
static const char SST39SF020ADeviceString[]   = "SST39SF020A ";
static const char SST39SF040DeviceString[]    = "SST39SF040  ";
static const char SST29EE010DeviceString[]    = "SST29EE010  "; // DeviceID == 0x0707
static const char SST29LE010DeviceString[]    = "SST29LE010  "; // DeviceID == 0x0808
static const char SST29EE020DeviceString[]    = "SST29EE020  "; // DeviceID == 0x1010
static const char SSTGreenliantDeviceString[] = "Greenliant  "; // DeviceID == 0x1212
static const char SST29xxxDeviceString[]      = "SST29-like  "; // Other
static const char AT29C010DeviceString[]      = "AT29C010    ";
static const char AM29F010DeviceString[]      = "AM29F010    ";

static volatile unsigned short* const ADDR_FLASH = (unsigned short*)0x22000000;
static volatile unsigned short* const ADDR_55    = (unsigned short*)0x2200AAAA; /* The flash sees this as 0x5555 */
static volatile unsigned short* const ADDR_AA    = (unsigned short*)0x22005554; /* The flash sees this as 0x2AAA */

/**
 * EraseOKDefault
 * Returns 1 of no erase error, 0 else.
**/
static int EraseOKDefault(const FlashDevice_t *pFlashDevice, Romcheck_t* pCheckResults)
{
    volatile unsigned short* flash = (volatile unsigned short*)(ADDR_FLASH);
    unsigned long i;

    memset((void*)pCheckResults, 0, sizeof(Romcheck_t));

    for(i=0; i<pFlashDevice->RomSize; i++)
    {
        if(flash[i] != 0xFFFF)
        {
            pCheckResults->ErrorOffset = i*sizeof(unsigned short);
            pCheckResults->pErrorData  = ((unsigned char*)flash) + (pCheckResults->ErrorOffset & ~0xF);
            return 0;
        }
    }

    return 1;
}

/**
 * WriteOKDefault
 * Returns 1 of no write error, 0 else.
**/
static int WriteOKDefault(const FlashDevice_t *pFlashDevice, Romcheck_t* pCheckResults, const unsigned short* pFirmData)
{
    volatile unsigned short* flash = (volatile unsigned short*)(ADDR_FLASH);
    unsigned long i;

    memset((void*)pCheckResults, 0, sizeof(Romcheck_t));

    for(i=0; i<pFlashDevice->RomSize; i++)
    {
        if(flash[i] != pFirmData[i])
        {
            pCheckResults->ErrorOffset = i*sizeof(unsigned short);
            pCheckResults->pErrorData  = ((unsigned char*)flash) + (pCheckResults->ErrorOffset & ~0xF);
            return 0;
        }
    }

    return 1;
}

static void FlashUnlock(void)
{
    *ADDR_55 = 0xAAAA;
    *ADDR_AA = 0x5555;
}

static void FlashCommand(const unsigned short Command)
{
    FlashUnlock();
    *ADDR_55 = Command;
}


static void EraseFlashSST29EE010(const FlashDevice_t *pDevice)
{
    volatile unsigned short* dest;
    unsigned long ii, jj;
    unsigned long nPages = pDevice->RomSize / pDevice->PageSize;

    for (ii = 0; ii < nPages; ii++)
    {
        FlashCommand(PAGEWRITE);
        dest = (volatile unsigned short*)(ADDR_FLASH + (pDevice->PageSize * ii));
        for (jj = 0; jj < pDevice->PageSize; jj++)
        {
            *dest = 0xFFFF;
            dest++;
        }

        tmr_usleep(WAIT_DELAY);
        tmr_usleep(WAIT_DELAY);

        /* Display erase progress. */
        if(pDevice->EraseCallback)
        {
            pDevice->EraseCallback(nPages, ii);
        }
    }

    /* Clear erase progress. */
    if(pDevice->EraseCallback)
    {
        pDevice->EraseCallback(0/*nPages*/, 0);
    }
}

static void WriteFlashPageSST29EE010(const FlashDevice_t *pDevice, const unsigned long RomPage,
                                  const unsigned short *pSourcePage)
{
    volatile unsigned short* dest;
    unsigned long ii;
    FlashCommand(PAGEWRITE);

    dest = (volatile unsigned short*)(ADDR_FLASH + (pDevice->PageSize * RomPage));
    for (ii = 0; ii < pDevice->PageSize; ii++)
    {
        *dest = *pSourcePage;
        dest++;
        pSourcePage++;
    }

    tmr_usleep(WAIT_DELAY);
    tmr_usleep(WAIT_DELAY);
}


static void EraseFlashAMD29F010(const FlashDevice_t *pDevice)
{
    FlashUnlock();
    *ADDR_55 = 0x8080;
    *ADDR_55 = 0xAAAA;
    *ADDR_AA = 0x5555;
    *ADDR_55 = 0x1010;

    tmr_usleep(WAIT_DELAY);
    do
    {
        tmr_usleep(WAIT_DELAY);
    } while(((*ADDR_FLASH) & 0x8080) != 0x8080);
}

static void WriteFlashPageAMD29F010(const FlashDevice_t *pDevice, const unsigned long RomPage,
                                    const unsigned short *pSourcePage)
{
    unsigned long ii;
    unsigned short CurrentData;
    volatile unsigned short *pCurrentAddr;

    for (ii = 0; ii < pDevice->PageSize; ii++)
    {
        pCurrentAddr = (ADDR_FLASH + (pDevice->PageSize * RomPage) + ii);
        CurrentData = pSourcePage[ii];
        FlashCommand(PAGEWRITE);
        *pCurrentAddr = CurrentData;

        //tmr_usleep(WAIT_DELAY);
        do
        {
            //tmr_usleep(WAIT_DELAY);
        } while (((*pCurrentAddr) & 0x8080) != (CurrentData & 0x8080));
    }
}


static void FillNoDeviceInfo(const unsigned short DeviceID, FlashDevice_t *pFlashDevice)
{
    /* In the case of unknown flash device, 
     * use ARP usual characteristics (device = SST29EE010), 
     * and warn user that this program will destroy his ARP.
     * Because it *may* be cheap flash chip with contents
     * reset at 0xFFFF and not showing its IDs.
     */
    pFlashDevice->BrickWarning = 1;
    pFlashDevice->Flashability = FLASH_MAYBEOK;
    pFlashDevice->pVendorString = NoVendorString;
    pFlashDevice->pDeviceString = NoDeviceString;
    pFlashDevice->RomSize = 131072;
    pFlashDevice->PageSize = 128;
    pFlashDevice->EraseFlash = EraseFlashSST29EE010;
    pFlashDevice->WriteFlashPage = WriteFlashPageSST29EE010;
    pFlashDevice->EraseOK = EraseOKDefault;
    pFlashDevice->WriteOK = WriteOKDefault;
}

static void FillUnknownDeviceInfo(const unsigned short DeviceID, FlashDevice_t *pFlashDevice)
{
    /* In the case of unknown flash device, 
     * use ARP usual characteristics (device = SST29EE010), 
     * and warn user that this program will destroy his ARP.
     * Because some cheap flash chips in Action Replay have
     * been reported not to respond to get flash vendor/device
     * IDs instructions, hence returning flash first bytes.
     * (typically the 4 first bytes of string "SEGA SEGASATURN ")
     */
    pFlashDevice->BrickWarning = 1;
    pFlashDevice->Flashability = FLASH_MAYBEOK;
    pFlashDevice->pVendorString = UnknownVendorString;
    pFlashDevice->pDeviceString = UnknownDeviceString;
    pFlashDevice->RomSize = 131072;
    pFlashDevice->PageSize = 128;
    pFlashDevice->EraseFlash = EraseFlashSST29EE010;
    pFlashDevice->WriteFlashPage = WriteFlashPageSST29EE010;
    pFlashDevice->EraseOK = EraseOKDefault;
    pFlashDevice->WriteOK = WriteOKDefault;
}

static void FillSSTDeviceInfo(const unsigned short DeviceID, FlashDevice_t *pFlashDevice)
{
    pFlashDevice->pVendorString = SSTVendorString;
    pFlashDevice->EraseOK = EraseOKDefault;
    pFlashDevice->WriteOK = WriteOKDefault;

    switch(DeviceID)
    {
    case 0xB5B5:
    case 0xB6B6:
    case 0xB7B7:
        pFlashDevice->BrickWarning = 0;
        pFlashDevice->Flashability = FLASH_RECOGNIZED;
        if(DeviceID == 0xB5B5)
        { // Total 256KB available on Saturn
            pFlashDevice->pDeviceString = SST39SF010ADeviceString;
            pFlashDevice->RomSize = 131072;
        }
        else if(DeviceID == 0xB6B6)
        { // Total 512KB available on Saturn
            pFlashDevice->pDeviceString = SST39SF020ADeviceString;
            pFlashDevice->RomSize = 262144;
        }
        else if(DeviceID == 0xB7B7)
        { // Total 1MB available on Saturn
            pFlashDevice->pDeviceString = SST39SF040DeviceString;
            pFlashDevice->RomSize = 524288;
        }
        pFlashDevice->PageSize = 4096;
        pFlashDevice->EraseFlash = EraseFlashAMD29F010;
        pFlashDevice->WriteFlashPage = WriteFlashPageAMD29F010;
        break;

    case 0x0707:
    case 0x0808:
    case 0x1010:
    case 0x1212:
        if(DeviceID == 0x0707)
        {
            pFlashDevice->BrickWarning = 0;
            pFlashDevice->Flashability = FLASH_RECOGNIZED;
            pFlashDevice->pDeviceString = SST29EE010DeviceString;
            pFlashDevice->RomSize = 131072;
            pFlashDevice->PageSize = 128;
            pFlashDevice->EraseFlash = EraseFlashSST29EE010;
            pFlashDevice->WriteFlashPage = WriteFlashPageSST29EE010;
        }
        else if(DeviceID == 0x0808)
        {
            pFlashDevice->BrickWarning = 0;
            pFlashDevice->Flashability = FLASH_RECOGNIZED;
            pFlashDevice->pDeviceString = SST29LE010DeviceString;
            pFlashDevice->RomSize = 131072;
            pFlashDevice->PageSize = 128;
            pFlashDevice->EraseFlash = EraseFlashSST29EE010;
            pFlashDevice->WriteFlashPage = WriteFlashPageSST29EE010;
        }
        else if(DeviceID == 0x1010)
        {
            pFlashDevice->BrickWarning = 0;
            pFlashDevice->Flashability = FLASH_RECOGNIZED;
            pFlashDevice->pDeviceString = SST29EE020DeviceString;
            /* A17 is not used on Action Replay SA19-5 ?
             * It seems to be connected to GND.
             * (Thank you Bruno for pictures of PCB)
             */
            //pFlashDevice->RomSize = 262144;
            pFlashDevice->RomSize = 131072;
            pFlashDevice->PageSize = 128;
            pFlashDevice->EraseFlash = EraseFlashSST29EE010;
            pFlashDevice->WriteFlashPage = WriteFlashPageSST29EE010;
        }
        else if(DeviceID == 0x1212)
        {
            pFlashDevice->BrickWarning = 1; // In case of ...
            pFlashDevice->Flashability = FLASH_RECOGNIZED;
            pFlashDevice->pDeviceString = SSTGreenliantDeviceString;
            pFlashDevice->RomSize = 131072;

            /* Should use AMD functions ? Not sure, need beta testers ... */
            pFlashDevice->PageSize = 128;
            pFlashDevice->EraseFlash = EraseFlashSST29EE010;
            pFlashDevice->WriteFlashPage = WriteFlashPageSST29EE010;
        }
        break;

    default:
        /* In the case of unknown SST flash device, 
         * use ARP usual characteristics (device = SST29EE010), 
         * and warn user that this program will destroy his ARP.
         */
        pFlashDevice->BrickWarning = 1;
        pFlashDevice->Flashability = FLASH_MAYBEOK;
        pFlashDevice->pDeviceString = SST29xxxDeviceString;
        pFlashDevice->RomSize = 131072;
        pFlashDevice->PageSize = 128;
        pFlashDevice->EraseFlash = EraseFlashSST29EE010;
        pFlashDevice->WriteFlashPage = WriteFlashPageSST29EE010;
        break;
    }
}

static void FillATDeviceInfo(const unsigned short DeviceID, FlashDevice_t *pFlashDevice)
{
    pFlashDevice->pVendorString = ATVendorString;
    pFlashDevice->EraseOK = EraseOKDefault;
    pFlashDevice->WriteOK = WriteOKDefault;

    switch(DeviceID)
    {
    case 0xD5D5:
        pFlashDevice->BrickWarning = 0;
        pFlashDevice->Flashability = FLASH_RECOGNIZED;
        pFlashDevice->pDeviceString = AT29C010DeviceString;
        pFlashDevice->RomSize = 131072;
        pFlashDevice->PageSize = 128;
        pFlashDevice->EraseFlash = EraseFlashSST29EE010;
        pFlashDevice->WriteFlashPage = WriteFlashPageSST29EE010;
        break;
    default:
        /* In the case of unknown flash device, 
         * prevent user from bricking his cartridge.
         */
        pFlashDevice->BrickWarning = 0;
        pFlashDevice->Flashability = FLASH_HOPELESS;
        pFlashDevice->pDeviceString = UnknownDeviceString;
        pFlashDevice->RomSize = 0;
        pFlashDevice->PageSize = 0;
        pFlashDevice->EraseFlash = NULL;
        pFlashDevice->WriteFlashPage = NULL;
        break;
    }
}

static void FillAMDDeviceInfo(const unsigned short DeviceID, FlashDevice_t *pFlashDevice)
{
    pFlashDevice->pVendorString = AMDVendorString;
    pFlashDevice->EraseOK = EraseOKDefault;
    pFlashDevice->WriteOK = WriteOKDefault;

    switch(DeviceID)
    {
    case 0x2020:
        pFlashDevice->BrickWarning = 0;
        pFlashDevice->Flashability = FLASH_RECOGNIZED;
        pFlashDevice->pDeviceString = AM29F010DeviceString;
        pFlashDevice->RomSize = 131072;
        pFlashDevice->PageSize = 1024*16;
        pFlashDevice->EraseFlash = EraseFlashAMD29F010;
        pFlashDevice->WriteFlashPage = WriteFlashPageAMD29F010;
        break;
    default:
        /* In the case of unknown flash device,
         * prevent user from bricking his cartridge.
         */
        pFlashDevice->BrickWarning = 0;
        pFlashDevice->Flashability = FLASH_HOPELESS;
        pFlashDevice->pDeviceString = UnknownDeviceString;
        pFlashDevice->RomSize = 0;
        pFlashDevice->PageSize = 0;
        pFlashDevice->EraseFlash = NULL;
        pFlashDevice->WriteFlashPage = NULL;
        break;
    }
}

void FillFlashDeviceInfo(const unsigned short VendorID, const unsigned short DeviceID,
                         FlashDevice_t *pFlashDevice)
{
    SetFlashEraseCallback(NULL, pFlashDevice);

    /* Reset flashability status. */
    pFlashDevice->Flashability = FLASH_HOPELESS;

    switch(VendorID)
    {
    case 0xBFBF:
        FillSSTDeviceInfo(DeviceID, pFlashDevice);
        break;
    case 0x1F1F:
        FillATDeviceInfo(DeviceID, pFlashDevice);
        break;
    case 0x0101:
        FillAMDDeviceInfo(DeviceID, pFlashDevice);
        break;
    case 0xFFFF:
        FillNoDeviceInfo(DeviceID, pFlashDevice);
        break;
    default:
        FillUnknownDeviceInfo(DeviceID, pFlashDevice);
        break;
    }

    /* Special cases when Flash IDs are incorrect, 
     * but chips are likely to be flashed.
     * "SEGA" = 0x53454741
     *  - 0x5345 is when flash chips don't return any ID (counterfeit chip ?).
     *  - 0x53BF and 0xBF45 is in the case one of 2 flash chips doesn't return any ID (yes, it sometimes happens).
     * In the case flash chip is write only, 5345/4741 is detected, but obviously flash will fail, so displaying warning message is needed.
     */
    if(VendorID == 0x5345)
    {
        pFlashDevice->BrickWarning = 1;
        pFlashDevice->Flashability = FLASH_RECOGNIZED;
    }
    if((VendorID == 0x53BF)
    || (VendorID == 0xBF45))
    {
        pFlashDevice->BrickWarning = 0;
        pFlashDevice->Flashability = FLASH_RECOGNIZED;
    }
}

void SetFlashEraseCallback(FctEraseCallback fct, FlashDevice_t *pFlashDevice)
{
    pFlashDevice->EraseCallback = fct;
}

unsigned short FlashGetVendorID(void)
{
    unsigned short VendorID;
    static volatile unsigned short* const ADDR_VENDOR = (unsigned short*)0x22000000;

    FlashCommand(ID_ENTRY);
    /* Bodge to wait for flash to complete the command */
    // tmr_usleep(WAIT_DELAY);
    // tmr_usleep(WAIT_DELAY);

    VendorID = *ADDR_VENDOR;
    FlashCommand(ID_EXIT);

    // tmr_usleep(WAIT_DELAY);
    // tmr_usleep(WAIT_DELAY);

    return VendorID;
}

unsigned short FlashGetDeviceID(void)
{
    unsigned short DeviceID;
    static volatile unsigned short* const ADDR_DEVICE = (unsigned short*)0x22000002; /* The flash sees this as 0x0001 */

    FlashCommand(ID_ENTRY);

    // tmr_usleep(WAIT_DELAY);
    // tmr_usleep(WAIT_DELAY);

    DeviceID = *ADDR_DEVICE;
    FlashCommand(ID_EXIT);

    // tmr_usleep(WAIT_DELAY);
    // tmr_usleep(WAIT_DELAY);

    return DeviceID;
}
