877 lines
20 KiB
C
877 lines
20 KiB
C
/* 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 <inttypes.h>
|
|
|
|
#include "libflash.h"
|
|
#include "libflash-priv.h"
|
|
#include "ecc.h"
|
|
#include "blocklevel.h"
|
|
|
|
static const struct flash_info flash_info[] = {
|
|
{ 0xc22018, 0x01000000, FL_ERASE_ALL | FL_CAN_4B, "Macronix MXxxL12835F"},
|
|
{ 0xc22019, 0x02000000, FL_ERASE_ALL | FL_CAN_4B, "Macronix MXxxL25635F"},
|
|
{ 0xc2201a, 0x04000000, FL_ERASE_ALL | FL_CAN_4B, "Macronix MXxxL51235F"},
|
|
{ 0xc2201b, 0x08000000, FL_ERASE_ALL | FL_CAN_4B, "Macronix MX66L1G45G"},
|
|
{ 0xef4018, 0x01000000, FL_ERASE_ALL, "Winbond W25Q128BV" },
|
|
{ 0xef4019, 0x02000000, FL_ERASE_ALL | FL_ERASE_64K | FL_CAN_4B |
|
|
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" },
|
|
{ 0x4d5444, 0x02000000, FL_ERASE_ALL | FL_CAN_4B, "File Abstraction"},
|
|
{ 0x55aa55, 0x00100000, FL_ERASE_ALL | FL_CAN_4B, "TEST_FLASH" },
|
|
{ 0xaa55aa, 0x02000000, FL_ERASE_ALL | FL_CAN_4B, "EMULATED_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 */
|
|
struct blocklevel_device bl;
|
|
};
|
|
|
|
#ifndef __SKIBOOT__
|
|
bool libflash_debug;
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
|
|
static int flash_read(struct blocklevel_device *bl, uint64_t pos, void *buf, uint64_t len)
|
|
{
|
|
struct flash_chip *c = container_of(bl, struct flash_chip, bl);
|
|
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);
|
|
}
|
|
|
|
#define COPY_BUFFER_LENGTH 4096
|
|
|
|
/*
|
|
* This provides a wrapper around flash_read on ECCed data
|
|
* len is length of data without ECC attached
|
|
*/
|
|
int flash_read_corrected(struct blocklevel_device *bl, uint32_t pos, void *buf,
|
|
uint32_t len, bool ecc)
|
|
{
|
|
struct ecc64 *bufecc;
|
|
uint32_t copylen;
|
|
int rc;
|
|
uint8_t ret;
|
|
|
|
if (!ecc)
|
|
return flash_read(bl, pos, buf, len);
|
|
|
|
/* Copy the buffer in chunks */
|
|
bufecc = malloc(ecc_buffer_size(COPY_BUFFER_LENGTH));
|
|
if (!bufecc)
|
|
return FLASH_ERR_MALLOC_FAILED;
|
|
|
|
while (len > 0) {
|
|
/* What's left to copy? */
|
|
copylen = MIN(len, COPY_BUFFER_LENGTH);
|
|
|
|
/* Read ECCed data from flash */
|
|
rc = flash_read(bl, pos, bufecc, ecc_buffer_size(copylen));
|
|
if (rc)
|
|
goto err;
|
|
|
|
/* Extract data from ECCed data */
|
|
ret = memcpy_from_ecc(buf, bufecc, copylen);
|
|
if (ret) {
|
|
rc = FLASH_ERR_ECC_INVALID;
|
|
goto err;
|
|
}
|
|
|
|
/* Update for next copy */
|
|
len -= copylen;
|
|
buf = (uint8_t *)buf + copylen;
|
|
pos += ecc_buffer_size(copylen);
|
|
}
|
|
|
|
rc = 0;
|
|
|
|
err:
|
|
free(bufecc);
|
|
return rc;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static int flash_erase(struct blocklevel_device *bl, uint64_t dst, uint64_t size)
|
|
{
|
|
struct flash_chip *c = container_of(bl, struct flash_chip, bl);
|
|
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%" PRIx64"..0%" PRIx64 "...\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);
|
|
}
|
|
|
|
static int flash_write(struct blocklevel_device *bl, uint32_t dst, const void *src,
|
|
uint32_t size, bool verify)
|
|
{
|
|
struct flash_chip *c = container_of(bl, struct flash_chip, bl);
|
|
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 > 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(bl, 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;
|
|
}
|
|
|
|
int flash_write_corrected(struct blocklevel_device *bl, uint32_t pos, const void *buf,
|
|
uint32_t len, bool verify, bool ecc)
|
|
{
|
|
struct ecc64 *bufecc;
|
|
uint32_t copylen, copylen_minus_ecc;
|
|
int rc;
|
|
uint8_t ret;
|
|
|
|
if (!ecc)
|
|
return flash_write(bl, pos, buf, len, verify);
|
|
|
|
/* Copy the buffer in chunks */
|
|
bufecc = malloc(ecc_buffer_size(COPY_BUFFER_LENGTH));
|
|
if (!bufecc)
|
|
return FLASH_ERR_MALLOC_FAILED;
|
|
|
|
while (len > 0) {
|
|
/* What's left to copy? */
|
|
copylen = MIN(len, COPY_BUFFER_LENGTH);
|
|
copylen_minus_ecc = ecc_buffer_size_minus_ecc(copylen);
|
|
|
|
/* Add the ecc byte to the data */
|
|
ret = memcpy_to_ecc(bufecc, buf, copylen_minus_ecc);
|
|
if (ret) {
|
|
rc = FLASH_ERR_ECC_INVALID;
|
|
goto err;
|
|
}
|
|
|
|
/* Write ECCed data to the flash */
|
|
rc = flash_write(bl, pos, bufecc, copylen, verify);
|
|
if (rc)
|
|
goto err;
|
|
|
|
/* Update for next copy */
|
|
len -= copylen_minus_ecc;
|
|
buf = (uint8_t *)buf + copylen_minus_ecc;
|
|
pos += copylen;
|
|
}
|
|
|
|
rc = 0;
|
|
|
|
err:
|
|
free(bufecc);
|
|
return rc;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static int flash_smart_write(struct blocklevel_device *bl, uint64_t dst, const void *src, uint64_t size)
|
|
{
|
|
struct flash_chip *c = container_of(bl, struct flash_chip, bl);
|
|
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%" PRIx64 "..0%" PRIx64 "...\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...\n",
|
|
page, page + er_size);
|
|
rc = flash_read(bl, page, c->smart_buf, er_size);
|
|
if (rc) {
|
|
FL_DBG("LIBFLASH: ...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("LIBFLASH: ...same !\n");
|
|
break;
|
|
case sm_need_write:
|
|
/* Just needs writing over */
|
|
FL_DBG("LIBFLASH: ...need write !\n");
|
|
rc = flash_write(bl, dst, src, chunk, true);
|
|
if (rc) {
|
|
FL_DBG("LIBFLASH: Write error %d !\n", rc);
|
|
return rc;
|
|
}
|
|
break;
|
|
case sm_need_erase:
|
|
FL_DBG("LIBFLASH: ...need erase !\n");
|
|
rc = flash_erase(bl, 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(bl, 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;
|
|
}
|
|
|
|
int flash_smart_write_corrected(struct blocklevel_device *bl, uint32_t dst, const void *src,
|
|
uint32_t size, bool ecc)
|
|
{
|
|
struct ecc64 *buf;
|
|
int rc;
|
|
|
|
if (!ecc)
|
|
return flash_smart_write(bl, dst, src, size);
|
|
|
|
buf = malloc(ecc_buffer_size(size));
|
|
if (!buf)
|
|
return FLASH_ERR_MALLOC_FAILED;
|
|
|
|
rc = memcpy_to_ecc(buf, src, size);
|
|
if (rc) {
|
|
rc = FLASH_ERR_ECC_INVALID;
|
|
goto out;
|
|
}
|
|
|
|
rc = flash_smart_write(bl, dst, buf, ecc_buffer_size(size));
|
|
|
|
out:
|
|
free(buf);
|
|
return rc;
|
|
}
|
|
|
|
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 = 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);
|
|
} 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 || 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;
|
|
|
|
/* Don't have low level interface, assume all is well */
|
|
if (!ct->cmd_wr)
|
|
return 0;
|
|
|
|
/* 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 = FLASH_ERR_4B_NOT_SUPPORTED;
|
|
|
|
/*
|
|
* 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 rc;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
static int flash_get_info(struct blocklevel_device *bl, const char **name,
|
|
uint64_t *total_size, uint32_t *erase_granule)
|
|
{
|
|
struct flash_chip *c = container_of(bl, struct flash_chip, bl);
|
|
if (name)
|
|
*name = c->info.name;
|
|
if (total_size)
|
|
*total_size = c->tsize;
|
|
if (erase_granule)
|
|
*erase_granule = c->min_erase_mask + 1;
|
|
return 0;
|
|
}
|
|
|
|
int flash_init(struct spi_flash_ctrl *ctrl, struct blocklevel_device **bl,
|
|
struct flash_chip **flash_chip)
|
|
{
|
|
struct flash_chip *c;
|
|
int rc;
|
|
|
|
if (!bl)
|
|
return FLASH_ERR_PARM_ERROR;
|
|
|
|
*bl = 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;
|
|
}
|
|
|
|
/* The flash backend doesn't support reiniting it */
|
|
c->bl.keep_alive = true;
|
|
c->bl.reacquire = NULL;
|
|
c->bl.release = NULL;
|
|
c->bl.read = &flash_read;
|
|
c->bl.write = &flash_smart_write;
|
|
c->bl.erase = &flash_erase;
|
|
c->bl.get_info = &flash_get_info;
|
|
c->bl.erase_mask = c->min_erase_mask;
|
|
c->bl.flags = WRITE_NEED_ERASE;
|
|
|
|
*bl = &(c->bl);
|
|
if (flash_chip)
|
|
*flash_chip = c;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void flash_exit(struct blocklevel_device *bl)
|
|
{
|
|
/* XXX Make sure we are idle etc... */
|
|
if (bl) {
|
|
struct flash_chip *c = container_of(bl, struct flash_chip, bl);
|
|
free(c->smart_buf);
|
|
free(c);
|
|
}
|
|
}
|
|
|
|
void flash_exit_close(struct blocklevel_device *bl, void (*close)(struct spi_flash_ctrl *ctrl))
|
|
{
|
|
if (bl) {
|
|
struct flash_chip *c = container_of(bl, struct flash_chip, bl);
|
|
close(c->ctrl);
|
|
free(c);
|
|
}
|
|
}
|