++

Fork of SerialFlash by tom dunigan

Revision:
0:e5c9fd5789d7
Child:
1:5f1a6c63fa57
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SerialFlash.cpp	Thu Apr 14 12:05:15 2016 +0000
@@ -0,0 +1,500 @@
+/* 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 "mbed.h"
+#include "SerialFlash.h"
+
+static SPI spi(D11,D12,D13); // mosi, miso, sclk
+   DigitalOut cspin(D6);
+
+#define CSASSERT()  cspin = 0
+#define CSRELEASE() cspin = 1
+
+uint16_t SerialFlashChip::dirindex = 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;
+    //Serial.print("wait-");
+    while (1) {
+        CSASSERT();
+        if (flags & FLAG_STATUS_CMD70) {
+            // some Micron chips require this different
+            // command to detect program and erase completion
+            spi.write(0x70);
+            status = spi.write(0);
+            CSRELEASE();
+            //Serial.printf("b=%02x.", status & 0xFF);
+            if ((status & 0x80)) break;
+        } else {
+            // all others work by simply reading the status reg
+            spi.write(0x05);
+            status = spi.write(0);
+            CSRELEASE();
+            //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;
+    b = busy;
+    if (b) {
+        // read status register ... chip may no longer be busy
+        CSASSERT();
+        if (flags & FLAG_STATUS_CMD70) {
+            spi.write(0x70);
+            status = spi.write(0);
+            if ((status & 0x80)) b = 0;
+        } else {
+            spi.write(0x05);
+            status = spi.write(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();
+            spi.write(0x06); // write enable (Micron req'd)
+            CSRELEASE();
+            wait_us(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();
+            spi.write(cmd); // Suspend command
+            CSRELEASE();
+            if (f & FLAG_STATUS_CMD70) {
+                // Micron chips don't actually suspend until flags read
+                CSASSERT();
+                spi.write(0x70);
+                do {
+                    status = spi.write(0);
+                } while (!(status & 0x80));
+                CSRELEASE();
+            } else {
+                CSASSERT();
+                spi.write(0x05);
+                do {
+                    status = spi.write(0);
+                } while ((status & 0x01));
+                CSRELEASE();
+            }
+        } else {
+            // chip is busy with an operation that can not suspend
+            wait();         // should we wait without ending
+            b = 0;          // the transaction??
+        }
+    }
+    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.write(0x03);
+            spi.write(addr >> 24);
+            spi.write(addr >> 16);
+            spi.write(addr >> 8);
+            spi.write(addr);
+        } else {
+            spi.write(3);
+            spi.write(addr >> 16);
+            spi.write(addr >> 8);
+            spi.write(addr);
+        }
+        uint32_t i = rdlen;  // need block transfer
+        while(i>0){
+            *p++ = spi.write(0);
+            i--;
+        }
+        CSRELEASE();
+        addr += rdlen;
+        len -= rdlen;
+    } while (len > 0);
+    if (b) {
+        CSASSERT();
+        spi.write(0x06); // write enable (Micron req'd)
+        CSRELEASE();
+        wait_us(1);
+        cmd = 0x7A;
+        if ((f & FLAG_DIFF_SUSPEND) && (b == 1)) cmd = 0x8A;
+        CSASSERT();
+        spi.write(cmd); // Resume program/erase
+        CSRELEASE();
+    }
+}
+
+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();
+        CSASSERT();
+        // write enable command
+        spi.write(0x06);
+        CSRELEASE();
+        max = 256 - (addr & 0xFF);
+        pagelen = (len <= max) ? len : max;
+         //Serial.printf("WR: addr %08X, pagelen %d\n", addr, pagelen);
+        wait_us(1); // TODO: reduce this, but prefer safety first
+        CSASSERT();
+        if (flags & FLAG_32BIT_ADDR) {
+            spi.write(0x02); // program page command
+            spi.write(addr >> 24);
+            spi.write(addr >> 16);
+            spi.write(addr >> 8);
+            spi.write(addr);
+        } else {
+            spi.write(2);
+            spi.write(addr >> 16);
+            spi.write(addr >> 8);
+            spi.write(addr);
+        }
+        addr += pagelen;
+        len -= pagelen;
+        do {
+            spi.write(*p++);
+        } while (--pagelen > 0);
+        CSRELEASE();
+        busy = 4;
+    } 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;
+        CSASSERT();
+        spi.write(0x06); // write enable command
+        CSRELEASE();
+         wait_us(1);
+        CSASSERT();
+        // die erase command
+        spi.write(0xC4);
+        spi.write((die_index * die_size) );
+        spi.write(0);
+        spi.write(0);
+        spi.write(0);
+        CSRELEASE();
+         //Serial.printf("Micron erase begin\n");
+        flags |= (die_index + 1) << 6;
+    } else {
+        // All other chips support the bulk erase command
+        CSASSERT();
+        // write enable command
+        spi.write(0x06);
+        CSRELEASE();
+         wait_us(1);
+        CSASSERT();
+        // bulk erase command
+        spi.write(0xC7);
+        CSRELEASE();
+    }
+    busy = 3;
+}
+
+void SerialFlashChip::eraseBlock(uint32_t addr)
+{
+    uint8_t f = flags;
+    if (busy) wait();
+    CSASSERT();
+    spi.write(0x06); // write enable command
+    CSRELEASE();
+     wait_us(1);
+    CSASSERT();
+    if (f & FLAG_32BIT_ADDR) {
+        spi.write(0xD8);
+        spi.write(addr >> 24);
+        spi.write(addr >> 16);
+        spi.write(addr >> 8);
+        spi.write(addr);
+    } else {
+        spi.write(0xD8);
+        spi.write(addr >> 16);
+        spi.write(addr >> 8);
+        spi.write(addr);
+    }
+    CSRELEASE();
+    busy = 2;
+}
+
+
+bool SerialFlashChip::ready()
+{
+    uint32_t status;
+    if (!busy) return true;
+    CSASSERT();
+    if (flags & FLAG_STATUS_CMD70) {
+        // some Micron chips require this different
+        // command to detect program and erase completion
+        spi.write(0x70);
+        status = spi.write(0);
+        CSRELEASE();
+        //Serial.printf("ready=%02x\n", status & 0xFF);
+        if ((status & 0x80) == 0) return false;
+    } else {
+        // all others work by simply reading the status reg
+        spi.write(0x05);
+        status = spi.write(0);
+        CSRELEASE();
+        //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 pin)
+{
+    uint8_t id[5];
+    uint8_t f;
+    uint32_t size;
+
+    spi.frequency(30000000);   // max
+    CSRELEASE();
+    readID(id);
+    f = 0;
+    size = capacity(id);
+    if (size > 16777216) {
+        // more than 16 Mbyte requires 32 bit addresses
+        f |= FLAG_32BIT_ADDR;
+        if (id[0] == ID0_SPANSION) {
+            // spansion uses MSB of bank register
+            CSASSERT();
+            spi.write(0x17); // bank register write
+            spi.write(0x80);
+            CSRELEASE();
+        } else {
+            // micron & winbond & macronix use command
+            CSASSERT();
+            spi.write(0x06); // write enable
+            CSRELEASE();
+            wait_us(1);
+            CSASSERT();
+            spi.write(0xB7); // enter 4 byte addr mode
+            CSRELEASE();
+        }
+        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();
+    CSASSERT();
+    spi.write(0xB9); // Deep power down command
+    CSRELEASE();
+}
+
+void SerialFlashChip::wakeup()
+{
+    CSASSERT();
+    spi.write(0xAB); // Wake up from deep power down command
+    CSRELEASE();
+}
+
+void SerialFlashChip::readID(uint8_t *buf)
+{
+    if (busy) wait();
+    CSASSERT();
+    spi.write(0x9F);
+    buf[0] = spi.write(0); // manufacturer ID
+    buf[1] = spi.write(0); // memory type
+    buf[2] = spi.write(0); // capacity
+    if (buf[0] == ID0_SPANSION) {
+        buf[3] = spi.write(0); // ID-CFI
+        buf[4] = spi.write(0); // sector size
+    }
+    CSRELEASE();
+    //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();
+    CSASSERT();
+    spi.write(0x4B);         
+    spi.write(0);  
+    spi.write(0);
+    spi.write(0);
+    spi.write(0);
+    for (int i=0; i<8; i++) {       
+        buf[i] = spi.write(0);
+    }
+    CSRELEASE();
+//  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[2] >= 16 && id[2] <= 31) {
+        n = 1ul << id[2];
+    } else
+    if (id[2] >= 32 && id[2] <= 37) {
+        n = 1ul << (id[2] - 6);
+    }
+    //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
+*/
+
+
+
+//          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;