|
|
@@ -37,31 +37,56 @@ |
|
|
|
#endif |
|
|
|
|
|
|
|
uint16_t SerialFlashChip::dirindex = 0; |
|
|
|
uint8_t SerialFlashChip::fourbytemode = 0; |
|
|
|
uint8_t SerialFlashChip::flags = 0; |
|
|
|
uint8_t SerialFlashChip::busy = 0; |
|
|
|
|
|
|
|
#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; |
|
|
|
do { |
|
|
|
//Serial.print("wait-"); |
|
|
|
while (1) { |
|
|
|
SPI.beginTransaction(SPICONFIG); |
|
|
|
CSASSERT(); |
|
|
|
status = SPI.transfer16(0x0500); |
|
|
|
CSRELEASE(); |
|
|
|
SPI.endTransaction(); |
|
|
|
} while ((status & 1)); |
|
|
|
if (flags & FLAG_STATUS_CMD70) { |
|
|
|
// some Micron chips require this different |
|
|
|
// command to detect program and erase completion |
|
|
|
SPI.transfer(0x70); |
|
|
|
status = SPI.transfer(0); |
|
|
|
CSRELEASE(); |
|
|
|
SPI.endTransaction(); |
|
|
|
//Serial.printf("b=%02x.", status & 0xFF); |
|
|
|
if ((status & 0x80)) break; |
|
|
|
} else { |
|
|
|
// all others work by simply reading the status reg |
|
|
|
SPI.transfer(0x05); |
|
|
|
status = SPI.transfer(0); |
|
|
|
CSRELEASE(); |
|
|
|
SPI.endTransaction(); |
|
|
|
//Serial.printf("b=%02x.", status & 0xFF); |
|
|
|
if (!(status & 1)) break; |
|
|
|
} |
|
|
|
} |
|
|
|
busy = 0; |
|
|
|
//Serial.println(); |
|
|
|
} |
|
|
|
|
|
|
|
void SerialFlashChip::read(void *buf, uint32_t addr, uint32_t len) |
|
|
|
void SerialFlashChip::read(uint32_t addr, void *buf, uint32_t len) |
|
|
|
{ |
|
|
|
uint8_t *p = (uint8_t *)buf; |
|
|
|
uint8_t b; |
|
|
|
uint8_t b, f; |
|
|
|
|
|
|
|
memset(p, 0, len); |
|
|
|
f = flags; |
|
|
|
b = busy; |
|
|
|
if (b) { |
|
|
|
if (b == 1) { |
|
|
|
if (b < 3) { |
|
|
|
// TODO: this may not work on Spansion chips |
|
|
|
// which apparently have 2 different suspend |
|
|
|
// commands, for program vs erase |
|
|
@@ -73,23 +98,35 @@ void SerialFlashChip::read(void *buf, uint32_t addr, uint32_t len) |
|
|
|
delayMicroseconds(20); // Tsus = 20us |
|
|
|
} else { |
|
|
|
wait(); |
|
|
|
b = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
SPI.beginTransaction(SPICONFIG); |
|
|
|
CSASSERT(); |
|
|
|
// TODO: FIFO optimize.... |
|
|
|
if (fourbytemode) { |
|
|
|
SPI.transfer(0x13); |
|
|
|
SPI.transfer16(addr >> 16); |
|
|
|
SPI.transfer16(addr); |
|
|
|
} else { |
|
|
|
SPI.transfer16(0x0300 | ((addr >> 16) & 255)); |
|
|
|
SPI.transfer16(addr); |
|
|
|
} |
|
|
|
SPI.transfer(p, len); |
|
|
|
CSRELEASE(); |
|
|
|
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) { |
|
|
|
SPI.transfer(0x03); |
|
|
|
SPI.transfer16(addr >> 16); |
|
|
|
SPI.transfer16(addr); |
|
|
|
} else { |
|
|
|
SPI.transfer16(0x0300 | ((addr >> 16) & 255)); |
|
|
|
SPI.transfer16(addr); |
|
|
|
} |
|
|
|
SPI.transfer(p, rdlen); |
|
|
|
CSRELEASE(); |
|
|
|
p += rdlen; |
|
|
|
addr += rdlen; |
|
|
|
len -= rdlen; |
|
|
|
} while (len > 0); |
|
|
|
SPI.endTransaction(); |
|
|
|
if (b == 1) { |
|
|
|
if (b) { |
|
|
|
SPI.beginTransaction(SPICONFIG); |
|
|
|
CSASSERT(); |
|
|
|
SPI.transfer(0x7A); // Resume program/erase |
|
|
@@ -98,62 +135,105 @@ void SerialFlashChip::read(void *buf, uint32_t addr, uint32_t len) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void SerialFlashChip::write(const void *buf, uint32_t addr, uint32_t len) |
|
|
|
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.println("write"); |
|
|
|
do { |
|
|
|
if (busy) wait(); |
|
|
|
//Serial.printf("pagelen=%d\n", pagelen); |
|
|
|
SPI.beginTransaction(SPICONFIG); |
|
|
|
CSASSERT(); |
|
|
|
// write enable command |
|
|
|
SPI.transfer(0x06); |
|
|
|
CSRELEASE(); |
|
|
|
//delayMicroseconds(1); |
|
|
|
max = 256 - (addr & 0xFF); |
|
|
|
pagelen = (len <= max) ? len : max; |
|
|
|
len -= pagelen; |
|
|
|
CSASSERT(); |
|
|
|
if (fourbytemode) { |
|
|
|
// TODO: Winbond doesn't implement 0x12 on W25Q256FV |
|
|
|
SPI.transfer(0x12); |
|
|
|
if (flags & FLAG_32BIT_ADDR) { |
|
|
|
//Serial.printf("write 32 bit addr %08X %02X\n", addr, addr >> 24); |
|
|
|
SPI.transfer(0x02); |
|
|
|
SPI.transfer16(addr >> 16); |
|
|
|
SPI.transfer16(addr); |
|
|
|
} else { |
|
|
|
SPI.transfer16(0x0200 | ((addr >> 16) & 255)); |
|
|
|
SPI.transfer16(addr); |
|
|
|
} |
|
|
|
// program page command |
|
|
|
do { |
|
|
|
SPI.transfer(*p++); |
|
|
|
} while (--pagelen > 0); |
|
|
|
CSRELEASE(); |
|
|
|
SPI.endTransaction(); |
|
|
|
busy = 1; |
|
|
|
//Serial.printf("busy=%d\n", busy); |
|
|
|
} while (len > 0); |
|
|
|
} |
|
|
|
|
|
|
|
void SerialFlashChip::eraseAll() |
|
|
|
{ |
|
|
|
if (busy) wait(); |
|
|
|
SPI.beginTransaction(SPICONFIG); |
|
|
|
CSASSERT(); |
|
|
|
SPI.transfer(0x06); |
|
|
|
CSRELEASE(); |
|
|
|
CSASSERT(); |
|
|
|
SPI.transfer(0xC7); |
|
|
|
CSRELEASE(); |
|
|
|
SPI.endTransaction(); |
|
|
|
busy = 2; |
|
|
|
uint8_t id[3]; |
|
|
|
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; |
|
|
|
SPI.beginTransaction(SPICONFIG); |
|
|
|
CSASSERT(); |
|
|
|
SPI.transfer(0x06); // write enable command |
|
|
|
CSRELEASE(); |
|
|
|
delayMicroseconds(1); |
|
|
|
CSASSERT(); |
|
|
|
// die erase command |
|
|
|
SPI.transfer(0xC4); |
|
|
|
SPI.transfer16((die_index * die_size) << 8); |
|
|
|
SPI.transfer16(0x0000); |
|
|
|
CSRELEASE(); |
|
|
|
//Serial.printf("Micron erase begin\n"); |
|
|
|
flags |= (die_index + 1) << 6; |
|
|
|
} else { |
|
|
|
// All other chips support the bulk erase command |
|
|
|
SPI.beginTransaction(SPICONFIG); |
|
|
|
CSASSERT(); |
|
|
|
// write enable command |
|
|
|
SPI.transfer(0x06); |
|
|
|
CSRELEASE(); |
|
|
|
delayMicroseconds(1); |
|
|
|
CSASSERT(); |
|
|
|
// bulk erase command |
|
|
|
SPI.transfer(0xC7); |
|
|
|
CSRELEASE(); |
|
|
|
SPI.endTransaction(); |
|
|
|
} |
|
|
|
busy = 3; |
|
|
|
} |
|
|
|
|
|
|
|
void SerialFlashChip::eraseBlock(uint32_t addr) |
|
|
|
{ |
|
|
|
uint8_t f = flags; |
|
|
|
if (busy) wait(); |
|
|
|
SPI.beginTransaction(SPICONFIG); |
|
|
|
CSASSERT(); |
|
|
|
if (fourbytemode) { |
|
|
|
// TODO: Winbond doesn't implement 0xDC on W25Q256FV |
|
|
|
SPI.transfer(0xDC); |
|
|
|
SPI.transfer(0x06); // write enable command |
|
|
|
CSRELEASE(); |
|
|
|
delayMicroseconds(1); |
|
|
|
CSASSERT(); |
|
|
|
if (f & FLAG_32BIT_ADDR) { |
|
|
|
SPI.transfer(0xD8); |
|
|
|
SPI.transfer16(addr >> 16); |
|
|
|
SPI.transfer16(addr); |
|
|
|
} else { |
|
|
@@ -162,7 +242,7 @@ void SerialFlashChip::eraseBlock(uint32_t addr) |
|
|
|
} |
|
|
|
CSRELEASE(); |
|
|
|
SPI.endTransaction(); |
|
|
|
busy = 1; |
|
|
|
busy = 2; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@@ -172,27 +252,93 @@ bool SerialFlashChip::ready() |
|
|
|
if (!busy) return true; |
|
|
|
SPI.beginTransaction(SPICONFIG); |
|
|
|
CSASSERT(); |
|
|
|
status = SPI.transfer16(0x0500); |
|
|
|
CSRELEASE(); |
|
|
|
SPI.endTransaction(); |
|
|
|
if ((status & 1)) return false; |
|
|
|
if (flags & FLAG_STATUS_CMD70) { |
|
|
|
// some Micron chips require this different |
|
|
|
// command to detect program and erase completion |
|
|
|
SPI.transfer(0x70); |
|
|
|
status = SPI.transfer(0); |
|
|
|
CSRELEASE(); |
|
|
|
SPI.endTransaction(); |
|
|
|
//Serial.printf("ready=%02x\n", status & 0xFF); |
|
|
|
if ((status & 0x80) == 0) return false; |
|
|
|
} else { |
|
|
|
// all others work by simply reading the status reg |
|
|
|
SPI.transfer(0x05); |
|
|
|
status = SPI.transfer(0); |
|
|
|
CSRELEASE(); |
|
|
|
SPI.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 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() |
|
|
|
{ |
|
|
|
uint8_t id[3]; |
|
|
|
uint8_t f; |
|
|
|
uint32_t size; |
|
|
|
|
|
|
|
SPI.begin(); |
|
|
|
CSCONFIG(); |
|
|
|
CSRELEASE(); |
|
|
|
if (capacity() <= 16777216) { |
|
|
|
fourbytemode = 0; |
|
|
|
} else { |
|
|
|
fourbytemode = 1; // chip larger than 16 MByte |
|
|
|
// TODO: need to configure for 32 bit address mode |
|
|
|
// because Winbond doesn't implement 0x12 & 0xDC |
|
|
|
readID(id); |
|
|
|
f = 0; |
|
|
|
size = capacity(id); |
|
|
|
if (size > 16777216) { |
|
|
|
// more than 16 Mbyte requires 32 bit addresses |
|
|
|
f |= FLAG_32BIT_ADDR; |
|
|
|
SPI.beginTransaction(SPICONFIG); |
|
|
|
if (id[0] == ID0_SPANSION) { |
|
|
|
// spansion uses MSB of bank register |
|
|
|
CSASSERT(); |
|
|
|
SPI.transfer16(0x1780); // bank register write |
|
|
|
CSRELEASE(); |
|
|
|
} else { |
|
|
|
// micron & winbond & macronix use command |
|
|
|
CSASSERT(); |
|
|
|
SPI.transfer(0x06); // write enable |
|
|
|
CSRELEASE(); |
|
|
|
delayMicroseconds(1); |
|
|
|
CSASSERT(); |
|
|
|
SPI.transfer(0xB7); // enter 4 byte addr mode |
|
|
|
CSRELEASE(); |
|
|
|
} |
|
|
|
SPI.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 (size >= 67108864) { |
|
|
|
// Spansion chips >= 512 mbit 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; |
|
|
|
} |
|
|
|
|
|
|
@@ -207,37 +353,34 @@ void SerialFlashChip::readID(uint8_t *buf) |
|
|
|
buf[2] = SPI.transfer(0); // capacity |
|
|
|
CSRELEASE(); |
|
|
|
SPI.endTransaction(); |
|
|
|
//Serial.printf("ID: %02X %02X %02X\n", buf[0], buf[1], buf[2]); |
|
|
|
} |
|
|
|
|
|
|
|
uint32_t SerialFlashChip::capacity() |
|
|
|
uint32_t SerialFlashChip::capacity(const uint8_t *id) |
|
|
|
{ |
|
|
|
uint8_t id[3]; |
|
|
|
uint32_t n = 1048576; // unknown chips, default to 1 MByte |
|
|
|
|
|
|
|
readID(id); |
|
|
|
//Serial.print("capacity "); |
|
|
|
//Serial.println(id[3], HEX); |
|
|
|
if (id[2] >= 16 && id[2] <= 31) { |
|
|
|
return 1 << id[2]; |
|
|
|
} |
|
|
|
n = 1ul << id[2]; |
|
|
|
} else |
|
|
|
if (id[2] >= 32 && id[2] <= 37) { |
|
|
|
return 1 << (id[2] - 6); |
|
|
|
n = 1ul << (id[2] - 6); |
|
|
|
} |
|
|
|
return 1048576; // unknown, guess 1 MByte |
|
|
|
//Serial.printf("capacity %lu\n", n); |
|
|
|
return n; |
|
|
|
} |
|
|
|
|
|
|
|
uint32_t SerialFlashChip::blockSize() |
|
|
|
{ |
|
|
|
uint8_t id[3]; |
|
|
|
|
|
|
|
readID(id); |
|
|
|
if (id[0] == 1 && id[2] > 0x19) { |
|
|
|
// Spansion chips >= 512 mbit use 256K sectors |
|
|
|
return 262144; |
|
|
|
} |
|
|
|
// 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 |
|
|
@@ -251,32 +394,35 @@ S25FL512S 256 |
|
|
|
SST26VF032 4 |
|
|
|
*/ |
|
|
|
|
|
|
|
// size sector |
|
|
|
// Part Mbit kbyte ID bytes Digikey |
|
|
|
// ---- ---- ----- -------- ------- |
|
|
|
// Winbond W25Q64CV 64 4/32/64 EF 40 17 W25Q128FVSIG-ND |
|
|
|
// Winbond W25Q128FV 128 4/32/64 EF 40 18 W25Q128FVSIG-ND |
|
|
|
// Winbond W25Q256FV 256 64 EF 40 19 |
|
|
|
// Spansion S25FL064A 64 01 02 16 ? |
|
|
|
// Spansion S25FL127S 128 64 01 20 18 1274-1045-ND |
|
|
|
// Spansion S25FL128P 128 64 01 20 18 |
|
|
|
// Spansion S25FL256S 256 64 01 02 19 |
|
|
|
// Spansion S25FL512S 512 256 01 02 20 |
|
|
|
// Macronix MX25L12805D 128 C2 20 18 |
|
|
|
// Numonyx M25P128 128 20 20 18 |
|
|
|
// Micron M25P80 8 20 20 14 |
|
|
|
// Micron N25Q512A 512 4 20 BA 20 557-1569-ND |
|
|
|
// Micron N25Q00AA 1024 4/64 20 BA 21 557-1571-5-ND |
|
|
|
// Micron MT25QL02GC 2048 4/64 20 BB 22 |
|
|
|
// SST SST25WF512 0.5 BF 25 01 |
|
|
|
// SST SST25WF010 1 BF 25 02 |
|
|
|
// SST SST25WF020 2 BF 25 03 |
|
|
|
// SST SST25WF040 4 BF 25 04 |
|
|
|
// SST SST25VF016B 16 BF 25 41 |
|
|
|
// SST26VF016 BF 26 01 |
|
|
|
// SST26VF032 BF 26 02 |
|
|
|
// SST25VF032 32 4/32/64 BF 25 4A |
|
|
|
// SST26VF064 64 BF 26 43 |
|
|
|
// LE25U40CMC 4 4/64 62 06 13 |
|
|
|
|
|
|
|
|
|
|
|
// 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 |
|
|
|
|
|
|
|
SerialFlashChip SerialFlash; |