hw/misc: implement SPI data transfers for the PSRAM in both QPI and OPI.

This commit is contained in:
Omar Chebib
2025-08-15 09:40:09 +08:00
parent 8066b0c9d4
commit 30f83141bb
2 changed files with 221 additions and 41 deletions
+218 -41
View File
@@ -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;
}
+3
View File
@@ -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;