* Command for accessing SPI flash.
*
* Copyright (C) 2008 Atmel Corporation
- * Licensed under the GPL-2 or later.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
+#include <div64.h>
+#include <dm.h>
#include <malloc.h>
+#include <spi.h>
#include <spi_flash.h>
#include <asm/io.h>
-
-#ifndef CONFIG_SF_DEFAULT_SPEED
-# define CONFIG_SF_DEFAULT_SPEED 1000000
-#endif
-#ifndef CONFIG_SF_DEFAULT_MODE
-# define CONFIG_SF_DEFAULT_MODE SPI_MODE_3
-#endif
-#ifndef CONFIG_SF_DEFAULT_CS
-# define CONFIG_SF_DEFAULT_CS 0
-#endif
-#ifndef CONFIG_SF_DEFAULT_BUS
-# define CONFIG_SF_DEFAULT_BUS 0
-#endif
+#include <dm/device-internal.h>
static struct spi_flash *flash;
-
/*
* This function computes the length argument for the erase command.
* The length on which the command is to operate can be given in two forms:
{
/* less accurate but avoids overflow */
if (len >= ((unsigned int) -1) / 1024)
- return len / (max(get_timer(start_ms) / 1024, 1));
+ return len / (max(get_timer(start_ms) / 1024, 1UL));
else
- return 1024 * len / max(get_timer(start_ms), 1);
+ return 1024 * len / max(get_timer(start_ms), 1UL);
}
static int do_spi_flash_probe(int argc, char * const argv[])
unsigned int speed = CONFIG_SF_DEFAULT_SPEED;
unsigned int mode = CONFIG_SF_DEFAULT_MODE;
char *endp;
+#ifdef CONFIG_DM_SPI_FLASH
+ struct udevice *new, *bus_dev;
+ int ret;
+#else
struct spi_flash *new;
+#endif
if (argc >= 2) {
cs = simple_strtoul(argv[1], &endp, 0);
return -1;
}
+#ifdef CONFIG_DM_SPI_FLASH
+ /* Remove the old device, otherwise probe will just be a nop */
+ ret = spi_find_bus_and_cs(bus, cs, &bus_dev, &new);
+ if (!ret) {
+ device_remove(new);
+ device_unbind(new);
+ }
+ flash = NULL;
+ ret = spi_flash_probe_bus_cs(bus, cs, speed, mode, &new);
+ if (ret) {
+ printf("Failed to initialize SPI flash at %u:%u (error %d)\n",
+ bus, cs, ret);
+ return 1;
+ }
+
+ flash = new->uclass_priv;
+#else
new = spi_flash_probe(bus, cs, speed, mode);
if (!new) {
printf("Failed to initialize SPI flash at %u:%u\n", bus, cs);
if (flash)
spi_flash_free(flash);
flash = new;
+#endif
return 0;
}
size_t len, const char *buf, char *cmp_buf, size_t *skipped)
{
debug("offset=%#x, sector_size=%#x, len=%#zx\n",
- offset, flash->sector_size, len);
- if (spi_flash_read(flash, offset, len, cmp_buf))
+ offset, flash->sector_size, len);
+ /* Read the entire sector so to allow for rewriting */
+ if (spi_flash_read(flash, offset, flash->sector_size, cmp_buf))
return "read";
+ /* Compare only what is meaningful (len) */
if (memcmp(cmp_buf, buf, len) == 0) {
debug("Skip region %x size %zx: no change\n",
- offset, len);
+ offset, len);
*skipped += len;
return NULL;
}
- if (spi_flash_erase(flash, offset, len))
+ /* Erase the entire sector */
+ if (spi_flash_erase(flash, offset, flash->sector_size))
return "erase";
+ /* Write the initial part of the block from the source */
if (spi_flash_write(flash, offset, len, buf))
return "write";
+ /* If it's a partial sector, rewrite the existing part */
+ if (len != flash->sector_size) {
+ /* Rewrite the original data to the end of the sector */
+ if (spi_flash_write(flash, offset + len,
+ flash->sector_size - len, &cmp_buf[len]))
+ return "write";
+ }
+
return NULL;
}
ulong last_update = get_timer(0);
for (; buf < end && !err_oper; buf += todo, offset += todo) {
- todo = min(end - buf, flash->sector_size);
+ todo = min_t(size_t, end - buf, flash->sector_size);
if (get_timer(last_update) > 100) {
printf(" \rUpdating, %zu%% %lu B/s",
- 100 - (end - buf) / scale,
+ 100 - (end - buf) / scale,
bytes_per_second(buf - start_buf,
start_time));
last_update = get_timer(0);
delta = get_timer(start_time);
printf("%zu bytes written, %zu bytes skipped", len - skipped,
- skipped);
+ skipped);
printf(" in %ld.%lds, speed %ld B/s\n",
- delta / 1000, delta % 1000, bytes_per_second(len, start_time));
+ delta / 1000, delta % 1000, bytes_per_second(len, start_time));
return 0;
}
unsigned long len;
void *buf;
char *endp;
- int ret;
+ int ret = 1;
if (argc < 4)
return -1;
/* Consistency checking */
if (offset + len > flash->size) {
printf("ERROR: attempting %s past flash size (%#x)\n",
- argv[0], flash->size);
+ argv[0], flash->size);
return 1;
}
return 1;
}
- if (strcmp(argv[0], "update") == 0)
+ if (strcmp(argv[0], "update") == 0) {
ret = spi_flash_update(flash, offset, len, buf);
- else if (strcmp(argv[0], "read") == 0)
- ret = spi_flash_read(flash, offset, len, buf);
- else
- ret = spi_flash_write(flash, offset, len, buf);
+ } else if (strncmp(argv[0], "read", 4) == 0 ||
+ strncmp(argv[0], "write", 5) == 0) {
+ int read;
+
+ read = strncmp(argv[0], "read", 4) == 0;
+ if (read)
+ ret = spi_flash_read(flash, offset, len, buf);
+ else
+ ret = spi_flash_write(flash, offset, len, buf);
+
+ printf("SF: %zu bytes @ %#x %s: %s\n", (size_t)len, (u32)offset,
+ read ? "Read" : "Written", ret ? "ERROR" : "OK");
+ }
unmap_physmem(buf, len);
- if (ret) {
- printf("SPI flash %s failed\n", argv[0]);
- return 1;
- }
-
- return 0;
+ return ret == 0 ? 0 : 1;
}
static int do_spi_flash_erase(int argc, char * const argv[])
/* Consistency checking */
if (offset + len > flash->size) {
printf("ERROR: attempting %s past flash size (%#x)\n",
- argv[0], flash->size);
+ argv[0], flash->size);
return 1;
}
ret = spi_flash_erase(flash, offset, len);
+ printf("SF: %zu bytes @ %#x Erased: %s\n", (size_t)len, (u32)offset,
+ ret ? "ERROR" : "OK");
+
+ return ret == 0 ? 0 : 1;
+}
+
+#ifdef CONFIG_CMD_SF_TEST
+enum {
+ STAGE_ERASE,
+ STAGE_CHECK,
+ STAGE_WRITE,
+ STAGE_READ,
+
+ STAGE_COUNT,
+};
+
+static char *stage_name[STAGE_COUNT] = {
+ "erase",
+ "check",
+ "write",
+ "read",
+};
+
+struct test_info {
+ int stage;
+ int bytes;
+ unsigned base_ms;
+ unsigned time_ms[STAGE_COUNT];
+};
+
+static void show_time(struct test_info *test, int stage)
+{
+ uint64_t speed; /* KiB/s */
+ int bps; /* Bits per second */
+
+ speed = (long long)test->bytes * 1000;
+ if (test->time_ms[stage])
+ do_div(speed, test->time_ms[stage] * 1024);
+ bps = speed * 8;
+
+ printf("%d %s: %d ticks, %d KiB/s %d.%03d Mbps\n", stage,
+ stage_name[stage], test->time_ms[stage],
+ (int)speed, bps / 1000, bps % 1000);
+}
+
+static void spi_test_next_stage(struct test_info *test)
+{
+ test->time_ms[test->stage] = get_timer(test->base_ms);
+ show_time(test, test->stage);
+ test->base_ms = get_timer(0);
+ test->stage++;
+}
+
+/**
+ * Run a test on the SPI flash
+ *
+ * @param flash SPI flash to use
+ * @param buf Source buffer for data to write
+ * @param len Size of data to read/write
+ * @param offset Offset within flash to check
+ * @param vbuf Verification buffer
+ * @return 0 if ok, -1 on error
+ */
+static int spi_flash_test(struct spi_flash *flash, uint8_t *buf, ulong len,
+ ulong offset, uint8_t *vbuf)
+{
+ struct test_info test;
+ int i;
+
+ printf("SPI flash test:\n");
+ memset(&test, '\0', sizeof(test));
+ test.base_ms = get_timer(0);
+ test.bytes = len;
+ if (spi_flash_erase(flash, offset, len)) {
+ printf("Erase failed\n");
+ return -1;
+ }
+ spi_test_next_stage(&test);
+
+ if (spi_flash_read(flash, offset, len, vbuf)) {
+ printf("Check read failed\n");
+ return -1;
+ }
+ for (i = 0; i < len; i++) {
+ if (vbuf[i] != 0xff) {
+ printf("Check failed at %d\n", i);
+ print_buffer(i, vbuf + i, 1,
+ min_t(uint, len - i, 0x40), 0);
+ return -1;
+ }
+ }
+ spi_test_next_stage(&test);
+
+ if (spi_flash_write(flash, offset, len, buf)) {
+ printf("Write failed\n");
+ return -1;
+ }
+ memset(vbuf, '\0', len);
+ spi_test_next_stage(&test);
+
+ if (spi_flash_read(flash, offset, len, vbuf)) {
+ printf("Read failed\n");
+ return -1;
+ }
+ spi_test_next_stage(&test);
+
+ for (i = 0; i < len; i++) {
+ if (buf[i] != vbuf[i]) {
+ printf("Verify failed at %d, good data:\n", i);
+ print_buffer(i, buf + i, 1,
+ min_t(uint, len - i, 0x40), 0);
+ printf("Bad data:\n");
+ print_buffer(i, vbuf + i, 1,
+ min_t(uint, len - i, 0x40), 0);
+ return -1;
+ }
+ }
+ printf("Test passed\n");
+ for (i = 0; i < STAGE_COUNT; i++)
+ show_time(&test, i);
+
+ return 0;
+}
+
+static int do_spi_flash_test(int argc, char * const argv[])
+{
+ unsigned long offset;
+ unsigned long len;
+ uint8_t *buf, *from;
+ char *endp;
+ uint8_t *vbuf;
+ int ret;
+
+ if (argc < 3)
+ return -1;
+ offset = simple_strtoul(argv[1], &endp, 16);
+ if (*argv[1] == 0 || *endp != 0)
+ return -1;
+ len = simple_strtoul(argv[2], &endp, 16);
+ if (*argv[2] == 0 || *endp != 0)
+ return -1;
+
+ vbuf = malloc(len);
+ if (!vbuf) {
+ printf("Cannot allocate memory (%lu bytes)\n", len);
+ return 1;
+ }
+ buf = malloc(len);
+ if (!buf) {
+ free(vbuf);
+ printf("Cannot allocate memory (%lu bytes)\n", len);
+ return 1;
+ }
+
+ from = map_sysmem(CONFIG_SYS_TEXT_BASE, 0);
+ memcpy(buf, from, len);
+ ret = spi_flash_test(flash, buf, len, offset, vbuf);
+ free(vbuf);
+ free(buf);
if (ret) {
- printf("SPI flash %s failed\n", argv[0]);
+ printf("Test failed\n");
return 1;
}
return 0;
}
+#endif /* CONFIG_CMD_SF_TEST */
-static int do_spi_flash(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
+static int do_spi_flash(cmd_tbl_t *cmdtp, int flag, int argc,
+ char * const argv[])
{
const char *cmd;
int ret;
ret = do_spi_flash_read_write(argc, argv);
else if (strcmp(cmd, "erase") == 0)
ret = do_spi_flash_erase(argc, argv);
+#ifdef CONFIG_CMD_SF_TEST
+ else if (!strcmp(cmd, "test"))
+ ret = do_spi_flash_test(argc, argv);
+#endif
else
ret = -1;
return CMD_RET_USAGE;
}
+#ifdef CONFIG_CMD_SF_TEST
+#define SF_TEST_HELP "\nsf test offset len " \
+ "- run a very basic destructive test"
+#else
+#define SF_TEST_HELP
+#endif
+
U_BOOT_CMD(
sf, 5, 1, do_spi_flash,
"SPI flash sub-system",
"probe [[bus:]cs] [hz] [mode] - init flash device on given SPI bus\n"
" and chip select\n"
- "sf read addr offset len - read `len' bytes starting at\n"
+ "sf read addr offset len - read `len' bytes starting at\n"
" `offset' to memory at `addr'\n"
"sf write addr offset len - write `len' bytes from memory\n"
" at `addr' to flash at `offset'\n"
" `+len' round up `len' to block size\n"
"sf update addr offset len - erase and write `len' bytes from memory\n"
" at `addr' to flash at `offset'"
+ SF_TEST_HELP
);