Wake QSPI flash before controller activation
This commit is contained in:
1 parent
a02c14138a
commit
cd402dd06e
4 files changed
+81
-17
No files matched your search
@@ -77,7 +77,7 @@ a hardware chip-select rework.
|
||||
|
||||
## One-time prerequisite
|
||||
|
||||
Install a QSPI-capable OTAFIX 2.4.1 preview.8 or newer bootloader for the exact
|
||||
Install a QSPI-capable OTAFIX 2.4.1 preview.9 or newer bootloader for the exact
|
||||
board from the
|
||||
[OTAFIX releases](https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases)
|
||||
before using LoRa OTA. The release notes must explicitly list that board's QSPI
|
||||
@@ -174,8 +174,12 @@ download is reopened only when its header and trailer are valid; every claimed
|
||||
block is re-hashed before it is trusted. Between a probe, transfer operation,
|
||||
or checkpoint, firmware puts the NOR into deep power-down, deactivates the nRF
|
||||
QSPI peripheral, and turns off a board-provided flash power-enable pin. The
|
||||
next operation powers and identifies the chip again, so merely running
|
||||
`ota self` does not leave QSPI drawing active-mode current.
|
||||
next operation powers the chip, shifts the NOR's `0xAB` wake command over GPIO,
|
||||
then activates the nRF QSPI peripheral and identifies the flash. Waking it
|
||||
before peripheral activation is required because a flash in deep power-down
|
||||
ignores the activation traffic itself. Merely running `ota self` therefore does
|
||||
not leave QSPI drawing active-mode current or prevent the following operation
|
||||
from reactivating it.
|
||||
|
||||
Installing a QSPI repeater build over a former companion build therefore
|
||||
repurposes the external flash and destroys companion filesystem data as OTA
|
||||
|
||||
@@ -162,6 +162,47 @@ static void configure_qspi_gpio(const nrf_qspi_pins_t& pins) {
|
||||
NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
|
||||
}
|
||||
|
||||
static void wake_qspi_flash_before_activate(const nrf_qspi_pins_t& pins) {
|
||||
// nRF52840 TASKS_ACTIVATE communicates with the NOR before CINSTR is
|
||||
// available. A flash put to sleep by our preceding 0xB9 ignores that
|
||||
// activation traffic, so issue the only command it accepts (0xAB) with a
|
||||
// short mode-0 GPIO transaction first. IO2/WP# and IO3/HOLD# stay high.
|
||||
nrf_gpio_pin_clear(pins.sck_pin);
|
||||
nrf_gpio_pin_set(pins.csn_pin);
|
||||
nrf_gpio_pin_set(pins.io0_pin);
|
||||
if (pins.io2_pin != NRF_QSPI_PIN_NOT_CONNECTED) nrf_gpio_pin_set(pins.io2_pin);
|
||||
if (pins.io3_pin != NRF_QSPI_PIN_NOT_CONNECTED) nrf_gpio_pin_set(pins.io3_pin);
|
||||
|
||||
nrf_gpio_cfg(pins.sck_pin, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT,
|
||||
NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
|
||||
nrf_gpio_cfg(pins.csn_pin, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT,
|
||||
NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
|
||||
nrf_gpio_cfg(pins.io0_pin, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT,
|
||||
NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
|
||||
nrf_gpio_cfg(pins.io1_pin, NRF_GPIO_PIN_DIR_INPUT, NRF_GPIO_PIN_INPUT_DISCONNECT,
|
||||
NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
|
||||
if (pins.io2_pin != NRF_QSPI_PIN_NOT_CONNECTED)
|
||||
nrf_gpio_cfg(pins.io2_pin, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT,
|
||||
NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
|
||||
if (pins.io3_pin != NRF_QSPI_PIN_NOT_CONNECTED)
|
||||
nrf_gpio_cfg(pins.io3_pin, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT,
|
||||
NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
|
||||
|
||||
delayMicroseconds(1);
|
||||
nrf_gpio_pin_clear(pins.csn_pin);
|
||||
delayMicroseconds(1);
|
||||
for (uint8_t bit = 0; bit < 8u; bit++) {
|
||||
nrf_gpio_pin_write(pins.io0_pin, mota_qspi_release_from_dpd_bit(bit));
|
||||
delayMicroseconds(1);
|
||||
nrf_gpio_pin_set(pins.sck_pin);
|
||||
delayMicroseconds(1);
|
||||
nrf_gpio_pin_clear(pins.sck_pin);
|
||||
delayMicroseconds(1);
|
||||
}
|
||||
nrf_gpio_pin_set(pins.csn_pin);
|
||||
delayMicroseconds(MOTA_QSPI_DPD_WAKE_GUARD_US);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OtaStoreQspiNrf52::OtaStoreQspiNrf52() {
|
||||
@@ -281,6 +322,12 @@ bool OtaStoreQspiNrf52::ensureFlash() {
|
||||
pins.io1_pin = qspi_io1_pin();
|
||||
pins.io2_pin = qspi_io2_pin();
|
||||
pins.io3_pin = qspi_io3_pin();
|
||||
_stage = OtaQspiStage::WAKE;
|
||||
// A reset may have interrupted a NOR program/erase. Until activation and a
|
||||
// successful RDSR prove WIP clear, every failure path must retain power and
|
||||
// hold CS# high instead of sending B9 or cutting the flash rail.
|
||||
_memory_operation_pending = true;
|
||||
wake_qspi_flash_before_activate(pins);
|
||||
configure_qspi_gpio(pins);
|
||||
nrf_qspi_pins_set(NRF_QSPI, &pins);
|
||||
|
||||
@@ -307,26 +354,17 @@ bool OtaStoreQspiNrf52::ensureFlash() {
|
||||
_stage = OtaQspiStage::ACTIVATE;
|
||||
nrf_qspi_event_clear(NRF_QSPI, NRF_QSPI_EVENT_READY);
|
||||
nrf_qspi_task_trigger(NRF_QSPI, NRF_QSPI_TASK_ACTIVATE);
|
||||
if (!waitReady(1000)) {
|
||||
// ACTIVATE itself can wait for a previously interrupted NOR erase. Match the
|
||||
// recovery window used by the explicit status poll below.
|
||||
if (!waitReady(30000)) {
|
||||
releaseFlash();
|
||||
fail("QSPI activate timed out");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Release from deep power-down, then identify capacity from the JEDEC byte.
|
||||
_stage = OtaQspiStage::WAKE;
|
||||
if (!customInstruction(0xAB, NRF_QSPI_CINSTR_LEN_1B)) {
|
||||
releaseFlash();
|
||||
fail("QSPI wake failed");
|
||||
return false;
|
||||
}
|
||||
// The GPIO 0xAB issued before TASKS_ACTIVATE already woke the NOR. CINSTR
|
||||
// cannot perform that first wake because it is unavailable until activation.
|
||||
_qspi_awake = true;
|
||||
// Until SR1 is read successfully, conservatively assume an operation from
|
||||
// the preceding boot may still be active. Only waitMemoryReady() may clear
|
||||
// this guard after observing WIP=0.
|
||||
_memory_operation_pending = true;
|
||||
delayMicroseconds(MOTA_QSPI_DPD_WAKE_GUARD_US);
|
||||
|
||||
// Recover cleanly if this boot/activation follows an interrupted program or
|
||||
// erase. RDID is not guaranteed to respond while WIP is set, so prove the
|
||||
// NOR idle before requesting its identity.
|
||||
|
||||
@@ -29,6 +29,17 @@ static const uint32_t MOTA_QSPI_DPD_ENTRY_GUARD_US = 50u;
|
||||
|
||||
// Release-from-deep-power-down latency is as high as 45 us on supported NOR.
|
||||
static const uint32_t MOTA_QSPI_DPD_WAKE_GUARD_US = 50u;
|
||||
static const uint8_t MOTA_QSPI_RELEASE_FROM_DPD_OPCODE = 0xABu;
|
||||
|
||||
// TASKS_ACTIVATE initiates traffic with the external memory. A NOR in deep
|
||||
// power-down ignores that traffic and the nRF QSPI peripheral can wait forever
|
||||
// for READY, so the 0xAB wake command has to be shifted over GPIO before QSPI
|
||||
// owns the pins. Keep the bit order host-testable instead of open-coding it in
|
||||
// the hardware-only path.
|
||||
inline bool mota_qspi_release_from_dpd_bit(uint8_t bit_index) {
|
||||
return bit_index < 8u &&
|
||||
((MOTA_QSPI_RELEASE_FROM_DPD_OPCODE >> (7u - bit_index)) & 1u) != 0;
|
||||
}
|
||||
|
||||
// The nRF QSPI READY event only says that a program/erase command and its
|
||||
// data reached the NOR. The memory can remain internally busy afterwards;
|
||||
|
||||
@@ -35,6 +35,17 @@ TEST(OtaQspiTiming, PreservesDeepPowerDownEntryAndWakeGuards) {
|
||||
EXPECT_GE(MOTA_QSPI_DPD_WAKE_GUARD_US, 45u);
|
||||
}
|
||||
|
||||
TEST(OtaQspiWake, ShiftsReleaseCommandMostSignificantBitFirst) {
|
||||
// This byte is emitted over GPIO before TASKS_ACTIVATE. If activation is
|
||||
// attempted first, a sleeping NOR ignores it and READY never arrives.
|
||||
const bool expected[] = {true, false, true, false, true, false, true, true};
|
||||
EXPECT_EQ(MOTA_QSPI_RELEASE_FROM_DPD_OPCODE, 0xABu);
|
||||
for (uint8_t bit = 0; bit < 8u; bit++) {
|
||||
EXPECT_EQ(mota_qspi_release_from_dpd_bit(bit), expected[bit]);
|
||||
}
|
||||
EXPECT_FALSE(mota_qspi_release_from_dpd_bit(8u));
|
||||
}
|
||||
|
||||
TEST(OtaQspiStatus, TreatsOnlyTheNorWriteInProgressBitAsBusy) {
|
||||
// Nordic READY is not flash completion. The hardware path polls RDSR until
|
||||
// this predicate clears after every program and erase operation.
|
||||
|
||||
Reference in new issue
Block a user