52ca658069
This flash chip is found on some Surfans F20 units. For our purposes it's the same as the GD5F1GA4xExx so just #define an alias instead of adding a whole new chip struct. Change-Id: I2f4c4fbf1faf3a0c7a1503534430afacbddc426e
465 lines
16 KiB
C
465 lines
16 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2021 Aidan MacDonald
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include "nand-x1000.h"
|
|
#include "sfc-x1000.h"
|
|
#include "system.h"
|
|
#include "logf.h"
|
|
#include <string.h>
|
|
|
|
static void winbond_setup_chip(struct nand_drv* drv);
|
|
|
|
static const struct nand_chip chip_ato25d1ga = {
|
|
.log2_ppb = 6, /* 64 pages */
|
|
.page_size = 2048,
|
|
.oob_size = 64,
|
|
.nr_blocks = 1024,
|
|
.bbm_pos = 2048,
|
|
.clock_freq = 150000000,
|
|
.dev_conf = jz_orf(SFC_DEV_CONF,
|
|
CE_DL(1), HOLD_DL(1), WP_DL(1),
|
|
CPHA(0), CPOL(0),
|
|
TSH(7), TSETUP(0), THOLD(0),
|
|
STA_TYPE_V(1BYTE), CMD_TYPE_V(8BITS),
|
|
SMP_DELAY(1)),
|
|
.flags = NAND_CHIPFLAG_QUAD | NAND_CHIPFLAG_HAS_QE_BIT,
|
|
.cmd_page_read = NANDCMD_PAGE_READ,
|
|
.cmd_program_execute = NANDCMD_PROGRAM_EXECUTE,
|
|
.cmd_block_erase = NANDCMD_BLOCK_ERASE,
|
|
.cmd_read_cache = NANDCMD_READ_CACHE_x4,
|
|
.cmd_program_load = NANDCMD_PROGRAM_LOAD_x4,
|
|
};
|
|
|
|
static const struct nand_chip chip_w25n01gvxx = {
|
|
.log2_ppb = 6, /* 64 pages */
|
|
.page_size = 2048,
|
|
.oob_size = 64,
|
|
.nr_blocks = 1024,
|
|
.bbm_pos = 2048,
|
|
.clock_freq = 150000000,
|
|
.dev_conf = jz_orf(SFC_DEV_CONF,
|
|
CE_DL(1), HOLD_DL(1), WP_DL(1),
|
|
CPHA(0), CPOL(0),
|
|
TSH(11), TSETUP(0), THOLD(0),
|
|
STA_TYPE_V(1BYTE), CMD_TYPE_V(8BITS),
|
|
SMP_DELAY(1)),
|
|
.flags = NAND_CHIPFLAG_ON_DIE_ECC,
|
|
/* TODO: quad mode? */
|
|
.cmd_page_read = NANDCMD_PAGE_READ,
|
|
.cmd_program_execute = NANDCMD_PROGRAM_EXECUTE,
|
|
.cmd_block_erase = NANDCMD_BLOCK_ERASE,
|
|
.cmd_read_cache = NANDCMD_READ_CACHE_SLOW,
|
|
.cmd_program_load = NANDCMD_PROGRAM_LOAD,
|
|
.setup_chip = winbond_setup_chip,
|
|
};
|
|
|
|
static const struct nand_chip chip_gd5f1gq4xexx = {
|
|
.log2_ppb = 6, /* 64 pages */
|
|
.page_size = 2048,
|
|
.oob_size = 64, /* 128B when hardware ECC is disabled */
|
|
.nr_blocks = 1024,
|
|
.bbm_pos = 2048,
|
|
.clock_freq = 150000000,
|
|
.dev_conf = jz_orf(SFC_DEV_CONF,
|
|
CE_DL(1), HOLD_DL(1), WP_DL(1),
|
|
CPHA(0), CPOL(0),
|
|
TSH(7), TSETUP(0), THOLD(0),
|
|
STA_TYPE_V(1BYTE), CMD_TYPE_V(8BITS),
|
|
SMP_DELAY(1)),
|
|
.flags = NAND_CHIPFLAG_QUAD | NAND_CHIPFLAG_HAS_QE_BIT |
|
|
NAND_CHIPFLAG_ON_DIE_ECC,
|
|
.cmd_page_read = NANDCMD_PAGE_READ,
|
|
.cmd_program_execute = NANDCMD_PROGRAM_EXECUTE,
|
|
.cmd_block_erase = NANDCMD_BLOCK_ERASE,
|
|
.cmd_read_cache = NANDCMD_READ_CACHE_x4,
|
|
.cmd_program_load = NANDCMD_PROGRAM_LOAD_x4,
|
|
};
|
|
|
|
#define chip_ds35x1gaxxx chip_gd5f1gq4xexx
|
|
|
|
const struct nand_chip_id supported_nand_chips[] = {
|
|
NAND_CHIP_ID(&chip_ato25d1ga, NAND_READID_ADDR, 0x9b, 0x12),
|
|
NAND_CHIP_ID(&chip_w25n01gvxx, NAND_READID_ADDR, 0xef, 0xaa, 0x21),
|
|
NAND_CHIP_ID(&chip_gd5f1gq4xexx, NAND_READID_ADDR, 0xc8, 0xd1),
|
|
NAND_CHIP_ID(&chip_gd5f1gq4xexx, NAND_READID_ADDR, 0xc8, 0xc1),
|
|
NAND_CHIP_ID(&chip_ds35x1gaxxx, NAND_READID_ADDR, 0xe5, 0x71), /* 3.3 V */
|
|
NAND_CHIP_ID(&chip_ds35x1gaxxx, NAND_READID_ADDR, 0xe5, 0x21), /* 1.8 V */
|
|
};
|
|
|
|
const size_t nr_supported_nand_chips = ARRAYLEN(supported_nand_chips);
|
|
|
|
static struct nand_drv static_nand_drv;
|
|
static uint8_t static_scratch_buf[NAND_DRV_SCRATCHSIZE] CACHEALIGN_ATTR;
|
|
static uint8_t static_page_buf[NAND_DRV_MAXPAGESIZE] CACHEALIGN_ATTR;
|
|
|
|
struct nand_drv* nand_init(void)
|
|
{
|
|
static bool inited = false;
|
|
if(!inited) {
|
|
mutex_init(&static_nand_drv.mutex);
|
|
static_nand_drv.scratch_buf = static_scratch_buf;
|
|
static_nand_drv.page_buf = static_page_buf;
|
|
static_nand_drv.refcount = 0;
|
|
}
|
|
|
|
return &static_nand_drv;
|
|
}
|
|
|
|
static uint8_t nand_get_reg(struct nand_drv* drv, uint8_t reg)
|
|
{
|
|
sfc_exec(NANDCMD_GET_FEATURE, reg, drv->scratch_buf, 1|SFC_READ);
|
|
return drv->scratch_buf[0];
|
|
}
|
|
|
|
static void nand_set_reg(struct nand_drv* drv, uint8_t reg, uint8_t val)
|
|
{
|
|
drv->scratch_buf[0] = val;
|
|
sfc_exec(NANDCMD_SET_FEATURE, reg, drv->scratch_buf, 1|SFC_WRITE);
|
|
}
|
|
|
|
static void nand_upd_reg(struct nand_drv* drv, uint8_t reg, uint8_t msk, uint8_t val)
|
|
{
|
|
uint8_t x = nand_get_reg(drv, reg);
|
|
x &= ~msk;
|
|
x |= val;
|
|
nand_set_reg(drv, reg, x);
|
|
}
|
|
|
|
static const struct nand_chip* identify_chip_method(uint8_t method,
|
|
const uint8_t* id_buf)
|
|
{
|
|
for (size_t i = 0; i < nr_supported_nand_chips; ++i) {
|
|
const struct nand_chip_id* chip_id = &supported_nand_chips[i];
|
|
if (chip_id->method == method &&
|
|
!memcmp(chip_id->id_bytes, id_buf, chip_id->num_id_bytes))
|
|
return chip_id->chip;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool identify_chip(struct nand_drv* drv)
|
|
{
|
|
/* Read ID command has some variations; Linux handles these 3:
|
|
* - no address or dummy bytes
|
|
* - 1 byte address, no dummy byte
|
|
* - no address byte, 1 byte dummy
|
|
*
|
|
* Currently we use the 2nd method, aka. address read ID, the
|
|
* other methods can be added when needed.
|
|
*/
|
|
sfc_exec(NANDCMD_READID_ADDR, 0, drv->scratch_buf, 4|SFC_READ);
|
|
drv->chip = identify_chip_method(NAND_READID_ADDR, drv->scratch_buf);
|
|
if (drv->chip)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static void setup_chip_data(struct nand_drv* drv)
|
|
{
|
|
drv->ppb = 1 << drv->chip->log2_ppb;
|
|
drv->fpage_size = drv->chip->page_size + drv->chip->oob_size;
|
|
}
|
|
|
|
static void winbond_setup_chip(struct nand_drv* drv)
|
|
{
|
|
/* Ensure we are in buffered read mode. */
|
|
nand_upd_reg(drv, FREG_CFG, FREG_CFG_WINBOND_BUF, FREG_CFG_WINBOND_BUF);
|
|
}
|
|
|
|
static void setup_chip_registers(struct nand_drv* drv)
|
|
{
|
|
/* Set chip registers to enter normal operation */
|
|
if(drv->chip->flags & NAND_CHIPFLAG_HAS_QE_BIT) {
|
|
bool en = (drv->chip->flags & NAND_CHIPFLAG_QUAD) != 0;
|
|
nand_upd_reg(drv, FREG_CFG, FREG_CFG_QUAD_ENABLE,
|
|
en ? FREG_CFG_QUAD_ENABLE : 0);
|
|
}
|
|
|
|
if(drv->chip->flags & NAND_CHIPFLAG_ON_DIE_ECC) {
|
|
/* Enable on-die ECC */
|
|
nand_upd_reg(drv, FREG_CFG, FREG_CFG_ECC_ENABLE, FREG_CFG_ECC_ENABLE);
|
|
}
|
|
|
|
/* Clear OTP bit to access the main data array */
|
|
nand_upd_reg(drv, FREG_CFG, FREG_CFG_OTP_ENABLE, 0);
|
|
|
|
/* Clear write protection bits */
|
|
nand_set_reg(drv, FREG_PROT, FREG_PROT_UNLOCK);
|
|
|
|
/* Call any chip-specific hooks */
|
|
if(drv->chip->setup_chip)
|
|
drv->chip->setup_chip(drv);
|
|
}
|
|
|
|
int nand_open(struct nand_drv* drv)
|
|
{
|
|
if(drv->refcount > 0) {
|
|
drv->refcount++;
|
|
return NAND_SUCCESS;
|
|
}
|
|
|
|
/* Initialize the controller */
|
|
sfc_open();
|
|
sfc_set_dev_conf(jz_orf(SFC_DEV_CONF,
|
|
CE_DL(1), HOLD_DL(1), WP_DL(1),
|
|
CPHA(0), CPOL(0),
|
|
TSH(15), TSETUP(0), THOLD(0),
|
|
STA_TYPE_V(1BYTE), CMD_TYPE_V(8BITS),
|
|
SMP_DELAY(0)));
|
|
sfc_set_clock(X1000_EXCLK_FREQ);
|
|
|
|
/* Send the software reset command */
|
|
sfc_exec(NANDCMD_RESET, 0, NULL, 0);
|
|
mdelay(10);
|
|
|
|
/* Chip identification and setup */
|
|
if(!identify_chip(drv))
|
|
return NAND_ERR_UNKNOWN_CHIP;
|
|
|
|
setup_chip_data(drv);
|
|
|
|
/* Set new SFC parameters */
|
|
sfc_set_dev_conf(drv->chip->dev_conf);
|
|
sfc_set_clock(drv->chip->clock_freq);
|
|
|
|
/* Enter normal operating mode */
|
|
setup_chip_registers(drv);
|
|
|
|
drv->refcount++;
|
|
return NAND_SUCCESS;
|
|
}
|
|
|
|
void nand_close(struct nand_drv* drv)
|
|
{
|
|
--drv->refcount;
|
|
if(drv->refcount > 0)
|
|
return;
|
|
|
|
/* Let's reset the chip... the idea is to restore the registers
|
|
* to whatever they should "normally" be */
|
|
sfc_exec(NANDCMD_RESET, 0, NULL, 0);
|
|
mdelay(10);
|
|
|
|
sfc_close();
|
|
}
|
|
|
|
void nand_enable_otp(struct nand_drv* drv, bool enable)
|
|
{
|
|
nand_upd_reg(drv, FREG_CFG, FREG_CFG_OTP_ENABLE,
|
|
enable ? FREG_CFG_OTP_ENABLE : 0);
|
|
}
|
|
|
|
static uint8_t nand_wait_busy(struct nand_drv* drv)
|
|
{
|
|
uint8_t reg;
|
|
do {
|
|
reg = nand_get_reg(drv, FREG_STATUS);
|
|
} while(reg & FREG_STATUS_BUSY);
|
|
return reg;
|
|
}
|
|
|
|
int nand_block_erase(struct nand_drv* drv, nand_block_t block)
|
|
{
|
|
sfc_exec(NANDCMD_WR_EN, 0, NULL, 0);
|
|
sfc_exec(drv->chip->cmd_block_erase, block, NULL, 0);
|
|
|
|
uint8_t status = nand_wait_busy(drv);
|
|
if(status & FREG_STATUS_EFAIL)
|
|
return NAND_ERR_ERASE_FAIL;
|
|
else
|
|
return NAND_SUCCESS;
|
|
}
|
|
|
|
int nand_page_program(struct nand_drv* drv, nand_page_t page, const void* buffer)
|
|
{
|
|
sfc_exec(NANDCMD_WR_EN, 0, NULL, 0);
|
|
sfc_exec(drv->chip->cmd_program_load,
|
|
0, (void*)buffer, drv->fpage_size|SFC_WRITE);
|
|
sfc_exec(drv->chip->cmd_program_execute, page, NULL, 0);
|
|
|
|
uint8_t status = nand_wait_busy(drv);
|
|
if(status & FREG_STATUS_PFAIL)
|
|
return NAND_ERR_PROGRAM_FAIL;
|
|
else
|
|
return NAND_SUCCESS;
|
|
}
|
|
|
|
int nand_page_read(struct nand_drv* drv, nand_page_t page, void* buffer)
|
|
{
|
|
sfc_exec(drv->chip->cmd_page_read, page, NULL, 0);
|
|
nand_wait_busy(drv);
|
|
sfc_exec(drv->chip->cmd_read_cache, 0, buffer, drv->fpage_size|SFC_READ);
|
|
|
|
if(drv->chip->flags & NAND_CHIPFLAG_ON_DIE_ECC) {
|
|
uint8_t status = nand_get_reg(drv, FREG_STATUS);
|
|
|
|
if(status & FREG_STATUS_ECC_UNCOR_ERR) {
|
|
logf("ecc uncorrectable error on page %08lx", (unsigned long)page);
|
|
return NAND_ERR_ECC_FAIL;
|
|
}
|
|
|
|
if(status & FREG_STATUS_ECC_HAS_FLIPS) {
|
|
logf("ecc corrected bitflips on page %08lx", (unsigned long)page);
|
|
}
|
|
}
|
|
|
|
return NAND_SUCCESS;
|
|
}
|
|
|
|
int nand_read_bytes(struct nand_drv* drv, uint32_t byte_addr, uint32_t byte_len, void* buffer)
|
|
{
|
|
if(byte_len == 0)
|
|
return NAND_SUCCESS;
|
|
|
|
int rc;
|
|
unsigned pg_size = drv->chip->page_size;
|
|
nand_page_t page = byte_addr / pg_size;
|
|
unsigned offset = byte_addr % pg_size;
|
|
while(1) {
|
|
rc = nand_page_read(drv, page, drv->page_buf);
|
|
if(rc < 0)
|
|
return rc;
|
|
|
|
memcpy(buffer, &drv->page_buf[offset], MIN(pg_size - offset, byte_len));
|
|
|
|
if(byte_len <= pg_size - offset)
|
|
break;
|
|
|
|
byte_len -= pg_size - offset;
|
|
buffer += pg_size - offset;
|
|
offset = 0;
|
|
page++;
|
|
}
|
|
|
|
return NAND_SUCCESS;
|
|
}
|
|
|
|
int nand_write_bytes(struct nand_drv* drv, uint32_t byte_addr, uint32_t byte_len, const void* buffer)
|
|
{
|
|
if(byte_len == 0)
|
|
return NAND_SUCCESS;
|
|
|
|
int rc;
|
|
unsigned pg_size = drv->chip->page_size;
|
|
unsigned blk_size = pg_size << drv->chip->log2_ppb;
|
|
|
|
if(byte_addr % blk_size != 0)
|
|
return NAND_ERR_UNALIGNED;
|
|
if(byte_len % blk_size != 0)
|
|
return NAND_ERR_UNALIGNED;
|
|
|
|
nand_page_t page = byte_addr / pg_size;
|
|
nand_page_t end_page = page + (byte_len / pg_size);
|
|
|
|
for(nand_block_t blk = page; blk < end_page; blk += drv->ppb) {
|
|
rc = nand_block_erase(drv, blk);
|
|
if(rc < 0)
|
|
return rc;
|
|
}
|
|
|
|
for(; page != end_page; ++page) {
|
|
memcpy(drv->page_buf, buffer, pg_size);
|
|
memset(&drv->page_buf[pg_size], 0xff, drv->chip->oob_size);
|
|
buffer += pg_size;
|
|
|
|
rc = nand_page_program(drv, page, drv->page_buf);
|
|
if(rc < 0)
|
|
return rc;
|
|
}
|
|
|
|
return NAND_SUCCESS;
|
|
}
|
|
|
|
/* TODO - NAND driver future improvements
|
|
*
|
|
* 1. Support sofware or on-die ECC transparently. Support debug ECC bypass.
|
|
*
|
|
* It's probably best to add an API call to turn ECC on or off. Software
|
|
* ECC and most or all on-die ECC implementations require some OOB bytes
|
|
* to function; which leads us to the next problem...
|
|
*
|
|
* 2. Allow safe access to OOB areas
|
|
*
|
|
* The OOB data area is not fully available to users; it is also occupied
|
|
* by ECC data and bad block markings. The NAND driver needs to provide a
|
|
* mapping which allows OOB data users to map around those reserved areas,
|
|
* otherwise it's not really possible to use OOB data.
|
|
*
|
|
* 3. Support partial page programming.
|
|
*
|
|
* This might already work. My understanding of NAND flash is that bits are
|
|
* represented by charge deposited on flash cells. In the case of SLC flash,
|
|
* cells are one bit. For MLC flash, cells can store more than one bit; but
|
|
* MLC flash is much less reliable than SLC. We probably don't have to be
|
|
* concerned about MLC flash, and its does not support partial programming
|
|
* anyway due to the cell characteristics, so I will only consider SLC here.
|
|
*
|
|
* For SLC there are two cell states -- an uncharged cell represents a "1"
|
|
* and a charged cell represents "0". Programming can only deposit charge
|
|
* on a cell and erasing can only remove charge. Therefore, "programming" a
|
|
* cell to 1 is actually a no-op.
|
|
*
|
|
* So, there's no datasheet which spells this out, but I suspect you just
|
|
* set the areas you're not interested in programming to 0xff. Programming
|
|
* can never change a written 0 back to a 1, so programming a 1 bit works
|
|
* more like a "don't care" (= keep whatever value is already there).
|
|
*
|
|
* What _is_ given by the datasheets is limits on how many times you can
|
|
* reprogram the same page without erasing it. This is an overall limit
|
|
* called NOP (number of programs) in many datasheets. In addition to this,
|
|
* sub-regions of the page have further limits: it's common for a 2048+64
|
|
* byte page to be split into 8 regions, with four 512-byte main areas and
|
|
* four 16-byte OOB areas. Usually, each subregion can only be programmed
|
|
* once. However, you can write multiple subregions with a single program.
|
|
*
|
|
* Violating programming constraints could cause data loss, so we need to
|
|
* communicate to upper layers what the limitations are here if they want
|
|
* to use partial programming safely.
|
|
*
|
|
* Programming the same page more than once increases the overall stress
|
|
* on the flash cells and can cause bitflips. For this reason, it's best
|
|
* to keep the number of programs as low as possible. Some sources suggest
|
|
* that programming the pages in a block in linear order is also better to
|
|
* reduce stress, although I don't know why this would be.
|
|
*
|
|
* These program/read stresses can flip bits, but it's only due to residual
|
|
* charge building up on uncharged cells; cells are not permanently damaged
|
|
* by these kind of stresses. Erasing the block will remove the charge and
|
|
* restore all the cells to a clean state.
|
|
*
|
|
* These slides are fairly informative on this subject:
|
|
* - https://cushychicken.github.io/assets/cooke_inconvenient_truths.pdf
|
|
*
|
|
* 4. Bad block management
|
|
*
|
|
* This probably doesn't belong in the NAND layer but it seems wise to keep
|
|
* at least a bad block table at the level of the NAND driver. Factory bad
|
|
* block marks are usually some non-0xFF byte in the OOB area, but bad blocks
|
|
* which develop over the device lifetime usually won't be marked; after all
|
|
* they are unreliable, so we can't program a marking on them and expect it
|
|
* to stick. So, most FTL systems keep a bad block table somewhere in flash
|
|
* and update it whenever a block goes bad.
|
|
*
|
|
* So, in addition to a bad block marker scan, we should try to gather bad
|
|
* block information from such tables.
|
|
*/
|