Skip to content

drivers/flash: rewrite around pbl_flash devices - #2047

Draft
gmarull wants to merge 3 commits into
coredevices:mainfrom
teslabs:flash-rewrite
Draft

gmarull wants to merge 3 commits into
coredevices:mainfrom
teslabs:flash-rewrite

Conversation

@gmarull

@gmarull gmarull commented Sep 8, 2026

Copy link
Copy Markdown
Member

Summary

Replaces the flash/QSPI driver stack with a single device-based API.

The old stack was four layers deep for one part: flash_api.c (global mutex, erase suspend/resume state machine) over flash_impl.h (a global, single-instance driver interface) over qspi_flash.h (per-part command tables) over the SoC qspi.c, plus a parallel coredump driver, a second async range-erase engine in flash_erase.c and a third synchronous one in flash_region.c.

Now:

  • include/pbl/drivers/flash.h: struct pbl_flash_device (geometry, security registers, ops, state with lock + erase engine). Drivers embed the device in their own struct and use container_of(). The driver exports its instance as FLASH.
  • src/fw/drivers/flash/flash.c is the only generic layer: locking, soft write protection, blank checks, CRC helpers, and one erase engine behind pbl_flash_erase() and pbl_flash_erase_async() (sector/subsector splitting, blank skipping, retries, suspend-for-reads on parts that support it).
  • pbl_flash_coredump_init() switches the same API into a no-OS mode, replacing the separate coredump driver and the dual-driver switch in core_dump.c.
  • Drivers: nrf5_qspi.c (command tables from nor_part.h), sf32lb52_mpi.c (SiFli HAL, XIP; deep power-down helpers for the SoC idle path) and qemu.c.
  • Consumers call pbl_flash_*(FLASH, ...); errors are -errno.
  • Devices are instantiated by the drivers, not by boards: each driver builds its instance from Kconfig (FLASH_NRF5_QSPI_* pins/clock/commands, FLASH_SF32LB52_MPI_* instance/DMA/window, FLASH_QEMU_SIZE) and the FLASH_NOR_PART choice, whose tables carry command sets, geometry and erase timings. Board files no longer mention the flash.

Behaviour change: the old blocking erase slept for 7/8 of the typical erase time before polling even when the driver had finished synchronously, so every sector erase on the SF32 boards carried an extra ~130 ms. Synchronous drivers now complete immediately.

Stage 2 (a generic JEDEC NOR layer between an SoC bus driver and the flash ops) is not part of this PR; the nRF5 driver still carries the command logic.

Verification

  • asterix, obelix, getafix and qemu_flint build; asterix PRF variant builds.
  • Full unit test suite passes; test_flash_api rewritten against a fake ops table.
  • qemu_flint boots to the watchface; erase flash and flash unprotect prompt commands work over PULSE.
  • Not tested on hardware yet: nRF5 erase suspend/resume under concurrent reads (asterix) and SF32 deep sleep (obelix/getafix).

🤖 Generated with Claude Code

gmarull and others added 3 commits September 8, 2026 15:22
Remove flash driver code that nothing builds or calls:

- py25q128ha.c and the py25q128ha/n25q flash region headers are not
  referenced by any CMake or Kconfig file.
- flash_sleep_when_idle() and flash_get_sleep_when_idle() are hardcoded
  no-ops, flash_enable_write_protection() is empty on every part and
  flash_switch_mode() is unimplemented ("NYI") everywhere.
- flash_whoami(), flash_get_size(), flash_is_whoami_correct(),
  debug_flash_dump_registers(), flash_use()/flash_release*() and
  flash_erase_bulk() have no callers (the latter was already #if 0).

The "flash switchmode" and commented-out "format flash" prompt commands
go with them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Gerard Marull-Paretas <gerard@teslabs.com>
The flash stack was four layers deep for one part: flash_api.c (global
mutex, erase suspend/resume state machine, timers) over flash_impl.h
(a global, single-instance driver interface) over qspi_flash.h (an
instance API with a per-part command table) over the SoC qspi.c, plus a
parallel coredump driver, a second async range-erase engine in
flash_erase.c and a third, synchronous one in flash_region.c.

Replace it with one device-based API in include/pbl/drivers/flash.h:

- struct pbl_flash_device carries geometry, security register info and a
  pointer to struct pbl_flash_ops; struct pbl_flash_device_state holds
  the lock and the erase engine state. Drivers embed the device in their
  own struct and recover it with container_of(). Boards define the
  device and export it as FLASH.
- flash.c is the only generic layer: locking, soft write protection,
  blank checks, CRC helpers and a single erase engine that serves both
  pbl_flash_erase() and pbl_flash_erase_async(). Ranges are split into
  sector and subsector erases, already-blank units are skipped, failed
  units are retried, and on parts that support it an in-flight erase is
  suspended for reads and writes and resumed from a timer, as before.
- pbl_flash_coredump_init() switches the same API into a no-OS mode
  (no locks, timers or sleeping) instead of a separate cd_flash driver,
  so core_dump.c no longer needs the dual-driver switch.
- Errors are -errno; write and erase assert on hardware failure as the
  old API did, since callers never checked.

Three drivers implement the ops: nrf5_qspi.c (command tables from
nor_part.h, formerly the QSPIFlashPart tables in gd25*.c), sf32lb52_mpi.c
(SiFli HAL, XIP) and qemu.c. Drivers whose erase completes synchronously
leave erase_status NULL, which also removes a latent slowdown: the old
blocking erase slept for 7/8 of the typical erase time before its first
poll even when the driver had already finished, so every sector erase on
the SF32 boards cost an extra ~130 ms.

Consumers now call pbl_flash_*(FLASH, ...). The four-argument
"optimal range" erase helpers become pbl_flash_erase(addr, len); callers
that relied on the upper bound to round to a sector pass the rounded
length themselves. flash_prf_set_protection() becomes
pbl_flash_protect()/pbl_flash_unprotect() on the PRF region. The SF32
deep-sleep path uses driver-specific pbl_flash_sf32lb52_mpi_dpd_*()
instead of reaching into the QSPI port state.

The fake flash and the flash driver tests are ported; the flash_region
erase tests are dropped with the code they covered.

Verified on asterix, obelix, getafix and the QEMU boards (build), with
the full unit test suite, and by booting qemu_flint and exercising the
erase and unprotect prompt commands.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Gerard Marull-Paretas <gerard@teslabs.com>
Boards no longer define the flash device. Each driver builds its own
instance from Kconfig and exports it as FLASH, in the direction of
Zephyr-style driver-owned instantiation:

- nrf5_qspi.c takes the clock frequency, pins and read/program commands
  from FLASH_NRF5_QSPI_*.
- sf32lb52_mpi.c takes the MPI instance, memory window size, clock
  divider and DMA channel/request from FLASH_SF32LB52_MPI_*.
- qemu.c takes the flash size from FLASH_QEMU_SIZE.

The NOR part becomes a Kconfig choice (FLASH_NOR_PART) and the part
table now carries the geometry and erase timings as a struct
pbl_flash_geometry, which the device points at, so a driver needs no
board input beyond the part selection and its controller settings. The
SF32 deep power-down latencies come from the part table too.

The driver structs move into the .c files; only the SF32 deep
power-down entry points remain in a public header, for the SoC idle
code.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Gerard Marull-Paretas <gerard@teslabs.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant