boot2_w25q080: W25Q16RV applies only SR1 of the two-byte 01h write — QE never set, board does not boot #3159

Open
opened 2026-08-25 03:24:44 -05:00 by wiznet-grace · 0 comments
wiznet-grace commented 2026-08-25 03:24:44 -05:00 (Migrated from github.com)

We build the WIZnet W5500-EVB-Pico family.We do not define a custom PICO_BOARD, so we inherit pico.h and therefore boot2_w25q080.S. That has always worked with the Winbond W25Q16JV we fit.

Winbond has issued a termination notice for the W25Q16JV; the designated drop-in replacement is the W25Q16RV (W25Q16JVZPIM → W25Q16RVCPJM, same WSON8 6x5mm package, same JEDEC ID EF 70 15).

With the RV fitted the board does not boot at all — no UART output, no LED. BOOTSEL/UF2 flashing still works, so the part is fine on plain SPI. We traced it to boot2_w25q080.S: it enables QE with the legacy two-byte 01h SR write, and the RV applies only the first of those two data bytes — SR1 is written, the SR2 byte carrying QE is discarded. The EBh quad read that follows then leaves the flash unresponsive. Measurements and code detail below.

What we would like to know

We have a local fix working — our own boot2 using 31h and a bit test, selected with PICO_DEFAULT_BOOT_STAGE2_FILE. It boots on the RV board, and we are happy to keep it local. Before we do, two questions:

  1. Do you want this fixed in the SDK, or should boards fitting an R-generation part supply their own boot2? If the former, we can send a PR.
  2. Pico itself carries the W25Q16JV named in the termination notice, and pico.h selects this same boot2 — how do you plan to handle that when the substitution reaches it?

Environment

SDK 2.3.0
Target RP2040
Flash Winbond W25Q16RVCPJM, JEDEC ID EF 70 15

Measured

RAM-resident (no_flash) diagnostic driving the QSPI lines directly, so it reports even when flash is unusable. We ran the same diagnostic on one board of each part:

W25Q16JV (what we fit) W25Q16RV (replacement)
JEDEC ID (9Fh) EF 70 15 EF 70 15
SR2 (35h) as found 0x02 0x04
SR2 bit 2 0 1
SR3 (15h) 0x60 0x40
01h + 1 data byte (SR1) applied applied
01h + 2 data bytes (SR1, SR2) — what boot2 sends both applied SR1 applied, SR2 discarded
31h + 1 data byte (SR2) applied applied

Our RV sample shipped with QE = 0. To observe both data bytes of the two-byte write we put a marker in SR1 first (the TB bit, which protects nothing while BP = 0) and then replayed boot2's exact byte sequence on each part:

                        W25Q16JV              W25Q16RV
06h + 01h 20            SR1 = 0x20            SR1 = 0x20      (1-byte 01h is accepted)
06h + 31h 00            SR2 = 0x00            SR2 = 0x04      (clear QE, set up the test)
  before                SR1=0x20 SR2=0x00     SR1=0x20 SR2=0x04
06h + 01h 00 02         SR1=0x00 SR2=0x02     SR1=0x00 SR2=0x04
  result                both bytes applied    SR1 applied, SR2 discarded

Both parts were restored to SR1 = 0x00, QE = 1 afterwards.

Three things follow: the parts are indistinguishable by JEDEC ID; the RV does not reject 01h, it truncates it to one data byte; and 31h is the only way to set QE that works on both, so a single boot2 can cover JV and RV.

Two problems in boot2_w25q080.S

1. The SR2 byte of the two-byte write is discarded.

movs r1, #CMD_WRITE_STATUS   // 0x01
str  r1, [r3, #SSI_DR0_OFFSET]
movs r0, #0                  // SR1
str  r0, [r3, #SSI_DR0_OFFSET]
str  r2, [r3, #SSI_DR0_OFFSET]   // SR2 = 0x02 (QE)

The RV accepts 01h, applies the first data byte to SR1, and discards the second, so QE is never set. (The JV applies both, which is why the same boot2 has always worked for us until now.) boot2 does not check the result and proceeds to the EBh quad read, driving IO2/IO3 while the device — QE still clear — treats them as /WP and /HOLD. The board is dead from there. We have not scoped the bus, but the same EBh configuration works on the same part once QE is set: our patched boot2 differs only in using 31h, and the RV board boots with it.

The source already flags the risk of using this form:

// This isn't great because it will remove block protections.

On the RV that side effect happens while the intended effect does not: SR1 is overwritten with 0x00, clearing the block protection bits, and QE is still not set. And because of problem 2 below the skip path is never taken on this part, so by the code path that write is reissued on every boot.

2. The skip check compares the whole byte.

movs r2, #SREG_DATA          // 0x02
cmp  r0, r2
beq  skip_sreg_programming

Our RV reads SR2 bit 2 back as 1, so SR2 is 0x06 once QE is set and this never matches — boot2 repeats a non-volatile SR write on every boot, which is what makes the SR1 side effect above permanent rather than one-off. tst r0, r2 fixes it, and is safer generally: any non-QE bit a part happens to set defeats a full-byte compare.

We have both parts on the bench here, so if there is anything you would like measured
before deciding, we can run it.

Thanks for your work on the SDK.

Best regards,
Grace

We build the WIZnet W5500-EVB-Pico family.We do not define a custom `PICO_BOARD`, so we inherit `pico.h` and therefore `boot2_w25q080.S`. That has always worked with the Winbond W25Q16JV we fit. Winbond has issued a termination notice for the W25Q16JV; the designated drop-in replacement is the **W25Q16RV** (`W25Q16JVZPIM` → `W25Q16RVCPJM`, same WSON8 6x5mm package, same JEDEC ID `EF 70 15`). **With the RV fitted the board does not boot at all** — no UART output, no LED. BOOTSEL/UF2 flashing still works, so the part is fine on plain SPI. We traced it to `boot2_w25q080.S`: it enables QE with the legacy two-byte `01h` SR write, and the RV applies only the first of those two data bytes — SR1 is written, the SR2 byte carrying QE is discarded. The `EBh` quad read that follows then leaves the flash unresponsive. Measurements and code detail below. ## What we would like to know We have a local fix working — our own boot2 using `31h` and a bit test, selected with `PICO_DEFAULT_BOOT_STAGE2_FILE`. It boots on the RV board, and we are happy to keep it local. Before we do, two questions: 1. Do you want this fixed in the SDK, or should boards fitting an R-generation part supply their own boot2? If the former, we can send a PR. 2. Pico itself carries the W25Q16JV named in the termination notice, and `pico.h` selects this same boot2 — how do you plan to handle that when the substitution reaches it? ## Environment | | | |---|---| | SDK | 2.3.0 | | Target | RP2040 | | Flash | Winbond `W25Q16RVCPJM`, JEDEC ID `EF 70 15` | ## Measured RAM-resident (`no_flash`) diagnostic driving the QSPI lines directly, so it reports even when flash is unusable. We ran the same diagnostic on one board of each part: | | W25Q16JV (what we fit) | W25Q16RV (replacement) | |---|---|---| | JEDEC ID (`9Fh`) | `EF 70 15` | `EF 70 15` | | SR2 (`35h`) as found | `0x02` | `0x04` | | SR2 bit 2 | `0` | **`1`** | | SR3 (`15h`) | `0x60` | `0x40` | | `01h` + 1 data byte (SR1) | applied | applied | | **`01h` + 2 data bytes (SR1, SR2) — what boot2 sends** | **both applied** | **SR1 applied, SR2 discarded** | | `31h` + 1 data byte (SR2) | applied | applied | Our RV sample shipped with QE = 0. To observe both data bytes of the two-byte write we put a marker in SR1 first (the TB bit, which protects nothing while BP = 0) and then replayed boot2's exact byte sequence on each part: ``` W25Q16JV W25Q16RV 06h + 01h 20 SR1 = 0x20 SR1 = 0x20 (1-byte 01h is accepted) 06h + 31h 00 SR2 = 0x00 SR2 = 0x04 (clear QE, set up the test) before SR1=0x20 SR2=0x00 SR1=0x20 SR2=0x04 06h + 01h 00 02 SR1=0x00 SR2=0x02 SR1=0x00 SR2=0x04 result both bytes applied SR1 applied, SR2 discarded ``` Both parts were restored to SR1 = 0x00, QE = 1 afterwards. Three things follow: the parts are **indistinguishable by JEDEC ID**; the RV does not reject `01h`, it **truncates it to one data byte**; and **`31h` is the only way to set QE that works on both**, so a single boot2 can cover JV and RV. ## Two problems in boot2_w25q080.S **1. The SR2 byte of the two-byte write is discarded.** ```asm movs r1, #CMD_WRITE_STATUS // 0x01 str r1, [r3, #SSI_DR0_OFFSET] movs r0, #0 // SR1 str r0, [r3, #SSI_DR0_OFFSET] str r2, [r3, #SSI_DR0_OFFSET] // SR2 = 0x02 (QE) ``` The RV accepts `01h`, applies the first data byte to SR1, and discards the second, so QE is never set. (The JV applies both, which is why the same boot2 has always worked for us until now.) boot2 does not check the result and proceeds to the `EBh` quad read, driving IO2/IO3 while the device — QE still clear — treats them as `/WP` and `/HOLD`. The board is dead from there. We have not scoped the bus, but the same `EBh` configuration works on the same part once QE is set: our patched boot2 differs only in using `31h`, and the RV board boots with it. The source already flags the risk of using this form: ``` // This isn't great because it will remove block protections. ``` On the RV that side effect happens while the intended effect does not: SR1 is overwritten with `0x00`, clearing the block protection bits, and QE is still not set. And because of problem 2 below the skip path is never taken on this part, so by the code path that write is reissued on every boot. **2. The skip check compares the whole byte.** ```asm movs r2, #SREG_DATA // 0x02 cmp r0, r2 beq skip_sreg_programming ``` Our RV reads SR2 bit 2 back as 1, so SR2 is `0x06` once QE is set and this never matches — boot2 repeats a non-volatile SR write on every boot, which is what makes the SR1 side effect above permanent rather than one-off. `tst r0, r2` fixes it, and is safer generally: any non-QE bit a part happens to set defeats a full-byte compare. We have both parts on the bench here, so if there is anything you would like measured before deciding, we can run it. Thanks for your work on the SDK. Best regards, Grace
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-raspberrypi/pico-sdk#3159
No description provided.