Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for SPI connected flash chips. #585

Merged
merged 2 commits into from
Dec 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This value should come from a table based on reported ID. A todo and an issue filed for enhancement is good enough for now. Of course, we may need to get this value before instantiating the SPIFFS so that it gets the correct sector size too.

We may find ourselves down the road needing to have a second source for the SPI flash that uses different sector and page sizes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are just the default initialization values. The expected usage is that there is a struct in HwInit which can be filled up for the given hardware. We could possibly put this into RAM and change the values, or select from more than one such structure that are in flash.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultimately, this is discoverable at runtime by evaluating the SPI FLASH ID itself. That is how we should get the value.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filed #586


/// 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)};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same.


/// 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