| #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, "Macronix MXxxL25635F"}, | |
| { 0xc2201a, 0x04000000, FL_ERASE_ALL | FL_CAN_4B, "Macronix MXxxL51235F"}, | |
| { 0xef4018, 0x01000000, FL_ERASE_ALL, "Winbond W25Q128BV" }, | |
| { 0xef4019, 0x02000000, FL_ERASE_ALL | FL_CAN_4B | FL_ERASE_64K|FL_ERASE_BULK, "Winbond W25Q256BV"}, | |
| { 0x20ba20, 0x04000000, FL_ERASE_4K | FL_ERASE_64K | FL_CAN_4B | | |
| FL_ERASE_BULK | FL_MICRON_BUGS, | |
| "Micron N25Qx512Ax" }, | |
| { 0x20ba19, 0x02000000, FL_ERASE_4K | FL_ERASE_64K | FL_CAN_4B | | |
| FL_ERASE_BULK | FL_MICRON_BUGS, | |
| "Micron N25Q256Ax" }, | |
| { 0x1940ef, 0x02000000, FL_ERASE_4K | FL_ERASE_64K | FL_CAN_4B | | |
| FL_ERASE_BULK | FL_MICRON_BUGS, | |
| "Micron N25Qx256Ax" }, | |
| { 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 */ | |
| }; | |
| bool libflash_debug; | |
| int fl_read_stat(struct spi_flash_ctrl *ct, uint8_t *stat) | |
| { | |
| return ct->cmd_rd(ct, CMD_RDSR, false, 0, stat, 1); | |
| } | |
| static void fl_micron_status(struct spi_flash_ctrl *ct) | |
| { | |
| uint8_t flst; | |
| /* | |
| * After a success status on a write or erase, we | |
| * need to do that command or some chip variants will | |
| * lock | |
| */ | |
| ct->cmd_rd(ct, CMD_MIC_RDFLST, false, 0, &flst, 1); | |
| } | |
| /* Synchronous write completion, probably need a yield hook */ | |
| int fl_sync_wait_idle(struct spi_flash_ctrl *ct) | |
| { | |
| uint8_t stat; | |
| int rc; | |
| /* XXX Add timeout */ | |
| for (;;) { | |
| rc = fl_read_stat(ct, &stat); | |
| if (rc) return rc; | |
| if (!(stat & STAT_WIP)) { | |
| if (ct->finfo->flags & FL_MICRON_BUGS) | |
| fl_micron_status(ct); | |
| return 0; | |
| } | |
| } | |
| /* return FLASH_ERR_WIP_TIMEOUT; */ | |
| } | |
| /* Exported for internal use */ | |
| int fl_wren(struct spi_flash_ctrl *ct) | |
| { | |
| int i, rc; | |
| uint8_t stat; | |
| /* Some flashes need it to be hammered */ | |
| for (i = 0; i < 1000; i++) { | |
| rc = ct->cmd_wr(ct, CMD_WREN, false, 0, NULL, 0); | |
| if (rc) return rc; | |
| rc = fl_read_stat(ct, &stat); | |
| if (rc) return rc; | |
| if (stat & STAT_WIP) { | |
| FL_ERR("LIBFLASH: WREN has WIP status set !\n"); | |
| rc = fl_sync_wait_idle(ct); | |
| if (rc) | |
| return rc; | |
| continue; | |
| } | |
| if (stat & STAT_WEN) | |
| return 0; | |
| } | |
| return FLASH_ERR_WREN_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(ct); | |
| 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(ct); | |
| 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|FL_ERASE_BULK))) | |
| 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(ct); | |
| if (rc) return rc; | |
| if (c->info.flags & FL_ERASE_CHIP) | |
| rc = ct->cmd_wr(ct, CMD_CE, false, 0, NULL, 0); | |
| else | |
| rc = ct->cmd_wr(ct, CMD_MIC_BULK_ERASE, false, 0, NULL, 0); | |
| if (rc) | |
| return rc; | |
| /* Wait for write complete */ | |
| return fl_sync_wait_idle(ct); | |
| } | |
| 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(ct); | |
| 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(ct); | |
| } | |
| 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 fl_chip_id(struct spi_flash_ctrl *ct, uint8_t *id_buf, | |
| uint32_t *id_size) | |
| { | |
| int rc; | |
| uint8_t stat; | |
| /* Check initial status */ | |
| rc = fl_read_stat(ct, &stat); | |
| if (rc) | |
| return rc; | |
| /* If stuck writing, wait for idle */ | |
| if (stat & STAT_WIP) { | |
| FL_ERR("LIBFLASH: Flash in writing state ! Waiting...\n"); | |
| rc = fl_sync_wait_idle(ct); | |
| if (rc) | |
| return rc; | |
| } else | |
| FL_DBG("LIBFLASH: Init status: %02x\n", stat); | |
| /* Fallback to get ID manually */ | |
| rc = ct->cmd_rd(ct, CMD_RDID, false, 0, id_buf, 3); | |
| if (rc) | |
| return rc; | |
| *id_size = 3; | |
| return 0; | |
| } | |
| static int flash_identify(struct flash_chip *c) | |
| { | |
| struct spi_flash_ctrl *ct = c->ctrl; | |
| const struct flash_info *info; | |
| 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); | |
| } else | |
| rc = fl_chip_id(ct, id, &id_size); | |
| if (rc) | |
| return rc; | |
| 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; | |
| ct->finfo = &c->info; | |
| /* | |
| * Let controller know about our settings and possibly | |
| * override them | |
| */ | |
| if (ct->setup) { | |
| rc = ct->setup(ct, &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; | |
| int rc; | |
| /* Some flash chips want this */ | |
| rc = fl_wren(ct); | |
| if (rc) { | |
| FL_ERR("LIBFLASH: Error %d enabling write for set_4b\n", rc); | |
| /* Ignore the error & move on (could be wrprotect chip) */ | |
| } | |
| /* Ignore error in case chip is write protected */ | |
| 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); | |
| } | |