-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds support for SPI connected flash chips. (#585)
* 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
1 parent
c65a6b6
commit e3cf5c7
Showing
9 changed files
with
575 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.