From 30f83141bb392ddffa3cb8260ebfd4e5afe3af9b Mon Sep 17 00:00:00 2001 From: Omar Chebib Date: Fri, 15 Aug 2025 09:40:09 +0800 Subject: [PATCH] hw/misc: implement SPI data transfers for the PSRAM in both QPI and OPI. --- hw/misc/ssi_psram.c | 259 ++++++++++++++++++++++++++++++------ include/hw/misc/ssi_psram.h | 3 + 2 files changed, 221 insertions(+), 41 deletions(-) diff --git a/hw/misc/ssi_psram.c b/hw/misc/ssi_psram.c index 331f7eabb4..b88ac3e787 100644 --- a/hw/misc/ssi_psram.c +++ b/hw/misc/ssi_psram.c @@ -36,13 +36,22 @@ typedef enum PsramCMD { /* Octal PSRAM commands */ OCT_READ_REG = 0x4040, - OCT_WRITE_REG = 0xc0c0 + OCT_WRITE_REG = 0xc0c0, + + OCT_READ_SYNC = 0x0000, + OCT_WRITE_SYNC = 0x8080, + + OCT_READ_LINEAR = 0x2020, + OCT_WRITE_LINEAR = 0xA0A0, } PsramCMD; - -#define PSRAM_ID_MFG 0x0d +/* If the manufacturer ID is 0xd, all the 8MB PSRAMs are in fact 4MB underneath + * So use another manufacturer ID. */ +#define PSRAM_ID_MFG 0xff #define PSRAM_ID_KGD 0x5d +#define MR0_GET_RD_LATENCY(v) (((v) >> 2) & 0x7) + #define MR0_DRIVE_STRENGHT_HALF ((uint8_t)0b01 << 0) #define MR0_RD_LATENCY_CODE ((uint8_t)0b010 << 2) #define MR0_RD_LT_VARIABLE ((uint8_t)0b0 << 5) @@ -68,6 +77,13 @@ typedef enum PsramCMD { #define MR8_HYBRID_BURST ((uint8_t)0b1 << 2) #define MR8_RBX_READ_DISABLE ((uint8_t)0b0 << 3) +/** + * Since the DQS line is not emulated, we have to set these values according to + * the default configuration, which is also used by IDF + */ +#define OCT_PSRAM_RD_DUMMY 3 +#define OCT_PSRAM_WR_DUMMY 1 + #define FAKE_16MB_ID 0x6a #define FAKE_32MB_ID 0x8e @@ -91,11 +107,132 @@ static int get_eid_by_size(uint32_t size_mbytes) { } } + +/** + * @brief Check if the current command is a write command + */ +static inline bool psram_is_write_command(SsiPsramState *s) +{ + return s->command == WRITE || s->command == QUAD_WRITE || + s->command == OCT_WRITE_SYNC || s->command == OCT_WRITE_LINEAR; +} + +/** + * @brief Check if the current command is a read command + */ +static inline bool psram_is_read_command(SsiPsramState *s) +{ + return s->command == READ || s->command == FAST_READ || s->command == FAST_READ_QUAD || + s->command == OCT_READ_SYNC || s->command == OCT_READ_LINEAR; +} + +/** + * @brief Write data to the PSRAM's internal RAM. + * The address will be taken from the `addr` field added to the `offset`. + */ +static void psram_write_data(SsiPsramState *s, off_t offset, uint8_t byte) +{ + uint8_t* ptr = (uint8_t*) memory_region_get_ram_ptr(&s->data_mr); + const uint32_t size_bytes = s->size_mbytes * 1024 * 1024; + off_t destination = s->addr + offset; + if (destination < size_bytes) { + ptr[destination] = byte; + } +} + + +/** + * @brief Read data from the PSRAM's internal RAM. + * The address will be taken from the `addr` field added to the `offset`. + */ +static uint8_t psram_read_data(SsiPsramState *s, off_t offset) +{ + uint8_t* ptr = (uint8_t*) memory_region_get_ram_ptr(&s->data_mr); + const uint32_t size_bytes = s->size_mbytes * 1024 * 1024; + off_t destination = s->addr + offset; + if (destination < size_bytes) { + return ptr[destination]; + } + return 0; +} + +static PsramState psram_quad_write_idle(SsiPsramState *s, uint32_t value) +{ + PsramState next_state = s->state; + /* Idle state, check if a new command is sent */ + switch (value) { + case NOP: + break; + case READ_ID: + /* Should already be 0 but let's be safe */ + s->byte_count = 0; + next_state = ST_READ_ID; + break; + case WRITE: + case QUAD_WRITE: + case READ: + case FAST_READ: + case FAST_READ_QUAD: + s->command = value; + s->byte_count = 0; + next_state = ST_CMD_READY; + break; + default: +#if PSRAM_WARNING + warn_report("\x1b[31m[QUAD PSRAM] Unsupported command 0x%02x \x1b[0m\n", value); +#endif + break; + } + return next_state; +} + +/** + * @brief Simulate a byte write on the PSRAM, returns the next state the PSRAM should be + * put in AFTER performing the associated `read`. + */ +static PsramState psram_quad_write(SsiPsramState *s, uint32_t value) +{ + /* By default, the state doens't change */ + PsramState next_state = s->state; + switch (s->state) { + case ST_IDLE: + next_state = psram_quad_write_idle(s, value); + break; + case ST_CMD_READY: + /* Received the (valid) command */ + s->addr = value; + next_state = ST_CMD_ADDR0; + break; + case ST_CMD_ADDR0: + s->addr = (s->addr << 8) | value; + next_state = ST_CMD_ADDR1; + break; + case ST_CMD_ADDR1: + s->addr = (s->addr << 8) | value; + /* Only 3 bytes (24-bit) addresses on QSPI PSRAM */ + next_state = ST_PROCESSING; + break; + case ST_PROCESSING: + if (psram_is_write_command(s)) { + /* Only increment the byte_count if we are in a write command, else, the + * `psram_quad_read` function is responsible for incrementing it */ + psram_write_data(s, s->byte_count++, value); + } + break; + case ST_READ_ID: + default: + /* In transaction state, keep track of the number of bytes transferred */ + s->byte_count++; + break; + } + return next_state; +} + static uint32_t psram_quad_read(SsiPsramState *s) { uint32_t result = 0; - if (s->state == ST_PROCESSING) { + if (s->state == ST_READ_ID) { const uint8_t read_id_response[] = { /* 1 byte for the command itself, 3 bytes for the address */ 0x00, 0x00, 0x00, 0x00, @@ -107,31 +244,26 @@ static uint32_t psram_quad_read(SsiPsramState *s) if (index < ARRAY_SIZE(read_id_response)) { result = read_id_response[index]; } + } else if (s->state == ST_PROCESSING && psram_is_read_command(s)) { + result = psram_read_data(s, s->byte_count++); } return result; } -static void psram_quad_write(SsiPsramState *s, uint32_t value) + + +static bool psram_octal_supported_commands(uint32_t command) { - if (s->state == ST_IDLE) { - /* Idle state, check if a new command is sent */ - switch (value) { - case NOP: - break; - case READ_ID: - s->state = ST_PROCESSING; - /* Should already be 0 but let's be safe */ - s->byte_count = 0; - break; - default: -#if PSRAM_WARNING - warn_report("\x1b[31m[QUAD PSRAM] Unsupported command 0x%02x \x1b[0m\n", value); -#endif - break; - } - } else { - /* In transaction state, keep track of the number of bytes transferred */ - s->byte_count++; + switch (command) { + case OCT_READ_REG: + case OCT_WRITE_REG: + case OCT_READ_SYNC: + case OCT_WRITE_SYNC: + case OCT_READ_LINEAR: + case OCT_WRITE_LINEAR: + return true; + default: + return false; } } @@ -142,8 +274,7 @@ static uint32_t psram_octal_read(SsiPsramState *s) if (s->state == ST_PROCESSING && s->command == OCT_READ_REG) { // Odd read bytes correspond to the next register - switch (s->addr) - { + switch (s->addr & 0xff) { case 0: result = (s->byte_count % 2) ? s->mr1 : s->mr0; break; @@ -166,53 +297,80 @@ static uint32_t psram_octal_read(SsiPsramState *s) // Should not happen break; } + s->byte_count++; + } else if (s->state == ST_PROCESSING && psram_is_read_command(s)) { + result = psram_read_data(s, s->byte_count++); } return result; } -static void psram_octal_write(SsiPsramState *s, uint32_t value) +static PsramState psram_octal_write(SsiPsramState *s, uint32_t value) { + PsramState next_state = s->state; + switch (s->state) { case ST_IDLE: s->command = value; - s->state = ST_CMD_LSB; + next_state = ST_CMD_LSB; break; case ST_CMD_LSB: s->command |= value << 8; - if (s->command == OCT_READ_REG || s->command == OCT_WRITE_REG) { - s->state = ST_CMD_READY; + if (psram_octal_supported_commands(s->command)) { + next_state = ST_CMD_READY; } else { #if PSRAM_WARNING if (s->command != 0) { warn_report("\x1b[31m[OCT PSRAM] Unsupported command 0x%04x \x1b[0m\n", value); } #endif - s->state = ST_IDLE; + next_state = ST_IDLE; } break; case ST_CMD_READY: /* Received the (valid) command */ s->addr = value; - s->state = ST_CMD_ADDR0; + next_state = ST_CMD_ADDR0; break; case ST_CMD_ADDR0: s->addr = (s->addr << 8) | value; - s->state = ST_CMD_ADDR1; + next_state = ST_CMD_ADDR1; break; case ST_CMD_ADDR1: s->addr = (s->addr << 8) | value; - s->state = ST_CMD_ADDR2; + next_state = ST_CMD_ADDR2; break; case ST_CMD_ADDR2: s->addr = (s->addr << 8) | value; /* Address was received, process data */ - s->state = ST_PROCESSING; + /* Reading and writing registers don't introdue a dummy cycle requirement */ + if (s->command == OCT_READ_REG || psram_is_write_command(s)) { + /* Only a single dummy byte in write mode and registers read mode */ + next_state = ST_DUMMY_CYCLE; + s->dummy_cycles = OCT_PSRAM_WR_DUMMY; + } else if (s->command == OCT_WRITE_REG) { + next_state = ST_PROCESSING; + } else { + /* Read command */ + next_state = ST_DUMMY_CYCLE; + s->dummy_cycles = OCT_PSRAM_RD_DUMMY; + } + break; + case ST_DUMMY_CYCLE: + s->dummy_cycles--; + if (s->dummy_cycles == 0) { + next_state = ST_PROCESSING; + } break; case ST_PROCESSING: - if (s->command == OCT_WRITE_REG) { + /* Register write only takes into account the first byte, ignores the rest */ + if (s->command == OCT_WRITE_REG && s->byte_count == 0) { switch (s->addr) { case 0: + /* Check the latency bits */ + if (MR0_GET_RD_LATENCY(value) != 2) { + warn_report("\x1b[31m[OCT PSRAM] Read Latency %d unsupported\x1b[0m\n", MR0_GET_RD_LATENCY(value)); + } s->mr0 = value; break; case 4: @@ -222,13 +380,19 @@ static void psram_octal_write(SsiPsramState *s, uint32_t value) s->mr8 = value; break; } + s->byte_count++; + } else if (psram_is_write_command(s)) { + /* Only increment the byte_count if we are in a write command, else, the + * `read` function is responsible for incrementing it */ + psram_write_data(s, s->byte_count++, value); } - s->byte_count++; break; default: break; } + + return next_state; } @@ -265,13 +429,20 @@ static int psram_octal_get_density(uint32_t size_mbytes) static uint32_t psram_transfer(SSIPeripheral *dev, uint32_t value) { SsiPsramState *s = SSI_PSRAM(dev); + PsramState next_state; + uint32_t data; + if (s->is_octal) { - psram_octal_write(s, value); - return psram_octal_read(s); + next_state = psram_octal_write(s, value); + data = psram_octal_read(s); } else { - psram_quad_write(s, value); - return psram_quad_read(s); + next_state = psram_quad_write(s, value); + data = psram_quad_read(s); } + + /* Set the new state AFTER calling read */ + s->state = next_state; + return data; } static int psram_cs(SSIPeripheral *ss, bool select) @@ -279,10 +450,16 @@ static int psram_cs(SSIPeripheral *ss, bool select) SsiPsramState *s = SSI_PSRAM(ss); if (!select) { + /* If data were written to the cache via the MemoryRegion, we need to + * mark the area as dirty since the ESP target's `cache` also uses it. */ + if (s->state == ST_PROCESSING && psram_is_write_command(s)) { + memory_region_set_dirty(&s->data_mr, s->addr, s->byte_count); + } s->state = ST_IDLE; s->byte_count = 0; s->command = -1; s->addr = -1; + s->dummy_cycles = 0; } return 0; } diff --git a/include/hw/misc/ssi_psram.h b/include/hw/misc/ssi_psram.h index b4ec7b2195..b0dfdd43aa 100644 --- a/include/hw/misc/ssi_psram.h +++ b/include/hw/misc/ssi_psram.h @@ -14,7 +14,9 @@ typedef enum PsramState { ST_CMD_ADDR0, /* Received the 1st byte of the 32-bit address */ ST_CMD_ADDR1, /* Received the 2nd byte of the 32-bit address */ ST_CMD_ADDR2, /* Received the 3rd byte of the 32-bit address */ + ST_DUMMY_CYCLE, /* Dummy cycles between the address and the data */ ST_PROCESSING, /* 32-bit address received, sending/receiving data */ + ST_READ_ID, /* Received ID command */ } PsramState; @@ -25,6 +27,7 @@ typedef struct SsiPsramState { int command; int addr; int byte_count; + int dummy_cycles; bool is_octal; uint8_t mr0;