Merge branch 'master' of git://git.denx.de/u-boot-x86
[oweals/u-boot.git] / common / cmd_sf.c
index 11a491df769fc81206d9dfe82584b443e468c029..98e4162114d689dacb903cd4943f39b6e1bee4a7 100644 (file)
@@ -6,6 +6,7 @@
  */
 
 #include <common.h>
+#include <malloc.h>
 #include <spi_flash.h>
 
 #include <asm/io.h>
 #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
 
 static struct spi_flash *flash;
 
@@ -53,7 +60,7 @@ static int sf_parse_len_arg(char *arg, ulong *len)
                return -1;
 
        if (round_up_len && flash->sector_size > 0)
-               *len = ROUND(len_arg - 1, flash->sector_size);
+               *len = ROUND(len_arg, flash->sector_size);
        else
                *len = len_arg;
 
@@ -62,27 +69,26 @@ static int sf_parse_len_arg(char *arg, ulong *len)
 
 static int do_spi_flash_probe(int argc, char * const argv[])
 {
-       unsigned int bus = 0;
-       unsigned int cs;
+       unsigned int bus = CONFIG_SF_DEFAULT_BUS;
+       unsigned int cs = CONFIG_SF_DEFAULT_CS;
        unsigned int speed = CONFIG_SF_DEFAULT_SPEED;
        unsigned int mode = CONFIG_SF_DEFAULT_MODE;
        char *endp;
        struct spi_flash *new;
 
-       if (argc < 2)
-               return -1;
-
-       cs = simple_strtoul(argv[1], &endp, 0);
-       if (*argv[1] == 0 || (*endp != 0 && *endp != ':'))
-               return -1;
-       if (*endp == ':') {
-               if (endp[1] == 0)
-                       return -1;
-
-               bus = cs;
-               cs = simple_strtoul(endp + 1, &endp, 0);
-               if (*endp != 0)
+       if (argc >= 2) {
+               cs = simple_strtoul(argv[1], &endp, 0);
+               if (*argv[1] == 0 || (*endp != 0 && *endp != ':'))
                        return -1;
+               if (*endp == ':') {
+                       if (endp[1] == 0)
+                               return -1;
+
+                       bus = cs;
+                       cs = simple_strtoul(endp + 1, &endp, 0);
+                       if (*endp != 0)
+                               return -1;
+               }
        }
 
        if (argc >= 3) {
@@ -109,6 +115,80 @@ static int do_spi_flash_probe(int argc, char * const argv[])
        return 0;
 }
 
+/**
+ * Write a block of data to SPI flash, first checking if it is different from
+ * what is already there.
+ *
+ * If the data being written is the same, then *skipped is incremented by len.
+ *
+ * @param flash                flash context pointer
+ * @param offset       flash offset to write
+ * @param len          number of bytes to write
+ * @param buf          buffer to write from
+ * @param cmp_buf      read buffer to use to compare data
+ * @param skipped      Count of skipped data (incremented by this function)
+ * @return NULL if OK, else a string containing the stage which failed
+ */
+static const char *spi_flash_update_block(struct spi_flash *flash, u32 offset,
+               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))
+               return "read";
+       if (memcmp(cmp_buf, buf, len) == 0) {
+               debug("Skip region %x size %zx: no change\n",
+                       offset, len);
+               *skipped += len;
+               return NULL;
+       }
+       if (spi_flash_erase(flash, offset, len))
+               return "erase";
+       if (spi_flash_write(flash, offset, len, buf))
+               return "write";
+       return NULL;
+}
+
+/**
+ * Update an area of SPI flash by erasing and writing any blocks which need
+ * to change. Existing blocks with the correct data are left unchanged.
+ *
+ * @param flash                flash context pointer
+ * @param offset       flash offset to write
+ * @param len          number of bytes to write
+ * @param buf          buffer to write from
+ * @return 0 if ok, 1 on error
+ */
+static int spi_flash_update(struct spi_flash *flash, u32 offset,
+               size_t len, const char *buf)
+{
+       const char *err_oper = NULL;
+       char *cmp_buf;
+       const char *end = buf + len;
+       size_t todo;            /* number of bytes to do in this pass */
+       size_t skipped = 0;     /* statistics */
+
+       cmp_buf = malloc(flash->sector_size);
+       if (cmp_buf) {
+               for (; buf < end && !err_oper; buf += todo, offset += todo) {
+                       todo = min(end - buf, flash->sector_size);
+                       err_oper = spi_flash_update_block(flash, offset, todo,
+                                       buf, cmp_buf, &skipped);
+               }
+       } else {
+               err_oper = "malloc";
+       }
+       free(cmp_buf);
+       if (err_oper) {
+               printf("SPI flash failed in %s step\n", err_oper);
+               return 1;
+       }
+       printf("%zu bytes written, %zu bytes skipped\n", len - skipped,
+              skipped);
+
+       return 0;
+}
+
 static int do_spi_flash_read_write(int argc, char * const argv[])
 {
        unsigned long addr;
@@ -137,7 +217,9 @@ static int do_spi_flash_read_write(int argc, char * const argv[])
                return 1;
        }
 
-       if (strcmp(argv[0], "read") == 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);
@@ -203,7 +285,8 @@ static int do_spi_flash(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[
                return 1;
        }
 
-       if (strcmp(cmd, "read") == 0 || strcmp(cmd, "write") == 0)
+       if (strcmp(cmd, "read") == 0 || strcmp(cmd, "write") == 0 ||
+           strcmp(cmd, "update") == 0)
                ret = do_spi_flash_read_write(argc, argv);
        else if (strcmp(cmd, "erase") == 0)
                ret = do_spi_flash_erase(argc, argv);
@@ -221,12 +304,14 @@ usage:
 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"
+       "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"
        "                                 `offset' to memory at `addr'\n"
        "sf write addr offset len       - write `len' bytes from memory\n"
        "                                 at `addr' to flash at `offset'\n"
        "sf erase offset [+]len         - erase `len' bytes from `offset'\n"
-       "                                 `+len' round up `len' to block size"
+       "                                 `+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'"
 );