formatter update, bump and contact iterator fix

This commit is contained in:
liquidraver committed 2026-10-06 21:24:28 +02:00
1 parent 42eea4f906
commit 8297d88bf7
12 files changed
+257 -57

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
# Repeater CLI Commands
This page has moved: the CLI reference for every role is now [CLI_commands.md](CLI_commands.md).
+40 -4
View File
@@ -1,6 +1,7 @@
nRF formatter tools
- QSPI is formatted for all supported boards
- Erases the internal LittleFS and BLE-bond NVS partitions, and the external QSPI flash on
every supported board that has one (see [QSPI flash](#qspi-flash) below)
- Watch out for softdevice version! Flashing the wrong version can corrupt the node and you'll need a full bootloader reflash with adafruit-nrfutil!
- You can check what softdevice version you use if you open INFO_UF2.TXT on the storage drive when in DFU mode. Bootloader should say "sxxx 6.x.x" for v6 and "sxxx 7.x.x" for v7
- That warning applies to the **`.uf2`** files, which have no version guard. The **`.zip`** packages carry an `--sd-req` guard (v6 = `0x00B6`, v7 = `0x0123`), so the bootloader *rejects* a mismatched package instead of corrupting the node.
@@ -11,8 +12,8 @@ nRF formatter tools
| SoftDevice | Boards |
|------------|--------|
| **v6** | RAK4631, RAK3401 1W, ThinkNode M1/M3/M6, RAK WisMesh Tag, LilyGo T-Echo, LilyGo T-Impulse Plus, ProMicro SX1262, Heltec T114, Heltec T096, GAT562 30s, muzi works R1 Neo |
| **v7** | Wio Tracker L1, T1000-E, Ikoka Nano 30dBm, SenseCAP Solar, XIAO nRF52840 |
| **v6** | RAK4631, RAK3401 1W, ThinkNode M1/M3/M6, RAK WisMesh Tag, LilyGo T-Echo, LilyGo T-Impulse Plus, ProMicro SX1262, ProMicro LR2021, Heltec T114, Heltec T096, Heltec Mesh Node T1, GAT562 30s, muzi works R1 Neo |
| **v7** | Wio Tracker L1, Wio Tracker L1 Pro 1W, T1000-E, MeshTracker X1, Ikoka Nano 30dBm, SenseCAP Solar, XIAO nRF52840 |
- **`.uf2`** — manual drag-and-drop onto the UF2 mass-storage drive.
- **`.zip`** — Adafruit DFU package. Used by the Mesh America configurator as ZephCore's
@@ -46,5 +47,40 @@ prints `Formatter DFU zip: ENABLED (sd-req=...)` when wired up, and silently ski
zip otherwise. `--sd-req` is read automatically from the build board's `board.conf`
(`CONFIG_ZEPHCORE_SD_FWID`), so it always matches the SoftDevice being targeted.
## QSPI flash
The formatter does not know which board it is running on. It tries each known QSPI pin
set in turn, and the first one where a flash answers its JEDEC ID gets a full chip erase.
| Pin set | Boards | Flash rail pin |
|---------|--------|----------------|
| Wio/XIAO/Ikoka/SenseCap | Wio Tracker L1, Wio Tracker L1 Pro 1W, XIAO nRF52840, Ikoka Nano 30dBm, SenseCAP Solar | — |
| ThinkNode M1 / TEcho | ThinkNode M1, LilyGo T-Echo | P0.12 |
| ThinkNode M6 | ThinkNode M6 (units with the flash fitted) | P0.21 |
| RAK4631 / GAT562 | RAK base boards with QSPI flash | — |
| LilyGo TEcho Lite | LilyGo T-Echo Lite | — |
| Nano G2 Ultra | Nano G2 Ultra | — |
| LilyGo T-Impulse Plus | LilyGo T-Impulse Plus (v6 build only) | P0.14 |
| MeshTracker X1 | MeshTracker X1 (v7 build only) | P0.15 |
- A flash left in deep power-down is woken first. Repeaters, room servers and observers
put it to sleep, and the reset into the formatter does not wake it.
- Where the flash sits behind a switched supply rail, the formatter raises that pin and
gives the part time to start; nothing else powers it after the bootloader.
- The erase is checked: the log shows the first bytes of the flash before the erase, and
the erase is reported as failed unless they read back blank afterwards.
- The last lines of the log say what happened: `All partitions erased successfully
(internal flash + QSPI)`, or that the internal flash was erased and no QSPI flash
answered. The second is normal on a board without one (T1000-E, RAK4631 on a base
board without flash, ...). On a board from the table above it means the QSPI was
**not** erased.
Verified on hardware: Wio Tracker L1 (as companion and as repeater) and T1000-E. The
MeshTracker X1, T-Impulse Plus and ThinkNode M1 / T-Echo rail entries follow the board
devicetrees and have not been run on those boards.
To support a new board, add its QSPI pin mapping to `known_boards[]` in
`zephcore/tools/formatter/src/qspi_probe.c` — no DTS or Kconfig changes needed.
`zephcore/tools/formatter/src/qspi_probe.c` — no DTS or Kconfig changes needed. Set
`pwr_pin` if a GPIO switches the flash's supply, and `sd` if the board exists on one
SoftDevice only: every probed pin set drives its pins on every board that build runs on,
so a set limited to its own build stays off unrelated boards.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+57 -21
View File
@@ -1,8 +1,10 @@
# ZephCore 1.17.7-zephcore
Faster GPS fixes on three trackers, a clock that survives a reboot, a fix for USB companions that
could stop responding right after boot, more reliable detection of clock chips, repeaters that power
down an external flash chip they do not use, and a few fixes ported from upstream MeshCore.
Faster GPS fixes on three trackers, a clock that survives a reboot, fixes for USB companions that
could stop responding right after boot or when the port closed during a contact sync, more reliable
detection of clock chips, repeaters that power down an external flash chip they do not use, a
formatter that erases that chip on every board that has one, and a few fixes ported from upstream
MeshCore.
> [!NOTE]
> A normal upgrade keeps your identity, settings, contacts and phone pairing.
@@ -16,8 +18,8 @@ a search from scratch. Three boards now keep that data:
- **SenseCAP T1000-E and MeshTracker X1**: the receiver uses its low-power backup sleep. On a
T1000-E, a fix after a minute asleep took about 9 seconds instead of about 77.
- **SenseCAP Solar**: the receiver stays in standby between fixes instead of being switched off. In
marginal reception it got a fix on 5 of 6 wakes, against 2 of 6 before.
- **SenseCAP Solar**: the receiver stays in standby between fixes instead of being switched off,
which makes a fix in marginal reception more likely.
Keeping that data uses a little power all the time, so it is only done when the GPS interval is one
hour or less. Longer intervals (a repeater's default is 48 hours) switch the receiver off completely,
@@ -72,22 +74,39 @@ traffic, while Bluetooth kept advertising. Only a reset recovered it.
The cause is in the USB serial driver of the Zephyr version 1.17.6 moved to: with its first 64-byte
buffer already full, it reported free space but accepted no data, and the firmware retried forever.
On a Wio Tracker L1 debug build, where the boot log fills that buffer, it happened on every such boot.
It was not reproduced on a release build, but the boot banner left only 3 bytes of margin there.
The driver is patched. On the Wio Tracker L1, 6 of 6 debug boots and 3 of 3 release boots now respond,
and the companion protocol test gives the same replies as 1.17.6.
The driver is patched.
The same patch covers a second way into the same loop: a host computer going to sleep with the port
open. That one is fixed from reading the code and has not been reproduced on hardware.
open.
## USB companion: a hang when the port closed during a contact sync
A companion sending its contact list could get stuck when the last connected app went away before
the list was complete: the app or browser tab was closed, or the tool exited. The node stayed in the
loop that sends the list, with nobody to send it to, and handled nothing else until an app connected
again; that app then received the rest of the list unasked. Present since 1.17.6.
The loop ran for as long as no app was busy. With no app left none is busy and nothing can be sent,
so it never ended. It now also stops when no app is connected.
## USB companion: a host that goes away without closing the port
A USB session ended only when the host closed the port or the cable lost power. A host that went
away any other way (a USB hub resetting, a computer going to sleep with the app open) left the node
treating the app as still connected. With one app that did no harm. With a phone on Bluetooth at the
same time, the node could stop answering the phone once the data waiting for the USB side filled its
buffer.
Now a reset of the USB bus ends the session, and a session on a sleeping computer does not count as a
connected app until the computer wakes. An app that keeps the port open but stops reading can still
hold up a second app.
## Repeaters: the external flash chip is powered down
Some boards carry a second flash chip, which companions keep contacts and channels on. Repeaters,
room servers and observers store nothing on it. Since 1.17.6 those roles no longer set the chip up at
all, and that left its control pins unconnected: on a Wio Tracker L1 repeater the chip-select line
read low, so the chip sat selected for as long as the node ran instead of idling. 1.17.4 and earlier
set the chip up at boot on every role.
all, and that left its control pins unconnected, so the chip could sit selected for as long as the
node ran instead of idling. 1.17.4 and earlier set the chip up at boot on every role.
These roles now set the chip up once at start-up and put it into its deep power-down mode, with
chip-select held high. A factory `erase` wakes the chip, erases it and puts it back. Companions are
@@ -96,14 +115,32 @@ unchanged: they use the chip, so it stays in standby.
Boards with this chip: T-Echo, T-Impulse Plus, MeshTracker X1, SenseCAP Solar, ThinkNode M1,
ThinkNode M6, Wio Tracker L1, Wio Tracker L1 Pro 1W and XIAO nRF52840.
Checked on a Wio Tracker L1 repeater after a normal boot, after a first-boot format and after
`erase`: the chip ignores an ID request, and answers again once it is woken. The current this saves
has not been measured, and the change has not been run on the other boards.
This was found while looking into a report of a XIAO nRF52840 repeater drawing about 4 mA more on
1.17.6 than on 1.17.4 (thanks to **Kimotu**). Whether it accounts for that difference is not
confirmed yet.
## Formatter: the external flash chip is erased on every board that has one
The nRF52 formatters (`SoftDevice_v6_formatter` and `SoftDevice_v7_formatter`, as `.uf2` and as the
`.zip` the Mesh America configurator uses for its erase step) wipe a node completely: the internal storage, the
Bluetooth pairings and the external flash chip. On some boards the formatter did not reach that chip,
left it as it was and still reported success. A companion flashed afterwards then found the old
contacts and channels on it.
- **MeshTracker X1 and T-Impulse Plus**: the formatter did not know how the chip is wired on these
boards. It does now.
- **Boards that switch the chip's power supply** (ThinkNode M1, T-Echo, T-Impulse Plus,
MeshTracker X1): the formatter now switches the supply on itself and waits for the chip to start.
- **A chip in deep power-down**: a repeater, room server or observer on this release puts the chip to
sleep (see above), and a sleeping chip answers nothing. The formatter now wakes it first.
The erase is also checked: the formatter reads the chip back afterwards and reports a failure if it
is not blank. The last lines of its serial log say whether the external chip was erased, or that no
such chip answered, which is normal on a board without one.
Use the formatters from this release; the file names have not changed. As before, pick the one that
matches the SoftDevice version of your bootloader.
## Also in this release
- **A repeater with adverts off is no longer discoverable**, as in upstream MeshCore: with both
@@ -130,9 +167,8 @@ confirmed yet.
companion port and the update page) was predictable, and is now derived from a secret as intended.
- **Heltec V3 and Wireless Tracker companions** (WiFi and Bluetooth together, no PSRAM) had under
1 KB of memory left; they now have about 2.5 KB. The Bluetooth controller's stack on ESP32-S3 and
ESP32-C3 companions was 4 KB and is now 2 KB: its measured use is 1.1 KB with WiFi connected.
Nothing changes in use. Measured on a XIAO ESP32-S3; not run on the two Heltec boards themselves or
on an ESP32-C3.
ESP32-C3 companions was 4 KB and is now 2 KB; it uses about 1.1 KB with WiFi connected. Nothing
changes in use.
- **Documentation**: the README was reorganised, build and flashing instructions moved to
`docs/BUILDING.md`, and the example board and porting guide were rewritten for the current board
layout.
+1 -1
View File
@@ -33,7 +33,7 @@ list(APPEND BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
# Service advertises (CONFIG_BT_DIS_FW_REV_STR, generated from it below), what the
# GitHub release is tagged/named, and what the Mesh America catalog uses as its
# version key. The release workflow reads this line directly — keep its form.
set(ZEPHCORE_FIRMWARE_VERSION "1.17.6-zephcore")
set(ZEPHCORE_FIRMWARE_VERSION "1.17.7-zephcore")
# ========== Helpers ==========
# Append FILE to the overlay list once, keeping its FIRST position. Position is
+7 -2
View File
@@ -339,14 +339,19 @@ static void process_companion_rx(void)
/* The contact dump, as far as every connected transport has room
* (MESH_EVENT_CONTACT_ITER): BLE and TCP below their 2/3 high-water mark and
* not congested, USB with room for a whole frame. A transport's TX idle
* brings us back. */
* brings us back.
*
* Only while a client is connected: with none, nothing is busy and no write
* succeeds, so the dump would not advance and this loop would not end. The
* last client can leave while we are in here (its callbacks preempt this
* thread), before MESH_EVENT_LINK has cancelled the dump. */
static void run_contact_iteration(void)
{
#ifdef ZEPHCORE_LORA
if (!companion_mesh_ptr) {
return;
}
while (!companion_serial.isWriteBusy()) {
while (companion_serial.isConnected() && !companion_serial.isWriteBusy()) {
if (!companion_mesh_ptr->continueContactIteration()) {
break; /* iteration complete */
}
+12 -2
View File
@@ -31,6 +31,7 @@
#include <zephyr/drivers/gpio.h>
#include <zephyr/storage/flash_map.h>
#include <zephyr/sys/reboot.h>
#include <errno.h>
#include "qspi_probe.h"
#if defined(CONFIG_SOC_SERIES_NRF52)
@@ -122,7 +123,13 @@ int main(void)
#endif
/* ── QSPI external flash (auto-detect via pin probing) ── */
qspi_probe_and_erase(); /* Probes all known boards; skips if no flash found */
/* Probes all known boards. Finding no flash is normal (most boards have
* none); a flash that answered and then failed to erase is an error. */
int qspi_rc = qspi_probe_and_erase();
if (qspi_rc && qspi_rc != -ENODEV) {
errors++;
}
led_off();
@@ -130,8 +137,11 @@ int main(void)
printk("\n");
if (errors) {
printk("Completed with %d error(s).\n", errors);
} else if (qspi_rc == 0) {
printk("All partitions erased successfully (internal flash + QSPI).\n");
} else {
printk("All partitions erased successfully.\n");
printk("Internal flash erased successfully. No external QSPI flash was\n");
printk("erased: none answered (normal on a board without one).\n");
}
printk("Rebooting into UF2 DFU mode...\n");
+135 -26
View File
@@ -12,6 +12,8 @@
*/
#include <zephyr/kernel.h>
#include <zephyr/devicetree.h>
#include <errno.h>
#include "qspi_probe.h"
#if defined(CONFIG_SOC_NRF52840)
@@ -25,6 +27,8 @@
#define CMD_WRITE_EN 0x06 /* Write Enable (required before erase) */
#define CMD_CHIP_ERASE 0xC7 /* Full chip erase */
#define CMD_READ_STATUS 0x05 /* Read Status Register 1 */
#define CMD_RELEASE_DPD 0xAB /* Release from Deep Power-Down */
#define CMD_READ 0x03 /* Read data, 24-bit address */
#define STATUS_WIP 0x01 /* Write-In-Progress bit in status register */
/* ── CINSTRCONF length values (opcode + N-1 data bytes) ───── */
@@ -32,6 +36,21 @@
#define CINSTR_1B 1 /* opcode only */
#define CINSTR_2B 2 /* opcode + 1 byte response */
#define CINSTR_4B 4 /* opcode + 3 bytes response (JEDEC ID) */
#define CINSTR_8B 8 /* opcode + 3 address bytes + 4 data bytes (READ) */
/* A flash behind a switched rail needs time after the rail comes up before it
* answers: one settle, then a few more tries (MeshTracker X1 answered 00 00 00
* right after its rail was switched on). */
#define PWR_SETTLE_MS 100
#define PWR_RETRY_MS 200
#define PWR_RETRIES 2
/* Which SoftDevice this image is built for, from where the application starts
* (0x26000 under v6, 0x27000 under v7). */
#define SD_ANY 0
#define SD_V6 6
#define SD_V7 7
#define BUILD_SD ((DT_REG_ADDR(DT_CHOSEN(zephyr_code_partition)) == 0x27000) ? SD_V7 : SD_V6)
/* ── Pin encoding: matches nRF52840 PSEL register format ──── */
@@ -49,20 +68,27 @@ struct qspi_pin_config {
const char *name;
uint8_t sck, csn, io0, io1, io2, io3;
uint8_t pwr_pin; /* GPIO to drive HIGH before probe, QPIN_NONE if none */
uint8_t sd; /* SD_V6/SD_V7: probe only in that build; SD_ANY: both */
};
/*
* Pin table — add new boards here.
* Sourced from ZephCore DTS + Arduino MeshCore variants.
*
* PWR is the GPIO that switches the flash's supply rail. The firmware raises it
* from a regulator node; here nothing else does, and the rail has been off for
* the bootloader's whole DFU, so a missing PWR pin reads as "no flash".
*
* Config SCK CSN IO0 IO1 IO2 IO3 PWR Flash chip Boards
* ───────────────────────── ──────── ──────── ──────── ──────── ──────── ──────── ──────── ──────────────── ────────────────────────────────────────
* Wio/XIAO/Ikoka/SenseCap P0.21 P0.25 P0.20 P0.24 P0.22 P0.23 — P25Q16H 2MB Wio Tracker L1, XIAO nRF52, Ikoka Stick/Nano/Handheld, SenseCap Solar
* ThinkNode M1 / TEcho P1.14 P1.15 P1.12 P1.13 P0.07 P0.05 — MX25R1635F 2MB ThinkNode M1, LilyGo TEcho
* Wio/XIAO/Ikoka/SenseCap P0.21 P0.25 P0.20 P0.24 P0.22 P0.23 — P25Q16H 2MB Wio Tracker L1 (+ Pro 1W), XIAO nRF52, Ikoka Stick/Nano/Handheld, SenseCap Solar
* ThinkNode M1 / TEcho P1.14 P1.15 P1.12 P1.13 P0.07 P0.05 P0.12 MX25R1635F 2MB ThinkNode M1, LilyGo TEcho
* ThinkNode M6 P1.03 P0.23 P1.01 P1.02 P1.04 P1.05 P0.21 MX25R1635F 2MB ThinkNode M6
* RAK4631 / GAT562 P0.03 P0.26 P0.30 P0.29 P0.28 P0.02 — IS25LP080D 1MB RAK4631, RAK3401, GAT562 variants
* LilyGo TEcho Lite P0.04 P0.12 P0.06 P0.08 P1.09 P0.26 — ZD25WQ32C 4MB LilyGo TEcho Lite
* Nano G2 Ultra P0.08 P1.07 P0.06 P0.26 P1.04 P1.02 — W25Q16JV 2MB Nano G2 Ultra
* LilyGo T-Impulse Plus P0.04 P0.12 P0.06 P1.09 P0.08 P0.26 P0.14 ZD25WQ32C 4MB LilyGo T-Impulse Plus (SD v6; IO1/IO2 are the TEcho Lite's swapped)
* MeshTracker X1 P0.19 P0.20 P0.21 P0.22 P0.23 P1.00 P0.15 GD25Q64E 8MB MeshTracker X1 (SD v7)
*/
static const struct qspi_pin_config known_boards[] = {
{
@@ -77,7 +103,7 @@ static const struct qspi_pin_config known_boards[] = {
.sck = QPIN(1, 14), .csn = QPIN(1, 15),
.io0 = QPIN(1, 12), .io1 = QPIN(1, 13),
.io2 = QPIN(0, 7), .io3 = QPIN(0, 5),
.pwr_pin = QPIN_NONE,
.pwr_pin = QPIN(0, 12),
},
{
.name = "ThinkNode M6",
@@ -107,6 +133,22 @@ static const struct qspi_pin_config known_boards[] = {
.io2 = QPIN(1, 4), .io3 = QPIN(1, 2),
.pwr_pin = QPIN_NONE,
},
{
.name = "LilyGo T-Impulse Plus",
.sck = QPIN(0, 4), .csn = QPIN(0, 12),
.io0 = QPIN(0, 6), .io1 = QPIN(1, 9),
.io2 = QPIN(0, 8), .io3 = QPIN(0, 26),
.pwr_pin = QPIN(0, 14),
.sd = SD_V6,
},
{
.name = "MeshTracker X1",
.sck = QPIN(0, 19), .csn = QPIN(0, 20),
.io0 = QPIN(0, 21), .io1 = QPIN(0, 22),
.io2 = QPIN(0, 23), .io3 = QPIN(1, 0),
.pwr_pin = QPIN(0, 15),
.sd = SD_V7,
},
};
/* ── GPIO helpers (raw register access) ───────────────────── */
@@ -220,50 +262,103 @@ static bool qspi_cinstr(uint8_t opcode, uint8_t len, uint32_t *dat0)
return true;
}
/**
* Read the 4 bytes at flash address 0 (READ 0x03, single-line).
* The address goes out from CINSTRDAT0 bytes 0-2; the data comes back in the
* byte slots after it: CINSTRDAT0 byte 3 and CINSTRDAT1 bytes 0-2.
*/
static bool qspi_read_first4(uint32_t *out)
{
NRF_QSPI->CINSTRDAT0 = 0;
NRF_QSPI->CINSTRDAT1 = 0;
NRF_QSPI->EVENTS_READY = 0;
NRF_QSPI->CINSTRCONF = ((uint32_t)CMD_READ) |
((uint32_t)CINSTR_8B << 8) |
(1UL << 12) | (1UL << 13);
if (!qspi_wait_ready(500)) {
return false;
}
*out = (NRF_QSPI->CINSTRDAT0 >> 24) | (NRF_QSPI->CINSTRDAT1 << 8);
return true;
}
/* ── Per-config probe and erase ───────────────────────────── */
/* @return 0 erased, -ENODEV nothing answered on these pins, -EIO a flash
* answered but the erase failed.
*/
static int probe_one(const struct qspi_pin_config *cfg)
{
uint32_t dat0;
uint32_t dat0, first4;
uint8_t mfr, type, cap;
int rc = -ENODEV;
printk(" Probing %s pins...", cfg->name);
/* Power-enable GPIO if needed */
if (cfg->pwr_pin != QPIN_NONE) {
gpio_set_output_high(cfg->pwr_pin);
k_msleep(2);
k_msleep(PWR_SETTLE_MS);
}
/* Configure and activate QSPI peripheral */
/* Activate with the pins disconnected, connect them afterwards (what
* Zephyr's nrf_qspi_nor exit_dpd() does). ACTIVATE polls the flash's
* status register until WIP reads 0; a part in deep power-down, or no
* part at all, leaves IO1 floating and READY may never come.
*/
NRF_QSPI->ENABLE = QSPI_ENABLE_ENABLE_Disabled;
qspi_disconnect_pins();
qspi_set_pins(cfg);
if (!qspi_activate()) {
printk(" activate timeout\n");
goto fail;
}
qspi_set_pins(cfg);
/* READ JEDEC ID (0x9F → 3 bytes: manufacturer, type, capacity) */
if (!qspi_cinstr(CMD_READ_ID, CINSTR_4B, &dat0)) {
printk(" read ID timeout\n");
goto fail_deactivate;
}
for (int attempt = 0; ; attempt++) {
/* A repeater, room server or observer leaves the flash in deep
* power-down, and the reset into this formatter does not wake
* it: it then ignores everything but this command.
*/
if (!qspi_cinstr(CMD_RELEASE_DPD, CINSTR_1B, NULL)) {
printk(" wake timeout\n");
goto fail_deactivate;
}
k_msleep(1);
mfr = (dat0 >> 0) & 0xFF;
type = (dat0 >> 8) & 0xFF;
cap = (dat0 >> 16) & 0xFF;
/* READ JEDEC ID (0x9F → 3 bytes: manufacturer, type, capacity) */
if (!qspi_cinstr(CMD_READ_ID, CINSTR_4B, &dat0)) {
printk(" read ID timeout\n");
goto fail_deactivate;
}
/* Validate — reject bus-float (0xFF) and bus-ground (0x00) */
if (mfr == 0xFF || mfr == 0x00) {
printk(" no flash (ID=%02X %02X %02X)\n", mfr, type, cap);
goto fail_deactivate;
mfr = (dat0 >> 0) & 0xFF;
type = (dat0 >> 8) & 0xFF;
cap = (dat0 >> 16) & 0xFF;
/* Validate — reject bus-float (0xFF) and bus-ground (0x00) */
if (mfr != 0xFF && mfr != 0x00) {
break;
}
if (cfg->pwr_pin == QPIN_NONE || attempt >= PWR_RETRIES) {
printk(" no flash (ID=%02X %02X %02X)\n", mfr, type, cap);
goto fail_deactivate;
}
k_msleep(PWR_RETRY_MS);
}
printk(" found! JEDEC=%02X %02X %02X (%u KB)\n",
mfr, type, cap, (unsigned)((1UL << cap) / 1024));
/* From here on a failure is an error, not "no flash". */
rc = -EIO;
if (qspi_read_first4(&first4)) {
printk(" First bytes before: %08X\n", (unsigned)first4);
}
/* ── Erase ── */
printk(" Erasing QSPI flash...");
@@ -294,6 +389,14 @@ static int probe_one(const struct qspi_pin_config *cfg)
}
if (!(dat0 & STATUS_WIP)) {
/* A chip erase the part refused (write-protected)
* also ends with WIP clear: check it took.
*/
if (!qspi_read_first4(&first4) || first4 != 0xFFFFFFFF) {
printk(" FAILED (first bytes after: %08X)\n",
(unsigned)first4);
goto fail_deactivate;
}
printk(" OK (%d.%ds)\n",
elapsed_ms / 1000,
(elapsed_ms % 1000) / 100);
@@ -313,24 +416,30 @@ fail:
if (cfg->pwr_pin != QPIN_NONE) {
gpio_release(cfg->pwr_pin);
}
return -1;
return rc;
}
/* ── Public API ───────────────────────────────────────────── */
int qspi_probe_and_erase(void)
{
printk(" QSPI: probing %d known pin configurations...\n",
(int)ARRAY_SIZE(known_boards));
printk(" QSPI: probing known pin configurations...\n");
for (int i = 0; i < (int)ARRAY_SIZE(known_boards); i++) {
if (probe_one(&known_boards[i]) == 0) {
return 0;
int rc;
if (known_boards[i].sd != SD_ANY && known_boards[i].sd != BUILD_SD) {
continue;
}
rc = probe_one(&known_boards[i]);
if (rc != -ENODEV) {
return rc;
}
}
printk(" QSPI: no external flash found — skipped\n");
return -1;
return -ENODEV;
}
#else /* !CONFIG_SOC_NRF52840 */
@@ -338,7 +447,7 @@ int qspi_probe_and_erase(void)
int qspi_probe_and_erase(void)
{
printk(" QSPI: not supported on this platform\n");
return -1;
return -ENODEV;
}
#endif /* CONFIG_SOC_NRF52840 */
+2 -1
View File
@@ -13,7 +13,8 @@
* Probe all known QSPI pin configurations for connected flash.
* If found, erase the entire chip.
*
* @return 0 if a chip was found and erased, -1 if no chip found or not supported
* @return 0 if a chip was found and erased, -ENODEV if no chip answered (or the
* platform has no QSPI), -EIO if a chip answered but the erase failed
*/
int qspi_probe_and_erase(void);