Skip to content

Commit

Permalink
Adds support for SPI connected flash chips. (#585)
Browse files Browse the repository at this point in the history
* Adds support for SPI connected flash chips.

- Adds a helper class that implements the common operations.
- Adds an instantiation of the SPIFFS driver to use the external flash chip.

* Revert log change
  • Loading branch information
balazsracz authored Dec 6, 2021
1 parent c65a6b6 commit e3cf5c7
Show file tree
Hide file tree
Showing 9 changed files with 575 additions and 3 deletions.
204 changes: 204 additions & 0 deletions src/freertos_drivers/common/SPIFlash.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/** \copyright
* Copyright (c) 2021, Balazs Racz
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* \file SPIFlash.cxx
*
* Shared implementation for operating spiflash devices. This class is intended
* to be used by other device drivers.
*
* @author Balazs Racz
* @date 4 Dec 2021
*/

//#define LOGLEVEL INFO

#include "freertos_drivers/common/SPIFlash.hxx"

#include <fcntl.h>
#include <spi/spidev.h>
#include <stropts.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "os/OS.hxx"
#include "utils/logging.h"

/// Conditional OSMutexLock which can handle a nullptr as mutex (in which case
/// it does not lock anything).
class LockIfExists
{
public:
LockIfExists(OSMutex *mu)
: mu_(mu)
{
if (mu_)
{
mu_->lock();
}
}

~LockIfExists()
{
if (mu_)
{
mu_->unlock();
}
}

private:
OSMutex *mu_;
};

#define LockIfExists(l) int error_omitted_mutex_lock_variable[-1]

void SPIFlash::init(const char *dev_name)
{
spiFd_ = ::open(dev_name, O_RDWR);
HASSERT(spiFd_ >= 0);

uint8_t spi_bpw = 8;
int ret;
ret = ::ioctl(spiFd_, SPI_IOC_WR_MODE, &cfg_->spiMode_);
HASSERT(ret == 0);
ret = ::ioctl(spiFd_, SPI_IOC_WR_BITS_PER_WORD, &spi_bpw);
HASSERT(ret == 0);
ret = ::ioctl(spiFd_, SPI_IOC_WR_MAX_SPEED_HZ, &cfg_->speedHz_);
HASSERT(ret == 0);
}

void SPIFlash::get_id(char id_out[3])
{
LockIfExists l(lock_);
struct spi_ioc_transfer xfer[2] = {0, 0};
xfer[0].tx_buf = (uintptr_t)&cfg_->idCommand_;
xfer[0].len = 1;
xfer[1].rx_buf = (uintptr_t)id_out;
xfer[1].len = 3;
xfer[1].cs_change = true;
::ioctl(spiFd_, SPI_IOC_MESSAGE(2), xfer);
}

void SPIFlash::read(uint32_t addr, void *buf, size_t len)
{
LockIfExists l(lock_);
struct spi_ioc_transfer xfer[2] = {0, 0};
uint8_t rdreq[5];
rdreq[0] = cfg_->readCommand_;
rdreq[1] = (addr >> 16) & 0xff;
rdreq[2] = (addr >> 8) & 0xff;
rdreq[3] = (addr)&0xff;
rdreq[4] = 0;
xfer[0].tx_buf = (uintptr_t)rdreq;
xfer[0].len = 4 + cfg_->readNeedsStuffing_;
xfer[1].rx_buf = (uintptr_t)buf;
xfer[1].len = len;
xfer[1].cs_change = true;
::ioctl(spiFd_, SPI_IOC_MESSAGE(2), xfer);

auto db = (const uint8_t *)buf;
LOG(INFO, "read [%x]=%02x%02x%02x%02x, %u bytes success", (unsigned)addr,
db[0], db[1], db[2], db[3], len);
}

void SPIFlash::write(uint32_t addr, const void *buf, size_t len)
{
HASSERT((addr & cfg_->pageSizeMask_) ==
((addr + len - 1) & cfg_->pageSizeMask_));
LockIfExists l(lock_);
struct spi_ioc_transfer xfer[3] = {0, 0, 0};
uint8_t wreq[4];
wreq[0] = cfg_->writeCommand_;
wreq[1] = (addr >> 16) & 0xff;
wreq[2] = (addr >> 8) & 0xff;
wreq[3] = addr & 0xff;
xfer[0].tx_buf = (uintptr_t)&cfg_->writeEnableCommand_;
xfer[0].len = 1;
xfer[0].cs_change = true;
xfer[1].tx_buf = (uintptr_t)wreq;
xfer[1].len = 4;
xfer[2].tx_buf = (uintptr_t)buf;
xfer[2].len = len;
xfer[2].cs_change = true;
::ioctl(spiFd_, SPI_IOC_MESSAGE(3), xfer);

unsigned waitcount = wait_for_write();
auto db = (const uint8_t *)buf;
LOG(INFO, "write [%x]=%02x%02x%02x%02x, %u bytes success after %u iter",
(unsigned)addr, db[0], db[1], db[2], db[3], len, waitcount);
}

unsigned SPIFlash::wait_for_write()
{
// Now we wait for the write to be complete.
unsigned waitcount = 0;
while (true)
{
struct spi_ioc_transfer sxfer = {0};
uint8_t streq[2];
streq[0] = cfg_->statusReadCommand_;
streq[1] = 0xFF;
sxfer.tx_buf = (uintptr_t)streq;
sxfer.rx_buf = (uintptr_t)streq;
sxfer.len = 2;
sxfer.cs_change = true;
::ioctl(spiFd_, SPI_IOC_MESSAGE(1), &sxfer);

if ((streq[1] & cfg_->statusWritePendingBit_) == 0)
{
return waitcount;
}
waitcount++;
}
}

void SPIFlash::erase(uint32_t addr, size_t len)
{
size_t end = addr + len;
while (addr < end)
{
struct spi_ioc_transfer xfer[2] = {0, 0};
uint8_t ereq[4];
ereq[0] = cfg_->eraseCommand_;
ereq[1] = (addr >> 16) & 0xff;
ereq[2] = (addr >> 8) & 0xff;
ereq[3] = (addr)&0xff;
xfer[0].tx_buf = (uintptr_t)&cfg_->writeEnableCommand_;
xfer[0].len = 1;
xfer[0].cs_change = true;
xfer[1].tx_buf = (uintptr_t)ereq;
xfer[1].len = 4;
xfer[1].cs_change = true;

::ioctl(spiFd_, SPI_IOC_MESSAGE(2), &xfer);

unsigned waitcount = wait_for_write();
LOG(INFO, "erase at %x, success after %u iter", (unsigned)addr,
waitcount);

addr += cfg_->sectorSize_;
}
}
168 changes: 168 additions & 0 deletions src/freertos_drivers/common/SPIFlash.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/** \copyright
* Copyright (c) 2021, Balazs Racz
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* \file SPIFlash.hxx
*
* Shared implementation for operating spiflash devices. This class is intended
* to be used by other device drivers.
*
* @author Balazs Racz
* @date 4 Dec 2021
*/

#ifndef _FREERTOS_DRIVERS_COMMON_SPIFLASH_HXX_
#define _FREERTOS_DRIVERS_COMMON_SPIFLASH_HXX_

#include <inttypes.h>
#include <spi/spidev.h>
#include <sys/types.h>

class OSMutex;
class SPI;

/// Create a const structure like this to tell the spiflash driver how to talk
/// to your spiflash device.
///
/// Use it like this:
///
/// static const SPIFlashConfig cfg = {
/// .speedHz_ = 2000000,
/// .spiMode_ = 3,
/// .writeCommand_ = 0xff,
///};
struct SPIFlashConfig
{
/// Use this frequency to talk to SPI.
uint32_t speedHz_ {1000000};

/// How many bytes is an erase sector.
uint32_t sectorSize_ {4 * 1024};

/// A page program operation might wrap around a page. This will cause
/// bytes to be written to the wrong place. There is a check that prevents
/// this.
///
/// This variable is the mask on the address bits that define the
/// page. Each write operation must start and finish within the same
/// address & pageSizeMask_.
uint32_t pageSizeMask_ {~(256u - 1)};

/// SPI mode to use.
uint8_t spiMode_ {SPI_MODE_0};

/// Command to use for get identification bytes.
uint8_t idCommand_ {0x9F};
/// Command to use for reads.
uint8_t readCommand_ {0x03};
/// Command sent out before each write/erase command.
uint8_t writeEnableCommand_ {0x06};
/// Command to use for writes.
uint8_t writeCommand_ {0x02};
/// Command to use for sector erases.
uint8_t eraseCommand_ {0x20};
/// Command to use for chip erase.
uint8_t chipEraseCommand_ {0x60};

/// Command to use for status register read.
uint8_t statusReadCommand_ {0x05};
/// Which bit to check in the status register for write complete. (This is
/// a mask, it should have exactly one bit set.)
uint8_t statusWritePendingBit_ {0x01};

/// Set this to 1 if the read command needs a dummy byte after the address.
uint8_t readNeedsStuffing_ : 1;
};

/// Shared implementation for operating spiflash devices. This class is intended
/// to be used by other device drivers.
class SPIFlash
{
public:
/// Constructor.
/// @param cfg static configuration for this SPI flash device.
/// @param lock this lock will be taken before performing any operation on
/// the chip. Can be null.
SPIFlash(const SPIFlashConfig *cfg, OSMutex *lock)
: cfg_(cfg)
, lock_(lock)
{
}

/// @return the configuration.
const SPIFlashConfig &cfg()
{
return *cfg_;
}

/// Opens the SPI bus. This is typically called in hw_postinit or main.
/// @param dev_name the name of the SPI device.
void init(const char *dev_name);

/// Performs write to the device. Thiscall is synchronous; does not return
/// until the write is complete.
/// @param addr where to write (0 = beginning of the device).
/// @param buf data to write
/// @param len how many bytes to write
void write(uint32_t addr, const void *buf, size_t len);

/// Reads data from the device.
/// @param addr where to read from
/// @param buf points to where to put the data read
/// @param len how many bytes to read
void read(uint32_t addr, void *buf, size_t len);

/// Erases sector(s) of the device.
/// @param addr beginning of the sector to erase. Must be sector aligned.
/// @param len how many bytes to erase (must be multiple of sector size).
void erase(uint32_t addr, size_t len);

/// Erases the entire device.
void chip_erase();

/// Fetches the identification bytes form the SPIFlash.
/// @param id_out return parameter, will be filled with the received
/// identification bytes.
void get_id(char id_out[3]);

private:
/// Waits until write is complete.
/// @return how many iterations the wait took
unsigned wait_for_write();

/// Configuration.
const SPIFlashConfig *cfg_;

/// Lock that protects accesses to the flash chip.
OSMutex *lock_;

/// File descriptor for the opened SPI bus.
int spiFd_ {-1};
/// Direct access of the SPI device pointer.
/// @todo maybe we are not actually using this.
SPI *spi_;
};

#endif // _FREERTOS_DRIVERS_COMMON_SPIFLASH_HXX_
5 changes: 4 additions & 1 deletion src/freertos_drivers/sources
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ CXXSRCS += Fileio.cxx \
WifiDefs.cxx \
PCA9685PWM.cxx \
SN74HC595GPO.cxx \
TCAN4550Can.cxx
TCAN4550Can.cxx \
SPIFlash.cxx \



ifeq ($(TARGET),freertos.mips4k.pic32mx)
Expand All @@ -43,6 +45,7 @@ SUBDIRS += mbed_lpc1768 drivers_lpc1768 tivaware lpc_chip_175x_6x \
net_freertos_tcp freertos_tcp ti_grlib \
spiffs_cc32x0sf spiffs_tm4c129 \
spiffs_stm32f303xe spiffs_stm32f767xx \
spiffs_spi \

#spiffs_tm4c123 \

Expand Down
Loading

0 comments on commit e3cf5c7

Please sign in to comment.