hw/misc: implement SPI data transfers for the PSRAM in both QPI and OPI.
This commit is contained in:
+218
-41
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user