Skip to content

QSPI: read the status register before rewriting it - #718

Open
lylepmills wants to merge 1 commit into
daisyaudio:masterfrom
lylepmills:qspi-read-status-before-write
Open

lylepmills wants to merge 1 commit into
daisyaudio:masterfrom
lylepmills:qspi-read-status-before-write

Conversation

@lylepmills

Copy link
Copy Markdown

Proposal

QuadEnable() and DefaultStatusRegister() write the flash's status register unconditionally. That register is non-volatile, and these writes happen often:

  • DefaultStatusRegister() runs on every boot.
  • QuadEnable() runs on every Init, so on every switch between indirect and memory-mapped mode. A Write or Erase from a running program does two of these, and so does every block the bootloader programs.

This PR reads the status register first and skips the write when it already holds the value the write would produce. I'm proposing this rather than asserting it's safe. If there is a reason these writes need to be unconditional that I've missed, I'd like to hear it.

What changes

  • New Impl::ReadStatusRegister(): a one-line RDSR (0x05), the same read AutopollingMemReady already uses. Returns false if the read fails.
  • QuadEnable(): skips WREN + WRSR when QE is set and WIP and WEL are both clear. That's the state the existing code polls for after its own write.
  • DefaultStatusRegister(): skips WREN + WRSR when the register reads exactly 0x40. The read happens before WREN, so it can't split the write command from its data byte.

Everything else still takes the existing write path:

  • a fresh chip;
  • block protection set;
  • SRWD set, the case QSPI Stability Improvements #670 recovers from, which reads as 0xC0 and is still rewritten;
  • a failed read;
  • a write still in progress.

No public API changes. 46 added lines in src/per/qspi.cpp; it builds with no warnings, and clang-format 10 reports no changes.

Why

On our unit, each of these writes had become slow. #691 describes a similar symptom: I/O slowing after many reprogramming cycles until the bootloader timed out. I can't show that the status register is the part that wore there, but in our case skipping these writes removed almost all of the time.

Testing

Simulation. I extracted the real DefaultStatusRegister, QuadEnable and ReadStatusRegister bodies and ran them against a simulated IS25LP status register. The simulation counts non-volatile writes and rejects a WRSR without WEL, or a command issued before the previous command's data phase.

Starting state Writes before Writes after
Already configured (0x40), boot + 1,000 mode switches 1,002 0
Fresh (0x00) 1,002 1
Block protection set 12 1
SRWD set (0xC0) 12 1
First read fails 12 1
Write in progress at first read 12 1

In every case the register ends at 0x40 with WEL clear. I'm happy to add this as a unit test if you'd like it; I left it out because it doesn't fit the existing test layout directly.

Hardware. One Daisy Seed3 on our development bench, heavily reflashed. Our firmware and our bootloader build are based on libDaisy e1f740a; its qspi.cpp is identical to current master.

  • Settings save (one 1 KB record, two mode switches): 920–971 ms before (5 saves), 1.6 ms after (4 saves), no errors.
  • Bootloader programming of an 835 KB image: 168 s before, 26 s after.
  • Unplug/replug with no buttons: the application started normally and read back its saved data.

Not tested:

  • a stock Daisy Seed, Seed 2 or Patch SM;
  • the IS25LP080D;
  • the QSPI Stability Improvements #670 SRWD recovery on real hardware (simulation only);
  • power loss during boot.

Questions

  1. Is there a chip revision, board or state where the status register can't be trusted at this point in Init, so the unconditional write is doing something the read can't?
  2. Would you prefer this behind a config option, or with the simulation added as a test?

🤖 Generated with Claude Code

QuadEnable() runs on every Init (every switch between indirect and
memory-mapped mode) and DefaultStatusRegister() on every boot. Both
wrote the non-volatile status register unconditionally. Read it first
and skip the write when it already holds the target state; any other
value, or a failed read, still takes the existing write path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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