/* SerialFlash Library - for filesystem-like access to SPI Serial Flash memory
 * https://github.com/PaulStoffregen/SerialFlash
 * Copyright (C) 2015, Paul Stoffregen, paul@pjrc.com
 *
 * Development of this library was funded by PJRC.COM, LLC by sales of Teensy.
 * Please support PJRC's efforts to develop open source software by purchasing
 * Teensy or other genuine PJRC products.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "SerialFlash.h"
#include "util/SerialFlash_directwrite.h"

#define CSASSERT()  DIRECT_WRITE_LOW(cspin_basereg, cspin_bitmask)
#define CSRELEASE() DIRECT_WRITE_HIGH(cspin_basereg, cspin_bitmask)
#define SPICONFIG   SPISettings(50000000, MSBFIRST, SPI_MODE0)

uint16_t SerialFlashChip::dirindex = 0;
uint8_t SerialFlashChip::flags = 0;
uint8_t SerialFlashChip::busy = 0;

static volatile IO_REG_TYPE *cspin_basereg;
static IO_REG_TYPE cspin_bitmask;

static SPIClass *SPIPORT = &SPI;

#define FLAG_32BIT_ADDR		0x01	// larger than 16 MByte address
#define FLAG_STATUS_CMD70	0x02	// requires special busy flag check
#define FLAG_DIFF_SUSPEND	0x04	// uses 2 different suspend commands
#define FLAG_MULTI_DIE		0x08	// multiple die, don't read cross 32M barrier
#define FLAG_256K_BLOCKS	0x10	// has 256K erase blocks
#define FLAG_DIE_MASK		0xC0	// top 2 bits count during multi-die erase

void SerialFlashChip::wait(void)
{
	uint32_t status;
	//Serial.print("wait-");
	while (1) {
		SPIPORT->beginTransaction(SPICONFIG);
		CSASSERT();
		if (flags & FLAG_STATUS_CMD70) {
			// some Micron chips require this different
			// command to detect program and erase completion
			SPIPORT->transfer(0x70);
			status = SPIPORT->transfer(0);
			CSRELEASE();
			SPIPORT->endTransaction();
			//Serial.printf("b=%02x.", status & 0xFF);
			if ((status & 0x80)) break;
		} else {
			// all others work by simply reading the status reg
			SPIPORT->transfer(0x05);
			status = SPIPORT->transfer(0);
			CSRELEASE();
			SPIPORT->endTransaction();
			//Serial.printf("b=%02x.", status & 0xFF);
			if (!(status & 1)) break;
		}
	}
	busy = 0;
	//Serial.println();
}

void SerialFlashChip::read(uint32_t addr, void *buf, uint32_t len)
{
	uint8_t *p = (uint8_t *)buf;
	uint8_t b, f, status, cmd;

	memset(p, 0, len);
	f = flags;
	SPIPORT->beginTransaction(SPICONFIG);
	b = busy;
	if (b) {
		// read status register ... chip may no longer be busy
		CSASSERT();
		if (flags & FLAG_STATUS_CMD70) {
			SPIPORT->transfer(0x70);
			status = SPIPORT->transfer(0);
			if ((status & 0x80)) b = 0;
		} else {
			SPIPORT->transfer(0x05);
			status = SPIPORT->transfer(0);
			if (!(status & 1)) b = 0;
		}
		CSRELEASE();
		if (b == 0) {
			// chip is no longer busy :-)
			busy = 0;
		} else if (b < 3) {
			// TODO: this may not work on Spansion chips
			// which apparently have 2 different suspend
			// commands, for program vs erase
			CSASSERT();
			SPIPORT->transfer(0x06); // write enable (Micron req'd)
			CSRELEASE();
			delayMicroseconds(1);
			cmd = 0x75; //Suspend program/erase for almost all chips
			// but Spansion just has to be different for program suspend!
			if ((f & FLAG_DIFF_SUSPEND) && (b == 1)) cmd = 0x85;
			CSASSERT();
			SPIPORT->transfer(cmd); // Suspend command
			CSRELEASE();
			if (f & FLAG_STATUS_CMD70) {
				// Micron chips don't actually suspend until flags read
				CSASSERT();
				SPIPORT->transfer(0x70);
				do {
					status = SPIPORT->transfer(0);
				} while (!(status & 0x80));
				CSRELEASE();
			} else {
				CSASSERT();
				SPIPORT->transfer(0x05);
				do {
					status = SPIPORT->transfer(0);
				} while ((status & 0x01));
				CSRELEASE();
			}
		} else {
			// chip is busy with an operation that can not suspend
			SPIPORT->endTransaction();	// is this a good idea?
			wait();			// should we wait without ending
			b = 0;			// the transaction??
			SPIPORT->beginTransaction(SPICONFIG);
		}
	}
	do {
		uint32_t rdlen = len;
		if (f & FLAG_MULTI_DIE) {
			if ((addr & 0xFE000000) != ((addr + len - 1) & 0xFE000000)) {
				rdlen = 0x2000000 - (addr & 0x1FFFFFF);
			}
		}
		CSASSERT();
		// TODO: FIFO optimize....
		if (f & FLAG_32BIT_ADDR) {
			SPIPORT->transfer(0x03);
			SPIPORT->transfer16(addr >> 16);
			SPIPORT->transfer16(addr);
		} else {
			SPIPORT->transfer16(0x0300 | ((addr >> 16) & 255));
			SPIPORT->transfer16(addr);
		}
		SPIPORT->transfer(p, rdlen);
		CSRELEASE();
		p += rdlen;
		addr += rdlen;
		len -= rdlen;
	} while (len > 0);
	if (b) {
		CSASSERT();
		SPIPORT->transfer(0x06); // write enable (Micron req'd)
		CSRELEASE();
		delayMicroseconds(1);
		cmd = 0x7A;
		if ((f & FLAG_DIFF_SUSPEND) && (b == 1)) cmd = 0x8A;
		CSASSERT();
		SPIPORT->transfer(cmd); // Resume program/erase
		CSRELEASE();
	}
	SPIPORT->endTransaction();
}

void SerialFlashChip::write(uint32_t addr, const void *buf, uint32_t len)
{
	const uint8_t *p = (const uint8_t *)buf;
	uint32_t max, pagelen;

	 //Serial.printf("WR: addr %08X, len %d\n", addr, len);
	do {
		if (busy) wait();
		SPIPORT->beginTransaction(SPICONFIG);
		CSASSERT();
		// write enable command
		SPIPORT->transfer(0x06);
		CSRELEASE();
		max = 256 - (addr & 0xFF);
		pagelen = (len <= max) ? len : max;
		 //Serial.printf("WR: addr %08X, pagelen %d\n", addr, pagelen);
		delayMicroseconds(1); // TODO: reduce this, but prefer safety first
		CSASSERT();
		if (flags & FLAG_32BIT_ADDR) {
			SPIPORT->transfer(0x02); // program page command
			SPIPORT->transfer16(addr >> 16);
			SPIPORT->transfer16(addr);
		} else {
			SPIPORT->transfer16(0x0200 | ((addr >> 16) & 255));
			SPIPORT->transfer16(addr);
		}
		addr += pagelen;
		len -= pagelen;
		do {
			SPIPORT->transfer(*p++);
		} while (--pagelen > 0);
		CSRELEASE();
		busy = 4;
		SPIPORT->endTransaction();
	} while (len > 0);
}

void SerialFlashChip::eraseAll()
{
	if (busy) wait();
	uint8_t id[5];
	readID(id);
	//Serial.printf("ID: %02X %02X %02X\n", id[0], id[1], id[2]);
	if (id[0] == 0x20 && id[2] >= 0x20 && id[2] <= 0x22) {
		// Micron's multi-die chips require special die erase commands
		//  N25Q512A	20 BA 20  2 dies  32 Mbyte/die   65 nm transitors
		//  N25Q00AA	20 BA 21  4 dies  32 Mbyte/die   65 nm transitors
		//  MT25QL02GC	20 BA 22  2 dies  128 Mbyte/die  45 nm transitors
		uint8_t die_count = 2;
		if (id[2] == 0x21) die_count = 4;
		uint8_t die_index = flags >> 6;
		 //Serial.printf("Micron die erase %d\n", die_index);
		flags &= 0x3F;
		if (die_index >= die_count) return; // all dies erased :-)
		uint8_t die_size = 2;  // in 16 Mbyte units
		if (id[2] == 0x22) die_size = 8;
		SPIPORT->beginTransaction(SPICONFIG);
		CSASSERT();
		SPIPORT->transfer(0x06); // write enable command
		CSRELEASE();
		 delayMicroseconds(1);
		CSASSERT();
		// die erase command
		SPIPORT->transfer(0xC4);
		SPIPORT->transfer16((die_index * die_size) << 8);
		SPIPORT->transfer16(0x0000);
		CSRELEASE();
		 //Serial.printf("Micron erase begin\n");
		flags |= (die_index + 1) << 6;
	} else {
		// All other chips support the bulk erase command
		SPIPORT->beginTransaction(SPICONFIG);
		CSASSERT();
		// write enable command
		SPIPORT->transfer(0x06);
		CSRELEASE();
		 delayMicroseconds(1);
		CSASSERT();
		// bulk erase command
		SPIPORT->transfer(0xC7);
		CSRELEASE();
		SPIPORT->endTransaction();
	}
	busy = 3;
}

void SerialFlashChip::eraseBlock(uint32_t addr)
{
	uint8_t f = flags;
	if (busy) wait();
	SPIPORT->beginTransaction(SPICONFIG);
	CSASSERT();
	SPIPORT->transfer(0x06); // write enable command
	CSRELEASE();
	 delayMicroseconds(1);
	CSASSERT();
	if (f & FLAG_32BIT_ADDR) {
		SPIPORT->transfer(0xD8);
		SPIPORT->transfer16(addr >> 16);
		SPIPORT->transfer16(addr);
	} else {
		SPIPORT->transfer16(0xD800 | ((addr >> 16) & 255));
		SPIPORT->transfer16(addr);
	}
	CSRELEASE();
	SPIPORT->endTransaction();
	busy = 2;
}


bool SerialFlashChip::ready()
{
	uint32_t status;
	if (!busy) return true;
	SPIPORT->beginTransaction(SPICONFIG);
	CSASSERT();
	if (flags & FLAG_STATUS_CMD70) {
		// some Micron chips require this different
		// command to detect program and erase completion
		SPIPORT->transfer(0x70);
		status = SPIPORT->transfer(0);
		CSRELEASE();
		SPIPORT->endTransaction();
		//Serial.printf("ready=%02x\n", status & 0xFF);
		if ((status & 0x80) == 0) return false;
	} else {
		// all others work by simply reading the status reg
		SPIPORT->transfer(0x05);
		status = SPIPORT->transfer(0);
		CSRELEASE();
		SPIPORT->endTransaction();
		//Serial.printf("ready=%02x\n", status & 0xFF);
		if ((status & 1)) return false;
	}
	busy = 0;
	if (flags & 0xC0) {
		// continue a multi-die erase
		eraseAll();
		return false;
	}
	return true;
}


#define ID0_WINBOND	0xEF
#define ID0_SPANSION	0x01
#define ID0_MICRON	0x20
#define ID0_MACRONIX	0xC2
#define ID0_SST		0xBF
#define ID0_ADESTO      0x1F

//#define FLAG_32BIT_ADDR	0x01	// larger than 16 MByte address
//#define FLAG_STATUS_CMD70	0x02	// requires special busy flag check
//#define FLAG_DIFF_SUSPEND	0x04	// uses 2 different suspend commands
//#define FLAG_256K_BLOCKS	0x10	// has 256K erase blocks

bool SerialFlashChip::begin(SPIClass& device, uint8_t pin)
{
	SPIPORT = &device;
	return begin(pin);
}

bool SerialFlashChip::begin(uint8_t pin)
{
	uint8_t id[5];
	uint8_t f;
	uint32_t size;

	cspin_basereg = PIN_TO_BASEREG(pin);
	cspin_bitmask = PIN_TO_BITMASK(pin);
	SPIPORT->begin();
	pinMode(pin, OUTPUT);
	CSRELEASE();
	readID(id);
	if ((id[0]==0 && id[1]==0 && id[2]==0) || (id[0]==255 && id[1]==255 && id[2]==255)) {
		return false;
	}
	f = 0;
	size = capacity(id);
	if (size > 16777216) {
		// more than 16 Mbyte requires 32 bit addresses
		f |= FLAG_32BIT_ADDR;
		SPIPORT->beginTransaction(SPICONFIG);
		if (id[0] == ID0_SPANSION) {
			// spansion uses MSB of bank register
			CSASSERT();
			SPIPORT->transfer16(0x1780); // bank register write
			CSRELEASE();
		} else {
			// micron & winbond & macronix use command
			CSASSERT();
			SPIPORT->transfer(0x06); // write enable
			CSRELEASE();
			delayMicroseconds(1);
			CSASSERT();
			SPIPORT->transfer(0xB7); // enter 4 byte addr mode
			CSRELEASE();
		}
		SPIPORT->endTransaction();
		if (id[0] == ID0_MICRON) f |= FLAG_MULTI_DIE;
	}
	if (id[0] == ID0_SPANSION) {
		// Spansion has separate suspend commands
		f |= FLAG_DIFF_SUSPEND;
		if (!id[4]) {
			// Spansion chips with id[4] == 0 use 256K sectors
			f |= FLAG_256K_BLOCKS;
		}
	}
	if (id[0] == ID0_MICRON) {
		// Micron requires busy checks with a different command
		f |= FLAG_STATUS_CMD70; // TODO: all or just multi-die chips?
	}
	flags = f;
	readID(id);
	return true;
}

// chips tested: https://github.com/PaulStoffregen/SerialFlash/pull/12#issuecomment-169596992
//
void SerialFlashChip::sleep()
{
	if (busy) wait();
	SPIPORT->beginTransaction(SPICONFIG);
	CSASSERT();
	SPIPORT->transfer(0xB9); // Deep power down command
	CSRELEASE();
}

void SerialFlashChip::wakeup()
{
	SPIPORT->beginTransaction(SPICONFIG);
	CSASSERT();
	SPIPORT->transfer(0xAB); // Wake up from deep power down command
	CSRELEASE();
}

void SerialFlashChip::readID(uint8_t *buf)
{
	if (busy) wait();
	SPIPORT->beginTransaction(SPICONFIG);
	CSASSERT();
	SPIPORT->transfer(0x9F);
	buf[0] = SPIPORT->transfer(0); // manufacturer ID
	buf[1] = SPIPORT->transfer(0); // memory type
	buf[2] = SPIPORT->transfer(0); // capacity
	if (buf[0] == ID0_SPANSION) {
		buf[3] = SPIPORT->transfer(0); // ID-CFI
		buf[4] = SPIPORT->transfer(0); // sector size
	}
	CSRELEASE();
	SPIPORT->endTransaction();
	//Serial.printf("ID: %02X %02X %02X\n", buf[0], buf[1], buf[2]);
}

void SerialFlashChip::readSerialNumber(uint8_t *buf) //needs room for 8 bytes
{
	if (busy) wait();
	SPIPORT->beginTransaction(SPICONFIG);
	CSASSERT();
	SPIPORT->transfer(0x4B);			
	SPIPORT->transfer16(0);	
	SPIPORT->transfer16(0);
	for (int i=0; i<8; i++) {		
		buf[i] = SPIPORT->transfer(0);
	}
	CSRELEASE();
	SPIPORT->endTransaction();
//	Serial.printf("Serial Number: %02X %02X %02X %02X %02X %02X %02X %02X\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);
}

uint32_t SerialFlashChip::capacity(const uint8_t *id)
{
	uint32_t n = 1048576; // unknown chips, default to 1 MByte

	if (id[0] == ID0_ADESTO && id[1] == 0x89) {
		n = 1048576*16; //16MB
	} else
	if (id[2] >= 16 && id[2] <= 31) {
		n = 1ul << id[2];
	} else
	if (id[2] >= 32 && id[2] <= 37) {
		n = 1ul << (id[2] - 6);
	} else
	if ((id[0]==0 && id[1]==0 && id[2]==0) || 
		(id[0]==255 && id[1]==255 && id[2]==255)) {
		n = 0;
	}
	//Serial.printf("capacity %lu\n", n);
	return n;
}

uint32_t SerialFlashChip::blockSize()
{
	// Spansion chips >= 512 mbit use 256K sectors
	if (flags & FLAG_256K_BLOCKS) return 262144;
	// everything else seems to have 64K sectors
	return 65536;
}




/*
Chip		Uniform Sector Erase
		20/21	52	D8/DC
		-----	--	-----
W25Q64CV	4	32	64
W25Q128FV	4	32	64
S25FL127S			64
N25Q512A	4		64
N25Q00AA	4		64
S25FL512S			256
SST26VF032	4
AT25SF128A              32      64
*/



//			size	sector			busy	pgm/erase	chip
// Part			Mbyte	kbyte	ID bytes	cmd	suspend		erase
// ----			----	-----	--------	---	-------		-----
// Winbond W25Q64CV	8	64	EF 40 17
// Winbond W25Q128FV	16	64	EF 40 18	05	single		60 & C7
// Winbond W25Q256FV	32	64	EF 40 19	
// Spansion S25FL064A	8	?	01 02 16
// Spansion S25FL127S	16	64	01 20 18	05
// Spansion S25FL128P	16	64	01 20 18
// Spansion S25FL256S	32	64	01 02 19	05			60 & C7
// Spansion S25FL512S	64	256	01 02 20
// Macronix MX25L12805D 16	?	C2 20 18
// Macronix MX66L51235F	64		C2 20 1A
// Numonyx M25P128	16	?	20 20 18
// Micron M25P80	1	?	20 20 14
// Micron N25Q128A	16	64	20 BA 18
// Micron N25Q512A	64	?	20 BA 20	70	single		C4 x2
// Micron N25Q00AA	128	64	20 BA 21		single		C4 x4
// Micron MT25QL02GC	256	64	20 BA 22	70			C4 x2
// SST SST25WF010	1/8	?	BF 25 02
// SST SST25WF020	1/4	?	BF 25 03
// SST SST25WF040	1/2	?	BF 25 04
// SST SST25VF016B	1	?	BF 25 41
// SST26VF016			?	BF 26 01
// SST26VF032			?	BF 26 02
// SST25VF032		4	64	BF 25 4A
// SST26VF064		8	?	BF 26 43
// LE25U40CMC		1/2	64	62 06 13
// Adesto AT25SF128A    16              1F 89 01

SerialFlashChip SerialFlash;
