NVM: deeply restructured W25Qx driver

Refactored the W25Qx driver in order to make it an SPI device conformant
to the nvmDevice driver interface. Removed the read/write/erase functions.
pull/292/head
Silvano Seva 2024-06-02 15:08:54 +02:00
rodzic 565a056e82
commit a4db8d2242
2 zmienionych plików z 181 dodań i 252 usunięć

Wyświetl plik

@ -37,14 +37,6 @@
#define CMD_PDWN 0xB9 /* Power down */ #define CMD_PDWN 0xB9 /* Power down */
#define CMD_ECHIP 0xC7 /* Full chip erase */ #define CMD_ECHIP 0xC7 /* Full chip erase */
/*
* Target-specific SPI interface functions, their implementation can be found
* in source files "spiFlash_xxx.c"
*/
extern uint8_t spiFlash_SendRecv(uint8_t val);
extern void spiFlash_init();
extern void spiFlash_terminate();
static const size_t PAGE_SIZE = 256; static const size_t PAGE_SIZE = 256;
static const size_t SECT_SIZE = 4096; static const size_t SECT_SIZE = 4096;
@ -56,7 +48,7 @@ static const size_t SECT_SIZE = 4096;
* @param timeout: wait timeout, in ms. * @param timeout: wait timeout, in ms.
* @return zero on success, -EIO if timeout expires. * @return zero on success, -EIO if timeout expires.
*/ */
static int waitUntilReady(uint32_t timeout) static int waitUntilReady(const struct W25QxCfg *cfg, uint32_t timeout)
{ {
// Each wait tick is 500us // Each wait tick is 500us
timeout *= 2; timeout *= 2;
@ -66,13 +58,15 @@ static int waitUntilReady(uint32_t timeout)
delayUs(500); delayUs(500);
timeout--; timeout--;
gpio_clearPin(FLASH_CS); uint8_t cmd[] = {CMD_RDSTA, 0x00};
spiFlash_SendRecv(CMD_RDSTA); uint8_t ret[2];
uint8_t status = spiFlash_SendRecv(0x00);
gpio_setPin(FLASH_CS); gpioPin_clear(&cfg->cs);
spi_transfer(cfg->spi, cmd, ret, 2);
gpioPin_set(&cfg->cs);
/* If busy flag is low, we're done */ /* If busy flag is low, we're done */
if((status & 0x01) == 0) if((ret[1] & 0x01) == 0)
return 0; return 0;
} }
@ -80,146 +74,162 @@ static int waitUntilReady(uint32_t timeout)
} }
void W25Qx_init() void W25Qx_init(const struct nvmDevice *dev)
{ {
gpio_setMode(FLASH_CS, OUTPUT); const struct W25QxCfg *cfg = (const struct W25QxCfg *) dev->priv;
gpio_setPin(FLASH_CS);
spiFlash_init(); gpioPin_setMode(&cfg->cs, OUTPUT);
W25Qx_wakeup(); gpioPin_set(&cfg->cs);
W25Qx_wakeup(dev);
// TODO: Implement sleep to increase power saving // TODO: Implement sleep to increase power saving
} }
void W25Qx_terminate() void W25Qx_terminate(const struct nvmDevice *dev)
{ {
W25Qx_sleep(); const struct W25QxCfg *cfg = (const struct W25QxCfg *) dev->priv;
gpio_setMode(FLASH_CS, INPUT); W25Qx_sleep(dev);
gpioPin_setMode(&cfg->cs, INPUT);
spiFlash_terminate();
} }
void W25Qx_wakeup() void W25Qx_wakeup(const struct nvmDevice *dev)
{ {
gpio_clearPin(FLASH_CS); const struct W25QxCfg *cfg = (const struct W25QxCfg *) dev->priv;
spiFlash_SendRecv(CMD_WKUP); const uint8_t cmd = CMD_WKUP;
gpio_setPin(FLASH_CS);
spi_acquire(cfg->spi);
gpioPin_clear(&cfg->cs);
spi_send(cfg->spi, &cmd, 1);
gpioPin_set(&cfg->cs);
spi_release(cfg->spi);
} }
void W25Qx_sleep() void W25Qx_sleep(const struct nvmDevice *dev)
{ {
gpio_clearPin(FLASH_CS); const struct W25QxCfg *cfg = (const struct W25QxCfg *) dev->priv;
spiFlash_SendRecv(CMD_PDWN); const uint8_t cmd = CMD_PDWN;
gpio_setPin(FLASH_CS);
spi_acquire(cfg->spi);
gpioPin_clear(&cfg->cs);
spi_send(cfg->spi, &cmd, 1);
gpioPin_set(&cfg->cs);
spi_release(cfg->spi);
} }
ssize_t W25Qx_readSecurityRegister(uint32_t addr, void* buf, size_t len) static int nvm_api_readSecReg(const struct nvmDevice *dev, uint32_t offset, void *data, size_t len)
{ {
uint32_t addrBase = addr & 0x3000; const struct W25QxSecRegDevice *pDev = (const struct W25QxSecRegDevice *) dev;
uint32_t addrRange = addr & 0xCFFF; const struct W25QxCfg *cfg = (const struct W25QxCfg *) dev->priv;
if((addrBase < 0x1000) || (addrBase > 0x3000)) return -1; /* Out of base */
if(addrRange > 0xFF) return -1; /* Out of range */
/* Keep 256-byte boundary to avoid wrap-around when reading */ // Keep 256-byte boundary to avoid wrap-around when reading
size_t readLen = len; size_t readLen = len;
if((addrRange + len) > 0xFF) if((offset + len) > 0xFF)
{ {
readLen = 0xFF - (addrRange & 0xFF); readLen = 0xFF - (offset & 0xFF);
} }
gpio_clearPin(FLASH_CS); uint32_t address = pDev->baseAddr + offset;
spiFlash_SendRecv(CMD_RSECR); /* Command */ const uint8_t command[] =
spiFlash_SendRecv((addr >> 16) & 0xFF); /* Address high */
spiFlash_SendRecv((addr >> 8) & 0xFF); /* Address middle */
spiFlash_SendRecv(addr & 0xFF); /* Address low */
spiFlash_SendRecv(0x00); /* Dummy byte */
for(size_t i = 0; i < readLen; i++)
{ {
((uint8_t *) buf)[i] = spiFlash_SendRecv(0x00); CMD_RSECR, // Command
} (address >> 16) & 0xFF, // Address high
(address >> 8) & 0xFF, // Address middle
address & 0xFF, // Address low
0x00 // Dummy byte
};
gpio_setPin(FLASH_CS); spi_acquire(cfg->spi);
gpioPin_clear(&cfg->cs);
return ((ssize_t) readLen); spi_send(cfg->spi, command, sizeof(command));
} spi_receive(cfg->spi, data, readLen);
int W25Qx_readData(uint32_t addr, void* buf, size_t len) gpioPin_set(&cfg->cs);
{ spi_release(cfg->spi);
gpio_clearPin(FLASH_CS);
spiFlash_SendRecv(CMD_READ); /* Command */
spiFlash_SendRecv((addr >> 16) & 0xFF); /* Address high */
spiFlash_SendRecv((addr >> 8) & 0xFF); /* Address middle */
spiFlash_SendRecv(addr & 0xFF); /* Address low */
for(size_t i = 0; i < len; i++)
{
((uint8_t *) buf)[i] = spiFlash_SendRecv(0x00);
}
gpio_setPin(FLASH_CS);
return 0; return 0;
} }
int W25Qx_erase(uint32_t addr, size_t size) static int nvm_api_read(const struct nvmDevice *dev, uint32_t offset, void *data, size_t len)
{ {
const struct W25QxCfg *cfg = (const struct W25QxCfg *) dev->priv;
const uint8_t command[] =
{
CMD_READ, // Command
(offset >> 16) & 0xFF, // Address high
(offset >> 8) & 0xFF, // Address middle
offset & 0xFF, // Address low
};
spi_acquire(cfg->spi);
gpioPin_clear(&cfg->cs);
spi_send(cfg->spi, command, sizeof(command));
spi_receive(cfg->spi, data, len);
gpioPin_set(&cfg->cs);
spi_release(cfg->spi);
return 0;
}
static int nvm_api_erase(const struct nvmDevice *dev, uint32_t offset, size_t size)
{
const struct W25QxCfg *cfg = (const struct W25QxCfg *) dev->priv;
uint8_t command[4];
// Addr or size not aligned to sector size // Addr or size not aligned to sector size
if(((addr % SECT_SIZE) != 0) || ((size % SECT_SIZE) != 0)) if(((offset % SECT_SIZE) != 0) || ((size % SECT_SIZE) != 0))
return -EINVAL; return -EINVAL;
gpio_clearPin(FLASH_CS); spi_acquire(cfg->spi);
spiFlash_SendRecv(CMD_WREN); /* Write enable */
gpio_setPin(FLASH_CS);
delayUs(5);
int ret = 0; int ret = 0;
while(size > 0) while(size > 0)
{ {
gpio_clearPin(FLASH_CS); // Write enable, has to be issued for each erase operation
spiFlash_SendRecv(CMD_ESECT); /* Command */ command[0] = CMD_WREN;
spiFlash_SendRecv((addr >> 16) & 0xFF); /* Address high */ gpioPin_clear(&cfg->cs);
spiFlash_SendRecv((addr >> 8) & 0xFF); /* Address middle */ spi_send(cfg->spi, command, 1);
spiFlash_SendRecv(addr & 0xFF); /* Address low */ gpioPin_set(&cfg->cs);
gpio_setPin(FLASH_CS);
ret = waitUntilReady(500); delayUs(5);
// Sector erase
command[0] = CMD_ESECT; // Command
command[1] = (offset >> 16) & 0xFF; // Address high
command[2] = (offset >> 8) & 0xFF; // Address middle
command[3] = offset & 0xFF; // Address low
gpioPin_clear(&cfg->cs);
spi_send(cfg->spi, command, 4);
gpioPin_set(&cfg->cs);
ret = waitUntilReady(cfg, 500);
if(ret < 0) if(ret < 0)
break; break;
size -= SECT_SIZE; size -= SECT_SIZE;
addr += SECT_SIZE; offset += SECT_SIZE;
} }
spi_release(cfg->spi);
return ret; return ret;
} }
bool W25Qx_eraseChip() static ssize_t W25Qx_writePage(const struct nvmDevice *dev, uint32_t addr,
const void* buf, size_t len)
{ {
gpio_clearPin(FLASH_CS); const struct W25QxCfg *cfg = (const struct W25QxCfg *) dev->priv;
spiFlash_SendRecv(CMD_WREN);
gpio_setPin(FLASH_CS);
delayUs(5); // Keep page boundary to avoid wrap-around when writing
gpio_clearPin(FLASH_CS);
spiFlash_SendRecv(CMD_ECHIP);
gpio_setPin(FLASH_CS);
/*
* Wait until erase terminates, timeout after 200s.
*/
int ret = waitUntilReady(200000);
if(ret == 0)
return true;
return false;
}
ssize_t W25Qx_writePage(uint32_t addr, const void* buf, size_t len)
{
/* Keep page boundary to avoid wrap-around when writing */
size_t addrRange = addr & (PAGE_SIZE - 1); size_t addrRange = addr & (PAGE_SIZE - 1);
size_t writeLen = len; size_t writeLen = len;
if((addrRange + len) > PAGE_SIZE) if((addrRange + len) > PAGE_SIZE)
@ -227,84 +237,55 @@ ssize_t W25Qx_writePage(uint32_t addr, const void* buf, size_t len)
writeLen = PAGE_SIZE - addrRange; writeLen = PAGE_SIZE - addrRange;
} }
gpio_clearPin(FLASH_CS); // Write enable bit has to be set before each page program
spiFlash_SendRecv(CMD_WREN); /* Write enable */ uint8_t command[4];
gpio_setPin(FLASH_CS); command[0] = CMD_WREN;
delayUs(5); spi_acquire(cfg->spi);
gpioPin_clear(&cfg->cs);
spi_send(cfg->spi, command, 1);
gpioPin_set(&cfg->cs);
gpio_clearPin(FLASH_CS); // Page program
spiFlash_SendRecv(CMD_WRITE); /* Command */ command[0] = CMD_WRITE; // Command
spiFlash_SendRecv((addr >> 16) & 0xFF); /* Address high */ command[1] = (addr >> 16) & 0xFF; // Address high
spiFlash_SendRecv((addr >> 8) & 0xFF); /* Address middle */ command[2] = (addr >> 8) & 0xFF; // Address middle
spiFlash_SendRecv(addr & 0xFF); /* Address low */ command[3] = addr & 0xFF; // Address low
for(size_t i = 0; i < writeLen; i++) gpioPin_clear(&cfg->cs);
{ spi_send(cfg->spi, command, 4);
uint8_t value = ((uint8_t *) buf)[i]; spi_send(cfg->spi, buf, writeLen);
(void) spiFlash_SendRecv(value); gpioPin_set(&cfg->cs);
}
gpio_setPin(FLASH_CS); // Wait until write terminates, timeout after 500ms.
int ret = waitUntilReady(cfg, 500);
spi_release(cfg->spi);
/*
* Wait until erase terminates, timeout after 500ms.
*/
int ret = waitUntilReady(500);
if(ret < 0) if(ret < 0)
return (ssize_t) ret; return (ssize_t) ret;
else
return writeLen; return writeLen;
}
int W25Qx_writeData(uint32_t addr, const void *buf, size_t len)
{
while(len > 0)
{
size_t toWrite = len;
// Maximum single-shot write lenght is one page
if(toWrite >= PAGE_SIZE)
toWrite = PAGE_SIZE;
ssize_t written = W25Qx_writePage(addr, buf, toWrite);
if(written < 0)
return (int) written;
len -= (size_t) written;
buf = ((const uint8_t *) buf) + (size_t) written;
addr += (size_t) written;
}
return 0;
}
static int nvm_api_readSecReg(const struct nvmDevice *dev, uint32_t offset, void *data, size_t len)
{
(void) dev;
return W25Qx_readSecurityRegister(offset, data, len);
}
static int nvm_api_read(const struct nvmDevice *dev, uint32_t offset, void *data, size_t len)
{
(void) dev;
return W25Qx_readData(offset, data, len);
} }
static int nvm_api_write(const struct nvmDevice *dev, uint32_t offset, const void *data, size_t len) static int nvm_api_write(const struct nvmDevice *dev, uint32_t offset, const void *data, size_t len)
{ {
(void) dev; while(len > 0)
{
// Maximum single-shot write length is one page
size_t toWrite = len;
if(toWrite >= PAGE_SIZE)
toWrite = PAGE_SIZE;
return W25Qx_writeData(offset, data, len); ssize_t written = W25Qx_writePage(dev, offset, data, toWrite);
} if(written < 0)
return (int) written;
static int nvm_api_erase(const struct nvmDevice *dev, uint32_t offset, size_t size) len -= (size_t) written;
{ data = ((const uint8_t *) data) + (size_t) written;
(void) dev; offset += (size_t) written;
}
return W25Qx_erase(offset, size); return 0;
} }
const struct nvmOps W25Qx_ops = const struct nvmOps W25Qx_ops =

Wyświetl plik

@ -24,6 +24,8 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <sys/types.h> #include <sys/types.h>
#include <peripherals/spi.h>
#include <peripherals/gpio.h>
#include <interfaces/nvmem.h> #include <interfaces/nvmem.h>
/** /**
@ -31,10 +33,19 @@
* volatile memory on various radios to store both calibration and contact data. * volatile memory on various radios to store both calibration and contact data.
*/ */
/**
* Configuration data structure for W25Qx memory device.
*/
struct W25QxCfg
{
const struct spiDevice *spi;
const struct gpioPin cs;
};
/** /**
* Driver data structure for W25Qx security registers. * Driver data structure for W25Qx security registers.
*/ */
struct w25qSecRegDevice struct W25QxSecRegDevice
{ {
const void *priv; ///< Device driver private data const void *priv; ///< Device driver private data
const struct nvmOps *ops; ///< Device operations const struct nvmOps *ops; ///< Device operations
@ -55,12 +66,13 @@ extern const struct nvmInfo W25Qx_info;
* @param name: device name. * @param name: device name.
* @param sz: memory size, in bytes. * @param sz: memory size, in bytes.
*/ */
#define W25Qx_DEVICE_DEFINE(name, sz) \ #define W25Qx_DEVICE_DEFINE(name, config, sz) \
struct nvmDevice name = \ const struct nvmDevice name = \
{ \ { \
.ops = &W25Qx_ops, \ .priv = &config, \
.info = &W25Qx_info, \ .ops = &W25Qx_ops, \
.size = sz \ .info = &W25Qx_info, \
.size = sz, \
}; };
/** /**
@ -76,24 +88,25 @@ extern const struct nvmInfo W25Qx_secReg_info;
* @param base: security register base address. * @param base: security register base address.
* @param sz: memory size, in bytes. * @param sz: memory size, in bytes.
*/ */
#define W25Qx_SECREG_DEFINE(name, base, sz) \ #define W25Qx_SECREG_DEFINE(name, config, base, sz) \
struct w25qSecRegDevice name = \ const struct W25QxSecRegDevice name = \
{ \ { \
.ops = &W25Qx_secReg_ops, \ .priv = &config, \
.info = &W25Qx_secReg_info, \ .ops = &W25Qx_secReg_ops, \
.size = sz, \ .info = &W25Qx_secReg_info, \
.baseAddr = base \ .size = sz, \
.baseAddr = base \
}; };
/** /**
* Initialise driver for external flash. * Initialise driver for external flash.
*/ */
void W25Qx_init(); void W25Qx_init(const struct nvmDevice *dev);
/** /**
* Terminate driver for external flash. * Terminate driver for external flash.
*/ */
void W25Qx_terminate(); void W25Qx_terminate(const struct nvmDevice *dev);
/** /**
* Release flash chip from power down mode, this function should be called at * Release flash chip from power down mode, this function should be called at
@ -102,76 +115,11 @@ void W25Qx_terminate();
* Application code must wait at least 3us before issuing any other command * Application code must wait at least 3us before issuing any other command
* after this one. * after this one.
*/ */
void W25Qx_wakeup(); void W25Qx_wakeup(const struct nvmDevice *dev);
/** /**
* Put flash chip in low power mode. * Put flash chip in low power mode.
*/ */
void W25Qx_sleep(); void W25Qx_sleep(const struct nvmDevice *dev);
/**
* Read data from one of the flash security registers, located at addresses
* 0x1000, 0x2000 and 0x3000 and 256-byte wide.
* NOTE: If a read operation goes beyond the 256 byte boundary, length will be
* truncated to the one reaching the end of the register.
*
* @param addr: start address for read operation, must be the full register address.
* @param buf: pointer to a buffer where data is written to.
* @param len: number of bytes to read.
* @return: -1 if address is not whithin security registers address range, the
* number of bytes effectively read otherwise.
*/
ssize_t W25Qx_readSecurityRegister(uint32_t addr, void *buf, size_t len);
/**
* Read data from flash memory.
*
* @param addr: start address for read operation.
* @param buf: pointer to a buffer where data is written to.
* @param len: number of bytes to read.
* @return zero on success, negative errno code on fail.
*/
int W25Qx_readData(uint32_t addr, void *buf, size_t len);
/**
* Erase a flash memory area.
* The start address and erase size must be aligned to page size. The function
* blocks until the erase process terminates.
*
* @param addr: start address.
* @param size: size of the area to be erased.
* @return zero on success, negative errno code on fail.
*/
int W25Qx_erase(uint32_t addr, size_t size);
/**
* Full chip erase.
* Function returns when erase process terminated.
*
* @return true on success, false on failure.
*/
bool W25Qx_eraseChip();
/**
* Write data to a 256-byte flash memory page.
* NOTE: if data size goes beyond the 256 byte boundary, length will be truncated
* to the one reaching the end of the page.
*
* @param addr: start address for write operation.
* @param buf: pointer to data buffer.
* @param len: number of bytes to written.
* @return: -1 on error, the number of bytes effectively written otherwise.
*/
ssize_t W25Qx_writePage(uint32_t addr, const void *buf, size_t len);
/**
* Write data to flash memory.
*
* @param addr: start address for read operation.
* @param buf: pointer to a buffer where data is written to.
* @param len: number of bytes to read.
* @return zero on success, negative errno code on fail.
*/
int W25Qx_writeData(uint32_t addr, const void *buf, size_t len);
#endif /* W25Qx_H */ #endif /* W25Qx_H */