blob: 70c8a597e60b64650c94d04216b85fec1fa5ae47 [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
/* Copyright 2013-2017 IBM Corp. */
#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);
}
}