rockbox/firmware/target/mips/ingenic_x1000/nand-x1000.c
Aidan MacDonald 52ca658069 x1000: Add support for DS35x1GAxxx flash chips
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
2022-12-01 12:11:46 -05:00

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.
*/