diff options
| author | Mike Frysinger <vapier@gentoo.org> | 2013-12-03 16:43:27 -0700 | 
|---|---|---|
| committer | Simon Glass <sjg@chromium.org> | 2013-12-09 12:22:39 -0700 | 
| commit | ffdb20bea16e00a326cc3d106f275e58bf302a0c (patch) | |
| tree | 468e8f02d9b56089d8e12b4ab873e0d2fb455342 | |
| parent | 6122813fa2cb9eef4a211bd47292322096db9fa8 (diff) | |
| download | olio-uboot-2014.01-ffdb20bea16e00a326cc3d106f275e58bf302a0c.tar.xz olio-uboot-2014.01-ffdb20bea16e00a326cc3d106f275e58bf302a0c.zip | |
sandbox: spi: Add new SPI flash driver
This adds a SPI flash driver which simulates SPI flash clients.
Currently supports the bare min that U-Boot requires: you can
probe, read, erase, and write.  Should be easy to extend to make
it behave more exactly like a real SPI flash, but this is good
enough to merge now.
sjg@chromium.org added a README and tidied up code a little.
Added a required map_sysmem() for sandbox.
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Signed-off-by: Simon Glass <sjg@chromium.org>
| -rw-r--r-- | board/sandbox/sandbox/README.sandbox | 54 | ||||
| -rw-r--r-- | doc/SPI/README.sandbox-spi | 64 | ||||
| -rw-r--r-- | drivers/mtd/spi/Makefile | 1 | ||||
| -rw-r--r-- | drivers/mtd/spi/sandbox.c | 483 | ||||
| -rw-r--r-- | drivers/mtd/spi/sf_internal.h | 1 | ||||
| -rw-r--r-- | drivers/mtd/spi/sf_probe.c | 3 | 
6 files changed, 605 insertions, 1 deletions
| diff --git a/board/sandbox/sandbox/README.sandbox b/board/sandbox/sandbox/README.sandbox index 30b05416a..69895574f 100644 --- a/board/sandbox/sandbox/README.sandbox +++ b/board/sandbox/sandbox/README.sandbox @@ -31,6 +31,60 @@ the console. It does not set the terminal into raw mode, so cursor keys and  history will not work yet. +SPI Emulation +------------- + +Sandbox supports SPI and SPI flash emulation. + +This is controlled by the spi_sf argument, the format of which is: + +   bus:cs:device:file + +   bus    - SPI bus number +   cs     - SPI chip select number +   device - SPI device emulation name +   file   - File on disk containing the data + +For example: + + dd if=/dev/zero of=spi.bin bs=1M count=4 + ./u-boot --spi_sf 0:0:M25P16:spi.bin + +With this setup you can issue SPI flash commands as normal: + +=>sf probe +SF: Detected M25P16 with page size 64 KiB, total 2 MiB +=>sf read 0 0 10000 +SF: 65536 bytes @ 0x0 Read: OK +=> + +Since this is a full SPI emulation (rather than just flash), you can +also use low-level SPI commands: + +=>sspi 0:0 32 9f +FF202015 + +This is issuing a READ_ID command and getting back 20 (ST Micro) part +0x2015 (the M25P16). + +Drivers are connected to a particular bus/cs using sandbox's state +structure (see the 'spi' member). A set of operations must be provided +for each driver. + + +Configuration settings for the curious are: + +CONFIG_SANDBOX_SPI_MAX_BUS +	The maximum number of SPI buses supported by the driver (default 1). + +CONFIG_SANDBOX_SPI_MAX_CS +	The maximum number of chip selects supported by the driver +	(default 10). + +CONFIG_SPI_IDLE_VAL +	The idle value on the SPI bus + +  Tests  ----- diff --git a/doc/SPI/README.sandbox-spi b/doc/SPI/README.sandbox-spi new file mode 100644 index 000000000..bb73eaf28 --- /dev/null +++ b/doc/SPI/README.sandbox-spi @@ -0,0 +1,64 @@ +Sandbox SPI/SPI Flash Implementation +==================================== + +U-Boot supports SPI and SPI flash emuation in sandbox. This must be enabled +using the --spi_sf paramter when starting U-Boot. + +For example: + +$ make O=sandbox sandbox_config +$ make O=sandbox +$ ./sandbox/u-boot --spi_sf 0:0:W25Q128:b/chromeos_peach/out/image.bin + +The four parameters to spi_sf are: + +   SPI bus number (typically 0) +   SPI chip select number (typically 0) +   SPI chip to emulate +   File containing emulated data + +Supported chips are W25Q16 (2MB), W25Q32 (4MB) and W25Q128 (16MB). Once +U-Boot it started you can use 'sf' commands as normal. For example: + +$ ./b/sandbox/u-boot --spi_sf 0:0:W25Q128:b/chromeos_peach/out/image.bin \ +	-c "sf probe; sf test 0 100000; sf read 0 1000 1000; \ +		sf erase 1000 1000; sf write 0 1000 1000" + + +U-Boot 2013.10-00237-gd4e0fdb (Nov 07 2013 - 20:08:15) + +DRAM:  128 MiB +Using default environment + +In:    serial +Out:   serial +Err:   serial +SF: Detected W25Q128BV with page size 256 Bytes, erase size 4 KiB, total 16 MiB +SPI flash test: +0 erase: 1 ticks, 1024000 KiB/s 8192.000 Mbps +1 check: 2 ticks, 512000 KiB/s 4096.000 Mbps +2 write: 6 ticks, 170666 KiB/s 1365.328 Mbps +3 read: 0 ticks, 1048576000 KiB/s -201326.-592 Mbps +Test passed +0 erase: 1 ticks, 1024000 KiB/s 8192.000 Mbps +1 check: 2 ticks, 512000 KiB/s 4096.000 Mbps +2 write: 6 ticks, 170666 KiB/s 1365.328 Mbps +3 read: 0 ticks, 1048576000 KiB/s -201326.-592 Mbps +SF: 4096 bytes @ 0x1000 Read: OK +SF: 4096 bytes @ 0x1000 Erased: OK +SF: 4096 bytes @ 0x1000 Written: OK + + +Since the SPI bus is fully implemented as well as the SPI flash connected to +it, you can also use low-level SPI commands to access the flash. For example +this reads the device ID from the emulated chip: + +=> sspi 0 32 9f +FFEF4018 + + +Simon Glass +sjg@chromium.org +7/11/2013 +Note that the sandbox SPI implementation was written by Mike Frysinger +<vapier@gentoo.org>. diff --git a/drivers/mtd/spi/Makefile b/drivers/mtd/spi/Makefile index 1bbeb7da3..26483a23f 100644 --- a/drivers/mtd/spi/Makefile +++ b/drivers/mtd/spi/Makefile @@ -13,4 +13,5 @@ endif  obj-$(CONFIG_CMD_SF)        += sf.o  obj-$(CONFIG_SPI_FLASH) += sf_probe.o sf_ops.o  obj-$(CONFIG_SPI_FRAM_RAMTRON) += ramtron.o +obj-$(CONFIG_SPI_FLASH_SANDBOX) += sandbox.o  obj-$(CONFIG_SPI_M95XXX) += eeprom_m95xxx.o diff --git a/drivers/mtd/spi/sandbox.c b/drivers/mtd/spi/sandbox.c new file mode 100644 index 000000000..a62ef4cbb --- /dev/null +++ b/drivers/mtd/spi/sandbox.c @@ -0,0 +1,483 @@ +/* + * 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> +#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> + +/* + * 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*/ +}; + +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", +	}; +	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 + +struct sandbox_spi_flash_erase_commands { +	u8 cmd; +	u32 size; +}; +#define IDCODE_LEN 5 +#define MAX_ERASE_CMDS 3 +struct sandbox_spi_flash_data { +	const char *name; +	u8 idcode[IDCODE_LEN]; +	u32 size; +	const struct sandbox_spi_flash_erase_commands +						erase_cmds[MAX_ERASE_CMDS]; +}; + +/* Structure describing all the flashes we know how to emulate */ +static const struct sandbox_spi_flash_data sandbox_sf_flashes[] = { +	{ +		"M25P16", { 0x20, 0x20, 0x15 }, (2 << 20), +		{	/* erase commands */ +			{ 0xd8, (64 << 10), }, /* sector */ +			{ 0xc7, (2 << 20), }, /* bulk */ +		}, +	}, +	{ +		"W25Q32", { 0xef, 0x40, 0x16 }, (4 << 20), +		{	/* erase commands */ +			{ 0x20, (4 << 10), }, /* 4KB */ +			{ 0xd8, (64 << 10), }, /* sector */ +			{ 0xc7, (4 << 20), }, /* bulk */ +		}, +	}, +	{ +		"W25Q128", { 0xef, 0x40, 0x18 }, (16 << 20), +		{	/* erase commands */ +			{ 0x20, (4 << 10), }, /* 4KB */ +			{ 0xd8, (64 << 10), }, /* sector */ +			{ 0xc7, (16 << 20), }, /* bulk */ +		}, +	}, +}; + +/* Used to quickly bulk erase backing store */ +static u8 sandbox_sf_0xff[0x1000]; + +/* Internal state data for each SPI flash */ +struct sandbox_spi_flash { +	/* +	 * 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; +	const void *cmd_data; +	/* 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 */ +	const struct sandbox_spi_flash_data *data; +	/* The file on disk to serv up data from */ +	int fd; +}; + +static int sandbox_sf_setup(void **priv, const char *spec) +{ +	/* spec = idcode:file */ +	struct sandbox_spi_flash *sbsf; +	const char *file; +	size_t i, len, idname_len; +	const struct sandbox_spi_flash_data *data; + +	file = strchr(spec, ':'); +	if (!file) { +		printf("sandbox_sf: unable to parse file\n"); +		goto error; +	} +	idname_len = file - spec; +	++file; + +	for (i = 0; i < ARRAY_SIZE(sandbox_sf_flashes); ++i) { +		data = &sandbox_sf_flashes[i]; +		len = strlen(data->name); +		if (idname_len != len) +			continue; +		if (!memcmp(spec, data->name, len)) +			break; +	} +	if (i == ARRAY_SIZE(sandbox_sf_flashes)) { +		printf("sandbox_sf: unknown flash '%*s'\n", (int)idname_len, +		       spec); +		goto error; +	} + +	if (sandbox_sf_0xff[0] == 0x00) +		memset(sandbox_sf_0xff, 0xff, sizeof(sandbox_sf_0xff)); + +	sbsf = calloc(sizeof(*sbsf), 1); +	if (!sbsf) { +		printf("sandbox_sf: out of memory\n"); +		goto error; +	} + +	sbsf->fd = os_open(file, 02); +	if (sbsf->fd == -1) { +		free(sbsf); +		printf("sandbox_sf: unable to open file '%s'\n", file); +		goto error; +	} + +	sbsf->data = data; + +	*priv = sbsf; +	return 0; + + error: +	return 1; +} + +static void sandbox_sf_free(void *priv) +{ +	struct sandbox_spi_flash *sbsf = priv; + +	os_close(sbsf->fd); +	free(sbsf); +} + +static void sandbox_sf_cs_activate(void *priv) +{ +	struct sandbox_spi_flash *sbsf = priv; + +	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; +} + +static void sandbox_sf_cs_deactivate(void *priv) +{ +	debug("sandbox_sf: CS deactivated; cmd done processing!\n"); +} + +/* 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 */ +	sandbox_spi_tristate(tx, 1); + +	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: + state_addr: +		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; +	default: { +		size_t i; + +		/* handle erase commands first */ +		for (i = 0; i < MAX_ERASE_CMDS; ++i) { +			const struct sandbox_spi_flash_erase_commands * +				erase_cmd = &sbsf->data->erase_cmds[i]; + +			if (erase_cmd->cmd == 0x00) +				continue; +			if (sbsf->cmd != erase_cmd->cmd) +				continue; + +			sbsf->cmd_data = erase_cmd; +			goto state_addr; +		} + +		debug(" cmd unknown: %#x\n", sbsf->cmd); +		return 1; +	} +	} + +	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) { +		todo = min(size, sizeof(sandbox_sf_0xff)); +		ret = os_write(sbsf->fd, sandbox_sf_0xff, todo); +		if (ret != todo) +			return ret; +		size -= todo; +	} + +	return 0; +} + +static int sandbox_sf_xfer(void *priv, const u8 *rx, u8 *tx, +		uint bytes) +{ +	struct sandbox_spi_flash *sbsf = priv; +	uint cnt, pos = 0; +	int ret; + +	debug("sandbox_sf: state:%x(%s) bytes:%u\n", sbsf->state, +	      sandbox_sf_state_name(sbsf->state), bytes); + +	if (sbsf->state == SF_CMD) { +		/* Figure out the initial state */ +		if (sandbox_sf_process_cmd(sbsf, rx, tx)) +			return 1; +		++pos; +	} + +	/* Process the remaining data */ +	while (pos < bytes) { +		switch (sbsf->state) { +		case SF_ID: { +			u8 id; + +			debug(" id: off:%u tx:", sbsf->off); +			if (sbsf->off < IDCODE_LEN) +				id = sbsf->data->idcode[sbsf->off]; +			else +				id = 0; +			debug("%02x\n", id); +			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); + +			sandbox_spi_tristate(&tx[pos++], 1); + +			/* 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"); +				return 1; +			} +			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); +			ret = os_read(sbsf->fd, tx + pos, cnt); +			if (ret < 0) { +				puts("sandbox_spi: os_read() failed\n"); +				return 1; +			} +			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; +		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); +			sandbox_spi_tristate(&tx[pos], cnt); +			ret = os_write(sbsf->fd, rx + pos, cnt); +			if (ret < 0) { +				puts("sandbox_spi: os_write() failed\n"); +				return 1; +			} +			pos += ret; +			sbsf->status &= ~STAT_WEL; +			break; +		case SF_ERASE: + case_sf_erase: { +			const struct sandbox_spi_flash_erase_commands * +						erase_cmd = sbsf->cmd_data; + +			if (!(sbsf->status & STAT_WEL)) { +				puts("sandbox_sf: write enable not set before erase\n"); +				goto done; +			} + +			/* verify address is aligned */ +			if (sbsf->off & (erase_cmd->size - 1)) { +				debug(" sector erase: cmd:%#x needs align:%#x, but we got %#x\n", +				      erase_cmd->cmd, erase_cmd->size, +				      sbsf->off); +				sbsf->status &= ~STAT_WEL; +				goto done; +			} + +			debug(" sector erase addr: %u\n", sbsf->off); + +			cnt = bytes - pos; +			sandbox_spi_tristate(&tx[pos], cnt); +			pos += cnt; + +			/* +			 * TODO(vapier@gentoo.org): latch WIP in status, and +			 * delay before clearing it ? +			 */ +			ret = sandbox_erase_part(sbsf, erase_cmd->size); +			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: +	return pos == bytes ? 0 : 1; +} + +static const struct sandbox_spi_emu_ops sandbox_sf_ops = { +	.setup         = sandbox_sf_setup, +	.free          = sandbox_sf_free, +	.cs_activate   = sandbox_sf_cs_activate, +	.cs_deactivate = sandbox_sf_cs_deactivate, +	.xfer          = sandbox_sf_xfer, +}; + +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].ops = &sandbox_sf_ops; +	state->spi[bus][cs].spec = spec; +	return 0; +} +SANDBOX_CMDLINE_OPT(spi_sf, 1, "connect a SPI flash: <bus>:<cs>:<id>:<file>"); diff --git a/drivers/mtd/spi/sf_internal.h b/drivers/mtd/spi/sf_internal.h index 732ddf836..d291746ed 100644 --- a/drivers/mtd/spi/sf_internal.h +++ b/drivers/mtd/spi/sf_internal.h @@ -28,6 +28,7 @@  #define CMD_PAGE_PROGRAM		0x02  #define CMD_WRITE_DISABLE		0x04  #define CMD_READ_STATUS			0x05 +#define CMD_READ_STATUS1		0x35  #define CMD_WRITE_ENABLE		0x06  #define CMD_READ_CONFIG			0x35  #define CMD_FLAG_STATUS			0x70 diff --git a/drivers/mtd/spi/sf_probe.c b/drivers/mtd/spi/sf_probe.c index ca127155b..c1eb75489 100644 --- a/drivers/mtd/spi/sf_probe.c +++ b/drivers/mtd/spi/sf_probe.c @@ -13,6 +13,7 @@  #include <malloc.h>  #include <spi.h>  #include <spi_flash.h> +#include <asm/io.h>  #include "sf_internal.h" @@ -279,7 +280,7 @@ int spi_flash_decode_fdt(const void *blob, struct spi_flash *flash)  		debug("%s: Memory map must cover entire device\n", __func__);  		return -1;  	} -	flash->memory_map = (void *)addr; +	flash->memory_map = map_sysmem(addr, size);  	return 0;  } |