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.
Diff: SST25VF064C.cpp
- Revision:
- 0:332d4b991d16
- Child:
- 2:33d8a5ea4a80
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SST25VF064C.cpp Tue Apr 10 07:17:51 2012 +0000 @@ -0,0 +1,480 @@ +/////////////////////////////////////////////////////////////////////////////// +// SST25VF064C.cpp - EEPROM 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; + } +}