2013-12-03 23:43:27 +00:00
|
|
|
/*
|
|
|
|
* Simulate a SPI flash
|
|
|
|
*
|
|
|
|
* Copyright (c) 2011-2013 The Chromium OS Authors.
|
|
|
|
* See file CREDITS for list of people who contributed to this
|
|
|
|
* project.
|
|
|
|
*
|
|
|
|
* Licensed under the GPL-2 or later.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <common.h>
|
2014-10-14 05:42:08 +00:00
|
|
|
#include <dm.h>
|
2013-12-03 23:43:27 +00:00
|
|
|
#include <malloc.h>
|
|
|
|
#include <spi.h>
|
|
|
|
#include <os.h>
|
|
|
|
|
|
|
|
#include <spi_flash.h>
|
|
|
|
#include "sf_internal.h"
|
|
|
|
|
|
|
|
#include <asm/getopt.h>
|
|
|
|
#include <asm/spi.h>
|
|
|
|
#include <asm/state.h>
|
2014-10-14 05:42:08 +00:00
|
|
|
#include <dm/device-internal.h>
|
|
|
|
#include <dm/lists.h>
|
|
|
|
#include <dm/uclass-internal.h>
|
|
|
|
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
2013-12-03 23:43:27 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The different states that our SPI flash transitions between.
|
|
|
|
* We need to keep track of this across multiple xfer calls since
|
|
|
|
* the SPI bus could possibly call down into us multiple times.
|
|
|
|
*/
|
|
|
|
enum sandbox_sf_state {
|
|
|
|
SF_CMD, /* default state -- we're awaiting a command */
|
|
|
|
SF_ID, /* read the flash's (jedec) ID code */
|
|
|
|
SF_ADDR, /* processing the offset in the flash to read/etc... */
|
|
|
|
SF_READ, /* reading data from the flash */
|
|
|
|
SF_WRITE, /* writing data to the flash, i.e. page programming */
|
|
|
|
SF_ERASE, /* erase the flash */
|
|
|
|
SF_READ_STATUS, /* read the flash's status register */
|
|
|
|
SF_READ_STATUS1, /* read the flash's status register upper 8 bits*/
|
2014-10-14 05:42:08 +00:00
|
|
|
SF_WRITE_STATUS, /* write the flash's status register */
|
2013-12-03 23:43:27 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const char *sandbox_sf_state_name(enum sandbox_sf_state state)
|
|
|
|
{
|
|
|
|
static const char * const states[] = {
|
|
|
|
"CMD", "ID", "ADDR", "READ", "WRITE", "ERASE", "READ_STATUS",
|
2014-10-14 05:42:08 +00:00
|
|
|
"READ_STATUS1", "WRITE_STATUS",
|
2013-12-03 23:43:27 +00:00
|
|
|
};
|
|
|
|
return states[state];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Bits for the status register */
|
|
|
|
#define STAT_WIP (1 << 0)
|
|
|
|
#define STAT_WEL (1 << 1)
|
|
|
|
|
|
|
|
/* Assume all SPI flashes have 3 byte addresses since they do atm */
|
|
|
|
#define SF_ADDR_LEN 3
|
|
|
|
|
2014-09-15 12:33:19 +00:00
|
|
|
#define IDCODE_LEN 3
|
2013-12-03 23:43:27 +00:00
|
|
|
|
|
|
|
/* Used to quickly bulk erase backing store */
|
|
|
|
static u8 sandbox_sf_0xff[0x1000];
|
|
|
|
|
|
|
|
/* Internal state data for each SPI flash */
|
|
|
|
struct sandbox_spi_flash {
|
2014-10-14 05:42:08 +00:00
|
|
|
unsigned int cs; /* Chip select we are attached to */
|
2013-12-03 23:43:27 +00:00
|
|
|
/*
|
|
|
|
* As we receive data over the SPI bus, our flash transitions
|
|
|
|
* between states. For example, we start off in the SF_CMD
|
|
|
|
* state where the first byte tells us what operation to perform
|
|
|
|
* (such as read or write the flash). But the operation itself
|
|
|
|
* can go through a few states such as first reading in the
|
|
|
|
* offset in the flash to perform the requested operation.
|
|
|
|
* Thus "state" stores the exact state that our machine is in
|
|
|
|
* while "cmd" stores the overall command we're processing.
|
|
|
|
*/
|
|
|
|
enum sandbox_sf_state state;
|
|
|
|
uint cmd;
|
2014-09-15 12:33:19 +00:00
|
|
|
/* Erase size of current erase command */
|
|
|
|
uint erase_size;
|
2013-12-03 23:43:27 +00:00
|
|
|
/* Current position in the flash; used when reading/writing/etc... */
|
|
|
|
uint off;
|
|
|
|
/* How many address bytes we've consumed */
|
|
|
|
uint addr_bytes, pad_addr_bytes;
|
|
|
|
/* The current flash status (see STAT_XXX defines above) */
|
|
|
|
u16 status;
|
|
|
|
/* Data describing the flash we're emulating */
|
2016-10-30 17:46:10 +00:00
|
|
|
const struct spi_flash_info *data;
|
2013-12-03 23:43:27 +00:00
|
|
|
/* The file on disk to serv up data from */
|
|
|
|
int fd;
|
|
|
|
};
|
|
|
|
|
2014-10-14 05:42:08 +00:00
|
|
|
struct sandbox_spi_flash_plat_data {
|
|
|
|
const char *filename;
|
|
|
|
const char *device_name;
|
|
|
|
int bus;
|
|
|
|
int cs;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This is a very strange probe function. If it has platform data (which may
|
|
|
|
* have come from the device tree) then this function gets the filename and
|
|
|
|
* device type from there. Failing that it looks at the command line
|
|
|
|
* parameter.
|
|
|
|
*/
|
|
|
|
static int sandbox_sf_probe(struct udevice *dev)
|
2013-12-03 23:43:27 +00:00
|
|
|
{
|
|
|
|
/* spec = idcode:file */
|
2014-10-14 05:42:08 +00:00
|
|
|
struct sandbox_spi_flash *sbsf = dev_get_priv(dev);
|
2013-12-03 23:43:27 +00:00
|
|
|
const char *file;
|
2014-09-15 12:33:19 +00:00
|
|
|
size_t len, idname_len;
|
2016-10-30 17:46:10 +00:00
|
|
|
const struct spi_flash_info *data;
|
2014-10-14 05:42:08 +00:00
|
|
|
struct sandbox_spi_flash_plat_data *pdata = dev_get_platdata(dev);
|
|
|
|
struct sandbox_state *state = state_get_current();
|
|
|
|
struct udevice *bus = dev->parent;
|
|
|
|
const char *spec = NULL;
|
|
|
|
int ret = 0;
|
|
|
|
int cs = -1;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
debug("%s: bus %d, looking for emul=%p: ", __func__, bus->seq, dev);
|
|
|
|
if (bus->seq >= 0 && bus->seq < CONFIG_SANDBOX_SPI_MAX_BUS) {
|
|
|
|
for (i = 0; i < CONFIG_SANDBOX_SPI_MAX_CS; i++) {
|
|
|
|
if (state->spi[bus->seq][i].emul == dev)
|
|
|
|
cs = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (cs == -1) {
|
2015-05-04 17:31:10 +00:00
|
|
|
printf("Error: Unknown chip select for device '%s'\n",
|
2014-10-14 05:42:08 +00:00
|
|
|
dev->name);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
debug("found at cs %d\n", cs);
|
|
|
|
|
|
|
|
if (!pdata->filename) {
|
|
|
|
struct sandbox_state *state = state_get_current();
|
|
|
|
|
|
|
|
assert(bus->seq != -1);
|
|
|
|
if (bus->seq < CONFIG_SANDBOX_SPI_MAX_BUS)
|
|
|
|
spec = state->spi[bus->seq][cs].spec;
|
2015-01-25 15:27:09 +00:00
|
|
|
if (!spec) {
|
2016-02-24 16:14:54 +00:00
|
|
|
debug("%s: No spec found for bus %d, cs %d\n",
|
|
|
|
__func__, bus->seq, cs);
|
2015-01-25 15:27:09 +00:00
|
|
|
ret = -ENOENT;
|
|
|
|
goto error;
|
|
|
|
}
|
2014-10-14 05:42:08 +00:00
|
|
|
|
|
|
|
file = strchr(spec, ':');
|
|
|
|
if (!file) {
|
2016-02-24 16:14:54 +00:00
|
|
|
printf("%s: unable to parse file\n", __func__);
|
2014-10-14 05:42:08 +00:00
|
|
|
ret = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
idname_len = file - spec;
|
|
|
|
pdata->filename = file + 1;
|
|
|
|
pdata->device_name = spec;
|
|
|
|
++file;
|
|
|
|
} else {
|
|
|
|
spec = strchr(pdata->device_name, ',');
|
|
|
|
if (spec)
|
|
|
|
spec++;
|
|
|
|
else
|
|
|
|
spec = pdata->device_name;
|
|
|
|
idname_len = strlen(spec);
|
2013-12-03 23:43:27 +00:00
|
|
|
}
|
2014-10-14 05:42:08 +00:00
|
|
|
debug("%s: device='%s'\n", __func__, spec);
|
2013-12-03 23:43:27 +00:00
|
|
|
|
2016-10-30 17:46:10 +00:00
|
|
|
for (data = spi_flash_ids; data->name; data++) {
|
2013-12-03 23:43:27 +00:00
|
|
|
len = strlen(data->name);
|
|
|
|
if (idname_len != len)
|
|
|
|
continue;
|
2014-10-14 05:42:08 +00:00
|
|
|
if (!strncasecmp(spec, data->name, len))
|
2013-12-03 23:43:27 +00:00
|
|
|
break;
|
|
|
|
}
|
2014-09-15 12:33:19 +00:00
|
|
|
if (!data->name) {
|
2016-02-24 16:14:54 +00:00
|
|
|
printf("%s: unknown flash '%*s'\n", __func__, (int)idname_len,
|
2013-12-03 23:43:27 +00:00
|
|
|
spec);
|
2014-10-14 05:42:08 +00:00
|
|
|
ret = -EINVAL;
|
2013-12-03 23:43:27 +00:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sandbox_sf_0xff[0] == 0x00)
|
|
|
|
memset(sandbox_sf_0xff, 0xff, sizeof(sandbox_sf_0xff));
|
|
|
|
|
2014-10-14 05:42:08 +00:00
|
|
|
sbsf->fd = os_open(pdata->filename, 02);
|
2013-12-03 23:43:27 +00:00
|
|
|
if (sbsf->fd == -1) {
|
2016-02-24 16:14:54 +00:00
|
|
|
printf("%s: unable to open file '%s'\n", __func__,
|
2014-10-14 05:42:08 +00:00
|
|
|
pdata->filename);
|
|
|
|
ret = -EIO;
|
2013-12-03 23:43:27 +00:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
sbsf->data = data;
|
2014-10-14 05:42:08 +00:00
|
|
|
sbsf->cs = cs;
|
2013-12-03 23:43:27 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
2015-01-25 15:27:09 +00:00
|
|
|
debug("%s: Got error %d\n", __func__, ret);
|
2014-10-14 05:42:08 +00:00
|
|
|
return ret;
|
2013-12-03 23:43:27 +00:00
|
|
|
}
|
|
|
|
|
2014-10-14 05:42:08 +00:00
|
|
|
static int sandbox_sf_remove(struct udevice *dev)
|
2013-12-03 23:43:27 +00:00
|
|
|
{
|
2014-10-14 05:42:08 +00:00
|
|
|
struct sandbox_spi_flash *sbsf = dev_get_priv(dev);
|
2013-12-03 23:43:27 +00:00
|
|
|
|
|
|
|
os_close(sbsf->fd);
|
2014-10-14 05:42:08 +00:00
|
|
|
|
|
|
|
return 0;
|
2013-12-03 23:43:27 +00:00
|
|
|
}
|
|
|
|
|
2014-10-14 05:42:08 +00:00
|
|
|
static void sandbox_sf_cs_activate(struct udevice *dev)
|
2013-12-03 23:43:27 +00:00
|
|
|
{
|
2014-10-14 05:42:08 +00:00
|
|
|
struct sandbox_spi_flash *sbsf = dev_get_priv(dev);
|
2013-12-03 23:43:27 +00:00
|
|
|
|
|
|
|
debug("sandbox_sf: CS activated; state is fresh!\n");
|
|
|
|
|
|
|
|
/* CS is asserted, so reset state */
|
|
|
|
sbsf->off = 0;
|
|
|
|
sbsf->addr_bytes = 0;
|
|
|
|
sbsf->pad_addr_bytes = 0;
|
|
|
|
sbsf->state = SF_CMD;
|
|
|
|
sbsf->cmd = SF_CMD;
|
|
|
|
}
|
|
|
|
|
2014-10-14 05:42:08 +00:00
|
|
|
static void sandbox_sf_cs_deactivate(struct udevice *dev)
|
2013-12-03 23:43:27 +00:00
|
|
|
{
|
|
|
|
debug("sandbox_sf: CS deactivated; cmd done processing!\n");
|
|
|
|
}
|
|
|
|
|
2014-10-14 05:42:08 +00:00
|
|
|
/*
|
|
|
|
* There are times when the data lines are allowed to tristate. What
|
|
|
|
* is actually sensed on the line depends on the hardware. It could
|
|
|
|
* always be 0xFF/0x00 (if there are pull ups/downs), or things could
|
|
|
|
* float and so we'd get garbage back. This func encapsulates that
|
|
|
|
* scenario so we can worry about the details here.
|
|
|
|
*/
|
|
|
|
static void sandbox_spi_tristate(u8 *buf, uint len)
|
|
|
|
{
|
|
|
|
/* XXX: make this into a user config option ? */
|
|
|
|
memset(buf, 0xff, len);
|
|
|
|
}
|
|
|
|
|
2013-12-03 23:43:27 +00:00
|
|
|
/* Figure out what command this stream is telling us to do */
|
|
|
|
static int sandbox_sf_process_cmd(struct sandbox_spi_flash *sbsf, const u8 *rx,
|
|
|
|
u8 *tx)
|
|
|
|
{
|
|
|
|
enum sandbox_sf_state oldstate = sbsf->state;
|
|
|
|
|
|
|
|
/* We need to output a byte for the cmd byte we just ate */
|
2014-10-14 05:42:08 +00:00
|
|
|
if (tx)
|
|
|
|
sandbox_spi_tristate(tx, 1);
|
2013-12-03 23:43:27 +00:00
|
|
|
|
|
|
|
sbsf->cmd = rx[0];
|
|
|
|
switch (sbsf->cmd) {
|
|
|
|
case CMD_READ_ID:
|
|
|
|
sbsf->state = SF_ID;
|
|
|
|
sbsf->cmd = SF_ID;
|
|
|
|
break;
|
|
|
|
case CMD_READ_ARRAY_FAST:
|
|
|
|
sbsf->pad_addr_bytes = 1;
|
|
|
|
case CMD_READ_ARRAY_SLOW:
|
|
|
|
case CMD_PAGE_PROGRAM:
|
|
|
|
sbsf->state = SF_ADDR;
|
|
|
|
break;
|
|
|
|
case CMD_WRITE_DISABLE:
|
|
|
|
debug(" write disabled\n");
|
|
|
|
sbsf->status &= ~STAT_WEL;
|
|
|
|
break;
|
|
|
|
case CMD_READ_STATUS:
|
|
|
|
sbsf->state = SF_READ_STATUS;
|
|
|
|
break;
|
|
|
|
case CMD_READ_STATUS1:
|
|
|
|
sbsf->state = SF_READ_STATUS1;
|
|
|
|
break;
|
|
|
|
case CMD_WRITE_ENABLE:
|
|
|
|
debug(" write enabled\n");
|
|
|
|
sbsf->status |= STAT_WEL;
|
|
|
|
break;
|
2014-10-14 05:42:08 +00:00
|
|
|
case CMD_WRITE_STATUS:
|
|
|
|
sbsf->state = SF_WRITE_STATUS;
|
|
|
|
break;
|
2013-12-03 23:43:27 +00:00
|
|
|
default: {
|
2014-09-15 12:33:19 +00:00
|
|
|
int flags = sbsf->data->flags;
|
|
|
|
|
|
|
|
/* we only support erase here */
|
|
|
|
if (sbsf->cmd == CMD_ERASE_CHIP) {
|
|
|
|
sbsf->erase_size = sbsf->data->sector_size *
|
2016-10-30 17:46:15 +00:00
|
|
|
sbsf->data->n_sectors;
|
2014-09-15 12:33:19 +00:00
|
|
|
} else if (sbsf->cmd == CMD_ERASE_4K && (flags & SECT_4K)) {
|
|
|
|
sbsf->erase_size = 4 << 10;
|
2016-08-08 11:49:08 +00:00
|
|
|
} else if (sbsf->cmd == CMD_ERASE_64K && !(flags & SECT_4K)) {
|
2014-09-15 12:33:19 +00:00
|
|
|
sbsf->erase_size = 64 << 10;
|
|
|
|
} else {
|
|
|
|
debug(" cmd unknown: %#x\n", sbsf->cmd);
|
2014-10-14 05:42:08 +00:00
|
|
|
return -EIO;
|
2013-12-03 23:43:27 +00:00
|
|
|
}
|
2014-09-15 12:33:19 +00:00
|
|
|
sbsf->state = SF_ADDR;
|
|
|
|
break;
|
2013-12-03 23:43:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (oldstate != sbsf->state)
|
|
|
|
debug(" cmd: transition to %s state\n",
|
|
|
|
sandbox_sf_state_name(sbsf->state));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sandbox_erase_part(struct sandbox_spi_flash *sbsf, int size)
|
|
|
|
{
|
|
|
|
int todo;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
while (size > 0) {
|
linux/kernel.h: sync min, max, min3, max3 macros with Linux
U-Boot has never cared about the type when we get max/min of two
values, but Linux Kernel does. This commit gets min, max, min3, max3
macros synced with the kernel introducing type checks.
Many of references of those macros must be fixed to suppress warnings.
We have two options:
- Use min, max, min3, max3 only when the arguments have the same type
(or add casts to the arguments)
- Use min_t/max_t instead with the appropriate type for the first
argument
Signed-off-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
Acked-by: Pavel Machek <pavel@denx.de>
Acked-by: Lukasz Majewski <l.majewski@samsung.com>
Tested-by: Lukasz Majewski <l.majewski@samsung.com>
[trini: Fixup arch/blackfin/lib/string.c]
Signed-off-by: Tom Rini <trini@ti.com>
2014-11-06 18:03:31 +00:00
|
|
|
todo = min(size, (int)sizeof(sandbox_sf_0xff));
|
2013-12-03 23:43:27 +00:00
|
|
|
ret = os_write(sbsf->fd, sandbox_sf_0xff, todo);
|
|
|
|
if (ret != todo)
|
|
|
|
return ret;
|
|
|
|
size -= todo;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-10-14 05:42:08 +00:00
|
|
|
static int sandbox_sf_xfer(struct udevice *dev, unsigned int bitlen,
|
|
|
|
const void *rxp, void *txp, unsigned long flags)
|
2013-12-03 23:43:27 +00:00
|
|
|
{
|
2014-10-14 05:42:08 +00:00
|
|
|
struct sandbox_spi_flash *sbsf = dev_get_priv(dev);
|
|
|
|
const uint8_t *rx = rxp;
|
|
|
|
uint8_t *tx = txp;
|
2013-12-03 23:43:27 +00:00
|
|
|
uint cnt, pos = 0;
|
2014-10-14 05:42:08 +00:00
|
|
|
int bytes = bitlen / 8;
|
2013-12-03 23:43:27 +00:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
debug("sandbox_sf: state:%x(%s) bytes:%u\n", sbsf->state,
|
|
|
|
sandbox_sf_state_name(sbsf->state), bytes);
|
|
|
|
|
2014-10-14 05:42:08 +00:00
|
|
|
if ((flags & SPI_XFER_BEGIN))
|
|
|
|
sandbox_sf_cs_activate(dev);
|
|
|
|
|
2013-12-03 23:43:27 +00:00
|
|
|
if (sbsf->state == SF_CMD) {
|
|
|
|
/* Figure out the initial state */
|
2014-10-14 05:42:08 +00:00
|
|
|
ret = sandbox_sf_process_cmd(sbsf, rx, tx);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2013-12-03 23:43:27 +00:00
|
|
|
++pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Process the remaining data */
|
|
|
|
while (pos < bytes) {
|
|
|
|
switch (sbsf->state) {
|
|
|
|
case SF_ID: {
|
|
|
|
u8 id;
|
|
|
|
|
|
|
|
debug(" id: off:%u tx:", sbsf->off);
|
2014-09-15 12:33:19 +00:00
|
|
|
if (sbsf->off < IDCODE_LEN) {
|
|
|
|
/* Extract correct byte from ID 0x00aabbcc */
|
2016-10-30 17:46:12 +00:00
|
|
|
id = ((JEDEC_MFR(sbsf->data) << 16) |
|
|
|
|
JEDEC_ID(sbsf->data)) >>
|
2014-09-15 12:33:19 +00:00
|
|
|
(8 * (IDCODE_LEN - 1 - sbsf->off));
|
|
|
|
} else {
|
2013-12-03 23:43:27 +00:00
|
|
|
id = 0;
|
2014-09-15 12:33:19 +00:00
|
|
|
}
|
|
|
|
debug("%d %02x\n", sbsf->off, id);
|
2013-12-03 23:43:27 +00:00
|
|
|
tx[pos++] = id;
|
|
|
|
++sbsf->off;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SF_ADDR:
|
|
|
|
debug(" addr: bytes:%u rx:%02x ", sbsf->addr_bytes,
|
|
|
|
rx[pos]);
|
|
|
|
|
|
|
|
if (sbsf->addr_bytes++ < SF_ADDR_LEN)
|
|
|
|
sbsf->off = (sbsf->off << 8) | rx[pos];
|
|
|
|
debug("addr:%06x\n", sbsf->off);
|
|
|
|
|
2014-10-14 05:42:08 +00:00
|
|
|
if (tx)
|
|
|
|
sandbox_spi_tristate(&tx[pos], 1);
|
|
|
|
pos++;
|
2013-12-03 23:43:27 +00:00
|
|
|
|
|
|
|
/* See if we're done processing */
|
|
|
|
if (sbsf->addr_bytes <
|
|
|
|
SF_ADDR_LEN + sbsf->pad_addr_bytes)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* Next state! */
|
|
|
|
if (os_lseek(sbsf->fd, sbsf->off, OS_SEEK_SET) < 0) {
|
|
|
|
puts("sandbox_sf: os_lseek() failed");
|
2014-10-14 05:42:08 +00:00
|
|
|
return -EIO;
|
2013-12-03 23:43:27 +00:00
|
|
|
}
|
|
|
|
switch (sbsf->cmd) {
|
|
|
|
case CMD_READ_ARRAY_FAST:
|
|
|
|
case CMD_READ_ARRAY_SLOW:
|
|
|
|
sbsf->state = SF_READ;
|
|
|
|
break;
|
|
|
|
case CMD_PAGE_PROGRAM:
|
|
|
|
sbsf->state = SF_WRITE;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* assume erase state ... */
|
|
|
|
sbsf->state = SF_ERASE;
|
|
|
|
goto case_sf_erase;
|
|
|
|
}
|
|
|
|
debug(" cmd: transition to %s state\n",
|
|
|
|
sandbox_sf_state_name(sbsf->state));
|
|
|
|
break;
|
|
|
|
case SF_READ:
|
|
|
|
/*
|
|
|
|
* XXX: need to handle exotic behavior:
|
|
|
|
* - reading past end of device
|
|
|
|
*/
|
|
|
|
|
|
|
|
cnt = bytes - pos;
|
|
|
|
debug(" tx: read(%u)\n", cnt);
|
2014-10-14 05:42:08 +00:00
|
|
|
assert(tx);
|
2013-12-03 23:43:27 +00:00
|
|
|
ret = os_read(sbsf->fd, tx + pos, cnt);
|
|
|
|
if (ret < 0) {
|
2014-10-14 05:42:08 +00:00
|
|
|
puts("sandbox_sf: os_read() failed\n");
|
|
|
|
return -EIO;
|
2013-12-03 23:43:27 +00:00
|
|
|
}
|
|
|
|
pos += ret;
|
|
|
|
break;
|
|
|
|
case SF_READ_STATUS:
|
|
|
|
debug(" read status: %#x\n", sbsf->status);
|
|
|
|
cnt = bytes - pos;
|
|
|
|
memset(tx + pos, sbsf->status, cnt);
|
|
|
|
pos += cnt;
|
|
|
|
break;
|
|
|
|
case SF_READ_STATUS1:
|
|
|
|
debug(" read status: %#x\n", sbsf->status);
|
|
|
|
cnt = bytes - pos;
|
|
|
|
memset(tx + pos, sbsf->status >> 8, cnt);
|
|
|
|
pos += cnt;
|
|
|
|
break;
|
2014-10-14 05:42:08 +00:00
|
|
|
case SF_WRITE_STATUS:
|
|
|
|
debug(" write status: %#x (ignored)\n", rx[pos]);
|
|
|
|
pos = bytes;
|
|
|
|
break;
|
2013-12-03 23:43:27 +00:00
|
|
|
case SF_WRITE:
|
|
|
|
/*
|
|
|
|
* XXX: need to handle exotic behavior:
|
|
|
|
* - unaligned addresses
|
|
|
|
* - more than a page (256) worth of data
|
|
|
|
* - reading past end of device
|
|
|
|
*/
|
|
|
|
if (!(sbsf->status & STAT_WEL)) {
|
|
|
|
puts("sandbox_sf: write enable not set before write\n");
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
cnt = bytes - pos;
|
|
|
|
debug(" rx: write(%u)\n", cnt);
|
2014-10-14 05:42:08 +00:00
|
|
|
if (tx)
|
|
|
|
sandbox_spi_tristate(&tx[pos], cnt);
|
2013-12-03 23:43:27 +00:00
|
|
|
ret = os_write(sbsf->fd, rx + pos, cnt);
|
|
|
|
if (ret < 0) {
|
|
|
|
puts("sandbox_spi: os_write() failed\n");
|
2014-10-14 05:42:08 +00:00
|
|
|
return -EIO;
|
2013-12-03 23:43:27 +00:00
|
|
|
}
|
|
|
|
pos += ret;
|
|
|
|
sbsf->status &= ~STAT_WEL;
|
|
|
|
break;
|
|
|
|
case SF_ERASE:
|
|
|
|
case_sf_erase: {
|
|
|
|
if (!(sbsf->status & STAT_WEL)) {
|
|
|
|
puts("sandbox_sf: write enable not set before erase\n");
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* verify address is aligned */
|
2014-09-15 12:33:19 +00:00
|
|
|
if (sbsf->off & (sbsf->erase_size - 1)) {
|
2013-12-03 23:43:27 +00:00
|
|
|
debug(" sector erase: cmd:%#x needs align:%#x, but we got %#x\n",
|
2014-09-15 12:33:19 +00:00
|
|
|
sbsf->cmd, sbsf->erase_size,
|
2013-12-03 23:43:27 +00:00
|
|
|
sbsf->off);
|
|
|
|
sbsf->status &= ~STAT_WEL;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
2014-09-15 12:33:19 +00:00
|
|
|
debug(" sector erase addr: %u, size: %u\n", sbsf->off,
|
|
|
|
sbsf->erase_size);
|
2013-12-03 23:43:27 +00:00
|
|
|
|
|
|
|
cnt = bytes - pos;
|
2014-10-14 05:42:08 +00:00
|
|
|
if (tx)
|
|
|
|
sandbox_spi_tristate(&tx[pos], cnt);
|
2013-12-03 23:43:27 +00:00
|
|
|
pos += cnt;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* TODO(vapier@gentoo.org): latch WIP in status, and
|
|
|
|
* delay before clearing it ?
|
|
|
|
*/
|
2014-09-15 12:33:19 +00:00
|
|
|
ret = sandbox_erase_part(sbsf, sbsf->erase_size);
|
2013-12-03 23:43:27 +00:00
|
|
|
sbsf->status &= ~STAT_WEL;
|
|
|
|
if (ret) {
|
|
|
|
debug("sandbox_sf: Erase failed\n");
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
debug(" ??? no idea what to do ???\n");
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
done:
|
2014-10-14 05:42:08 +00:00
|
|
|
if (flags & SPI_XFER_END)
|
|
|
|
sandbox_sf_cs_deactivate(dev);
|
|
|
|
return pos == bytes ? 0 : -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sandbox_sf_ofdata_to_platdata(struct udevice *dev)
|
|
|
|
{
|
|
|
|
struct sandbox_spi_flash_plat_data *pdata = dev_get_platdata(dev);
|
|
|
|
const void *blob = gd->fdt_blob;
|
2017-01-17 23:52:55 +00:00
|
|
|
int node = dev_of_offset(dev);
|
2014-10-14 05:42:08 +00:00
|
|
|
|
|
|
|
pdata->filename = fdt_getprop(blob, node, "sandbox,filename", NULL);
|
|
|
|
pdata->device_name = fdt_getprop(blob, node, "compatible", NULL);
|
|
|
|
if (!pdata->filename || !pdata->device_name) {
|
|
|
|
debug("%s: Missing properties, filename=%s, device_name=%s\n",
|
|
|
|
__func__, pdata->filename, pdata->device_name);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2013-12-03 23:43:27 +00:00
|
|
|
}
|
|
|
|
|
2014-10-14 05:42:08 +00:00
|
|
|
static const struct dm_spi_emul_ops sandbox_sf_emul_ops = {
|
2013-12-03 23:43:27 +00:00
|
|
|
.xfer = sandbox_sf_xfer,
|
|
|
|
};
|
|
|
|
|
2014-10-14 05:42:08 +00:00
|
|
|
#ifdef CONFIG_SPI_FLASH
|
2013-12-03 23:43:27 +00:00
|
|
|
static int sandbox_cmdline_cb_spi_sf(struct sandbox_state *state,
|
|
|
|
const char *arg)
|
|
|
|
{
|
|
|
|
unsigned long bus, cs;
|
|
|
|
const char *spec = sandbox_spi_parse_spec(arg, &bus, &cs);
|
|
|
|
|
|
|
|
if (!spec)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* It is safe to not make a copy of 'spec' because it comes from the
|
|
|
|
* command line.
|
|
|
|
*
|
|
|
|
* TODO(sjg@chromium.org): It would be nice if we could parse the
|
|
|
|
* spec here, but the problem is that no U-Boot init has been done
|
|
|
|
* yet. Perhaps we can figure something out.
|
|
|
|
*/
|
|
|
|
state->spi[bus][cs].spec = spec;
|
2016-02-24 16:14:54 +00:00
|
|
|
debug("%s: Setting up spec '%s' for bus %ld, cs %ld\n", __func__,
|
|
|
|
spec, bus, cs);
|
|
|
|
|
2013-12-03 23:43:27 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
SANDBOX_CMDLINE_OPT(spi_sf, 1, "connect a SPI flash: <bus>:<cs>:<id>:<file>");
|
2014-10-14 05:42:08 +00:00
|
|
|
|
|
|
|
int sandbox_sf_bind_emul(struct sandbox_state *state, int busnum, int cs,
|
|
|
|
struct udevice *bus, int of_offset, const char *spec)
|
|
|
|
{
|
|
|
|
struct udevice *emul;
|
|
|
|
char name[20], *str;
|
|
|
|
struct driver *drv;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
/* now the emulator */
|
|
|
|
strncpy(name, spec, sizeof(name) - 6);
|
|
|
|
name[sizeof(name) - 6] = '\0';
|
|
|
|
strcat(name, "-emul");
|
|
|
|
str = strdup(name);
|
|
|
|
if (!str)
|
|
|
|
return -ENOMEM;
|
|
|
|
drv = lists_driver_lookup_name("sandbox_sf_emul");
|
|
|
|
if (!drv) {
|
|
|
|
puts("Cannot find sandbox_sf_emul driver\n");
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
ret = device_bind(bus, drv, str, NULL, of_offset, &emul);
|
|
|
|
if (ret) {
|
|
|
|
printf("Cannot create emul device for spec '%s' (err=%d)\n",
|
|
|
|
spec, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
state->spi[busnum][cs].emul = emul;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void sandbox_sf_unbind_emul(struct sandbox_state *state, int busnum, int cs)
|
|
|
|
{
|
2015-01-25 15:27:12 +00:00
|
|
|
struct udevice *dev;
|
|
|
|
|
|
|
|
dev = state->spi[busnum][cs].emul;
|
2017-03-20 11:51:48 +00:00
|
|
|
device_remove(dev, DM_REMOVE_NORMAL);
|
2015-01-25 15:27:12 +00:00
|
|
|
device_unbind(dev);
|
2014-10-14 05:42:08 +00:00
|
|
|
state->spi[busnum][cs].emul = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sandbox_sf_bind_bus_cs(struct sandbox_state *state, int busnum,
|
|
|
|
int cs, const char *spec)
|
|
|
|
{
|
|
|
|
struct udevice *bus, *slave;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = uclass_find_device_by_seq(UCLASS_SPI, busnum, true, &bus);
|
|
|
|
if (ret) {
|
|
|
|
printf("Invalid bus %d for spec '%s' (err=%d)\n", busnum,
|
|
|
|
spec, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
2014-11-11 17:46:22 +00:00
|
|
|
ret = spi_find_chip_select(bus, cs, &slave);
|
2014-10-14 05:42:08 +00:00
|
|
|
if (!ret) {
|
|
|
|
printf("Chip select %d already exists for spec '%s'\n", cs,
|
|
|
|
spec);
|
|
|
|
return -EEXIST;
|
|
|
|
}
|
|
|
|
|
2014-11-11 17:46:23 +00:00
|
|
|
ret = device_bind_driver(bus, "spi_flash_std", spec, &slave);
|
2014-10-14 05:42:08 +00:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return sandbox_sf_bind_emul(state, busnum, cs, bus, -1, spec);
|
|
|
|
}
|
|
|
|
|
|
|
|
int sandbox_spi_get_emul(struct sandbox_state *state,
|
|
|
|
struct udevice *bus, struct udevice *slave,
|
|
|
|
struct udevice **emulp)
|
|
|
|
{
|
|
|
|
struct sandbox_spi_info *info;
|
|
|
|
int busnum = bus->seq;
|
|
|
|
int cs = spi_chip_select(slave);
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
info = &state->spi[busnum][cs];
|
|
|
|
if (!info->emul) {
|
|
|
|
/* Use the same device tree node as the SPI flash device */
|
|
|
|
debug("%s: busnum=%u, cs=%u: binding SPI flash emulation: ",
|
|
|
|
__func__, busnum, cs);
|
|
|
|
ret = sandbox_sf_bind_emul(state, busnum, cs, bus,
|
2017-01-17 23:52:55 +00:00
|
|
|
dev_of_offset(slave), slave->name);
|
2014-10-14 05:42:08 +00:00
|
|
|
if (ret) {
|
|
|
|
debug("failed (err=%d)\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
debug("OK\n");
|
|
|
|
}
|
|
|
|
*emulp = info->emul;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int dm_scan_other(bool pre_reloc_only)
|
|
|
|
{
|
|
|
|
struct sandbox_state *state = state_get_current();
|
|
|
|
int busnum, cs;
|
|
|
|
|
|
|
|
if (pre_reloc_only)
|
|
|
|
return 0;
|
|
|
|
for (busnum = 0; busnum < CONFIG_SANDBOX_SPI_MAX_BUS; busnum++) {
|
|
|
|
for (cs = 0; cs < CONFIG_SANDBOX_SPI_MAX_CS; cs++) {
|
|
|
|
const char *spec = state->spi[busnum][cs].spec;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (spec) {
|
|
|
|
ret = sandbox_sf_bind_bus_cs(state, busnum,
|
|
|
|
cs, spec);
|
|
|
|
if (ret) {
|
|
|
|
debug("%s: Bind failed for bus %d, cs %d\n",
|
|
|
|
__func__, busnum, cs);
|
|
|
|
return ret;
|
|
|
|
}
|
2016-02-24 16:14:54 +00:00
|
|
|
debug("%s: Setting up spec '%s' for bus %d, cs %d\n",
|
|
|
|
__func__, spec, busnum, cs);
|
2014-10-14 05:42:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static const struct udevice_id sandbox_sf_ids[] = {
|
|
|
|
{ .compatible = "sandbox,spi-flash" },
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
|
|
|
U_BOOT_DRIVER(sandbox_sf_emul) = {
|
|
|
|
.name = "sandbox_sf_emul",
|
|
|
|
.id = UCLASS_SPI_EMUL,
|
|
|
|
.of_match = sandbox_sf_ids,
|
|
|
|
.ofdata_to_platdata = sandbox_sf_ofdata_to_platdata,
|
|
|
|
.probe = sandbox_sf_probe,
|
|
|
|
.remove = sandbox_sf_remove,
|
|
|
|
.priv_auto_alloc_size = sizeof(struct sandbox_spi_flash),
|
|
|
|
.platdata_auto_alloc_size = sizeof(struct sandbox_spi_flash_plat_data),
|
|
|
|
.ops = &sandbox_sf_emul_ops,
|
|
|
|
};
|