summaryrefslogtreecommitdiffstats
path: root/qemu/hw/block/m25p80.c
diff options
context:
space:
mode:
Diffstat (limited to 'qemu/hw/block/m25p80.c')
-rw-r--r--qemu/hw/block/m25p80.c334
1 files changed, 314 insertions, 20 deletions
diff --git a/qemu/hw/block/m25p80.c b/qemu/hw/block/m25p80.c
index efc43dde6..906b71257 100644
--- a/qemu/hw/block/m25p80.c
+++ b/qemu/hw/block/m25p80.c
@@ -21,10 +21,12 @@
* with this program; if not, see <http://www.gnu.org/licenses/>.
*/
+#include "qemu/osdep.h"
#include "hw/hw.h"
#include "sysemu/block-backend.h"
#include "sysemu/blockdev.h"
-#include "hw/ssi.h"
+#include "hw/ssi/ssi.h"
+#include "qemu/bitops.h"
#ifndef M25P80_ERR_DEBUG
#define M25P80_ERR_DEBUG 0
@@ -45,7 +47,10 @@
/* set to allow the page program command to write 0s back to 1. Useful for
* modelling EEPROM with SPI flash command set
*/
-#define WR_1 0x100
+#define EEPROM 0x100
+
+/* 16 MiB max in 3 byte address mode */
+#define MAX_3BYTES_SIZE 0x1000000
typedef struct FlashPartInfo {
const char *part_name;
@@ -60,7 +65,7 @@ typedef struct FlashPartInfo {
uint32_t sector_size;
uint32_t n_sectors;
uint32_t page_size;
- uint8_t flags;
+ uint16_t flags;
} FlashPartInfo;
/* adapted from linux */
@@ -78,6 +83,30 @@ typedef struct FlashPartInfo {
#define JEDEC_WINBOND 0xEF
#define JEDEC_SPANSION 0x01
+/* Numonyx (Micron) Configuration register macros */
+#define VCFG_DUMMY 0x1
+#define VCFG_WRAP_SEQUENTIAL 0x2
+#define NVCFG_XIP_MODE_DISABLED (7 << 9)
+#define NVCFG_XIP_MODE_MASK (7 << 9)
+#define VCFG_XIP_MODE_ENABLED (1 << 3)
+#define CFG_DUMMY_CLK_LEN 4
+#define NVCFG_DUMMY_CLK_POS 12
+#define VCFG_DUMMY_CLK_POS 4
+#define EVCFG_OUT_DRIVER_STRENGHT_DEF 7
+#define EVCFG_VPP_ACCELERATOR (1 << 3)
+#define EVCFG_RESET_HOLD_ENABLED (1 << 4)
+#define NVCFG_DUAL_IO_MASK (1 << 2)
+#define EVCFG_DUAL_IO_ENABLED (1 << 6)
+#define NVCFG_QUAD_IO_MASK (1 << 3)
+#define EVCFG_QUAD_IO_ENABLED (1 << 7)
+#define NVCFG_4BYTE_ADDR_MASK (1 << 0)
+#define NVCFG_LOWER_SEGMENT_MASK (1 << 1)
+#define CFG_UPPER_128MB_SEG_ENABLED 0x3
+
+/* Numonyx (Micron) Flag Status Register macros */
+#define FSR_4BYTE_ADDR_MODE_ENABLED 0x1
+#define FSR_FLASH_READY (1 << 7)
+
static const FlashPartInfo known_devices[] = {
/* Atmel -- some are (confusingly) marketed as "DataFlash" */
{ INFO("at25fs010", 0x1f6601, 0, 32 << 10, 4, ER_4K) },
@@ -94,6 +123,12 @@ static const FlashPartInfo known_devices[] = {
{ INFO("at45db081d", 0x1f2500, 0, 64 << 10, 16, ER_4K) },
+ /* Atmel EEPROMS - it is assumed, that don't care bit in command
+ * is set to 0. Block protection is not supported.
+ */
+ { INFO("at25128a-nonjedec", 0x0, 0, 1, 131072, EEPROM) },
+ { INFO("at25256a-nonjedec", 0x0, 0, 1, 262144, EEPROM) },
+
/* EON -- en25xxx */
{ INFO("en25f32", 0x1c3116, 0, 64 << 10, 64, ER_4K) },
{ INFO("en25p32", 0x1c2016, 0, 64 << 10, 64, 0) },
@@ -163,6 +198,7 @@ static const FlashPartInfo known_devices[] = {
{ INFO("sst25wf010", 0xbf2502, 0, 64 << 10, 2, ER_4K) },
{ INFO("sst25wf020", 0xbf2503, 0, 64 << 10, 4, ER_4K) },
{ INFO("sst25wf040", 0xbf2504, 0, 64 << 10, 8, ER_4K) },
+ { INFO("sst25wf080", 0xbf2505, 0, 64 << 10, 16, ER_4K) },
/* ST Microelectronics -- newer production may have feature updates */
{ INFO("m25p05", 0x202010, 0, 32 << 10, 2, 0) },
@@ -204,8 +240,9 @@ static const FlashPartInfo known_devices[] = {
{ INFO("w25q80bl", 0xef4014, 0, 64 << 10, 16, ER_4K) },
{ INFO("w25q256", 0xef4019, 0, 64 << 10, 512, ER_4K) },
- /* Numonyx -- n25q128 */
{ INFO("n25q128", 0x20ba18, 0, 64 << 10, 256, 0) },
+ { INFO("n25q256a", 0x20ba19, 0, 64 << 10, 512, ER_4K) },
+ { INFO("n25q512a", 0x20ba20, 0, 64 << 10, 1024, ER_4K) },
};
typedef enum {
@@ -216,21 +253,49 @@ typedef enum {
WREN = 0x6,
JEDEC_READ = 0x9f,
BULK_ERASE = 0xc7,
+ READ_FSR = 0x70,
- READ = 0x3,
- FAST_READ = 0xb,
+ READ = 0x03,
+ READ4 = 0x13,
+ FAST_READ = 0x0b,
+ FAST_READ4 = 0x0c,
DOR = 0x3b,
+ DOR4 = 0x3c,
QOR = 0x6b,
+ QOR4 = 0x6c,
DIOR = 0xbb,
+ DIOR4 = 0xbc,
QIOR = 0xeb,
+ QIOR4 = 0xec,
- PP = 0x2,
+ PP = 0x02,
+ PP4 = 0x12,
DPP = 0xa2,
QPP = 0x32,
ERASE_4K = 0x20,
+ ERASE4_4K = 0x21,
ERASE_32K = 0x52,
ERASE_SECTOR = 0xd8,
+ ERASE4_SECTOR = 0xdc,
+
+ EN_4BYTE_ADDR = 0xB7,
+ EX_4BYTE_ADDR = 0xE9,
+
+ EXTEND_ADDR_READ = 0xC8,
+ EXTEND_ADDR_WRITE = 0xC5,
+
+ RESET_ENABLE = 0x66,
+ RESET_MEMORY = 0x99,
+
+ RNVCR = 0xB5,
+ WNVCR = 0xB1,
+
+ RVCR = 0x85,
+ WVCR = 0x81,
+
+ REVCR = 0x65,
+ WEVCR = 0x61,
} FlashCMD;
typedef enum {
@@ -244,8 +309,6 @@ typedef enum {
typedef struct Flash {
SSISlave parent_obj;
- uint32_t r;
-
BlockBackend *blk;
uint8_t *storage;
@@ -259,7 +322,13 @@ typedef struct Flash {
uint8_t needed_bytes;
uint8_t cmd_in_progress;
uint64_t cur_addr;
+ uint32_t nonvolatile_cfg;
+ uint32_t volatile_cfg;
+ uint32_t enh_volatile_cfg;
bool write_enable;
+ bool four_bytes_address_mode;
+ bool reset_enable;
+ uint8_t ear;
int64_t dirty_page;
@@ -331,6 +400,7 @@ static void flash_erase(Flash *s, int offset, FlashCMD cmd)
switch (cmd) {
case ERASE_4K:
+ case ERASE4_4K:
len = 4 << 10;
capa_to_assert = ER_4K;
break;
@@ -339,6 +409,7 @@ static void flash_erase(Flash *s, int offset, FlashCMD cmd)
capa_to_assert = ER_32K;
break;
case ERASE_SECTOR:
+ case ERASE4_SECTOR:
len = s->pi->sector_size;
break;
case BULK_ERASE:
@@ -385,7 +456,7 @@ void flash_write8(Flash *s, uint64_t addr, uint8_t data)
" -> %" PRIx8 "\n", addr, prev, data);
}
- if (s->pi->flags & WR_1) {
+ if (s->pi->flags & EEPROM) {
s->storage[s->cur_addr] = data;
} else {
s->storage[s->cur_addr] &= data;
@@ -395,11 +466,43 @@ void flash_write8(Flash *s, uint64_t addr, uint8_t data)
s->dirty_page = page;
}
+static inline int get_addr_length(Flash *s)
+{
+ /* check if eeprom is in use */
+ if (s->pi->flags == EEPROM) {
+ return 2;
+ }
+
+ switch (s->cmd_in_progress) {
+ case PP4:
+ case READ4:
+ case QIOR4:
+ case ERASE4_4K:
+ case ERASE4_SECTOR:
+ case FAST_READ4:
+ case DOR4:
+ case QOR4:
+ case DIOR4:
+ return 4;
+ default:
+ return s->four_bytes_address_mode ? 4 : 3;
+ }
+}
+
static void complete_collecting_data(Flash *s)
{
- s->cur_addr = s->data[0] << 16;
- s->cur_addr |= s->data[1] << 8;
- s->cur_addr |= s->data[2];
+ int i;
+
+ s->cur_addr = 0;
+
+ for (i = 0; i < get_addr_length(s); ++i) {
+ s->cur_addr <<= 8;
+ s->cur_addr |= s->data[i];
+ }
+
+ if (get_addr_length(s) == 3) {
+ s->cur_addr += (s->ear & 0x3) * MAX_3BYTES_SIZE;
+ }
s->state = STATE_IDLE;
@@ -407,19 +510,28 @@ static void complete_collecting_data(Flash *s)
case DPP:
case QPP:
case PP:
+ case PP4:
s->state = STATE_PAGE_PROGRAM;
break;
case READ:
+ case READ4:
case FAST_READ:
+ case FAST_READ4:
case DOR:
+ case DOR4:
case QOR:
+ case QOR4:
case DIOR:
+ case DIOR4:
case QIOR:
+ case QIOR4:
s->state = STATE_READ;
break;
case ERASE_4K:
+ case ERASE4_4K:
case ERASE_32K:
case ERASE_SECTOR:
+ case ERASE4_SECTOR:
flash_erase(s, s->cur_addr, s->cmd_in_progress);
break;
case WRSR:
@@ -427,49 +539,128 @@ static void complete_collecting_data(Flash *s)
s->write_enable = false;
}
break;
+ case EXTEND_ADDR_WRITE:
+ s->ear = s->data[0];
+ break;
+ case WNVCR:
+ s->nonvolatile_cfg = s->data[0] | (s->data[1] << 8);
+ break;
+ case WVCR:
+ s->volatile_cfg = s->data[0];
+ break;
+ case WEVCR:
+ s->enh_volatile_cfg = s->data[0];
+ break;
default:
break;
}
}
+static void reset_memory(Flash *s)
+{
+ s->cmd_in_progress = NOP;
+ s->cur_addr = 0;
+ s->ear = 0;
+ s->four_bytes_address_mode = false;
+ s->len = 0;
+ s->needed_bytes = 0;
+ s->pos = 0;
+ s->state = STATE_IDLE;
+ s->write_enable = false;
+ s->reset_enable = false;
+
+ if (((s->pi->jedec >> 16) & 0xFF) == JEDEC_NUMONYX) {
+ s->volatile_cfg = 0;
+ s->volatile_cfg |= VCFG_DUMMY;
+ s->volatile_cfg |= VCFG_WRAP_SEQUENTIAL;
+ if ((s->nonvolatile_cfg & NVCFG_XIP_MODE_MASK)
+ != NVCFG_XIP_MODE_DISABLED) {
+ s->volatile_cfg |= VCFG_XIP_MODE_ENABLED;
+ }
+ s->volatile_cfg |= deposit32(s->volatile_cfg,
+ VCFG_DUMMY_CLK_POS,
+ CFG_DUMMY_CLK_LEN,
+ extract32(s->nonvolatile_cfg,
+ NVCFG_DUMMY_CLK_POS,
+ CFG_DUMMY_CLK_LEN)
+ );
+
+ s->enh_volatile_cfg = 0;
+ s->enh_volatile_cfg |= EVCFG_OUT_DRIVER_STRENGHT_DEF;
+ s->enh_volatile_cfg |= EVCFG_VPP_ACCELERATOR;
+ s->enh_volatile_cfg |= EVCFG_RESET_HOLD_ENABLED;
+ if (s->nonvolatile_cfg & NVCFG_DUAL_IO_MASK) {
+ s->enh_volatile_cfg |= EVCFG_DUAL_IO_ENABLED;
+ }
+ if (s->nonvolatile_cfg & NVCFG_QUAD_IO_MASK) {
+ s->enh_volatile_cfg |= EVCFG_QUAD_IO_ENABLED;
+ }
+ if (!(s->nonvolatile_cfg & NVCFG_4BYTE_ADDR_MASK)) {
+ s->four_bytes_address_mode = true;
+ }
+ if (!(s->nonvolatile_cfg & NVCFG_LOWER_SEGMENT_MASK)) {
+ s->ear = CFG_UPPER_128MB_SEG_ENABLED;
+ }
+ }
+
+ DB_PRINT_L(0, "Reset done.\n");
+}
+
static void decode_new_cmd(Flash *s, uint32_t value)
{
s->cmd_in_progress = value;
DB_PRINT_L(0, "decoded new command:%x\n", value);
+ if (value != RESET_MEMORY) {
+ s->reset_enable = false;
+ }
+
switch (value) {
case ERASE_4K:
+ case ERASE4_4K:
case ERASE_32K:
case ERASE_SECTOR:
+ case ERASE4_SECTOR:
case READ:
+ case READ4:
case DPP:
case QPP:
case PP:
- s->needed_bytes = 3;
+ case PP4:
+ s->needed_bytes = get_addr_length(s);
s->pos = 0;
s->len = 0;
s->state = STATE_COLLECTING_DATA;
break;
case FAST_READ:
+ case FAST_READ4:
case DOR:
+ case DOR4:
case QOR:
- s->needed_bytes = 4;
+ case QOR4:
+ s->needed_bytes = get_addr_length(s);
+ if (((s->pi->jedec >> 16) & 0xFF) == JEDEC_NUMONYX) {
+ /* Dummy cycles modeled with bytes writes instead of bits */
+ s->needed_bytes += extract32(s->volatile_cfg, 4, 4);
+ }
s->pos = 0;
s->len = 0;
s->state = STATE_COLLECTING_DATA;
break;
case DIOR:
+ case DIOR4:
switch ((s->pi->jedec >> 16) & 0xFF) {
case JEDEC_WINBOND:
case JEDEC_SPANSION:
s->needed_bytes = 4;
break;
- case JEDEC_NUMONYX:
default:
- s->needed_bytes = 5;
+ s->needed_bytes = get_addr_length(s);
+ /* Dummy cycles modeled with bytes writes instead of bits */
+ s->needed_bytes += extract32(s->volatile_cfg, 4, 4);
}
s->pos = 0;
s->len = 0;
@@ -477,14 +668,16 @@ static void decode_new_cmd(Flash *s, uint32_t value)
break;
case QIOR:
+ case QIOR4:
switch ((s->pi->jedec >> 16) & 0xFF) {
case JEDEC_WINBOND:
case JEDEC_SPANSION:
s->needed_bytes = 6;
break;
- case JEDEC_NUMONYX:
default:
- s->needed_bytes = 8;
+ s->needed_bytes = get_addr_length(s);
+ /* Dummy cycles modeled with bytes writes instead of bits */
+ s->needed_bytes += extract32(s->volatile_cfg, 4, 4);
}
s->pos = 0;
s->len = 0;
@@ -514,6 +707,16 @@ static void decode_new_cmd(Flash *s, uint32_t value)
s->state = STATE_READING_DATA;
break;
+ case READ_FSR:
+ s->data[0] = FSR_FLASH_READY;
+ if (s->four_bytes_address_mode) {
+ s->data[0] |= FSR_4BYTE_ADDR_MODE_ENABLED;
+ }
+ s->pos = 0;
+ s->len = 1;
+ s->state = STATE_READING_DATA;
+ break;
+
case JEDEC_READ:
DB_PRINT_L(0, "populated jedec code\n");
s->data[0] = (s->pi->jedec >> 16) & 0xff;
@@ -541,6 +744,77 @@ static void decode_new_cmd(Flash *s, uint32_t value)
break;
case NOP:
break;
+ case EN_4BYTE_ADDR:
+ s->four_bytes_address_mode = true;
+ break;
+ case EX_4BYTE_ADDR:
+ s->four_bytes_address_mode = false;
+ break;
+ case EXTEND_ADDR_READ:
+ s->data[0] = s->ear;
+ s->pos = 0;
+ s->len = 1;
+ s->state = STATE_READING_DATA;
+ break;
+ case EXTEND_ADDR_WRITE:
+ if (s->write_enable) {
+ s->needed_bytes = 1;
+ s->pos = 0;
+ s->len = 0;
+ s->state = STATE_COLLECTING_DATA;
+ }
+ break;
+ case RNVCR:
+ s->data[0] = s->nonvolatile_cfg & 0xFF;
+ s->data[1] = (s->nonvolatile_cfg >> 8) & 0xFF;
+ s->pos = 0;
+ s->len = 2;
+ s->state = STATE_READING_DATA;
+ break;
+ case WNVCR:
+ if (s->write_enable) {
+ s->needed_bytes = 2;
+ s->pos = 0;
+ s->len = 0;
+ s->state = STATE_COLLECTING_DATA;
+ }
+ break;
+ case RVCR:
+ s->data[0] = s->volatile_cfg & 0xFF;
+ s->pos = 0;
+ s->len = 1;
+ s->state = STATE_READING_DATA;
+ break;
+ case WVCR:
+ if (s->write_enable) {
+ s->needed_bytes = 1;
+ s->pos = 0;
+ s->len = 0;
+ s->state = STATE_COLLECTING_DATA;
+ }
+ break;
+ case REVCR:
+ s->data[0] = s->enh_volatile_cfg & 0xFF;
+ s->pos = 0;
+ s->len = 1;
+ s->state = STATE_READING_DATA;
+ break;
+ case WEVCR:
+ if (s->write_enable) {
+ s->needed_bytes = 1;
+ s->pos = 0;
+ s->len = 0;
+ s->state = STATE_COLLECTING_DATA;
+ }
+ break;
+ case RESET_ENABLE:
+ s->reset_enable = true;
+ break;
+ case RESET_MEMORY:
+ if (s->reset_enable) {
+ reset_memory(s);
+ }
+ break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "M25P80: Unknown cmd %x\n", value);
break;
@@ -647,14 +921,26 @@ static int m25p80_init(SSISlave *ss)
return 0;
}
+static void m25p80_reset(DeviceState *d)
+{
+ Flash *s = M25P80(d);
+
+ reset_memory(s);
+}
+
static void m25p80_pre_save(void *opaque)
{
flash_sync_dirty((Flash *)opaque, -1);
}
+static Property m25p80_properties[] = {
+ DEFINE_PROP_UINT32("nonvolatile-cfg", Flash, nonvolatile_cfg, 0x8FFF),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
static const VMStateDescription vmstate_m25p80 = {
.name = "xilinx_spi",
- .version_id = 1,
+ .version_id = 2,
.minimum_version_id = 1,
.pre_save = m25p80_pre_save,
.fields = (VMStateField[]) {
@@ -666,6 +952,12 @@ static const VMStateDescription vmstate_m25p80 = {
VMSTATE_UINT8(cmd_in_progress, Flash),
VMSTATE_UINT64(cur_addr, Flash),
VMSTATE_BOOL(write_enable, Flash),
+ VMSTATE_BOOL_V(reset_enable, Flash, 2),
+ VMSTATE_UINT8_V(ear, Flash, 2),
+ VMSTATE_BOOL_V(four_bytes_address_mode, Flash, 2),
+ VMSTATE_UINT32_V(nonvolatile_cfg, Flash, 2),
+ VMSTATE_UINT32_V(volatile_cfg, Flash, 2),
+ VMSTATE_UINT32_V(enh_volatile_cfg, Flash, 2),
VMSTATE_END_OF_LIST()
}
};
@@ -681,6 +973,8 @@ static void m25p80_class_init(ObjectClass *klass, void *data)
k->set_cs = m25p80_cs;
k->cs_polarity = SSI_CS_LOW;
dc->vmsd = &vmstate_m25p80;
+ dc->props = m25p80_properties;
+ dc->reset = m25p80_reset;
mc->pi = data;
}