a7fd6915d8
Currently, M25P80 uses an object property to differentiate between flash parts. Changed this over to use QOM sub-classes - the actual names of the different parts are used to create a set of dynamic classes which passes the part info as class data. The object no longer needs to search the known_devices table for itself, instead it just gets its info from its own class. Kept the intermediate class definition private to m25p80.c for the moment, as the expectation is parts will only be added as new entries in the table. We can factor out the TYPE_M25P80 abstraction into a header on a demand basis. Signed-off-by: Peter Crosthwaite <peter.crosthwaite@xilinx.com> Message-id: e24e156d-ff96-4901-997a-e31178b08bee@VA3EHSMHS021.ehs.local Reviewed-by: Andreas Färber <afaerber@suse.de> Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
673 lines
19 KiB
C
673 lines
19 KiB
C
/*
|
|
* ST M25P80 emulator. Emulate all SPI flash devices based on the m25p80 command
|
|
* set. Known devices table current as of Jun/2012 and taken from linux.
|
|
* See drivers/mtd/devices/m25p80.c.
|
|
*
|
|
* Copyright (C) 2011 Edgar E. Iglesias <edgar.iglesias@gmail.com>
|
|
* Copyright (C) 2012 Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
|
|
* Copyright (C) 2012 PetaLogix
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 or
|
|
* (at your option) a later version of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "hw.h"
|
|
#include "sysemu/blockdev.h"
|
|
#include "ssi.h"
|
|
#include "devices.h"
|
|
|
|
#ifdef M25P80_ERR_DEBUG
|
|
#define DB_PRINT(...) do { \
|
|
fprintf(stderr, ": %s: ", __func__); \
|
|
fprintf(stderr, ## __VA_ARGS__); \
|
|
} while (0);
|
|
#else
|
|
#define DB_PRINT(...)
|
|
#endif
|
|
|
|
/* Fields for FlashPartInfo->flags */
|
|
|
|
/* erase capabilities */
|
|
#define ER_4K 1
|
|
#define ER_32K 2
|
|
/* 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
|
|
|
|
typedef struct FlashPartInfo {
|
|
const char *part_name;
|
|
/* jedec code. (jedec >> 16) & 0xff is the 1st byte, >> 8 the 2nd etc */
|
|
uint32_t jedec;
|
|
/* extended jedec code */
|
|
uint16_t ext_jedec;
|
|
/* there is confusion between manufacturers as to what a sector is. In this
|
|
* device model, a "sector" is the size that is erased by the ERASE_SECTOR
|
|
* command (opcode 0xd8).
|
|
*/
|
|
uint32_t sector_size;
|
|
uint32_t n_sectors;
|
|
uint32_t page_size;
|
|
uint8_t flags;
|
|
} FlashPartInfo;
|
|
|
|
/* adapted from linux */
|
|
|
|
#define INFO(_part_name, _jedec, _ext_jedec, _sector_size, _n_sectors, _flags)\
|
|
.part_name = (_part_name),\
|
|
.jedec = (_jedec),\
|
|
.ext_jedec = (_ext_jedec),\
|
|
.sector_size = (_sector_size),\
|
|
.n_sectors = (_n_sectors),\
|
|
.page_size = 256,\
|
|
.flags = (_flags),\
|
|
|
|
#define JEDEC_NUMONYX 0x20
|
|
#define JEDEC_WINBOND 0xEF
|
|
#define JEDEC_SPANSION 0x01
|
|
|
|
static const FlashPartInfo known_devices[] = {
|
|
/* Atmel -- some are (confusingly) marketed as "DataFlash" */
|
|
{ INFO("at25fs010", 0x1f6601, 0, 32 << 10, 4, ER_4K) },
|
|
{ INFO("at25fs040", 0x1f6604, 0, 64 << 10, 8, ER_4K) },
|
|
|
|
{ INFO("at25df041a", 0x1f4401, 0, 64 << 10, 8, ER_4K) },
|
|
{ INFO("at25df321a", 0x1f4701, 0, 64 << 10, 64, ER_4K) },
|
|
{ INFO("at25df641", 0x1f4800, 0, 64 << 10, 128, ER_4K) },
|
|
|
|
{ INFO("at26f004", 0x1f0400, 0, 64 << 10, 8, ER_4K) },
|
|
{ INFO("at26df081a", 0x1f4501, 0, 64 << 10, 16, ER_4K) },
|
|
{ INFO("at26df161a", 0x1f4601, 0, 64 << 10, 32, ER_4K) },
|
|
{ INFO("at26df321", 0x1f4700, 0, 64 << 10, 64, ER_4K) },
|
|
|
|
/* EON -- en25xxx */
|
|
{ INFO("en25f32", 0x1c3116, 0, 64 << 10, 64, ER_4K) },
|
|
{ INFO("en25p32", 0x1c2016, 0, 64 << 10, 64, 0) },
|
|
{ INFO("en25q32b", 0x1c3016, 0, 64 << 10, 64, 0) },
|
|
{ INFO("en25p64", 0x1c2017, 0, 64 << 10, 128, 0) },
|
|
|
|
/* Intel/Numonyx -- xxxs33b */
|
|
{ INFO("160s33b", 0x898911, 0, 64 << 10, 32, 0) },
|
|
{ INFO("320s33b", 0x898912, 0, 64 << 10, 64, 0) },
|
|
{ INFO("640s33b", 0x898913, 0, 64 << 10, 128, 0) },
|
|
|
|
/* Macronix */
|
|
{ INFO("mx25l4005a", 0xc22013, 0, 64 << 10, 8, ER_4K) },
|
|
{ INFO("mx25l8005", 0xc22014, 0, 64 << 10, 16, 0) },
|
|
{ INFO("mx25l1606e", 0xc22015, 0, 64 << 10, 32, ER_4K) },
|
|
{ INFO("mx25l3205d", 0xc22016, 0, 64 << 10, 64, 0) },
|
|
{ INFO("mx25l6405d", 0xc22017, 0, 64 << 10, 128, 0) },
|
|
{ INFO("mx25l12805d", 0xc22018, 0, 64 << 10, 256, 0) },
|
|
{ INFO("mx25l12855e", 0xc22618, 0, 64 << 10, 256, 0) },
|
|
{ INFO("mx25l25635e", 0xc22019, 0, 64 << 10, 512, 0) },
|
|
{ INFO("mx25l25655e", 0xc22619, 0, 64 << 10, 512, 0) },
|
|
|
|
/* Spansion -- single (large) sector size only, at least
|
|
* for the chips listed here (without boot sectors).
|
|
*/
|
|
{ INFO("s25sl004a", 0x010212, 0, 64 << 10, 8, 0) },
|
|
{ INFO("s25sl008a", 0x010213, 0, 64 << 10, 16, 0) },
|
|
{ INFO("s25sl016a", 0x010214, 0, 64 << 10, 32, 0) },
|
|
{ INFO("s25sl032a", 0x010215, 0, 64 << 10, 64, 0) },
|
|
{ INFO("s25sl032p", 0x010215, 0x4d00, 64 << 10, 64, ER_4K) },
|
|
{ INFO("s25sl064a", 0x010216, 0, 64 << 10, 128, 0) },
|
|
{ INFO("s25fl256s0", 0x010219, 0x4d00, 256 << 10, 128, 0) },
|
|
{ INFO("s25fl256s1", 0x010219, 0x4d01, 64 << 10, 512, 0) },
|
|
{ INFO("s25fl512s", 0x010220, 0x4d00, 256 << 10, 256, 0) },
|
|
{ INFO("s70fl01gs", 0x010221, 0x4d00, 256 << 10, 256, 0) },
|
|
{ INFO("s25sl12800", 0x012018, 0x0300, 256 << 10, 64, 0) },
|
|
{ INFO("s25sl12801", 0x012018, 0x0301, 64 << 10, 256, 0) },
|
|
{ INFO("s25fl129p0", 0x012018, 0x4d00, 256 << 10, 64, 0) },
|
|
{ INFO("s25fl129p1", 0x012018, 0x4d01, 64 << 10, 256, 0) },
|
|
{ INFO("s25fl016k", 0xef4015, 0, 64 << 10, 32, ER_4K | ER_32K) },
|
|
{ INFO("s25fl064k", 0xef4017, 0, 64 << 10, 128, ER_4K | ER_32K) },
|
|
|
|
/* SST -- large erase sizes are "overlays", "sectors" are 4<< 10 */
|
|
{ INFO("sst25vf040b", 0xbf258d, 0, 64 << 10, 8, ER_4K) },
|
|
{ INFO("sst25vf080b", 0xbf258e, 0, 64 << 10, 16, ER_4K) },
|
|
{ INFO("sst25vf016b", 0xbf2541, 0, 64 << 10, 32, ER_4K) },
|
|
{ INFO("sst25vf032b", 0xbf254a, 0, 64 << 10, 64, ER_4K) },
|
|
{ INFO("sst25wf512", 0xbf2501, 0, 64 << 10, 1, ER_4K) },
|
|
{ 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) },
|
|
|
|
/* ST Microelectronics -- newer production may have feature updates */
|
|
{ INFO("m25p05", 0x202010, 0, 32 << 10, 2, 0) },
|
|
{ INFO("m25p10", 0x202011, 0, 32 << 10, 4, 0) },
|
|
{ INFO("m25p20", 0x202012, 0, 64 << 10, 4, 0) },
|
|
{ INFO("m25p40", 0x202013, 0, 64 << 10, 8, 0) },
|
|
{ INFO("m25p80", 0x202014, 0, 64 << 10, 16, 0) },
|
|
{ INFO("m25p16", 0x202015, 0, 64 << 10, 32, 0) },
|
|
{ INFO("m25p32", 0x202016, 0, 64 << 10, 64, 0) },
|
|
{ INFO("m25p64", 0x202017, 0, 64 << 10, 128, 0) },
|
|
{ INFO("m25p128", 0x202018, 0, 256 << 10, 64, 0) },
|
|
|
|
{ INFO("m45pe10", 0x204011, 0, 64 << 10, 2, 0) },
|
|
{ INFO("m45pe80", 0x204014, 0, 64 << 10, 16, 0) },
|
|
{ INFO("m45pe16", 0x204015, 0, 64 << 10, 32, 0) },
|
|
|
|
{ INFO("m25pe80", 0x208014, 0, 64 << 10, 16, 0) },
|
|
{ INFO("m25pe16", 0x208015, 0, 64 << 10, 32, ER_4K) },
|
|
|
|
{ INFO("m25px32", 0x207116, 0, 64 << 10, 64, ER_4K) },
|
|
{ INFO("m25px32-s0", 0x207316, 0, 64 << 10, 64, ER_4K) },
|
|
{ INFO("m25px32-s1", 0x206316, 0, 64 << 10, 64, ER_4K) },
|
|
{ INFO("m25px64", 0x207117, 0, 64 << 10, 128, 0) },
|
|
|
|
/* Winbond -- w25x "blocks" are 64k, "sectors" are 4KiB */
|
|
{ INFO("w25x10", 0xef3011, 0, 64 << 10, 2, ER_4K) },
|
|
{ INFO("w25x20", 0xef3012, 0, 64 << 10, 4, ER_4K) },
|
|
{ INFO("w25x40", 0xef3013, 0, 64 << 10, 8, ER_4K) },
|
|
{ INFO("w25x80", 0xef3014, 0, 64 << 10, 16, ER_4K) },
|
|
{ INFO("w25x16", 0xef3015, 0, 64 << 10, 32, ER_4K) },
|
|
{ INFO("w25x32", 0xef3016, 0, 64 << 10, 64, ER_4K) },
|
|
{ INFO("w25q32", 0xef4016, 0, 64 << 10, 64, ER_4K) },
|
|
{ INFO("w25x64", 0xef3017, 0, 64 << 10, 128, ER_4K) },
|
|
{ INFO("w25q64", 0xef4017, 0, 64 << 10, 128, ER_4K) },
|
|
|
|
/* Numonyx -- n25q128 */
|
|
{ INFO("n25q128", 0x20ba18, 0, 64 << 10, 256, 0) },
|
|
};
|
|
|
|
typedef enum {
|
|
NOP = 0,
|
|
WRSR = 0x1,
|
|
WRDI = 0x4,
|
|
RDSR = 0x5,
|
|
WREN = 0x6,
|
|
JEDEC_READ = 0x9f,
|
|
BULK_ERASE = 0xc7,
|
|
|
|
READ = 0x3,
|
|
FAST_READ = 0xb,
|
|
DOR = 0x3b,
|
|
QOR = 0x6b,
|
|
DIOR = 0xbb,
|
|
QIOR = 0xeb,
|
|
|
|
PP = 0x2,
|
|
DPP = 0xa2,
|
|
QPP = 0x32,
|
|
|
|
ERASE_4K = 0x20,
|
|
ERASE_32K = 0x52,
|
|
ERASE_SECTOR = 0xd8,
|
|
} FlashCMD;
|
|
|
|
typedef enum {
|
|
STATE_IDLE,
|
|
STATE_PAGE_PROGRAM,
|
|
STATE_READ,
|
|
STATE_COLLECTING_DATA,
|
|
STATE_READING_DATA,
|
|
} CMDState;
|
|
|
|
typedef struct Flash {
|
|
SSISlave ssidev;
|
|
uint32_t r;
|
|
|
|
BlockDriverState *bdrv;
|
|
|
|
uint8_t *storage;
|
|
uint32_t size;
|
|
int page_size;
|
|
|
|
uint8_t state;
|
|
uint8_t data[16];
|
|
uint32_t len;
|
|
uint32_t pos;
|
|
uint8_t needed_bytes;
|
|
uint8_t cmd_in_progress;
|
|
uint64_t cur_addr;
|
|
bool write_enable;
|
|
|
|
int64_t dirty_page;
|
|
|
|
const FlashPartInfo *pi;
|
|
|
|
} Flash;
|
|
|
|
typedef struct M25P80Class {
|
|
SSISlaveClass parent_class;
|
|
FlashPartInfo *pi;
|
|
} M25P80Class;
|
|
|
|
#define TYPE_M25P80 "m25p80-generic"
|
|
#define M25P80(obj) \
|
|
OBJECT_CHECK(Flash, (obj), TYPE_M25P80)
|
|
#define M25P80_CLASS(klass) \
|
|
OBJECT_CLASS_CHECK(M25P80Class, (klass), TYPE_M25P80)
|
|
#define M25P80_GET_CLASS(obj) \
|
|
OBJECT_GET_CLASS(M25P80Class, (obj), TYPE_M25P80)
|
|
|
|
static void bdrv_sync_complete(void *opaque, int ret)
|
|
{
|
|
/* do nothing. Masters do not directly interact with the backing store,
|
|
* only the working copy so no mutexing required.
|
|
*/
|
|
}
|
|
|
|
static void flash_sync_page(Flash *s, int page)
|
|
{
|
|
if (s->bdrv) {
|
|
int bdrv_sector, nb_sectors;
|
|
QEMUIOVector iov;
|
|
|
|
bdrv_sector = (page * s->pi->page_size) / BDRV_SECTOR_SIZE;
|
|
nb_sectors = DIV_ROUND_UP(s->pi->page_size, BDRV_SECTOR_SIZE);
|
|
qemu_iovec_init(&iov, 1);
|
|
qemu_iovec_add(&iov, s->storage + bdrv_sector * BDRV_SECTOR_SIZE,
|
|
nb_sectors * BDRV_SECTOR_SIZE);
|
|
bdrv_aio_writev(s->bdrv, bdrv_sector, &iov, nb_sectors,
|
|
bdrv_sync_complete, NULL);
|
|
}
|
|
}
|
|
|
|
static inline void flash_sync_area(Flash *s, int64_t off, int64_t len)
|
|
{
|
|
int64_t start, end, nb_sectors;
|
|
QEMUIOVector iov;
|
|
|
|
if (!s->bdrv) {
|
|
return;
|
|
}
|
|
|
|
assert(!(len % BDRV_SECTOR_SIZE));
|
|
start = off / BDRV_SECTOR_SIZE;
|
|
end = (off + len) / BDRV_SECTOR_SIZE;
|
|
nb_sectors = end - start;
|
|
qemu_iovec_init(&iov, 1);
|
|
qemu_iovec_add(&iov, s->storage + (start * BDRV_SECTOR_SIZE),
|
|
nb_sectors * BDRV_SECTOR_SIZE);
|
|
bdrv_aio_writev(s->bdrv, start, &iov, nb_sectors, bdrv_sync_complete, NULL);
|
|
}
|
|
|
|
static void flash_erase(Flash *s, int offset, FlashCMD cmd)
|
|
{
|
|
uint32_t len;
|
|
uint8_t capa_to_assert = 0;
|
|
|
|
switch (cmd) {
|
|
case ERASE_4K:
|
|
len = 4 << 10;
|
|
capa_to_assert = ER_4K;
|
|
break;
|
|
case ERASE_32K:
|
|
len = 32 << 10;
|
|
capa_to_assert = ER_32K;
|
|
break;
|
|
case ERASE_SECTOR:
|
|
len = s->pi->sector_size;
|
|
break;
|
|
case BULK_ERASE:
|
|
len = s->size;
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
|
|
DB_PRINT("offset = %#x, len = %d\n", offset, len);
|
|
if ((s->pi->flags & capa_to_assert) != capa_to_assert) {
|
|
hw_error("m25p80: %dk erase size not supported by device\n", len);
|
|
}
|
|
|
|
if (!s->write_enable) {
|
|
DB_PRINT("erase with write protect!\n");
|
|
return;
|
|
}
|
|
memset(s->storage + offset, 0xff, len);
|
|
flash_sync_area(s, offset, len);
|
|
}
|
|
|
|
static inline void flash_sync_dirty(Flash *s, int64_t newpage)
|
|
{
|
|
if (s->dirty_page >= 0 && s->dirty_page != newpage) {
|
|
flash_sync_page(s, s->dirty_page);
|
|
s->dirty_page = newpage;
|
|
}
|
|
}
|
|
|
|
static inline
|
|
void flash_write8(Flash *s, uint64_t addr, uint8_t data)
|
|
{
|
|
int64_t page = addr / s->pi->page_size;
|
|
uint8_t prev = s->storage[s->cur_addr];
|
|
|
|
if (!s->write_enable) {
|
|
DB_PRINT("write with write protect!\n");
|
|
}
|
|
|
|
if ((prev ^ data) & data) {
|
|
DB_PRINT("programming zero to one! addr=%lx %x -> %x\n",
|
|
addr, prev, data);
|
|
}
|
|
|
|
if (s->pi->flags & WR_1) {
|
|
s->storage[s->cur_addr] = data;
|
|
} else {
|
|
s->storage[s->cur_addr] &= data;
|
|
}
|
|
|
|
flash_sync_dirty(s, page);
|
|
s->dirty_page = page;
|
|
}
|
|
|
|
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];
|
|
|
|
s->state = STATE_IDLE;
|
|
|
|
switch (s->cmd_in_progress) {
|
|
case DPP:
|
|
case QPP:
|
|
case PP:
|
|
s->state = STATE_PAGE_PROGRAM;
|
|
break;
|
|
case READ:
|
|
case FAST_READ:
|
|
case DOR:
|
|
case QOR:
|
|
case DIOR:
|
|
case QIOR:
|
|
s->state = STATE_READ;
|
|
break;
|
|
case ERASE_4K:
|
|
case ERASE_32K:
|
|
case ERASE_SECTOR:
|
|
flash_erase(s, s->cur_addr, s->cmd_in_progress);
|
|
break;
|
|
case WRSR:
|
|
if (s->write_enable) {
|
|
s->write_enable = false;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void decode_new_cmd(Flash *s, uint32_t value)
|
|
{
|
|
s->cmd_in_progress = value;
|
|
DB_PRINT("decoded new command:%x\n", value);
|
|
|
|
switch (value) {
|
|
|
|
case ERASE_4K:
|
|
case ERASE_32K:
|
|
case ERASE_SECTOR:
|
|
case READ:
|
|
case DPP:
|
|
case QPP:
|
|
case PP:
|
|
s->needed_bytes = 3;
|
|
s->pos = 0;
|
|
s->len = 0;
|
|
s->state = STATE_COLLECTING_DATA;
|
|
break;
|
|
|
|
case FAST_READ:
|
|
case DOR:
|
|
case QOR:
|
|
s->needed_bytes = 4;
|
|
s->pos = 0;
|
|
s->len = 0;
|
|
s->state = STATE_COLLECTING_DATA;
|
|
break;
|
|
|
|
case DIOR:
|
|
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->pos = 0;
|
|
s->len = 0;
|
|
s->state = STATE_COLLECTING_DATA;
|
|
break;
|
|
|
|
case QIOR:
|
|
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->pos = 0;
|
|
s->len = 0;
|
|
s->state = STATE_COLLECTING_DATA;
|
|
break;
|
|
|
|
case WRSR:
|
|
if (s->write_enable) {
|
|
s->needed_bytes = 1;
|
|
s->pos = 0;
|
|
s->len = 0;
|
|
s->state = STATE_COLLECTING_DATA;
|
|
}
|
|
break;
|
|
|
|
case WRDI:
|
|
s->write_enable = false;
|
|
break;
|
|
case WREN:
|
|
s->write_enable = true;
|
|
break;
|
|
|
|
case RDSR:
|
|
s->data[0] = (!!s->write_enable) << 1;
|
|
s->pos = 0;
|
|
s->len = 1;
|
|
s->state = STATE_READING_DATA;
|
|
break;
|
|
|
|
case JEDEC_READ:
|
|
DB_PRINT("populated jedec code\n");
|
|
s->data[0] = (s->pi->jedec >> 16) & 0xff;
|
|
s->data[1] = (s->pi->jedec >> 8) & 0xff;
|
|
s->data[2] = s->pi->jedec & 0xff;
|
|
if (s->pi->ext_jedec) {
|
|
s->data[3] = (s->pi->ext_jedec >> 8) & 0xff;
|
|
s->data[4] = s->pi->ext_jedec & 0xff;
|
|
s->len = 5;
|
|
} else {
|
|
s->len = 3;
|
|
}
|
|
s->pos = 0;
|
|
s->state = STATE_READING_DATA;
|
|
break;
|
|
|
|
case BULK_ERASE:
|
|
if (s->write_enable) {
|
|
DB_PRINT("chip erase\n");
|
|
flash_erase(s, 0, BULK_ERASE);
|
|
} else {
|
|
DB_PRINT("chip erase with write protect!\n");
|
|
}
|
|
break;
|
|
case NOP:
|
|
break;
|
|
default:
|
|
DB_PRINT("Unknown cmd %x\n", value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int m25p80_cs(SSISlave *ss, bool select)
|
|
{
|
|
Flash *s = FROM_SSI_SLAVE(Flash, ss);
|
|
|
|
if (select) {
|
|
s->len = 0;
|
|
s->pos = 0;
|
|
s->state = STATE_IDLE;
|
|
flash_sync_dirty(s, -1);
|
|
}
|
|
|
|
DB_PRINT("%sselect\n", select ? "de" : "");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t m25p80_transfer8(SSISlave *ss, uint32_t tx)
|
|
{
|
|
Flash *s = FROM_SSI_SLAVE(Flash, ss);
|
|
uint32_t r = 0;
|
|
|
|
switch (s->state) {
|
|
|
|
case STATE_PAGE_PROGRAM:
|
|
DB_PRINT("page program cur_addr=%lx data=%x\n", s->cur_addr,
|
|
(uint8_t)tx);
|
|
flash_write8(s, s->cur_addr, (uint8_t)tx);
|
|
s->cur_addr++;
|
|
break;
|
|
|
|
case STATE_READ:
|
|
r = s->storage[s->cur_addr];
|
|
DB_PRINT("READ 0x%lx=%x\n", s->cur_addr, r);
|
|
s->cur_addr = (s->cur_addr + 1) % s->size;
|
|
break;
|
|
|
|
case STATE_COLLECTING_DATA:
|
|
s->data[s->len] = (uint8_t)tx;
|
|
s->len++;
|
|
|
|
if (s->len == s->needed_bytes) {
|
|
complete_collecting_data(s);
|
|
}
|
|
break;
|
|
|
|
case STATE_READING_DATA:
|
|
r = s->data[s->pos];
|
|
s->pos++;
|
|
if (s->pos == s->len) {
|
|
s->pos = 0;
|
|
s->state = STATE_IDLE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
case STATE_IDLE:
|
|
decode_new_cmd(s, (uint8_t)tx);
|
|
break;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static int m25p80_init(SSISlave *ss)
|
|
{
|
|
DriveInfo *dinfo;
|
|
Flash *s = FROM_SSI_SLAVE(Flash, ss);
|
|
M25P80Class *mc = M25P80_GET_CLASS(s);
|
|
|
|
s->pi = mc->pi;
|
|
|
|
s->size = s->pi->sector_size * s->pi->n_sectors;
|
|
s->dirty_page = -1;
|
|
s->storage = qemu_blockalign(s->bdrv, s->size);
|
|
|
|
dinfo = drive_get_next(IF_MTD);
|
|
|
|
if (dinfo && dinfo->bdrv) {
|
|
DB_PRINT("Binding to IF_MTD drive\n");
|
|
s->bdrv = dinfo->bdrv;
|
|
/* FIXME: Move to late init */
|
|
if (bdrv_read(s->bdrv, 0, s->storage, DIV_ROUND_UP(s->size,
|
|
BDRV_SECTOR_SIZE))) {
|
|
fprintf(stderr, "Failed to initialize SPI flash!\n");
|
|
return 1;
|
|
}
|
|
} else {
|
|
memset(s->storage, 0xFF, s->size);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void m25p80_pre_save(void *opaque)
|
|
{
|
|
flash_sync_dirty((Flash *)opaque, -1);
|
|
}
|
|
|
|
static const VMStateDescription vmstate_m25p80 = {
|
|
.name = "xilinx_spi",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.minimum_version_id_old = 1,
|
|
.pre_save = m25p80_pre_save,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT8(state, Flash),
|
|
VMSTATE_UINT8_ARRAY(data, Flash, 16),
|
|
VMSTATE_UINT32(len, Flash),
|
|
VMSTATE_UINT32(pos, Flash),
|
|
VMSTATE_UINT8(needed_bytes, Flash),
|
|
VMSTATE_UINT8(cmd_in_progress, Flash),
|
|
VMSTATE_UINT64(cur_addr, Flash),
|
|
VMSTATE_BOOL(write_enable, Flash),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static void m25p80_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
|
|
M25P80Class *mc = M25P80_CLASS(klass);
|
|
|
|
k->init = m25p80_init;
|
|
k->transfer = m25p80_transfer8;
|
|
k->set_cs = m25p80_cs;
|
|
k->cs_polarity = SSI_CS_LOW;
|
|
dc->vmsd = &vmstate_m25p80;
|
|
mc->pi = data;
|
|
}
|
|
|
|
static const TypeInfo m25p80_info = {
|
|
.name = TYPE_M25P80,
|
|
.parent = TYPE_SSI_SLAVE,
|
|
.instance_size = sizeof(Flash),
|
|
.class_size = sizeof(M25P80Class),
|
|
.abstract = true,
|
|
};
|
|
|
|
static void m25p80_register_types(void)
|
|
{
|
|
int i;
|
|
|
|
type_register_static(&m25p80_info);
|
|
for (i = 0; i < ARRAY_SIZE(known_devices); ++i) {
|
|
TypeInfo ti = {
|
|
.name = known_devices[i].part_name,
|
|
.parent = TYPE_M25P80,
|
|
.class_init = m25p80_class_init,
|
|
.class_data = (void *)&known_devices[i],
|
|
};
|
|
type_register(&ti);
|
|
}
|
|
}
|
|
|
|
type_init(m25p80_register_types)
|