diff --git a/src/main/drivers/bus_spi.c b/src/main/drivers/bus_spi.c index bef870b32f9..0386a765465 100644 --- a/src/main/drivers/bus_spi.c +++ b/src/main/drivers/bus_spi.c @@ -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) { diff --git a/src/main/drivers/bus_spi.h b/src/main/drivers/bus_spi.h index 9b416432beb..8ebc0a98220 100644 --- a/src/main/drivers/bus_spi.h +++ b/src/main/drivers/bus_spi.h @@ -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] diff --git a/src/main/drivers/flash_w25n01g.c b/src/main/drivers/flash_w25n01g.c index 87c3f604194..4cb93cd4bb1 100644 --- a/src/main/drivers/flash_w25n01g.c +++ b/src/main/drivers/flash_w25n01g.c @@ -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) @@ -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; } @@ -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) { @@ -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) { @@ -490,18 +504,14 @@ 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(?). */ @@ -509,6 +519,8 @@ 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)) @@ -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. @@ -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; } }