summaryrefslogtreecommitdiffstats
path: root/libflash/libflash.c
diff options
context:
space:
mode:
Diffstat (limited to 'libflash/libflash.c')
-rw-r--r--libflash/libflash.c636
1 files changed, 636 insertions, 0 deletions
diff --git a/libflash/libflash.c b/libflash/libflash.c
new file mode 100644
index 00000000..a3e6ff29
--- /dev/null
+++ b/libflash/libflash.c
@@ -0,0 +1,636 @@
+/* Copyright 2013-2014 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "libflash.h"
+#include "libflash-priv.h"
+
+static const struct flash_info flash_info[] = {
+ { 0xc22019, 0x02000000, FL_ERASE_ALL | FL_CAN_4B, "MXxxL25635F"},
+ { 0xc2201a, 0x04000000, FL_ERASE_ALL | FL_CAN_4B, "MXxxL51235F"},
+ { 0xef4018, 0x01000000, FL_ERASE_ALL, "W25Q128BV" },
+ { 0x55aa55, 0x00100000, FL_ERASE_ALL | FL_CAN_4B, "TEST_FLASH"},
+};
+
+struct flash_chip {
+ struct spi_flash_ctrl *ctrl; /* Controller */
+ struct flash_info info; /* Flash info */
+ uint32_t tsize; /* Corrected flash size */
+ uint32_t min_erase_mask; /* Minimum erase size */
+ bool mode_4b; /* Flash currently in 4b mode */
+ struct flash_req *cur_req; /* Current request */
+ void *smart_buf; /* Buffer for smart writes */
+};
+
+static int fl_read_stat(struct flash_chip *c, uint8_t *stat)
+{
+ struct spi_flash_ctrl *ct = c->ctrl;
+
+ return ct->cmd_rd(ct, CMD_RDSR, false, 0, stat, 1);
+}
+
+static int fl_wren(struct flash_chip *c)
+{
+ struct spi_flash_ctrl *ct = c->ctrl;
+ uint8_t stat;
+ int i, rc;
+
+ /* Some flashes need it to be hammered */
+ for (i = 0; i < 10; i++) {
+ rc = ct->cmd_wr(ct, CMD_WREN, false, 0, NULL, 0);
+ if (rc) return rc;
+ rc = fl_read_stat(c, &stat);
+ if (rc) return rc;
+ if (stat & STAT_WEN)
+ return 0;
+ }
+ return FLASH_ERR_WREN_TIMEOUT;
+}
+
+/* Synchronous write completion, probably need a yield hook */
+static int fl_sync_wait_idle(struct flash_chip *c)
+{
+ int rc;
+ uint8_t stat;
+
+ /* XXX Add timeout */
+ for (;;) {
+ rc = fl_read_stat(c, &stat);
+ if (rc) return rc;
+ if (!(stat & STAT_WIP))
+ return 0;
+ }
+ /* return FLASH_ERR_WIP_TIMEOUT; */
+}
+
+int flash_read(struct flash_chip *c, uint32_t pos, void *buf, uint32_t len)
+{
+ struct spi_flash_ctrl *ct = c->ctrl;
+
+ /* XXX Add sanity/bound checking */
+
+ /*
+ * If the controller supports read and either we are in 3b mode
+ * or we are in 4b *and* the controller supports it, then do a
+ * high level read.
+ */
+ if ((!c->mode_4b || ct->set_4b) && ct->read)
+ return ct->read(ct, pos, buf, len);
+
+ /* Otherwise, go manual if supported */
+ if (!ct->cmd_rd)
+ return FLASH_ERR_CTRL_CMD_UNSUPPORTED;
+ return ct->cmd_rd(ct, CMD_READ, true, pos, buf, len);
+}
+
+static void fl_get_best_erase(struct flash_chip *c, uint32_t dst, uint32_t size,
+ uint32_t *chunk, uint8_t *cmd)
+{
+ /* Smaller than 32k, use 4k */
+ if ((dst & 0x7fff) || (size < 0x8000)) {
+ *chunk = 0x1000;
+ *cmd = CMD_SE;
+ return;
+ }
+ /* Smaller than 64k and 32k is supported, use it */
+ if ((c->info.flags & FL_ERASE_32K) &&
+ ((dst & 0xffff) || (size < 0x10000))) {
+ *chunk = 0x8000;
+ *cmd = CMD_BE32K;
+ return;
+ }
+ /* If 64K is not supported, use whatever smaller size is */
+ if (!(c->info.flags & FL_ERASE_64K)) {
+ if (c->info.flags & FL_ERASE_32K) {
+ *chunk = 0x8000;
+ *cmd = CMD_BE32K;
+ } else {
+ *chunk = 0x1000;
+ *cmd = CMD_SE;
+ }
+ return;
+ }
+ /* Allright, let's go for 64K */
+ *chunk = 0x10000;
+ *cmd = CMD_BE;
+}
+
+int flash_erase(struct flash_chip *c, uint32_t dst, uint32_t size)
+{
+ struct spi_flash_ctrl *ct = c->ctrl;
+ uint32_t chunk;
+ uint8_t cmd;
+ int rc;
+
+ /* Some sanity checking */
+ if (((dst + size) <= dst) || !size || (dst + size) > c->tsize)
+ return FLASH_ERR_PARM_ERROR;
+
+ /* Check boundaries fit erase blocks */
+ if ((dst | size) & c->min_erase_mask)
+ return FLASH_ERR_ERASE_BOUNDARY;
+
+ FL_DBG("LIBFLASH: Erasing 0x%08x..0%08x...\n", dst, dst + size);
+
+ /* Use controller erase if supported */
+ if (ct->erase)
+ return ct->erase(ct, dst, size);
+
+ /* Allright, loop as long as there's something to erase */
+ while(size) {
+ /* How big can we make it based on alignent & size */
+ fl_get_best_erase(c, dst, size, &chunk, &cmd);
+
+ /* Poke write enable */
+ rc = fl_wren(c);
+ if (rc)
+ return rc;
+
+ /* Send erase command */
+ rc = ct->cmd_wr(ct, cmd, true, dst, NULL, 0);
+ if (rc)
+ return rc;
+
+ /* Wait for write complete */
+ rc = fl_sync_wait_idle(c);
+ if (rc)
+ return rc;
+
+ size -= chunk;
+ dst += chunk;
+ }
+ return 0;
+}
+
+int flash_erase_chip(struct flash_chip *c)
+{
+ struct spi_flash_ctrl *ct = c->ctrl;
+ int rc;
+
+ /* XXX TODO: Fallback to using normal erases */
+ if (!(c->info.flags & FL_ERASE_CHIP))
+ return FLASH_ERR_CHIP_ER_NOT_SUPPORTED;
+
+ FL_DBG("LIBFLASH: Erasing chip...\n");
+
+ /* Use controller erase if supported */
+ if (ct->erase)
+ return ct->erase(ct, 0, 0xffffffff);
+
+ rc = fl_wren(c);
+ if (rc) return rc;
+
+ rc = ct->cmd_wr(ct, CMD_CE, false, 0, NULL, 0);
+ if (rc)
+ return rc;
+
+ /* Wait for write complete */
+ return fl_sync_wait_idle(c);
+}
+
+static int fl_wpage(struct flash_chip *c, uint32_t dst, const void *src,
+ uint32_t size)
+{
+ struct spi_flash_ctrl *ct = c->ctrl;
+ int rc;
+
+ if (size < 1 || size > 0x100)
+ return FLASH_ERR_BAD_PAGE_SIZE;
+
+ rc = fl_wren(c);
+ if (rc) return rc;
+
+ rc = ct->cmd_wr(ct, CMD_PP, true, dst, src, size);
+ if (rc)
+ return rc;
+
+ /* Wait for write complete */
+ return fl_sync_wait_idle(c);
+}
+
+int flash_write(struct flash_chip *c, uint32_t dst, const void *src,
+ uint32_t size, bool verify)
+{
+ struct spi_flash_ctrl *ct = c->ctrl;
+ uint32_t todo = size;
+ uint32_t d = dst;
+ const void *s = src;
+ uint8_t vbuf[0x100];
+ int rc;
+
+ /* Some sanity checking */
+ if (((dst + size) <= dst) || !size || (dst + size) > c->tsize)
+ return FLASH_ERR_PARM_ERROR;
+
+ FL_DBG("LIBFLASH: Writing to 0x%08x..0%08x...\n", dst, dst + size);
+
+ /*
+ * If the controller supports write and either we are in 3b mode
+ * or we are in 4b *and* the controller supports it, then do a
+ * high level write.
+ */
+ if ((!c->mode_4b || ct->set_4b) && ct->write) {
+ rc = ct->write(ct, dst, src, size);
+ if (rc)
+ return rc;
+ goto writing_done;
+ }
+
+ /* Otherwise, go manual if supported */
+ if (!ct->cmd_wr)
+ return FLASH_ERR_CTRL_CMD_UNSUPPORTED;
+
+ /* Iterate for each page to write */
+ while(todo) {
+ uint32_t chunk;
+
+ /* Handle misaligned start */
+ chunk = 0x100 - (d & 0xff);
+ if (chunk > 0x100)
+ chunk = 0x100;
+ if (chunk > todo)
+ chunk = todo;
+
+ rc = fl_wpage(c, d, s, chunk);
+ if (rc) return rc;
+ d += chunk;
+ s += chunk;
+ todo -= chunk;
+ }
+
+ writing_done:
+ if (!verify)
+ return 0;
+
+ /* Verify */
+ FL_DBG("LIBFLASH: Verifying...\n");
+
+ while(size) {
+ uint32_t chunk;
+
+ chunk = sizeof(vbuf);
+ if (chunk > size)
+ chunk = size;
+ rc = flash_read(c, dst, vbuf, chunk);
+ if (rc) return rc;
+ if (memcmp(vbuf, src, chunk)) {
+ FL_ERR("LIBFLASH: Miscompare at 0x%08x\n", dst);
+ return FLASH_ERR_VERIFY_FAILURE;
+ }
+ dst += chunk;
+ src += chunk;
+ size -= chunk;
+ }
+ return 0;
+}
+
+enum sm_comp_res {
+ sm_no_change,
+ sm_need_write,
+ sm_need_erase,
+};
+
+static enum sm_comp_res flash_smart_comp(struct flash_chip *c,
+ const void *src,
+ uint32_t offset, uint32_t size)
+{
+ uint8_t *b = c->smart_buf + offset;
+ const uint8_t *s = src;
+ bool is_same = true;
+ uint32_t i;
+
+ /* SRC DEST NEED_ERASE
+ * 0 1 0
+ * 1 1 0
+ * 0 0 0
+ * 1 0 1
+ */
+ for (i = 0; i < size; i++) {
+ /* Any bit need to be set, need erase */
+ if (s[i] & ~b[i])
+ return sm_need_erase;
+ if (is_same && (b[i] != s[i]))
+ is_same = false;
+ }
+ return is_same ? sm_no_change : sm_need_write;
+}
+
+int flash_smart_write(struct flash_chip *c, uint32_t dst, const void *src,
+ uint32_t size)
+{
+ uint32_t er_size = c->min_erase_mask + 1;
+ uint32_t end = dst + size;
+ int rc;
+
+ /* Some sanity checking */
+ if (end <= dst || !size || end > c->tsize) {
+ FL_DBG("LIBFLASH: Smart write param error\n");
+ return FLASH_ERR_PARM_ERROR;
+ }
+
+ FL_DBG("LIBFLASH: Smart writing to 0x%08x..0%08x...\n",
+ dst, dst + size);
+
+ /* As long as we have something to write ... */
+ while(dst < end) {
+ uint32_t page, off, chunk;
+ enum sm_comp_res sr;
+
+ /* Figure out which erase page we are in and read it */
+ page = dst & ~c->min_erase_mask;
+ off = dst & c->min_erase_mask;
+ FL_DBG("LIBFLASH: reading page 0x%08x..0x%08x...",
+ page, page + er_size);
+ rc = flash_read(c, page, c->smart_buf, er_size);
+ if (rc) {
+ FL_DBG(" error %d!\n", rc);
+ return rc;
+ }
+
+ /* Locate the chunk of data we are working on */
+ chunk = er_size - off;
+ if (size < chunk)
+ chunk = size;
+
+ /* Compare against what we are writing and ff */
+ sr = flash_smart_comp(c, src, off, chunk);
+ switch(sr) {
+ case sm_no_change:
+ /* Identical, skip it */
+ FL_DBG(" same !\n");
+ break;
+ case sm_need_write:
+ /* Just needs writing over */
+ FL_DBG(" need write !\n");
+ rc = flash_write(c, dst, src, chunk, true);
+ if (rc) {
+ FL_DBG("LIBFLASH: Write error %d !\n", rc);
+ return rc;
+ }
+ break;
+ case sm_need_erase:
+ FL_DBG(" need erase !\n");
+ rc = flash_erase(c, page, er_size);
+ if (rc) {
+ FL_DBG("LIBFLASH: erase error %d !\n", rc);
+ return rc;
+ }
+ /* Then update the portion of the buffer and write the block */
+ memcpy(c->smart_buf + off, src, chunk);
+ rc = flash_write(c, page, c->smart_buf, er_size, true);
+ if (rc) {
+ FL_DBG("LIBFLASH: write error %d !\n", rc);
+ return rc;
+ }
+ break;
+ }
+ dst += chunk;
+ src += chunk;
+ size -= chunk;
+ }
+ return 0;
+}
+
+
+static int flash_identify(struct flash_chip *c)
+{
+ struct spi_flash_ctrl *ct = c->ctrl;
+ const struct flash_info *info = NULL;
+ uint32_t iid, id_size;
+#define MAX_ID_SIZE 16
+ uint8_t id[MAX_ID_SIZE];
+ int rc, i;
+
+ if (ct->chip_id) {
+ /* High level controller interface */
+ id_size = MAX_ID_SIZE;
+ rc = ct->chip_id(ct, id, &id_size);
+ if (rc)
+ return rc;
+ } else {
+ /* Fallback to get ID manually */
+ rc = ct->cmd_rd(ct, CMD_RDID, false, 0, id, 3);
+ if (rc)
+ return rc;
+ id_size = 3;
+ }
+ if (id_size < 3)
+ return FLASH_ERR_CHIP_UNKNOWN;
+
+ /* Convert to a dword for lookup */
+ iid = id[0];
+ iid = (iid << 8) | id[1];
+ iid = (iid << 8) | id[2];
+
+ FL_DBG("LIBFLASH: Flash ID: %02x.%02x.%02x (%06x)\n",
+ id[0], id[1], id[2], iid);
+
+ /* Lookup in flash_info */
+ for (i = 0; i < ARRAY_SIZE(flash_info); i++) {
+ info = &flash_info[i];
+ if (info->id == iid)
+ break;
+ }
+ if (info->id != iid)
+ return FLASH_ERR_CHIP_UNKNOWN;
+
+ c->info = *info;
+ c->tsize = info->size;
+
+ /*
+ * Let controller know about our settings and possibly
+ * override them
+ */
+ if (ct->setup) {
+ rc = ct->setup(ct, &c->info, &c->tsize);
+ if (rc)
+ return rc;
+ }
+
+ /* Calculate min erase granularity */
+ if (c->info.flags & FL_ERASE_4K)
+ c->min_erase_mask = 0xfff;
+ else if (c->info.flags & FL_ERASE_32K)
+ c->min_erase_mask = 0x7fff;
+ else if (c->info.flags & FL_ERASE_64K)
+ c->min_erase_mask = 0xffff;
+ else {
+ /* No erase size ? oops ... */
+ FL_ERR("LIBFLASH: No erase sizes !\n");
+ return FLASH_ERR_CTRL_CONFIG_MISMATCH;
+ }
+
+ FL_DBG("LIBFLASH: Found chip %s size %dM erase granule: %dK\n",
+ c->info.name, c->tsize >> 20, (c->min_erase_mask + 1) >> 10);
+
+ return 0;
+}
+
+static int flash_set_4b(struct flash_chip *c, bool enable)
+{
+ struct spi_flash_ctrl *ct = c->ctrl;
+
+ return ct->cmd_wr(ct, enable ? CMD_EN4B : CMD_EX4B, false, 0, NULL, 0);
+}
+
+int flash_force_4b_mode(struct flash_chip *c, bool enable_4b)
+{
+ struct spi_flash_ctrl *ct = c->ctrl;
+ int rc;
+
+ /*
+ * We only allow force 4b if both controller and flash do 4b
+ * as this is mainly used if a 3rd party tries to directly
+ * access a direct mapped read region
+ */
+ if (enable_4b && !((c->info.flags & FL_CAN_4B) && ct->set_4b))
+ return FLASH_ERR_4B_NOT_SUPPORTED;
+
+ /* Only send to flash directly on controllers that implement
+ * the low level callbacks
+ */
+ if (ct->cmd_wr) {
+ rc = flash_set_4b(c, enable_4b);
+ if (rc)
+ return rc;
+ }
+
+ /* Then inform the controller */
+ if (ct->set_4b)
+ rc = ct->set_4b(ct, enable_4b);
+ return rc;
+}
+
+static int flash_configure(struct flash_chip *c)
+{
+ struct spi_flash_ctrl *ct = c->ctrl;
+ int rc;
+
+ /* Crop flash size if necessary */
+ if (c->tsize > 0x01000000 && !(c->info.flags & FL_CAN_4B)) {
+ FL_ERR("LIBFLASH: Flash chip cropped to 16M, no 4b mode\n");
+ c->tsize = 0x01000000;
+ }
+
+ /* If flash chip > 16M, enable 4b mode */
+ if (c->tsize > 0x01000000) {
+ FL_DBG("LIBFLASH: Flash >16MB, enabling 4B mode...\n");
+
+ /* Set flash to 4b mode if we can */
+ if (ct->cmd_wr) {
+ rc = flash_set_4b(c, true);
+ if (rc) {
+ FL_ERR("LIBFLASH: Failed to set flash 4b mode\n");
+ return rc;
+ }
+ }
+
+
+ /* Set controller to 4b mode if supported */
+ if (ct->set_4b) {
+ FL_DBG("LIBFLASH: Enabling controller 4B mode...\n");
+ rc = ct->set_4b(ct, true);
+ if (rc) {
+ FL_ERR("LIBFLASH: Failed"
+ " to set controller 4b mode\n");
+ return rc;
+ }
+ }
+ } else {
+ FL_DBG("LIBFLASH: Flash <=16MB, disabling 4B mode...\n");
+
+ /*
+ * If flash chip supports 4b mode, make sure we disable
+ * it in case it was left over by the previous user
+ */
+ if (c->info.flags & FL_CAN_4B) {
+ rc = flash_set_4b(c, false);
+ if (rc) {
+ FL_ERR("LIBFLASH: Failed to"
+ " clear flash 4b mode\n");
+ return rc;
+ }
+ }
+
+ /* Set controller to 3b mode if mode switch is supported */
+ if (ct->set_4b) {
+ FL_DBG("LIBFLASH: Disabling controller 4B mode...\n");
+ rc = ct->set_4b(ct, false);
+ if (rc) {
+ FL_ERR("LIBFLASH: Failed to"
+ " clear controller 4b mode\n");
+ return rc;
+ }
+ }
+ }
+ return 0;
+}
+
+int flash_get_info(struct flash_chip *chip, const char **name,
+ uint32_t *total_size, uint32_t *erase_granule)
+{
+ if (name)
+ *name = chip->info.name;
+ if (total_size)
+ *total_size = chip->tsize;
+ if (erase_granule)
+ *erase_granule = chip->min_erase_mask + 1;
+ return 0;
+}
+
+int flash_init(struct spi_flash_ctrl *ctrl, struct flash_chip **flash)
+{
+ struct flash_chip *c;
+ int rc;
+
+ *flash = NULL;
+ c = malloc(sizeof(struct flash_chip));
+ if (!c)
+ return FLASH_ERR_MALLOC_FAILED;
+ memset(c, 0, sizeof(*c));
+ c->ctrl = ctrl;
+
+ rc = flash_identify(c);
+ if (rc) {
+ FL_ERR("LIBFLASH: Flash identification failed\n");
+ goto bail;
+ }
+ c->smart_buf = malloc(c->min_erase_mask + 1);
+ if (!c->smart_buf) {
+ FL_ERR("LIBFLASH: Failed to allocate smart buffer !\n");
+ rc = FLASH_ERR_MALLOC_FAILED;
+ goto bail;
+ }
+ rc = flash_configure(c);
+ if (rc)
+ FL_ERR("LIBFLASH: Flash configuration failed\n");
+ bail:
+ if (rc) {
+ free(c);
+ return rc;
+ }
+ *flash = c;
+ return 0;
+}
+
+void flash_exit(struct flash_chip *chip)
+{
+ /* XXX Make sure we are idle etc... */
+ free(chip);
+}
+
OpenPOWER on IntegriCloud