From 514aa77fe897c03f9a684dce00fedd88580a9edd Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Sun, 27 Sep 2026 13:57:10 +0200 Subject: [PATCH] QSPI-bringup stability enhancements --- docs/ARCHITECTURE.md | 4 +- docs/DESIGN.md | 3 + .../RELEASE_NOTES_1.17.6a-zephcore.md | 20 +++++++ .../adapters/datastore/ZephyrDataStore.cpp | 53 +++-------------- zephcore/adapters/datastore/ZephyrFsFormat.c | 58 ++++++++++++++++--- zephcore/adapters/datastore/ZephyrFsFormat.h | 10 ++++ zephcore/boards/common/qspi-ext.dtsi | 26 ++++++++- .../heltec_wireless_tracker_v2_procpu.dts | 16 ++--- zephcore/boards/example_board/board.overlay | 2 +- .../heltec_t096/heltec_t096_nrf52840.dts | 23 ++++---- .../nrf52840/heltec_t1/heltec_t1_nrf52840.dts | 14 ++--- .../nrf52840/lilygo_techo/lilygo_techo.dts | 9 ++- .../lilygo_timpulse_plus.dts | 8 +-- .../boards/nrf52840/meshtracker_x1/board.conf | 5 -- .../meshtracker_x1_nrf52840.dts | 32 +++++----- .../promicro_lr2021/promicro_lr2021.dts | 7 +-- .../promicro_sx1262/promicro_sx1262.dts | 7 +-- .../nrf52840/rak3401_1watt/rak3401_1watt.dts | 7 +-- .../rak_wismesh_tag/rak_wismesh_tag.dts | 7 +-- .../sensecap_solar_nrf52840.dts | 1 - .../nrf52840/thinknode_m1/thinknode_m1.dts | 1 - .../nrf52840/wio_tracker_l1/board.overlay | 1 - .../wio_tracker_l1_1w_nrf52840.dts | 8 +-- .../xiao_nrf52840/xiao_nrf52840_nrf52840.dts | 1 - zephcore/helpers/qspi_probe.c | 9 +++ 25 files changed, 187 insertions(+), 145 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index d3045cb..5c29b75 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -769,7 +769,9 @@ Full command reference with constraints and remote-admin restrictions: `Repeater ### 7.2 DataStore (`adapters/datastore/`) - **Internal**: LittleFS on flash (`/lfs`), 256-byte cache for reduced flash I/O -- **External**: Optional LittleFS on QSPI (`/ext`) with auto-migration +- **External**: Optional LittleFS on QSPI (`/ext`) with auto-migration. The flash is `zephyr,deferred-init` and `/ext` is + not automounted (`boards/common/qspi-ext.dtsi`): `zephcore_fs_mount_ext()` brings it up on first use, because a + boot-time probe could beat a cold power rail (first boot after a UF2 update) and silently drop to internal flash - **BLE bonds**: NVS (`storage_partition`, 0xD0000 on nRF52) via Zephyr settings backend (≥1.16.2) - **Prefs**: `prefs.json` through upstream's `ConfigSerializer`, with upstream's key names for shared fields and ZephCore's own under `zc` (see §13); the older binary file is read once to migrate and kept - **Contacts**: 152-byte records, stored on external flash if available diff --git a/docs/DESIGN.md b/docs/DESIGN.md index c89d383..78da1b6 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -312,6 +312,9 @@ it polls external state. Repeater and room server share one loop (`server_main_c ### 8.5 Persistence - LittleFS `/lfs` (internal) and optional `/ext` (QSPI). BLE bonds in NVS, isolated from LittleFS on nRF52/ESP32. +- `/ext` is never probed at boot: its flash is deferred-init and `zephcore_fs_mount_ext()` brings it up on first + use, since a boot-time probe can beat a switched rail that is still cold (first boot after a UF2 update). If it + still fails, the companion falls back to `/lfs` and the next boot that mounts `/ext` keeps the `/ext` copy. - Prefs are upstream's `prefs.json` (ZephCore fields under `zc`); the legacy binary layouts are migrated once and kept for downgrades. - Atomic replace for identity, prefs and channels; contacts on `/ext` only. diff --git a/releasenotes/RELEASE_NOTES_1.17.6a-zephcore.md b/releasenotes/RELEASE_NOTES_1.17.6a-zephcore.md index be2242c..d9c30b6 100644 --- a/releasenotes/RELEASE_NOTES_1.17.6a-zephcore.md +++ b/releasenotes/RELEASE_NOTES_1.17.6a-zephcore.md @@ -100,6 +100,26 @@ write exactly what is already stored is skipped. Pending writes are now flushed before every clean reboot and power-off (CLI, UI, bootloader and OTA paths), not only before the companion app's reboot command. +## Contacts and channels no longer disappear after an update + +Boards with external QSPI flash keep contacts, channels and the advert cache on it (`/ext`); identity and +settings stay on the chip's internal flash. On some of these boards the flash is powered through a switched +rail, and the firmware used to check for the flash very early in boot, just after switching that rail on. +After a normal reboot the rail is still charged and the flash answers. After a UF2 update the rail has been +off for the whole time the bootloader ran, the flash was not ready in time, and `/ext` was silently not +used: the node came up with an empty contact list and default channels, while its name, radio settings +and keys were all intact. Reported on a ThinkNode M1 upgrading to 1.17.5. + +The flash is now brought up when contacts are first loaded instead of during boot, on every board with +`/ext`: ThinkNode M1, T-Echo, T-Impulse Plus, MeshTracker X1 (which already had this), SenseCAP Solar, +Wio Tracker L1, Wio Tracker L1 Pro 1W and XIAO nRF52840. + +> [!NOTE] +> **If this happened to you, your contacts were not erased.** They are still on the external flash, and +> the next boot that finds it brings them back (a plain reboot is usually enough). Contacts and channels +> added while the list was empty were stored on internal flash instead; they are dropped when the old +> list returns, so re-add those few by hand. + ## Remote CLI now executes on the device (protocol v14) The companion protocol's v14 additions — `CMD_RUN_CLI_COMMAND`/`PACKET_CLI_REPLY`, and diff --git a/zephcore/adapters/datastore/ZephyrDataStore.cpp b/zephcore/adapters/datastore/ZephyrDataStore.cpp index 4f766b0..458ee01 100644 --- a/zephcore/adapters/datastore/ZephyrDataStore.cpp +++ b/zephcore/adapters/datastore/ZephyrDataStore.cpp @@ -51,52 +51,13 @@ bool ZephyrDataStore::mount() return false; } - /* Check if external QSPI was automounted */ - if (zephcore_fs_is_mounted(extMountPoint())) { - ext_lfs_mounted = true; - LOG_INF("External QSPI LittleFS at %s (automounted, 100 blobs)", extMountPoint()); - } else { -#if DT_NODE_EXISTS(DT_NODELABEL(qspi_lfs)) - /* Boot-time automount can miss the QSPI on the first boot after a - * factory-erase (blank flash) or an early-boot timing race with QSPI - * init. Retry the mount explicitly (fs_mount auto-formats blank flash, - * and mounts valid data without touching it) so contacts/channels land - * on /ext. Without this the store silently falls back to internal /lfs, - * and the next boot that does mount /ext runs a needless contact - * migration — the "Migrating contacts to external storage" churn. */ -#if DT_NODE_HAS_PROP(DT_COMPAT_GET_ANY_STATUS_OKAY(nordic_qspi_nor), zephyr_deferred_init) - /* The flash is deferred-init: probing it at boot raced its own - * power rail and the JEDEC read came back 00 00 00, so the - * driver failed and /ext could never mount. Initialising it here - * instead means the part has had until first use to wake up — - * over a second — rather than us guessing a settling delay. */ - { - const struct device *qspi_dev = - DEVICE_DT_GET(DT_COMPAT_GET_ANY_STATUS_OKAY(nordic_qspi_nor)); - - if (!device_is_ready(qspi_dev)) { - int drc = device_init(qspi_dev); - - LOG_INF("QSPI flash deferred init: rc=%d ready=%d", - drc, (int)device_is_ready(qspi_dev)); - } - } -#endif - - FS_FSTAB_DECLARE_ENTRY(DT_NODELABEL(qspi_lfs)); - int rc = fs_mount(&FS_FSTAB_ENTRY(DT_NODELABEL(qspi_lfs))); - if (zephcore_fs_is_mounted(extMountPoint())) { - ext_lfs_mounted = true; - LOG_INF("External QSPI LittleFS at %s (mounted on retry, rc=%d)", - extMountPoint(), rc); - } else { - ext_lfs_mounted = false; - LOG_WRN("External QSPI mount retry failed (rc=%d) - using internal only (20 blobs)", rc); - } -#else - ext_lfs_mounted = false; - LOG_INF("External QSPI NOT mounted at %s - using internal only (20 blobs)", extMountPoint()); -#endif + /* External QSPI: deferred flash init + explicit mount, never at boot. + * If it fails, contacts/channels fall back to internal /lfs, and the + * next boot that does mount /ext keeps the /ext copy. */ + ext_lfs_mounted = zephcore_fs_mount_ext(); + if (!ext_lfs_mounted) { + LOG_INF("External QSPI not mounted at %s - using internal only (20 blobs)", + extMountPoint()); } return true; diff --git a/zephcore/adapters/datastore/ZephyrFsFormat.c b/zephcore/adapters/datastore/ZephyrFsFormat.c index 80329ef..094f4ff 100644 --- a/zephcore/adapters/datastore/ZephyrFsFormat.c +++ b/zephcore/adapters/datastore/ZephyrFsFormat.c @@ -7,6 +7,7 @@ #include "ZephyrFsUtil.h" #include +#include #include #include #include @@ -36,6 +37,53 @@ static void flatten(uint8_t id, const char *tag) flash_area_close(fap); } +#if FIXED_PARTITION_EXISTS(qspi_storage_partition) +/* qspi-ext.dtsi makes the flash under /ext zephyr,deferred-init, so nothing + * probes it at boot. The flash sits behind a GPIO-switched rail on several + * boards, and the boot-time JEDEC read went out before a cold rail had come + * up: the driver failed, /ext stayed unmounted and contacts silently fell + * back to internal flash. A warm reboot keeps the rail charged; the first + * boot after a UF2 update (seconds in the bootloader, rail off) did not. + * Bringing the part up here, on first use, is long after the rail. Each role + * reaches this on its own path, so it is idempotent: -EALREADY means an + * earlier call already ran the init, whatever the result was. */ +static void ext_flash_init(void) +{ + const struct device *dev = FIXED_PARTITION_DEVICE(qspi_storage_partition); + + if (!device_is_ready(dev)) { + int rc = device_init(dev); + + LOG_INF("%s flash init: rc=%d ready=%d", EXT_MNT_POINT, rc, + (int)device_is_ready(dev)); + } +} +#endif + +bool zephcore_fs_mount_ext(void) +{ +#if DT_NODE_EXISTS(DT_NODELABEL(qspi_lfs)) + if (zephcore_fs_is_mounted(EXT_MNT_POINT)) { + return true; + } + ext_flash_init(); + + /* fs_mount() auto-formats blank flash and mounts valid data untouched. */ + FS_FSTAB_DECLARE_ENTRY(DT_NODELABEL(qspi_lfs)); + int rc = fs_mount(&FS_FSTAB_ENTRY(DT_NODELABEL(qspi_lfs))); + bool mounted = zephcore_fs_is_mounted(EXT_MNT_POINT); + + if (mounted) { + LOG_INF("%s mounted (rc=%d)", EXT_MNT_POINT, rc); + } else { + LOG_ERR("%s mount failed (rc=%d)", EXT_MNT_POINT, rc); + } + return mounted; +#else + return false; +#endif +} + bool zephcore_fs_format_all(bool *out_ext_mounted) { LOG_INF("zephcore_fs_format_all: starting..."); @@ -71,6 +119,8 @@ bool zephcore_fs_format_all(bool *out_ext_mounted) #endif #if FIXED_PARTITION_EXISTS(qspi_storage_partition) + /* A repeater never mounts /ext, so its flash is still uninitialised. */ + ext_flash_init(); flatten(PARTITION_ID(qspi_storage_partition), "qspi_storage_partition"); #endif @@ -93,14 +143,8 @@ bool zephcore_fs_format_all(bool *out_ext_mounted) * contacts/channels save to /lfs and get needlessly migrated back to * /ext on the next boot ("Migrating contacts to external storage"). */ { - int ext_rc = fs_mount(&FS_FSTAB_ENTRY(DT_NODELABEL(qspi_lfs))); - bool ext_mounted = zephcore_fs_is_mounted(EXT_MNT_POINT); + bool ext_mounted = zephcore_fs_mount_ext(); - if (ext_mounted) { - LOG_INF("format: %s remounted (rc=%d)", EXT_MNT_POINT, ext_rc); - } else { - LOG_ERR("format: %s remount failed (rc=%d)", EXT_MNT_POINT, ext_rc); - } if (out_ext_mounted) { *out_ext_mounted = ext_mounted; } diff --git a/zephcore/adapters/datastore/ZephyrFsFormat.h b/zephcore/adapters/datastore/ZephyrFsFormat.h index c003970..038579b 100644 --- a/zephcore/adapters/datastore/ZephyrFsFormat.h +++ b/zephcore/adapters/datastore/ZephyrFsFormat.h @@ -34,6 +34,16 @@ extern "C" { */ bool zephcore_fs_format_all(bool *out_ext_mounted); +/** + * @brief Mount /ext (QSPI) on first use, initialising its deferred flash. + * + * Idempotent. The flash is zephyr,deferred-init (qspi-ext.dtsi) and /ext is + * not automounted, so this is the only way /ext comes up. + * + * @return true if /ext is mounted; always false on boards with no QSPI. + */ +bool zephcore_fs_mount_ext(void); + #ifdef __cplusplus } #endif diff --git a/zephcore/boards/common/qspi-ext.dtsi b/zephcore/boards/common/qspi-ext.dtsi index a55b98b..38841f7 100644 --- a/zephcore/boards/common/qspi-ext.dtsi +++ b/zephcore/boards/common/qspi-ext.dtsi @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: MIT - * QSPI external flash /ext automount — optional add-on for any platform + * QSPI external flash /ext — optional add-on for any platform * * See nrf52_partitions_sdv7.dtsi for full include guide. * @@ -8,7 +8,8 @@ * and blobs to QSPI flash (more space, faster writes on native 4KB blocks). * Prefs and identity stay on internal flash. * - * Requires board DTS to define: qspi_storage_partition + * Requires board DTS to define: qspi_flash (the flash device node) and + * qspi_storage_partition * * Usage in board overlay (in addition to partition or filesystem include): * #include "../../common/qspi-ext.dtsi" @@ -16,6 +17,26 @@ * Works with all platforms using /lfs mount. */ +/* + * The flash is not probed at boot. On boards where it sits behind a switched + * rail, the boot-time JEDEC read could beat a cold rail (first boot after a + * UF2 update, or a power-up): the driver failed, /ext stayed unmounted and + * contacts/channels silently fell back to internal flash. A warm reboot keeps + * the rail charged, which is why only updates showed it. The store initialises + * the flash and mounts /ext on first use (zephcore_fs_mount_ext()), so /ext is + * not automounted either. + * + * t-exit-dpd (nanoseconds, so 50 us) is how long the driver waits after + * Release-from-Deep-Power-Down, which it sends once at init to wake a part a + * previous firmware left asleep. Boards used to carry 30, 35 or 50 us each; 50 + * covers all of them, and the extra few us run once per flash init. It + * assumes a powered part, so it does nothing for the cold-rail race above. + */ +&qspi_flash { + zephyr,deferred-init; + t-exit-dpd = <50000>; +}; + / { fstab { /* External QSPI for contacts, channels, blobs */ @@ -23,7 +44,6 @@ compatible = "zephyr,fstab,littlefs"; mount-point = "/ext"; partition = <&qspi_storage_partition>; - automount; read-size = <16>; prog-size = <16>; cache-size = <64>; diff --git a/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2_procpu.dts b/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2_procpu.dts index d5cd4c2..ef959e5 100644 --- a/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2_procpu.dts +++ b/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2_procpu.dts @@ -88,11 +88,11 @@ enable-gpios = <&gpio0 3 (GPIO_ACTIVE_HIGH | ESP32_GPIO_SLEEP_HOLD_EN)>; regulator-boot-on; regulator-always-on; - /* Documents the rail's requirement, but do not rely on it. It is - * inert twice over: regulator-boot-on sends regulator_common_init() - * down the refcount-only branch so regulator_delay() never runs, and - * regulator-always-on makes regulator_enable() return at its first - * line. The real settle time comes from the ST7735R driver's own + /* The rail's requirement. It runs once at boot (Zephyr + * 3b4821f1ba6, pinned from 1.17.5; it was skipped before), and + * never again: regulator-always-on makes regulator_enable() return + * at its first line. Do not rely on it alone: the real settle time + * comes from the ST7735R driver's own * mipi_dbi_reset() + exit-sleep delay at DISPLAY_INIT_PRIORITY 85; * the GNSS side is safe because gnss-nmea-generic never probes the * receiver, it just parses whatever the UART delivers. */ @@ -123,9 +123,9 @@ enable-gpios = <&gpio0 7 (GPIO_ACTIVE_HIGH | ESP32_GPIO_SLEEP_HOLD_EN)>; /* held across light sleep */ regulator-boot-on; regulator-always-on; - /* Documents the FEM's requirement, but do not rely on it — inert for - * the same two reasons as vext above. Harmless because the FEM is a - * passive gain block with no bus to probe. */ + /* The FEM's requirement; runs once at boot, as vext above. Nothing + * depends on it: the FEM is a passive gain block with no bus to + * probe. */ startup-delay-us = <1000>; }; diff --git a/zephcore/boards/example_board/board.overlay b/zephcore/boards/example_board/board.overlay index c6d23f6..6605064 100644 --- a/zephcore/boards/example_board/board.overlay +++ b/zephcore/boards/example_board/board.overlay @@ -571,7 +571,7 @@ * size = <16777216>; * has-dpd; * t-enter-dpd = <3000>; - * t-exit-dpd = <30000>; + * (t-exit-dpd comes from qspi-ext.dtsi: 50 us) * quad-enable-requirements = "S2B1v1"; * }; * }; diff --git a/zephcore/boards/nrf52840/heltec_t096/heltec_t096_nrf52840.dts b/zephcore/boards/nrf52840/heltec_t096/heltec_t096_nrf52840.dts index 38bdc1c..9be1080 100644 --- a/zephcore/boards/nrf52840/heltec_t096/heltec_t096_nrf52840.dts +++ b/zephcore/boards/nrf52840/heltec_t096/heltec_t096_nrf52840.dts @@ -104,10 +104,9 @@ regulator-name = "gps-power"; enable-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>; regulator-boot-on; - /* Documents the module's requirement, but do not rely on it: - * regulator-boot-on sends regulator_common_init() down the - * refcount-only branch, so regulator_delay() never runs and this - * value is inert. Harmless here because gnss-nmea-generic never + /* The module's requirement. It runs at boot (Zephyr 3b4821f1ba6, + * pinned from 1.17.5; it was skipped before), but nothing here + * depends on it: gnss-nmea-generic never * probes the receiver — it attaches a UART and parses whatever * arrives, so an unpowered module means no sentences yet, not a * failed init. ZephyrGPSManager owns the rail at runtime via @@ -130,11 +129,10 @@ regulator-name = "vfem-enable"; enable-gpios = <&gpio0 30 GPIO_ACTIVE_HIGH>; regulator-boot-on; - /* Documents the FEM's requirement, but do not rely on it: - * regulator-boot-on sends regulator_common_init() down the - * refcount-only branch, so regulator_delay() never runs and this - * value is inert. Harmless here because the FEM is a passive - * gain block with no bus to probe — nothing addresses it at init. */ + /* The FEM's requirement. It runs at boot (Zephyr 3b4821f1ba6, + * pinned from 1.17.5; it was skipped before), but nothing depends + * on it: the FEM is a passive gain block with no bus to probe — + * nothing addresses it at init. */ startup-delay-us = <1000>; }; @@ -154,10 +152,9 @@ regulator-name = "tft-pwr-enable"; enable-gpios = <&gpio0 26 GPIO_ACTIVE_HIGH>; regulator-boot-on; - /* Documents the panel's requirement, but do not rely on it: - * regulator-boot-on sends regulator_common_init() down the - * refcount-only branch, so regulator_delay() never runs and this - * value is inert. The real settle time comes from the ST7735R + /* The panel's requirement. It runs at boot (Zephyr 3b4821f1ba6, + * pinned from 1.17.5; it was skipped before), but do not rely on + * it alone: the real settle time comes from the ST7735R * driver's own mipi_dbi_reset() + exit-sleep delay, at * DISPLAY_INIT_PRIORITY 85 against this rail's 75. */ startup-delay-us = <10000>; diff --git a/zephcore/boards/nrf52840/heltec_t1/heltec_t1_nrf52840.dts b/zephcore/boards/nrf52840/heltec_t1/heltec_t1_nrf52840.dts index 7a281bb..71a3795 100644 --- a/zephcore/boards/nrf52840/heltec_t1/heltec_t1_nrf52840.dts +++ b/zephcore/boards/nrf52840/heltec_t1/heltec_t1_nrf52840.dts @@ -168,10 +168,9 @@ regulator-name = "gps-power"; enable-gpios = <&gpio0 4 GPIO_ACTIVE_LOW>; regulator-boot-on; - /* Documents the module's requirement, but do not rely on it: - * regulator-boot-on sends regulator_common_init() down the - * refcount-only branch, so regulator_delay() never runs and this - * value is inert. Harmless here because gnss-nmea-generic never + /* The module's requirement. It runs at boot (Zephyr 3b4821f1ba6, + * pinned from 1.17.5; it was skipped before), but nothing here + * depends on it: gnss-nmea-generic never * probes the receiver — it attaches a UART and parses whatever * arrives, so an unpowered module means no sentences yet, not a * failed init. ZephyrGPSManager owns the rail at runtime via @@ -234,10 +233,9 @@ regulator-name = "tft-pwr-enable"; enable-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>; regulator-boot-on; - /* Documents the panel's requirement, but do not rely on it: - * regulator-boot-on sends regulator_common_init() down the - * refcount-only branch, so regulator_delay() never runs and this - * value is inert. The real settle time comes from the ST7735R + /* The panel's requirement. It runs at boot (Zephyr 3b4821f1ba6, + * pinned from 1.17.5; it was skipped before), but do not rely on + * it alone: the real settle time comes from the ST7735R * driver's own mipi_dbi_reset() + exit-sleep delay, at * DISPLAY_INIT_PRIORITY 85 against this rail's 75. */ startup-delay-us = <10000>; diff --git a/zephcore/boards/nrf52840/lilygo_techo/lilygo_techo.dts b/zephcore/boards/nrf52840/lilygo_techo/lilygo_techo.dts index c9e900a..90134ca 100644 --- a/zephcore/boards/nrf52840/lilygo_techo/lilygo_techo.dts +++ b/zephcore/boards/nrf52840/lilygo_techo/lilygo_techo.dts @@ -147,10 +147,10 @@ * regulator framework brings them up before the SX1262 driver init — * REGULATOR_FIXED_INIT_PRIORITY 75 against LORA_INIT_PRIORITY 90. * - * Ordering is all that guarantee covers. It does NOT mean a rail has - * settled: regulator-boot-on sends regulator_common_init() down the - * refcount-only branch, so regulator_delay() never runs and the - * startup-delay-us below is inert. The SX1262 is safe because its driver + * Ordering is all that guarantee covers. The startup-delay-us below does + * run at boot (Zephyr 3b4821f1ba6, pinned from 1.17.5; it was skipped + * before), but it is a settle floor, not a readiness check. The SX1262 is + * safe because its driver * resets the part (5 ms NRESET pulse + 5 ms wait + BUSY poll) before any * SPI traffic. Anything put behind these rails that does NOT * reset-then-wait needs zephyr,deferred-init instead; see @@ -385,7 +385,6 @@ size = <16777216>; /* 2MB = 16Mbit */ has-dpd; t-enter-dpd = <10000>; - t-exit-dpd = <35000>; }; }; diff --git a/zephcore/boards/nrf52840/lilygo_timpulse_plus/lilygo_timpulse_plus.dts b/zephcore/boards/nrf52840/lilygo_timpulse_plus/lilygo_timpulse_plus.dts index 008cbaf..e32f566 100644 --- a/zephcore/boards/nrf52840/lilygo_timpulse_plus/lilygo_timpulse_plus.dts +++ b/zephcore/boards/nrf52840/lilygo_timpulse_plus/lilygo_timpulse_plus.dts @@ -125,10 +125,9 @@ regulator-name = "pwr-enable"; enable-gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>; regulator-boot-on; - /* Documents the LDO's requirement, but do not rely on it: - * regulator-boot-on sends regulator_common_init() down the - * refcount-only branch, so regulator_delay() never runs and this - * value is inert. What actually gives this rail time to settle is the + /* The LDO's requirement. It runs at boot (Zephyr 3b4821f1ba6, + * pinned from 1.17.5; it was skipped before), but do not rely on + * it alone. What also gives this rail time to settle is the * priority-30 pin above — every consumer here (QSPI 41, OLED 85, * SX1262 90) has real boot work between it and the rail, and the * SX1262 and SSD1315 both reset-then-wait besides. The GPS, which @@ -357,7 +356,6 @@ size = <33554432>; /* size is in BITS: 32Mbit = 4MB */ has-dpd; t-enter-dpd = <10000>; /* tDP 3us (conservative) */ - t-exit-dpd = <35000>; /* tRES1 8us (conservative) */ }; }; diff --git a/zephcore/boards/nrf52840/meshtracker_x1/board.conf b/zephcore/boards/nrf52840/meshtracker_x1/board.conf index 0b0a8b8..c5089d0 100644 --- a/zephcore/boards/nrf52840/meshtracker_x1/board.conf +++ b/zephcore/boards/nrf52840/meshtracker_x1/board.conf @@ -21,8 +21,3 @@ CONFIG_ZEPHCORE_RADIO_LR2021=y # DRV2605L vibration motor CONFIG_HAPTICS=y CONFIG_HAPTICS_DRV2605=y - -# The flash sits behind the P0.15 power switch, modelled as a regulator so its -# power-up time is a startup-delay rather than a race. Regulators come up at 75; -# probe the flash after that. -CONFIG_NORDIC_QSPI_NOR_INIT_PRIORITY=80 diff --git a/zephcore/boards/nrf52840/meshtracker_x1/meshtracker_x1_nrf52840.dts b/zephcore/boards/nrf52840/meshtracker_x1/meshtracker_x1_nrf52840.dts index d3d7e07..3f7de1e 100644 --- a/zephcore/boards/nrf52840/meshtracker_x1/meshtracker_x1_nrf52840.dts +++ b/zephcore/boards/nrf52840/meshtracker_x1/meshtracker_x1_nrf52840.dts @@ -113,12 +113,11 @@ regulator-name = "flash-power"; enable-gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>; regulator-boot-on; - /* Documents the part's requirement, but do not rely on it: - * regulator_fixed_init() drives the enable GPIO active and then - * passes is_enabled=true into regulator_common_init(), which - * takes the refcount-only branch — regulator_delay() sits on the - * other one and never runs. The flash gets its settling time - * from deferred init instead (see the qspi_flash node). */ + /* Runs at boot: since Zephyr 3b4821f1ba6 (pinned from 1.17.5) + * regulator_fixed_init() sleeps this long after driving a + * regulator-boot-on rail active. Before that it was skipped. The + * flash's real margin is still its deferred init (qspi-ext.dtsi), + * which runs when the store first mounts /ext. */ startup-delay-us = <100000>; }; @@ -290,9 +289,10 @@ * * sensor_power comes up at CONFIG_REGULATOR_FIXED_INIT_PRIORITY (75) and * both drivers run at 90 — microseconds later, with nothing scheduled in - * between on this board. The node's startup-delay-us does not save them: - * with regulator-boot-on, regulator_common_init() takes the refcount-only - * branch and regulator_delay() never runs. Same trap as flash_power below. + * between on this board. The node's startup-delay-us did not save them: + * until Zephyr 3b4821f1ba6 (pinned from 1.17.5) it was skipped for a + * regulator-boot-on rail. It now runs (5 ms), but the parts were never + * validated against it, so the deferral below stays. * (vin-supply + regulator_enable() would not help either — that applies * off-on-delay-us, and only on the 0->1 refcount transition.) * @@ -365,14 +365,11 @@ qspi_flash: qspi_flash@0 { compatible = "nordic,qspi-nor"; reg = <0>; - /* Do not probe this at boot. The rail is switched on by - * flash-power a few init priorities earlier, and the part is not - * driving the bus yet when the driver's JEDEC read goes out — - * it answers 00 00 00 at 0.4 s and a correct c8 40 17 by 5.9 s - * on the very same boot. A fixed settling delay would only be a - * guess at where in between it wakes; instead the driver is - * initialised on demand, when the store first mounts /ext. */ - zephyr,deferred-init; + /* Not probed at boot: qspi-ext.dtsi makes it zephyr,deferred-init. + * This board is where that came from — probed a few init + * priorities after flash-power switched the rail on, the part + * answered 00 00 00 at 0.4 s and a correct c8 40 17 by 5.9 s on + * the very same boot. */ /* Single-line, as every other QSPI board here uses. * * Quad needs the part's QE bit set, and the binding defaults @@ -395,7 +392,6 @@ size = <67108864>; /* 64Mbit = 8MB, in bits */ has-dpd; t-enter-dpd = <3000>; - t-exit-dpd = <30000>; }; }; diff --git a/zephcore/boards/nrf52840/promicro_lr2021/promicro_lr2021.dts b/zephcore/boards/nrf52840/promicro_lr2021/promicro_lr2021.dts index 4a7deb2..f71a4f4 100644 --- a/zephcore/boards/nrf52840/promicro_lr2021/promicro_lr2021.dts +++ b/zephcore/boards/nrf52840/promicro_lr2021/promicro_lr2021.dts @@ -62,10 +62,9 @@ regulator-name = "vcc3v3-enable"; enable-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; regulator-boot-on; - /* Documents the module's requirement, but do not rely on it: - * regulator-boot-on sends regulator_common_init() down the - * refcount-only branch, so regulator_delay() never runs and this - * value is inert. The real settle time comes from lr20xx_hal_reset(), + /* The module's requirement. It runs at boot (Zephyr 3b4821f1ba6, + * pinned from 1.17.5; it was skipped before), but do not rely on + * it alone: the real settle time comes from lr20xx_hal_reset(), * which the driver runs (with two retries, 10 ms apart) before any * SPI traffic — and at LORA_INIT_PRIORITY 90 against this rail's 75. * Anything put behind this rail that does NOT reset-then-wait needs diff --git a/zephcore/boards/nrf52840/promicro_sx1262/promicro_sx1262.dts b/zephcore/boards/nrf52840/promicro_sx1262/promicro_sx1262.dts index b86d424..2b16847 100644 --- a/zephcore/boards/nrf52840/promicro_sx1262/promicro_sx1262.dts +++ b/zephcore/boards/nrf52840/promicro_sx1262/promicro_sx1262.dts @@ -68,10 +68,9 @@ regulator-name = "vcc3v3-enable"; enable-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; regulator-boot-on; - /* Documents the module's requirement, but do not rely on it: - * regulator-boot-on sends regulator_common_init() down the - * refcount-only branch, so regulator_delay() never runs and this - * value is inert. The real settle time comes from the SX126x driver's + /* The module's requirement. It runs at boot (Zephyr 3b4821f1ba6, + * pinned from 1.17.5; it was skipped before), but do not rely on + * it alone: the real settle time comes from the SX126x driver's * own reset (5 ms NRESET pulse + 5 ms wait + BUSY poll) before any * SPI traffic, at LORA_INIT_PRIORITY 90 against this rail's 75. * Anything put behind this rail that does NOT reset-then-wait needs diff --git a/zephcore/boards/nrf52840/rak3401_1watt/rak3401_1watt.dts b/zephcore/boards/nrf52840/rak3401_1watt/rak3401_1watt.dts index 8af7387..895c4b0 100644 --- a/zephcore/boards/nrf52840/rak3401_1watt/rak3401_1watt.dts +++ b/zephcore/boards/nrf52840/rak3401_1watt/rak3401_1watt.dts @@ -93,10 +93,9 @@ regulator-name = "lora-pwr-enable"; enable-gpios = <&gpio0 21 GPIO_ACTIVE_HIGH>; regulator-boot-on; - /* Documents the FEM's requirement, but do not rely on it: - * regulator-boot-on sends regulator_common_init() down the - * refcount-only branch, so regulator_delay() never runs and this - * value is inert. The real settle time comes from the SX126x driver's + /* The FEM's requirement. It runs at boot (Zephyr 3b4821f1ba6, + * pinned from 1.17.5; it was skipped before), but do not rely on + * it alone: the real settle time comes from the SX126x driver's * own reset (5 ms NRESET pulse + 5 ms wait + BUSY poll) before any * SPI traffic, at LORA_INIT_PRIORITY 90 against this rail's 75. * Anything put behind this rail that does NOT reset-then-wait needs diff --git a/zephcore/boards/nrf52840/rak_wismesh_tag/rak_wismesh_tag.dts b/zephcore/boards/nrf52840/rak_wismesh_tag/rak_wismesh_tag.dts index 33120de..c275616 100644 --- a/zephcore/boards/nrf52840/rak_wismesh_tag/rak_wismesh_tag.dts +++ b/zephcore/boards/nrf52840/rak_wismesh_tag/rak_wismesh_tag.dts @@ -120,10 +120,9 @@ regulator-name = "lora-pwr-enable"; enable-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>; regulator-boot-on; - /* Documents the module's requirement, but do not rely on it: - * regulator-boot-on sends regulator_common_init() down the - * refcount-only branch, so regulator_delay() never runs and this - * value is inert. The real settle time comes from the SX126x driver's + /* The module's requirement. It runs at boot (Zephyr 3b4821f1ba6, + * pinned from 1.17.5; it was skipped before), but do not rely on + * it alone: the real settle time comes from the SX126x driver's * own reset (5 ms NRESET pulse + 5 ms wait + BUSY poll) before any * SPI traffic, at LORA_INIT_PRIORITY 90 against this rail's 75. * Anything put behind this rail that does NOT reset-then-wait needs diff --git a/zephcore/boards/nrf52840/sensecap_solar/sensecap_solar_nrf52840.dts b/zephcore/boards/nrf52840/sensecap_solar/sensecap_solar_nrf52840.dts index b4bf3a8..d876052 100644 --- a/zephcore/boards/nrf52840/sensecap_solar/sensecap_solar_nrf52840.dts +++ b/zephcore/boards/nrf52840/sensecap_solar/sensecap_solar_nrf52840.dts @@ -218,7 +218,6 @@ size = <16777216>; has-dpd; t-enter-dpd = <3000>; - t-exit-dpd = <50000>; }; }; diff --git a/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1.dts b/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1.dts index 79f3d1e..1d4e859 100644 --- a/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1.dts +++ b/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1.dts @@ -359,7 +359,6 @@ size = <16777216>; /* 2MB = 16Mbit */ has-dpd; t-enter-dpd = <10000>; - t-exit-dpd = <35000>; }; }; diff --git a/zephcore/boards/nrf52840/wio_tracker_l1/board.overlay b/zephcore/boards/nrf52840/wio_tracker_l1/board.overlay index 8e4cded..f722612 100644 --- a/zephcore/boards/nrf52840/wio_tracker_l1/board.overlay +++ b/zephcore/boards/nrf52840/wio_tracker_l1/board.overlay @@ -331,7 +331,6 @@ sh1106: &oled { size = <16777216>; /* 2MB = 16Mbit */ has-dpd; t-enter-dpd = <3000>; - t-exit-dpd = <50000>; /* 50ms for reliable wakeup */ /* quad-enable not needed for single-bit mode */ partitions { diff --git a/zephcore/boards/nrf52840/wio_tracker_l1_1w/wio_tracker_l1_1w_nrf52840.dts b/zephcore/boards/nrf52840/wio_tracker_l1_1w/wio_tracker_l1_1w_nrf52840.dts index e5d8d63..c17dc48 100644 --- a/zephcore/boards/nrf52840/wio_tracker_l1_1w/wio_tracker_l1_1w_nrf52840.dts +++ b/zephcore/boards/nrf52840/wio_tracker_l1_1w/wio_tracker_l1_1w_nrf52840.dts @@ -152,10 +152,9 @@ regulator-name = "lora-pwr-enable"; enable-gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>; regulator-boot-on; - /* Documents the LDO + PA settle requirement but does not enforce - * it: regulator-boot-on takes regulator_common_init()'s - * refcount-only branch, so regulator_delay() never runs. The real - * settle time comes from the SX126x driver's own reset (5 ms + /* The LDO + PA settle requirement. It runs at boot (Zephyr + * 3b4821f1ba6, pinned from 1.17.5; it was skipped before), but do + * not rely on it alone: the real settle time comes from the SX126x driver's own reset (5 ms * NRESET pulse + 5 ms wait + BUSY poll) 15 init priorities later. * See the identical note on rak3401_1watt. */ startup-delay-us = <10000>; @@ -392,7 +391,6 @@ size = <16777216>; /* 2MB = 16Mbit */ has-dpd; t-enter-dpd = <3000>; - t-exit-dpd = <50000>; /* 50ms for reliable wakeup */ /* quad-enable not needed for single-bit mode */ }; }; diff --git a/zephcore/boards/nrf52840/xiao_nrf52840/xiao_nrf52840_nrf52840.dts b/zephcore/boards/nrf52840/xiao_nrf52840/xiao_nrf52840_nrf52840.dts index 9fa1efa..5fd8477 100644 --- a/zephcore/boards/nrf52840/xiao_nrf52840/xiao_nrf52840_nrf52840.dts +++ b/zephcore/boards/nrf52840/xiao_nrf52840/xiao_nrf52840_nrf52840.dts @@ -230,7 +230,6 @@ size = <16777216>; has-dpd; t-enter-dpd = <3000>; - t-exit-dpd = <50000>; }; }; diff --git a/zephcore/helpers/qspi_probe.c b/zephcore/helpers/qspi_probe.c index e343b2e..1155ff9 100644 --- a/zephcore/helpers/qspi_probe.c +++ b/zephcore/helpers/qspi_probe.c @@ -67,6 +67,15 @@ static int qspi_rdid_probe(void) return 0; } + if (!flash->state->initialized) { + /* qspi-ext.dtsi defers the flash to first use, and a repeater + * never mounts /ext. Stealing the pins now would break the init + * a later factory reset runs, so there is nothing to probe. */ + LOG_INF("qspi flash '%s' not initialised yet - RDID probe skipped", + flash->name); + return 0; + } + if (device_is_ready(flash)) { /* Bit-banging steals the QSPI pins as GPIOs and leaves them * that way, which would break a flash that just came up fine.