Driver for SST 64Mbit flash (8Mbyte) model 25VF064C including higher level methods for rewrite and buffered read/write to help optimize I/O. Can also work with other 25 series flash and eeprom devices, requiring minor revisions for their capabilities.

Dependencies:   SPIDebug

SST25VF064C.cpp

Committer:
davervw
Date:
2012-04-12
Revision:
2:33d8a5ea4a80
Parent:
0:332d4b991d16

File content as of revision 2:33d8a5ea4a80:

///////////////////////////////////////////////////////////////////////////////
// SST25VF064C.cpp - Flash driver
//
// COPYRIGHT (c) 2012 by David Van Wagner
//
// dave@vanwagner.org
// http://techwithdave.blogspot.com
//
// License: Creative Commons Attribution-ShareAlike 3.0 Unported License
// http://creativecommons.org/licenses/by-sa/3.0/
///////////////////////////////////////////////////////////////////////////////

#include "SST25VF064C.h"
//#include <assert.h>

SST25VF064C::uint8 SST25VF064C::SID_buffer[SID_LEN];
SST25VF064C::uint8 SST25VF064C::sector_buffer[SECTOR_LEN];
SST25VF064C::int32 SST25VF064C::buffered_addr;
bool SST25VF064C::buffered_erased;

SST25VF064C::SST25VF064C(PinName mosi, PinName miso, PinName sclk, PinName cs, int frequency)
{
#ifdef SPIDEBUG
    this->cs = new CSDebug(cs);
#else
    this->cs = new DigitalOut(cs);    
#endif    
    this->cs->write(true);
    this->frequency = frequency;
#ifdef SPIDEBUG    
    spi = new SPIDebug(mosi, miso, sclk);
#else
    spi = new SPI(mosi, miso, sclk);    
#endif    
    spi->format(8, 0);
    spi->frequency(frequency);
    buffered_addr = -1;
    buffered_erased = false;
    //assert(Id() == 0x4bbf);
    //assert(JEDECId() == 0xbf254b);
}

SST25VF064C::~SST25VF064C()
{
    delete spi;
    delete cs;
}

SST25VF064C::uint16 SST25VF064C::Id()
{
    cs->write(0);
    spi->write(0xab);
    spi->write(0);
    spi->write(0);
    spi->write(0);
    unsigned id = (spi->write(0) << 8) | spi->write(0);
    cs->write(1);
    return id;
}

SST25VF064C::uint32 SST25VF064C::JEDECId()
{
    cs->write(0);
    spi->write(0x9f);
    unsigned id = (spi->write(0) << 16) | (spi->write(0) << 8) | spi->write(0);
    cs->write(1);
    return id;
}

SST25VF064C::uint8* SST25VF064C::SID()
{
    cs->write(0);
    spi->write(0x88);
    spi->write(0); // address
    spi->write(0); // dummy
    for (int i=0; i<SID_LEN; ++i)
        SID_buffer[i] = spi->write(0);
    cs->write(1);
    return SID_buffer;
}

// Read Status Register    
// bit 0 BUSY 1=Write in progress
// bit 1 WEL  1=Write Enabled
// bit 2 BP0  block write protection
// bit 3 BP1  block write protection
// bit 4 BP2  block write protection
// bit 5 BP3  block write protection
// bit 6 SEC  1=Security ID space locked
// bit 7 BPL  1=BP0..BP3 are read-only, 0=r/w
SST25VF064C::uint8 SST25VF064C::RDSR()
{
    cs->write(0);
    spi->write(0x05);
    uint8 status = spi->write(0);
    cs->write(1);
    return status;
}

// Enable Write Status Register
void SST25VF064C::EWSR()
{
    cs->write(0);
    spi->write(0x50);
    cs->write(1);
}

// Write Status Register
void SST25VF064C::WRSR(SST25VF064C::uint8 status)
{
    cs->write(0);
    spi->write(0x01);
    spi->write(status);
    cs->write(1);
}

// Write Enable
void SST25VF064C::WREN()
{
    cs->write(0);
    spi->write(0x06);
    cs->write(1);
}

// Write Disable
void SST25VF064C::WRDI()
{
    cs->write(0);
    spi->write(0x04);
    cs->write(1);
}

bool SST25VF064C::BUSY()
{
    return (RDSR() & 0x01) != 0;
}

bool SST25VF064C::WEL()
{
    return (RDSR() & 0x02) != 0;
}

SST25VF064C::uint8 SST25VF064C::BP()
{
    return (RDSR() >> 2) & 0xF;
}

bool SST25VF064C::SEC()
{
    return (RDSR() & 0x40) != 0;
}

bool SST25VF064C::BPL()
{
    return (RDSR() & 0x80) != 0;
}

void SST25VF064C::wait_while_busy()
{
    while (BUSY())
        ;
}

SST25VF064C::uint8 SST25VF064C::read(SST25VF064C::int32 addr)
{
    cs->write(0);
    spi->write(0x03);
    spi->write((addr >> 16) & 0xff);
    spi->write((addr >> 8) & 0xff);
    spi->write(addr & 0xff);
    uint8 data = spi->write(0);
    cs->write(1);
    return data;
}

bool SST25VF064C::read(SST25VF064C::int32 addr, SST25VF064C::uint8* dst, SST25VF064C::int32 len)
{
    if (addr < 0 || addr >= MAX_ADDR || dst == 0 || len < 1 || addr+len > MAX_ADDR)
        return false;

    cs->write(0);
    spi->write(0x03);
    spi->write((addr >> 16) & 0xff);
    spi->write((addr >> 8) & 0xff);
    spi->write(addr & 0xff);
    for (int32 i=0; i<len; ++i)
        dst[i] = spi->write(0);
    cs->write(1);
    
    return true;
}

void SST25VF064C::hsread(SST25VF064C::int32 addr, SST25VF064C::uint8* dst, SST25VF064C::int32 len, int frequency)
{
    int save_frequency = this->frequency;
    spi->frequency(frequency);
    cs->write(0);
    spi->write(0x0B);
    spi->write((addr >> 16) & 0xff);
    spi->write((addr >> 8) & 0xff);
    spi->write(addr & 0xff);
    spi->write(0); // dummy
    for (int32 i=0; i<len; ++i)
        dst[i] = spi->write(0);
    cs->write(1);
    spi->frequency(save_frequency);
}

void SST25VF064C::sector_erase_4k(SST25VF064C::int32 addr)
{
    cs->write(0);
    spi->write(0x20);
    spi->write((uint8)(addr >> 16));
    spi->write((uint8)(addr >> 8));
    spi->write((uint8)addr);
    cs->write(1);
    wait_while_busy();
}

void SST25VF064C::block_erase_32k(SST25VF064C::int32 addr)
{
    cs->write(0);
    spi->write(0x52);
    spi->write((uint8)(addr >> 16));
    spi->write((uint8)(addr >> 8));
    spi->write((uint8)addr);
    cs->write(1);
    wait_while_busy();
}    

void SST25VF064C::block_erase_64k(SST25VF064C::int32 addr)
{
    cs->write(0);
    spi->write(0xd8);
    spi->write((uint8)(addr >> 16));
    spi->write((uint8)(addr >> 8));
    spi->write((uint8)addr);
    cs->write(1);
    wait_while_busy();
}    

void SST25VF064C::chip_erase()
{
    cs->write(0);
    spi->write(0x60);
    cs->write(1);
    wait_while_busy();
}

bool SST25VF064C::page_program(SST25VF064C::int32 addr, SST25VF064C::uint8* write_buffer, short len)
{
    // no point in writing FF as an empty sector already has those
    // (and if not empty, write won't succeed)
    bool skipped = false;
    while (len > 0 && *write_buffer == 0xFF)
    {
        ++write_buffer;
        --len;
        ++addr;
        skipped = true;
    }
    if (len == 0 && skipped)
        return true; // special case when succeeds when nothing to do

    if (len < 1 || len > 256)
        return false;
        
    // write enable
    WREN();

    cs->write(0);
    spi->write(0x02);
    spi->write((uint8)(addr >> 16));
    spi->write((uint8)(addr >> 8));
    spi->write((uint8)addr);
    for (short i=0; i<len; ++i)
        spi->write(write_buffer[i]);
    cs->write(1);
    wait_while_busy();
    
    return true;
}

bool SST25VF064C::write(SST25VF064C::int32 addr, SST25VF064C::uint8* write_buffer, SST25VF064C::int32 len)
{
    if (len < 0 || addr < 0 || addr > MAX_ADDR || (addr+len > MAX_ADDR) || (addr+len < 0))
        return false;

    if (len == 0)
        return true; // done!

    // calculate first page size based on address and length
    int32 page_len = PAGE_LEN-(addr & (PAGE_LEN-1)); // remaining space in page

    while (len > 0)
    {
        if (page_len > len)
            page_len = len;

        page_program(addr, write_buffer, page_len);

        addr += page_len;        
        write_buffer += page_len;
        len -= page_len;
        page_len = PAGE_LEN;
    }
    
    return true;
}    

bool SST25VF064C::rewrite(SST25VF064C::int32 addr, SST25VF064C::uint8* write_buffer, SST25VF064C::int32 len)
{
    // validate parameters
    if (len < 0 || addr < 0 || addr > MAX_ADDR || (addr+len > MAX_ADDR) || (addr+len < 0))
        return false;

    // are we done before we've started?
    if (len == 0)
        return true; // done!

    // calculate first sector size based on address and length
    int32 sector_len = SECTOR_LEN-(addr & (SECTOR_LEN-1)); // remaining space in sector

    while (len > 0)
    {
        // adjust if buffer than sector size
        if (sector_len > len)
            sector_len = len;

        // read existing data into entire sector buffer
        read(addr & (MAX_ADDR ^ (SECTOR_LEN-1)), sector_buffer, SECTOR_LEN);
        
        // overwrite with requested data at proper offset
        memcpy(sector_buffer + (addr & (SECTOR_LEN-1)), write_buffer, sector_len);

        // reset to beginning of sector
        addr = addr ^ (addr & (SECTOR_LEN-1));
        
        // erase sector
        WREN();
        sector_erase_4k(addr);            
        
        // rewrite the sector
        uint8 *p = sector_buffer;
        int sectors_in_page = SECTOR_LEN/PAGE_LEN;
        for (int i=0; i<sectors_in_page; ++i)
        {
            page_program(addr, p, PAGE_LEN);
            addr += PAGE_LEN;
            p += PAGE_LEN;
        }

        // adjust where we are, what left to do
        write_buffer += sector_len;
        len -= sector_len;
        sector_len = SECTOR_LEN;
    }

    wait_while_busy();
    
    return true;
}    

bool SST25VF064C::buffered_write(SST25VF064C::uint8 data)
{
    int32& addr = buffered_addr;

    if (addr < 0 || addr > MAX_ADDR)
        return false;

    bool result = true;

    // if at sector boundary
    if ((addr & (SECTOR_LEN-1)) == 0 || !buffered_erased)
    {
        WREN();
        sector_erase_4k(addr ^ (addr & (SECTOR_LEN-1)));
        buffered_erased = true;
    }

    sector_buffer[addr & (SECTOR_LEN-1)] = data;
    
    ++addr;

    // if at sector boundary
    if ((addr & (SECTOR_LEN-1)) == 0)
    {
        // write sector
        result = write(addr-SECTOR_LEN, sector_buffer, SECTOR_LEN);
        buffered_erased = false;

        // read more
        if (addr != MAX_ADDR)
            result = result && read(addr, sector_buffer, SECTOR_LEN);
    }
    
    return result;
}

bool SST25VF064C::buffered_write(SST25VF064C::uint8* src, int len)
{
    bool result = true;
    
    while (result && len > 0)
    {
        result = buffered_write(*src);
        --len;
        ++src;
    }
    
    return result;
}

SST25VF064C::uint8 SST25VF064C::buffered_read()
{
    int32& addr = buffered_addr;
    if (addr < 0 || addr >= MAX_ADDR)
    {
        addr = -1;
        return 0xff;
    }
    uint8 data = sector_buffer[addr & (SECTOR_LEN-1)];
    ++addr;
    if ((addr & (SECTOR_LEN-1)) == 0)
    {
        if (buffered_erased)
        {
            write(addr-SECTOR_LEN, sector_buffer, SECTOR_LEN);
            buffered_erased = false;
        }
        if (addr == MAX_ADDR)
            addr = -1;
        else
            read(addr, sector_buffer, SECTOR_LEN);
    }
    return data;
}

bool SST25VF064C::buffered_read(SST25VF064C::uint8* dest, int len)
{
    while (len > 0)
    {
        if (buffered_addr < 0 || buffered_addr >= MAX_ADDR)
            return false;
    
        *dest = buffered_read();
        --len;
        ++dest;
    }
    
    return true;
}

void SST25VF064C::buffered_seek(SST25VF064C::int32 addr)
{
    if (buffered_erased)
        write(buffered_addr ^ (buffered_addr & (SECTOR_LEN-1)), sector_buffer, SECTOR_LEN);
    if (addr < 0 || addr >= MAX_ADDR)
    {
        buffered_addr = -1;
        buffered_erased = false;
    }
    else
    {
        read(addr ^ (addr & (SECTOR_LEN-1)), sector_buffer, SECTOR_LEN);
        buffered_addr = addr;
        buffered_erased = false;
    }
}

void SST25VF064C::buffered_sync()
{
    int32& addr = buffered_addr;

    if (buffered_erased)
    {
        write(addr ^ (addr & (SECTOR_LEN-1)), sector_buffer, SECTOR_LEN);
        buffered_erased = false;
    }
}