mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-10-22 21:11:38 +00:00
formatter update, bump and contact iterator fix
This commit is contained in:
1 parent
42eea4f906
commit
8297d88bf7
12 files changed
+257
-57
No files matched your search
@@ -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
@@ -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.
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 */
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in new issue
Block a user