Forum Discussion

miscellaneous-mice's avatar
miscellaneous-mice
Icon for New Contributor rankNew Contributor
3 days ago

Reading S25FL256S OTP region via QSPI Indirect Transfer on Cyclone V HPS — data comes back incorrect

What I'm trying to do

I need to read OTP memory region of the S25FL256S flash using command 0x4B. The system normally runs in quad-SPI mode (0xEC, 4-byte address). Since the data I need is more than 8 bytes (STIG limit), I'm trying to use the indirect transfer path.

The 0x4B command requires:

  • Single-SPI (1-wire address and data)
  • 3-byte address
  • 1 fixed dummy byte (8 clocks) after address

Setup

The QSPI controller is initialized at startup using the standard Altera HAL:

alt_qspi_init(); // detects flash JEDEC ID, configures timing, sets up qspi_config struct
alt_qspi_enable(); // enables controller, sets quad mode (0xEC, 4-byte addr, LC=10b)

After this, normal quad-SPI reads via alt_qspi_read() works correctly.

What I'm doing

Reconfigure controller for 0x4B (OTP Read)

ALT_QSPI_DEV_INST_CONFIG_t read_cfg = {
        .op_code = 0x4B,
       .inst_type = ALT_QSPI_MODE_SINGLE,
       .addr_xfer_type = ALT_QSPI_MODE_SINGLE,
       .data_xfer_type = ALT_QSPI_MODE_SINGLE,
       .dummy_cycles = 8
};

alt_qspi_device_read_config_set(&read_cfg);

ALT_QSPI_DEV_SIZE_CONFIG_t size_cfg = {
       ...
       .addr_size = 2, // N+1 encoding → 3 bytes on wire
       .page_size = 256,
       ...
};

alt_qspi_device_size_config_set(&size_cfg);

Disable QUAD on the flash device itself via STIG

alt_qspi_read_register(0x35, &regs[1]); // read CR1
alt_qspi_read_register(0x05, &regs[0]); // read SR1
alt_qspi_device_wren();
regs[1] = (regs[1] & 0x3D) | 0x00 | 0x80; // QUAD=0, LC=10b
alt_qspi_stig_wr_cmd(0x01, 0, 2, (uint32_t*)regs, timeout);
alt_qspi_sr_wait_write(timeout);

Execute indirect read

// Internally: sets INDRDSTADDR, INDRDCNT, starts transfer,
// then CPU drains SRAM FIFO via ALT_QSPIDATA_ADDR
alt_qspi_read(dst, src, size);

Problem

The data returned by the indirect read is incorrect. A STIG-based read of the same region (using the same 0x4B command, 8 bytes at a time) returns the correct data. The indirect read returns wrong/shifted bytes. Hence do I need to configure anything else?

Result

correct value : 97C5995C5C1E9D5D7A00D4E6BD4ED53E
read value : FF97C5995C5C1E9D5D7A00D4E6BD4ED5

2 Replies

  • Archer_Altera's avatar
    Archer_Altera
    Icon for Occasional Contributor rankOccasional Contributor

    Hi miscellaneous-mice,

    Please try following 4 items on your side and see anyone can solve the issue.

    Item 1: Manually Increase Dummy Cycle Configuration

    Although the datasheet specifies 8 dummy clocks, the Cyclone V QSPI controller in indirect mode sometimes requires an additional cycle to compensate for internal delays.

    Try setting dummy cycles to 16 (equivalent to 2 bytes) to check alignment:

    read_cfg.dummy_cycles = 16; // Attempt to increase dummy cycles

    Forces the controller to wait longer before data arrival, ensuring the internal FIFO pointer points to the first valid data.

    Item 2: Verify and Force Address Length Configuration

    You set .addr_size = 2 (representing 3 bytes). Ensure the register is actually written after calling alt_qspi_device_size_config_set.

    More importantly, explicitly reset certain controller states before switching back to Quad mode or before each Indirect read.

    Attempt to execute a small STIG read (e.g., reading the status register) before performing an indirect read to “wake up” or synchronize the controller to Single SPI mode:

    // Perform a small STIG operation to synchronize status before indirect read

    uint8_t dummy_sr;

    alt_qspi_stig_rd_cmd(0x05, 0, 1, &dummy_sr, timeout); // Read Status Register

    // Then perform indirect read

    alt_qspi_read(dst, src, size);

    Item 3: Correcting Post-Read Offset (Software Workaround)

    If you confirm the hardware behavior is reading an extra 0xFF (this has appeared in errata for some Flash controllers, where Indirect Read always includes a leading dummy byte), you can handle it at the software level:

    Allocate 1 extra byte when allocating the buffer.

    After reading, shift the pointer back by 1 byte, or skip the first byte during copying.

    // Temporary buffer, allocate 1 extra byte

    uint8_t temp_buf[size + 1];

    // Execute read operation

    alt_qspi_read(temp_buf, src, size + 1); // Note: Length must match here, or read only size while ignoring the first byte

    // Data actually starts at temp_buf[1]

    memcpy(dst, temp_buf + 1, size);

    Verification method: If you observe temp_buf[0] always being 0xFF while temp_buf[1] contains correct data like 97, this confirms a fixed 1-byte leading offset in the controller.

    Item 4: Check alignment of INDRDCNT and INDRDSTADDR

    Cyclone V QSPI data register accesses sometimes require 4-byte alignment.

    If your dst pointer is not 4-byte aligned, or size is not a multiple of 4, the memcpy inside the HAL library may fail, or misalignment may occur when the hardware FIFO pops.

    Ensure the dst pointer is 4-byte aligned (((uint32_t)dst % 4) == 0).

    If size is small, try rounding up to a multiple of 4 for reads, then truncating to valid data.

     

    Archer_Altera

    • miscellaneous-mice's avatar
      miscellaneous-mice
      Icon for New Contributor rankNew Contributor

      Hi Archer_Altera,

      Thanks for the update, really appreciate the solution. Regarding the solutions
      1) I tried to manually increase the dummy cycles, but that wouldn't help as the number of dummy cycles required for OTP read (0x4B) is 8 dummy cycles, so data would be shifted if made to 16.
      2) The address width is 24-bit which is 3 bytes, which wouldn't work either as per the QSPI flash documentation for OTP Read. But as you specified running small STIG commands before executing indirect read does synchronize the controller to single SPI mode (but data still comes shifted by a single byte)
      3) This is a good work-around, but the issue arises when the read happens from address 0x0. As the data is shifted right by a single byte, byte[0] cannot be retreived (lowest byte of the read data). Hence the exact byte at address 0x0 cannot be retreived using this workaround.
      4) Byte alignment check is done in alt_qspi_read  itself, so this check would be reduntant.

       

      I have figured out the issue, it is related to configuration in devrd register.

      • The modebit is enabled by default on bootup for Quad read (0xEC) / write (0x34) configuration , therefore this adds extra cycles in read operation which shifts the data bits by 1 byte for Single mode commands like Fast read (0xB), OTP read (0x4B), etc.
      • Here even though the modebit value is specified as '0' on reset, and although my ARM application does not set this bit anywhere, I think this bit is getting configured from the uboot itself. There I temporarily disable this bit for doing OTP read (0x4B) using alt_qspi_mode_bit_disable.

       

       Hence the resulting code will look something like this :
      ... // Code specified previously for OTP Read configuration
      alt_qspi_mode_bit_disable();
      alt_qspi_read(dst, src, size);
      ...