// NOTE - The 27 series device support is a work in progress.  It
//        has not been tested or documented.

#include "Configure.h"
#if defined(PROM_IS_27)

#include "PromAddressDriver.h"

// IO lines for the EPROM device control
// Pins D2..D9 are used for the data bus.
#define WE              A0
#define CE              A1
#define OE              A2

// Set the status of the device control pins
static void enableChip()       { digitalWrite(CE, LOW); }
static void disableChip()      { digitalWrite(CE, HIGH);}
static void enableOutput()     { digitalWrite(OE, LOW); }
static void disableOutput()    { digitalWrite(OE, HIGH);}
static void enableWrite()      { digitalWrite(WE, LOW); }
static void disableWrite()     { digitalWrite(WE, HIGH);}


PromDevice27::PromDevice27(uint32_t size, E27C_PGM pgmType, unsigned long pulseWidthUsec,
                           unsigned writeAttempts, unsigned overwriteMultiplier, bool verify)
    : PromDevice(size, 0, 0, false),
      mPgmType(pgmType),
      mPulseWidthUsec(pulseWidthUsec),
      mWriteAttempts(writeAttempts),
      mOverwriteMultiplier(overwriteMultiplier),
      mVerifyByte(verify)
{
}


void PromDevice27::begin()
{
    // Define the data bus as input initially so that it does not put out a
    // signal that could collide with output on the data pins of the EEPROM.
    setDataBusMode(INPUT);

    // Define the EEPROM control pins as output, making sure they are all
    // in the disabled state.
    digitalWrite(OE, HIGH);
    pinMode(OE, OUTPUT);
    digitalWrite(CE, HIGH);
    pinMode(CE, OUTPUT);
    digitalWrite(WE, HIGH);
    pinMode(WE, OUTPUT);

    // This chip uses the shift register hardware for addresses, so initialize that.
    PromAddressDriver::begin();
}


// BEGIN PRIVATE METHODS
//

// Use the PromAddressDriver to set a 16 bit address in the two address shift registers.
void PromDevice27::setAddress(uint32_t address)
{
    PromAddressDriver::setAddress(address);
}


// Read a byte from a given address
byte PromDevice27::readByte(uint32_t address)
{
    byte data = 0;
    setAddress(address);
    setDataBusMode(INPUT);
    disableOutput();
    disableWrite();
    enableChip();
    enableOutput();
    data = readDataBus();
    disableOutput();
    disableChip();

    return data;
}


// Burn a byte to the chip and verify that it was written.
bool PromDevice27::burnByte(byte value, uint32_t address)
{
    switch(mPgmType) {
    case E27C_PGM_WE:  return burnByteWE(value, address);  break;
    case E27C_PGM_CE:  return burnByteCE(value, address);  break;
    default: return false;  // PGM_D13 not implemented yet
    }
}


// Burn a byte to the chip and verify that it was written.
//
// This uses a dedicated WE or PGM chip that operates on TTL levels and is active LOW.
// Overwrite burning is supported.
bool PromDevice27::burnByteWE(byte value, uint32_t address)
{
    bool status = false;
    unsigned writeCount = 0;

    disableOutput();
    disableWrite();
    enableChip();
    setAddress(address);

    while (!status && (writeCount < mWriteAttempts)) {
        setDataBusMode(OUTPUT);
        writeDataBus(value);
        delayMicroseconds(1);
        enableWrite();
        myDelay(mPulseWidthUsec);
        disableWrite();
        ++writeCount;

        if (mVerifyByte) {
			setDataBusMode(INPUT);
	        enableOutput();
	        status = readDataBus() == value;
	        disableOutput();
		} else {
            status = true;
        }
    }

    if (status && (mOverwriteMultiplier > 0)) {
        setDataBusMode(OUTPUT);
        writeDataBus(value);
        delayMicroseconds(1);
        enableWrite();
        myDelay(mPulseWidthUsec * mOverwriteMultiplier * (writeCount + 1));
        disableWrite();
    }

	setDataBusMode(INPUT);
    disableChip();
    return status;
}


// Burn a byte to the chip and verify that it was written.
//
// This uses an active LOW program pulse on the CE line and a verify operation with CE
// HIGH.  Overwrite is not supported, but could be added is a chip is found that needs it.
// Chips that use this mode require a programming voltage on the PGM or VPP pin and
// possibly on other pins as well  The above applies to the W27C257 EEPROM.  The W27C512
// is a bit more difficult because it does not have a dedicated VPP or PGM pin.  Instead,
// the programming voltage is applied to OE to put the chip in programming mode and OE is
// then switched LOW for the verify operation. Because the voltage switch would require
// additional hardware, this code does not support verify-after-write for the W27C512 chip
// and will instead just do a single write cycle.
//
// VCC may also have a non-standard voltage in program mode.  Be sure to separate the
// PROM's VCC line from system VCC if a non-standard voltage is used.
bool PromDevice27::burnByteCE(byte value, uint32_t address)
{
    bool status = false;
    unsigned writeCount = 0;

    disableOutput();
    disableWrite();
    disableChip();
    setAddress(address);

    while (!status && (writeCount < mWriteAttempts)) {
        setDataBusMode(OUTPUT);
        writeDataBus(value);
        delayMicroseconds(2);
        enableChip();
        myDelay(mPulseWidthUsec);
        disableChip();
        delayMicroseconds(2);
        ++writeCount;

        if (mVerifyByte) {
	        setDataBusMode(INPUT);
	        enableOutput();
	        status = readDataBus() == value;
	        disableOutput();
		} else {
            status = true;
        }
    }

    if (status && (mOverwriteMultiplier > 0)) {
        setDataBusMode(OUTPUT);
        writeDataBus(value);
        delayMicroseconds(1);
        enableChip();
        myDelay(mPulseWidthUsec * mOverwriteMultiplier * (writeCount + 1));
        disableChip();
    }

	setDataBusMode(INPUT);
	disableChip();
    return status;
}


ERET PromDevice27::erase(uint32_t start, uint32_t end)
{
	ERET status = RET_FAIL;

    disableChip();
    disableOutput();
    setAddress(0);
    setDataBusMode(OUTPUT);
    writeDataBus(0xff);
    delayMicroseconds(2);

    if (mPgmType == E27C_PGM_WE) {
		// Erase code for the SST27C0x0.  The Vpp and A9 pins are held at 12V for the
		// erase cycle.  This erases the entire chip, so the start and end address
		// parameters are ignored.  There is no erase verification for this chip.
		enableChip();
        delayMicroseconds(1);
		enableWrite();
		delayMicroseconds(100);	// Hard coded for SST27F020
		disableWrite();
		disableChip();
		setDataBusMode(INPUT);
		status = RET_OK;
    } else {
		// Erase code for the 27E257 and 27C257.  The Vpp and A9 pins are held at 14V for
		// the erase and verify cycle.  This erases the entire chip, so the start and end
		// address parameters are ignored.
	    unsigned writeCount = 0;
	    while ((status == RET_FAIL) && (writeCount < mWriteAttempts)) {
	        setAddress(0);
	        setDataBusMode(OUTPUT);
	        writeDataBus(0xff);
	        delayMicroseconds(2);
	        enableChip();
	        delay(100);
	        disableChip();
	        delayMicroseconds(2);

	        // Read back the data to verify all cells are erased.  Note That this is done
	        // while CE is HIGH.  This is the erase verify mode.
	        setDataBusMode(INPUT);
	        for (uint32_t address = 0; (address < mSize); address++) {
	            setAddress(address);
	            enableOutput();
	            uint8_t b = readDataBus();
	            disableOutput();
	            if (b != 0xff)  break;
	        }
	        status = RET_OK;
	    }
	}

    return status;
}


void PromDevice27::myDelay(uint32_t us)
{
    if (us > 16000) {
        // The delayMicroseconds code can't do delays longer than 16ms, so use the
        // ms delay code for larger values.  This rounds down to the nearest ms, so
        // it is not possible to delay for 40.5 ms, for example.
        delay(uint16_t(us / 1000));
    } else {
        delayMicroseconds(uint16_t(us));
    }
}


#endif // #if defined(PROM_IS_27)
