raspberrypi / pico-sdk

BSD 3-Clause "New" or "Revised" License
3.55k stars 881 forks source link

QSPI on SSI hardware doesn't work for any frame format except standard #1100

Open khill25 opened 1 year ago

khill25 commented 1 year ago

I have PSRAM (part APS6404L-3SQR QSPI PSRAM) chips connected to the QSPI pins on a custom board with two RP2040s. A w25q128jv flash chip is also present. With the binary set to copy to ram on startup, reading and writing to the PSRAM array works fine as long as I'm not trying to configure the SSI hardware for quad spi. The PSRAM chips work with more or less the same commands as the flash chips but don't have a "continuation mode". Chip select is handled manually with a few gpio acting as a mux.

I wrote some code to try 288 permutations of frame format, wait cycle, trans type, and xip/read cmds.

There were a few permutations that had data that looked like it was reading actual data I loaded on the PSRAM chip but seemed to be bit shifted or out of phase. This is the output of one of the "close" attempts.

Configuration for ssi hardware of a "close" attempt: cmd:eb, frf:02, trans_type:01, dfs:31, waitCycles: 4, baudDivider:2

"close" Output:

0400800f 20343674 58804000 00b4af00 
a40c3cf0 000000fd ff2016ff 0089ad0f 
70000025 90000000 00111601 002eae00 
0089ad00 b0083c00 b0c80220 20082420 

Expected output:

40123780 0f000000 00040080 44140000 
648f45d7 5fb34552 00000000 00000000 
6f636950 74726143 54203436 20747365
004d4f52 00000000 00000000 00000000

Below is the code that should work to setup quad spi reads in Fast Read Quad mode for the PSRAM chips. Not using a continuation bit so will just issue the XIP command everytime. I left in the note I wrote based on the bootloader assembly that setup the flash chips.

ssi_hw->ssienr = 0;
// Clear sticky errors (clear-on-read)
(void)ssi_hw->sr;
(void)ssi_hw->icr;

// ssi->baudr = 2;
// ssi->baudr = 6;
ssi->baudr = 4;

ssi_hw->ctrlr0 = ((SSI_CTRLR0_SPI_FRF_VALUE_QUAD << SSI_CTRLR0_SPI_FRF_LSB) |   // 
                 (31 << SSI_CTRLR0_DFS_32_LSB) |    
                 (SSI_CTRLR0_TMOD_VALUE_EEPROM_READ << SSI_CTRLR0_TMOD_LSB)
);

ssi_hw->ctrlr1 = 0;         // NDF=0 (single 32b read)

/*
FROM THE BOOT LOADER ASSEMBLY
// Note that the INST_L field is used to select what XIP data gets pushed into
// the TX FIFO:
//      INST_L_0_BITS   {ADDR[23:0],XIP_CMD[7:0]}       Load "mode bits" into XIP_CMD
//      Anything else   {XIP_CMD[7:0],ADDR[23:0]}       Load SPI command into XIP_CMD
// So in this case SSI_SPI_CTRLR0_ADDR_L_LSB should be 8 = (8 bit command + 24 bit address)/4
*/

// 4 wait cycles for "fast read" 0x0B when in quad mode
// 8 wait cycles for "fast read" 0x0B when in spi mode / quad read
// 6 wait cycles for "fast read quad" 0xEB when in quad mode
ssi_hw->spi_ctrlr0 = ((0xEB << SSI_SPI_CTRLR0_XIP_CMD_LSB) |    //
                     (6 << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) |    /* Hi-Z dummy clocks following address + mode */
                     (8 << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Total number of address + mode bits, not using mode
                     (SSI_SPI_CTRLR0_INST_L_VALUE_8B << SSI_SPI_CTRLR0_INST_L_LSB) |    /* Instruction is 8 bits  */
                     (SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C2A << SSI_SPI_CTRLR0_TRANS_TYPE_LSB)    /* Command in standard spi and address both in FRF specified format */
);

// Slave selected when transfers in progress
ssi_hw->ser = 1;
// Re-enable
ssi_hw->ssienr = 1;

I have spent weeks playing with this and simply cannot get it working. My code is open source and available on demand (albeit very messy due to all my hair tearing). The data loads are simply too slow with standard read commands for my project. Theoretically this should all just work, as it does if you make your reads from the flash chip.

alastairpatrick commented 1 year ago

A difference between W25Q128JV (at least IQ option as used by Pico boards) and APS6404L-3SQR is that Quad Mode is enabled by default for the former and disabled by default for APS6404L-3SQR. To enable Quad Mode for APS6404L-3SQR, need to send it command 'h35 while its in SPI mode. I don't see that in your code snippet. It's probably just elsewhere in your code but thought I would check.

khill25 commented 1 year ago

The PSRAM has two modes, SPI and QPI. 0x0B Fast read and 0xEB Fast read quad are supported in both formats.

In SPI Column version (no need to issue the 0x35 command) 0x0B sends serial command, sends serial address (has 8 wait cycles) then gets serial data 0xEB sends serial command, sends quad address (has 6 wait cycles) then gets quad data.

The SSI_SPI_CTRLR0_TRANS_TYPE_LSB states

0x0 -> Command and address both in standard SPI frame format
0x1 -> Command in standard SPI format, address in format specified by FRF
0x2 -> Command and address both in format specified by FRF (e.g. Dual-SPI)

So in my example config above where I am trying to use fast read quad in spi mode, wants a serial command, quad address and then quad data as SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C2A = 0x1 no need to enter QPI mode.

I have also tried entering QPI mode (sending 0x35) command before setting up the SSI hardware and setting trans type to 0x2 but had no change.

From my testing it seems that SSI_SPI_CTRLR0_WAIT_CYCLES_LSB only has an effect when not in SSI_CTRLR0_SPI_FRF_VALUE_STD mode. SSI_SPI_CTRLR0_ADDR_L_LSB also seems to behave the same way.

khill25 commented 1 year ago

This configuration almost works.

ssi->ssienr = 0;

// Clear sticky errors (clear-on-read)
(void)ssi_hw->sr;
(void)ssi_hw->icr;

ssi_hw->baudr = 2;

ssi_hw->ctrlr0 = ((SSI_CTRLR0_SPI_FRF_VALUE_STD << SSI_CTRLR0_SPI_FRF_LSB) | 
(31 << SSI_CTRLR0_DFS_32_LSB) | 
(SSI_CTRLR0_TMOD_VALUE_EEPROM_READ << SSI_CTRLR0_TMOD_LSB) 
    );
ssi_hw->ctrlr1 = 0;         // NDF=0 (single 32b read)

ssi_hw->spi_ctrlr0 = ((0x0B << SSI_SPI_CTRLR0_XIP_CMD_LSB) |    
                        (8 << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) |
                        (SSI_SPI_CTRLR0_INST_L_VALUE_8B << SSI_SPI_CTRLR0_INST_L_LSB) | /*  */
                        (8 << SSI_SPI_CTRLR0_ADDR_L_LSB) |  /* Total number of address + mode bits */
                        (SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C1A << SSI_SPI_CTRLR0_TRANS_TYPE_LSB) /* Send Address in Quad I/O mode (and Command but that is zero bits long) */
    );

ssi_hw->ssienr = 1;

But the data seems to be dropping data. Lowering baud for this setup from 4 to 2, sees the data return which to me says it's not honoring the (8) wait cycles since the first 2 values of each 32bit value are gone. GOT:

12378000 00000000 04008000 14000000 8f45d700 b3455200

EXPECTED:

40123780 0f000000 00040080 44140000 648f45d7 5fb34552
deltabeard commented 1 year ago

Hello. It is by sheer coincidence that I came across this issue, as I'm also interested in using PSRAM as well as NOR flash on the XIP peripheral of the RP2040. Would you be able to link your schematic and source code for me to inspect please?

The PSRAM chips work with more or less the same commands as the flash chips but don't have a "continuation mode".

The PSRAM seems to have a continuation mode named "Linear Burst", which is its default mode of operation (Boundary Crossing is also default). This mode has a lower maximum frequency of 84MHz.

It's my understanding that the RP2040 and the PSRAM will only support "SPI" and "SPI Quad" communication with eachother, as the RP2040 does not support QPI, and the PSRAM does not support "DDR Mode" (RP2040 datasheet: 4.10.10.5. Dual Data-Rate (DDR) Support in SPI Operation). So make sure that the PSRAM is not placed in QPI mode.

khill25 commented 1 year ago

I was able to get this working but several things had to be done. The PSRAM will only work in quad mode as the hardware doesn't respect serial address, quad data. When using quad mode, you must configure the hardware for standard mode (03h) and send the enter quad mode command, then you can configure the qspi hardware for quad mode.

It's sadly not as fast as flash memory though since psram doesn't support the magical continuation mode that saves addresses bits for sequential reads.

You can checkout this commit c0bb956c026dfd070aa7d3031aa75c773880ee09 from my repo https://github.com/khill25/PicoCart64. In qpi_helper.c function void qspi_init_qspi(void) is where I configure the qspi hardware for the psram. Also keep in mind you want to use the CS pin connected to the qspi hadware.

Originally I was toggling it manually but rewired my hardware to use the CS pin to toggle my demux. Then all I had to do was set the address and use the CS as enable with the demux output connected to the CS pins of the chips.

deltabeard commented 1 year ago

It's great that you got it working!

I misunderstood the "Linear Burst (continuous)" mode mentioned in the PSRAM datasheet, which is just reading sequential data. Whereas the "continuous read mode" in the W25Q NOR datasheet means that you don't need to issue the Fast Quad Read command even after the CS pin has toggled and can just enter a new address immediately. This removes the overhead of issuing the instruction (8 clock cycles), but instead send a mode byte after the address in quad mode (only 2 clock cyles).

It looks like you're using the SPI Fast Read command 0x0B at https://github.com/khill25/PicoCart64/blob/c0bb956c026dfd070aa7d3031aa75c773880ee09/sw/picocart64_v2/qspi_helper.c#L372 . How come you aren't using the SPI Fast Quad Read 0xEB command instead?