Skip to content

Commit

Permalink
Make w25n01g FLASH driver non-blocking for SPI (betaflight#13555)
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveCEvans committed Apr 21, 2024
1 parent 30415a3 commit f4d6a2c
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 9 deletions.
13 changes: 13 additions & 0 deletions src/main/drivers/bus_spi.c
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,19 @@ uint8_t spiGetExtDeviceCount(const extDevice_t *dev)
return dev->bus->deviceCount;
}

// Link two segment lists
// Note that there is no need to unlink segment lists as this is done automatically as they are processed
void spiLinkSegments(const extDevice_t *dev, busSegment_t *firstSegment, busSegment_t *secondSegment)
{
busSegment_t *endSegment;

// Find the last segment of the new transfer
for (endSegment = firstSegment; endSegment->len; endSegment++);

endSegment->u.link.dev = dev;
endSegment->u.link.segments = secondSegment;
}

// DMA transfer setup and start
void spiSequence(const extDevice_t *dev, busSegment_t *segments)
{
Expand Down
3 changes: 3 additions & 0 deletions src/main/drivers/bus_spi.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ void spiRelease(const extDevice_t *dev);
// Return true if DMA engine is busy
bool spiIsBusy(const extDevice_t *dev);

// Link two segment lists
void spiLinkSegments(const extDevice_t *dev, busSegment_t *firstSegment, busSegment_t *secondSegment);

/*
* Routine naming convention is:
* spi[Read][Write][Reg][Msk][Buf][RB]
Expand Down
195 changes: 186 additions & 9 deletions src/main/drivers/flash_w25n01g.c
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,19 @@ static void w25n01g_deviceReset(flashDevice_t *fdevice)

bool w25n01g_isReady(flashDevice_t *fdevice)
{
uint8_t status = w25n01g_readRegister(&fdevice->io, W25N01G_STAT_REG);
// If we're waiting on DMA completion, then SPI is busy
if (fdevice->io.mode == FLASHIO_SPI) {
if (fdevice->io.handle.dev->bus->useDMA && spiIsBusy(fdevice->io.handle.dev)) {
return false;
}
}

// Irrespective of the current state of fdevice->couldBeBusy read the status or device blocks

// Poll the FLASH device to see if it's busy
fdevice->couldBeBusy = ((w25n01g_readRegister(&fdevice->io, W25N01G_STAT_REG) & W25N01G_STATUS_FLAG_BUSY) != 0);

return ((status & W25N01G_STATUS_FLAG_BUSY) == 0);
return !fdevice->couldBeBusy;
}

static bool w25n01g_waitForReady(flashDevice_t *fdevice)
Expand Down Expand Up @@ -343,6 +353,8 @@ bool w25n01g_identify(flashDevice_t *fdevice, uint32_t jedecID)
fdevice->couldBeBusy = true; // Just for luck we'll assume the chip could be busy even though it isn't specced to be
fdevice->vTable = &w25n01g_vTable;

spiSetClkDivisor(fdevice->io.handle.dev, spiCalculateDivider(100000000));

return true;
}

Expand Down Expand Up @@ -400,6 +412,7 @@ void w25n01g_eraseCompletely(flashDevice_t *fdevice)
}
}

#ifdef USE_QUADSPI
static void w25n01g_programDataLoad(flashDevice_t *fdevice, uint16_t columnAddress, const uint8_t *data, int length)
{

Expand Down Expand Up @@ -462,6 +475,7 @@ static void w25n01g_randomProgramDataLoad(flashDevice_t *fdevice, uint16_t colum
w25n01g_setTimeout(fdevice, W25N01G_TIMEOUT_PAGE_PROGRAM_MS);

}
#endif

static void w25n01g_programExecute(flashDevice_t *fdevice, uint32_t pageAddress)
{
Expand Down Expand Up @@ -490,25 +504,23 @@ flashfs page program behavior
To cope with this behavior.
pageProgramBegin:
If buffer is dirty and programLoadAddress != address, then the last page is a partial write;
issue PAGE_PROGRAM_EXECUTE to flash buffer contents, clear dirty and record the address as programLoadAddress and programStartAddress.
Else do nothing.
pageProgramContinue:
Mark buffer as dirty.
If programLoadAddress is on page boundary, then issue PROGRAM_LOAD_DATA, else issue RANDOM_PROGRAM_LOAD_DATA.
Update programLoadAddress.
Optionally observe the programLoadAddress, and if it's on page boundary, issue PAGE_PROGRAM_EXECUTE.
pageProgramFinish:
Observe programLoadAddress. If it's on page boundary, issue PAGE_PROGRAM_EXECUTE and clear dirty, else just return.
If pageProgramContinue observes the page boundary, then do nothing(?).
*/

static uint32_t programStartAddress;
static uint32_t programLoadAddress;
bool bufferDirty = false;

#ifdef USE_QUADSPI
bool isProgramming = false;

void w25n01g_pageProgramBegin(flashDevice_t *fdevice, uint32_t address, void (*callback)(uint32_t length))
Expand Down Expand Up @@ -580,6 +592,174 @@ void w25n01g_pageProgramFinish(flashDevice_t *fdevice)
programStartAddress = programLoadAddress;
}
}
#else
void w25n01g_pageProgramBegin(flashDevice_t *fdevice, uint32_t address, void (*callback)(uint32_t length))
{
fdevice->callback = callback;
fdevice->currentWriteAddress = address;

}

static uint32_t currentPage = UINT32_MAX;

// Called in ISR context
// Check if the status was busy and if so repeat the poll
busStatus_e w25n01g_callbackReady(uint32_t arg)
{
flashDevice_t *fdevice = (flashDevice_t *)arg;
extDevice_t *dev = fdevice->io.handle.dev;

uint8_t readyPoll = dev->bus->curSegment->u.buffers.rxData[2];

if (readyPoll & W25N01G_STATUS_FLAG_BUSY) {
return BUS_BUSY;
}

// Bus is now known not to be busy
fdevice->couldBeBusy = false;

return BUS_READY;
}

// Called in ISR context
// A write enable has just been issued
busStatus_e w25n01g_callbackWriteEnable(uint32_t arg)
{
flashDevice_t *fdevice = (flashDevice_t *)arg;

// As a write has just occurred, the device could be busy
fdevice->couldBeBusy = true;

return BUS_READY;
}

// Called in ISR context
// Write operation has just completed
busStatus_e w25n01g_callbackWriteComplete(uint32_t arg)
{
flashDevice_t *fdevice = (flashDevice_t *)arg;

fdevice->currentWriteAddress += fdevice->callbackArg;
// Call transfer completion callback
if (fdevice->callback) {
fdevice->callback(fdevice->callbackArg);
}

return BUS_READY;
}

uint32_t w25n01g_pageProgramContinue(flashDevice_t *fdevice, uint8_t const **buffers, uint32_t *bufferSizes, uint32_t bufferCount)
{
if (bufferCount < 1) {
fdevice->callback(0);
return 0;
}

// The segment list cannot be in automatic storage as this routine is non-blocking
STATIC_DMA_DATA_AUTO uint8_t readStatus[] = { W25N01G_INSTRUCTION_READ_STATUS_REG, W25N01G_STAT_REG, 0 };
STATIC_DMA_DATA_AUTO uint8_t readyStatus[3];
STATIC_DMA_DATA_AUTO uint8_t writeEnable[] = { W25N01G_INSTRUCTION_WRITE_ENABLE };
STATIC_DMA_DATA_AUTO uint8_t progExecCmd[] = { W25N01G_INSTRUCTION_PROGRAM_EXECUTE, 0, 0, 0};
STATIC_DMA_DATA_AUTO uint8_t progExecDataLoad[] = { W25N01G_INSTRUCTION_PROGRAM_DATA_LOAD, 0, 0};
STATIC_DMA_DATA_AUTO uint8_t progRandomProgDataLoad[] = { W25N01G_INSTRUCTION_RANDOM_PROGRAM_DATA_LOAD, 0, 0};

static busSegment_t segmentsFlash[] = {
{.u.buffers = {readStatus, readyStatus}, sizeof(readStatus), true, w25n01g_callbackReady},
{.u.buffers = {writeEnable, NULL}, sizeof(writeEnable), true, w25n01g_callbackWriteEnable},
{.u.buffers = {progExecCmd, NULL}, sizeof(progExecCmd), true, w25n01g_callbackWriteComplete},
{.u.link = {NULL, NULL}, 0, true, NULL},
};

static busSegment_t segmentsDataLoad[] = {
{.u.buffers = {readStatus, readyStatus}, sizeof(readStatus), true, w25n01g_callbackReady},
{.u.buffers = {writeEnable, NULL}, sizeof(writeEnable), true, w25n01g_callbackWriteEnable},
{.u.buffers = {progExecDataLoad, NULL}, sizeof(progExecDataLoad), false, NULL},
{.u.buffers = {NULL, NULL}, 0, true, NULL}, // Patch in pointer to data buffer here
{.u.link = {NULL, NULL}, 0, true, NULL},
};

static busSegment_t segmentsRandomDataLoad[] = {
{.u.buffers = {readStatus, readyStatus}, sizeof(readStatus), true, w25n01g_callbackReady},
{.u.buffers = {writeEnable, NULL}, sizeof(writeEnable), true, w25n01g_callbackWriteEnable},
{.u.buffers = {progRandomProgDataLoad, NULL}, sizeof(progRandomProgDataLoad), false, NULL},
{.u.buffers = {NULL, NULL}, 0, true, NULL}, // Patch in pointer to data buffer here
{.u.link = {NULL, NULL}, 0, true, NULL},
};

busSegment_t *programSegment;

// Ensure any prior DMA has completed before continuing
spiWait(fdevice->io.handle.dev);

uint32_t columnAddress;

if (bufferDirty) {
columnAddress = W25N01G_LINEAR_TO_COLUMN(programLoadAddress);
// Set the address and buffer details for the random data load
progRandomProgDataLoad[1] = (columnAddress >> 8) & 0xff;
progRandomProgDataLoad[2] = columnAddress & 0xff;
segmentsRandomDataLoad[3].u.buffers.txData = (uint8_t *)buffers[0];
segmentsRandomDataLoad[3].len = bufferSizes[0];

programSegment = segmentsRandomDataLoad;
} else {
programStartAddress = programLoadAddress = fdevice->currentWriteAddress;
columnAddress = W25N01G_LINEAR_TO_COLUMN(programLoadAddress);
// Set the address and buffer details for the data load
progExecDataLoad[1] = (columnAddress >> 8) & 0xff;
progExecDataLoad[2] = columnAddress & 0xff;
segmentsDataLoad[3].u.buffers.txData = (uint8_t *)buffers[0];
segmentsDataLoad[3].len = bufferSizes[0];

programSegment = segmentsDataLoad;
}

bufferDirty = true;
programLoadAddress += bufferSizes[0];

if (W25N01G_LINEAR_TO_COLUMN(programLoadAddress) == 0) {
// Flash the loaded data
currentPage = W25N01G_LINEAR_TO_PAGE(programStartAddress);

progExecCmd[2] = (currentPage >> 8) & 0xff;
progExecCmd[3] = currentPage & 0xff;

// Don't callback on completion of data load but rather after flashing
programSegment[3].callback = NULL;

spiLinkSegments(fdevice->io.handle.dev, programSegment, segmentsFlash);

bufferDirty = false;

programStartAddress = programLoadAddress;
} else {
// Callback on completion of data load
programSegment[3].callback = w25n01g_callbackWriteComplete;
}

if (!fdevice->couldBeBusy) {
// Skip the ready check
programSegment++;
}

fdevice->callbackArg = bufferSizes[0];

spiSequence(fdevice->io.handle.dev, programSegment);

if (fdevice->callback == NULL) {
// No callback was provided so block
// Block pending completion of SPI access
spiWait(fdevice->io.handle.dev);
}

return fdevice->callbackArg;
}

void w25n01g_pageProgramFinish(flashDevice_t *fdevice)
{
UNUSED(fdevice);
}
#endif // USE_QUADSPI

/**
* Write bytes to a flash page. Address must not cross a page boundary.
Expand Down Expand Up @@ -612,9 +792,6 @@ void w25n01g_flush(flashDevice_t *fdevice)
w25n01g_programExecute(fdevice, W25N01G_LINEAR_TO_PAGE(programStartAddress));

bufferDirty = false;
isProgramming = true;
} else {
isProgramming = false;
}
}

Expand Down

0 comments on commit f4d6a2c

Please sign in to comment.