x1000: NAND rewrite

This new design saves on binary size and stack usage. The API is
also block- and page-based, requiring awareness of the chip layout
to use properly. Out-of-band areas are also exposed for reading
and writing.

The byte-oriented routines are kept for compatibility with the
existing installer and SPL.

Change-Id: Iaacc694d2f651ab74d45330e0434ee778a0d91bc
This commit is contained in:
Aidan MacDonald 2021-06-19 17:48:13 +01:00
parent 9246cbc65e
commit 9f950d8bbf
6 changed files with 711 additions and 802 deletions

View File

@ -32,75 +32,45 @@
#define IMAGE_SIZE (128 * 1024)
#define TAR_SIZE (256 * 1024)
static int flash_prepare(void)
{
int mf_id, dev_id;
int rc;
rc = nand_open();
if(rc < 0)
return INSTALL_ERR_FLASH(NAND_OPEN, rc);
rc = nand_identify(&mf_id, &dev_id);
if(rc < 0) {
nand_close();
return INSTALL_ERR_FLASH(NAND_IDENTIFY, rc);
}
return INSTALL_SUCCESS;
}
static void flash_finish(void)
{
/* Ensure writes are always disabled when we finish.
* Errors are safe to ignore here, there's nothing we could do anyway. */
nand_enable_writes(false);
nand_close();
}
static int flash_img_read(uint8_t* buffer)
{
int rc = flash_prepare();
nand_drv* drv = nand_init();
nand_lock(drv);
int rc = nand_open(drv);
if(rc < 0)
goto error;
rc = nand_read(0, IMAGE_SIZE, buffer);
rc = nand_read_bytes(drv, 0, IMAGE_SIZE, buffer);
if(rc < 0) {
rc = INSTALL_ERR_FLASH(NAND_READ, rc);
goto error;
}
error:
flash_finish();
nand_close(drv);
nand_unlock(drv);
return rc;
}
static int flash_img_write(const uint8_t* buffer)
{
int rc = flash_prepare();
nand_drv* drv = nand_init();
nand_lock(drv);
int rc = nand_open(drv);
if(rc < 0)
goto error;
rc = nand_enable_writes(true);
if(rc < 0) {
rc = INSTALL_ERR_FLASH(NAND_ENABLE_WRITES, rc);
goto error;
}
rc = nand_erase(0, IMAGE_SIZE);
if(rc < 0) {
rc = INSTALL_ERR_FLASH(NAND_ERASE, rc);
goto error;
}
rc = nand_write(0, IMAGE_SIZE, buffer);
rc = nand_write_bytes(drv, 0, IMAGE_SIZE, buffer);
if(rc < 0) {
rc = INSTALL_ERR_FLASH(NAND_WRITE, rc);
goto error;
}
error:
flash_finish();
nand_close(drv);
nand_unlock(drv);
return rc;
}

View File

@ -119,6 +119,29 @@ void spl_error(void)
}
}
nand_drv* alloc_nand_drv(uint32_t laddr, uint32_t lsize)
{
size_t need_size = sizeof(nand_drv) +
NAND_DRV_SCRATCHSIZE +
NAND_DRV_MAXPAGESIZE;
/* find a hole to keep the buffers */
uintptr_t addr;
if(X1000_SDRAM_BASE + need_size <= laddr)
addr = X1000_SDRAM_BASE;
else
addr = CACHEALIGN_UP(X1000_SDRAM_BASE + laddr + lsize);
uint8_t* page_buf = (uint8_t*)addr;
uint8_t* scratch_buf = page_buf + NAND_DRV_MAXPAGESIZE;
nand_drv* drv = (nand_drv*)(scratch_buf + NAND_DRV_SCRATCHSIZE);
drv->page_buf = page_buf;
drv->scratch_buf = scratch_buf;
drv->refcount = 0;
return drv;
}
void spl_target_boot(void)
{
int opt_index = spl_get_boot_option();
@ -134,33 +157,26 @@ void spl_target_boot(void)
gpioz_configure(GPIO_A, 0x3f << 26, GPIOF_DEVICE(1));
/* Open NAND chip */
int rc = nand_open();
nand_drv* ndrv = alloc_nand_drv(opt->load_addr, opt->nand_size);
int rc = nand_open(ndrv);
if(rc)
spl_error();
int mf_id, dev_id;
rc = nand_identify(&mf_id, &dev_id);
if(rc)
goto nand_err;
/* For OF only: load DMA coprocessor's firmware from flash */
if(opt_index != BOOTOPTION_ROCKBOX) {
rc = nand_read(0x4000, 0x2000, (uint8_t*)0xb3422000);
rc = nand_read_bytes(ndrv, 0x4000, 0x2000, (uint8_t*)0xb3422000);
if(rc)
goto nand_err;
}
/* Read the firmware */
rc = nand_read(opt->nand_addr, opt->nand_size, load_addr);
rc = nand_read_bytes(ndrv, opt->nand_addr, opt->nand_size, load_addr);
if(rc)
goto nand_err;
/* Rockbox doesn't need the NAND; for the OF, we should leave it open
* and also make sure to turn off the write protect bits. */
/* Rockbox doesn't need the NAND; for the OF, we should leave it open */
if(opt_index == BOOTOPTION_ROCKBOX)
nand_close();
else
nand_enable_writes(true);
nand_close(ndrv);
/* Kernel arguments pointer, for Linux only */
char** kargv = (char**)0x80004000;
@ -184,7 +200,7 @@ void spl_target_boot(void)
__builtin_unreachable();
nand_err:
nand_close();
nand_close(ndrv);
spl_error();
}

View File

@ -22,480 +22,394 @@
#include "nand-x1000.h"
#include "sfc-x1000.h"
#include "system.h"
#include <stddef.h>
#include <string.h>
/* NAND command numbers */
#define NAND_CMD_READ_ID 0x9f
#define NAND_CMD_WRITE_ENABLE 0x06
#define NAND_CMD_GET_FEATURE 0x0f
#define NAND_CMD_SET_FEATURE 0x1f
#define NAND_CMD_PAGE_READ_TO_CACHE 0x13
#define NAND_CMD_READ_FROM_CACHE 0x0b
#define NAND_CMD_READ_FROM_CACHEx4 0x6b
#define NAND_CMD_PROGRAM_LOAD 0x02
#define NAND_CMD_PROGRAM_LOADx4 0x32
#define NAND_CMD_PROGRAM_EXECUTE 0x10
#define NAND_CMD_BLOCK_ERASE 0xd8
/* cmd mode a d phase format has data */
#define NANDCMD_RESET SFC_CMD(0xff, SFC_TMODE_1_1_1, 0, 0, SFC_PFMT_ADDR_FIRST, 0)
#define NANDCMD_READID(x,y) SFC_CMD(0x9f, SFC_TMODE_1_1_1, x, y, SFC_PFMT_ADDR_FIRST, 1)
#define NANDCMD_WR_EN SFC_CMD(0x06, SFC_TMODE_1_1_1, 0, 0, SFC_PFMT_ADDR_FIRST, 0)
#define NANDCMD_GET_FEATURE SFC_CMD(0x0f, SFC_TMODE_1_1_1, 1, 0, SFC_PFMT_ADDR_FIRST, 1)
#define NANDCMD_SET_FEATURE SFC_CMD(0x1f, SFC_TMODE_1_1_1, 1, 0, SFC_PFMT_ADDR_FIRST, 1)
#define NANDCMD_PAGE_READ(x) SFC_CMD(0x13, SFC_TMODE_1_1_1, x, 0, SFC_PFMT_ADDR_FIRST, 0)
#define NANDCMD_READ_CACHE(x) SFC_CMD(0x0b, SFC_TMODE_1_1_1, x, 8, SFC_PFMT_ADDR_FIRST, 1)
#define NANDCMD_READ_CACHE_x4(x) SFC_CMD(0x6b, SFC_TMODE_1_1_4, x, 8, SFC_PFMT_ADDR_FIRST, 1)
#define NANDCMD_PROGRAM_LOAD(x) SFC_CMD(0x02, SFC_TMODE_1_1_1, x, 0, SFC_PFMT_ADDR_FIRST, 1)
#define NANDCMD_PROGRAM_LOAD_x4(x) SFC_CMD(0x32, SFC_TMODE_1_1_4, x, 0, SFC_PFMT_ADDR_FIRST, 1)
#define NANDCMD_PROGRAM_EXECUTE(x) SFC_CMD(0x10, SFC_TMODE_1_1_1, x, 0, SFC_PFMT_ADDR_FIRST, 0)
#define NANDCMD_BLOCK_ERASE(x) SFC_CMD(0xd8, SFC_TMODE_1_1_1, x, 0, SFC_PFMT_ADDR_FIRST, 0)
/* NAND device register addresses for GET_FEATURE / SET_FEATURE */
#define NAND_FREG_PROTECTION 0xa0
#define NAND_FREG_FEATURE 0xb0
#define NAND_FREG_STATUS 0xc0
/* Feature registers are found in linux/mtd/spinand.h,
* apparently these are pretty standardized */
#define FREG_PROT 0xa0
#define FREG_PROT_UNLOCK 0x00
/* Protection register bits */
#define NAND_FREG_PROTECTION_BRWD 0x80
#define NAND_FREG_PROTECTION_BP2 0x20
#define NAND_FREG_PROTECTION_BP1 0x10
#define NAND_FREG_PROTECTION_BP0 0x08
/* Mask of BP bits 0-2 */
#define NAND_FREG_PROTECTION_ALLBP 0x38
#define FREG_CFG 0xb0
#define FREG_CFG_OTP_ENABLE (1 << 6)
#define FREG_CFG_ECC_ENABLE (1 << 4)
#define FREG_CFG_QUAD_ENABLE (1 << 0)
/* Feature register bits */
#define NAND_FREG_FEATURE_QE 0x01
#define FREG_STATUS 0xc0
#define FREG_STATUS_BUSY (1 << 0)
#define FREG_STATUS_EFAIL (1 << 2)
#define FREG_STATUS_PFAIL (1 << 3)
#define FREG_STATUS_ECC_MASK (3 << 4)
#define FREG_STATUS_ECC_NO_FLIPS (0 << 4)
#define FREG_STATUS_ECC_HAS_FLIPS (1 << 4)
#define FREG_STATUS_ECC_UNCOR_ERR (2 << 4)
/* Status register bits */
#define NAND_FREG_STATUS_OIP 0x01
#define NAND_FREG_STATUS_WEL 0x02
#define NAND_FREG_STATUS_E_FAIL 0x04
#define NAND_FREG_STATUS_P_FAIL 0x08
/* NAND chip config */
const nand_chip_data target_nand_chip_data[] = {
#ifdef FIIO_M3K
const nand_chip supported_nand_chips[] = {
#if defined(FIIO_M3K)
{
/* ATO25D1GA */
.mf_id = 0x9b,
.dev_id = 0x12,
.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)),
.row_cycles = 3,
.col_cycles = 2,
.log2_ppb = 6, /* 64 pages */
.page_size = 2048,
.oob_size = 64,
.nr_blocks = 1024,
.clock_freq = 150000000,
.log2_page_size = 11, /* = 2048 bytes */
.log2_block_size = 6, /* = 64 pages */
.rowaddr_width = 3,
.coladdr_width = 2,
.flags = NANDCHIP_FLAG_QUAD,
}
.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,
},
#else
/* Nobody will use this anyway if the device has no NAND flash */
{ 0 },
#endif
};
const size_t target_nand_chip_count =
sizeof(target_nand_chip_data) / sizeof(nand_chip_data);
const size_t nr_supported_nand_chips =
sizeof(supported_nand_chips) / sizeof(nand_chip);
/* NAND ops -- high level primitives used by the driver */
static int nandop_wait_status(int errbit);
static int nandop_read_page(uint32_t row_addr, uint8_t* buf);
static int nandop_write_page(uint32_t row_addr, const uint8_t* buf);
static int nandop_erase_block(uint32_t block_addr);
static int nandop_set_write_protect(bool en);
static 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;
/* NAND commands -- 1-to-1 mapping between chip commands and functions */
static int nandcmd_read_id(int* mf_id, int* dev_id);
static int nandcmd_write_enable(void);
static int nandcmd_get_feature(uint8_t reg);
static int nandcmd_set_feature(uint8_t reg, uint8_t val);
static int nandcmd_page_read_to_cache(uint32_t row_addr);
static int nandcmd_read_from_cache(uint8_t* buf);
static int nandcmd_program_load(const uint8_t* buf);
static int nandcmd_program_execute(uint32_t row_addr);
static int nandcmd_block_erase(uint32_t block_addr);
struct nand_drv {
const nand_chip_data* chip_data;
bool write_enabled;
};
static struct nand_drv nand_drv;
static uint8_t nand_auxbuf[32] CACHEALIGN_ATTR;
static void nand_drv_reset(void)
nand_drv* nand_init(void)
{
nand_drv.chip_data = NULL;
nand_drv.write_enabled = false;
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;
}
int nand_open(void)
static uint8_t nand_get_reg(nand_drv* drv, uint8_t reg)
{
sfc_init();
sfc_lock();
nand_drv_reset();
sfc_open();
const nand_chip_data* chip_data = &target_nand_chip_data[0];
sfc_set_dev_conf(chip_data->dev_conf);
sfc_set_clock(chip_data->clock_freq);
sfc_unlock();
return NAND_SUCCESS;
sfc_exec(NANDCMD_GET_FEATURE, reg, drv->scratch_buf, 1|SFC_READ);
return drv->scratch_buf[0];
}
void nand_close(void)
static void nand_set_reg(nand_drv* drv, uint8_t reg, uint8_t val)
{
sfc_lock();
sfc_close();
nand_drv_reset();
sfc_unlock();
drv->scratch_buf[0] = val;
sfc_exec(NANDCMD_SET_FEATURE, reg, drv->scratch_buf, 1|SFC_WRITE);
}
int nand_identify(int* mf_id, int* dev_id)
static void nand_upd_reg(nand_drv* drv, uint8_t reg, uint8_t msk, uint8_t val)
{
sfc_lock();
uint8_t x = nand_get_reg(drv, reg);
x &= ~msk;
x |= val;
nand_set_reg(drv, reg, x);
}
int status = nandcmd_read_id(mf_id, dev_id);
if(status < 0)
goto error;
static bool identify_chip(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
*
* Right now there is only a need for the 2nd variation, as that is
* the method used by the ATO25D1GA.
*
* Some chips also output more than 2 ID bytes.
*/
sfc_exec(NANDCMD_READID(1, 0), 0, drv->scratch_buf, 2|SFC_READ);
drv->mf_id = drv->scratch_buf[0];
drv->dev_id = drv->scratch_buf[1];
for(size_t i = 0; i < target_nand_chip_count; ++i) {
const nand_chip_data* data = &target_nand_chip_data[i];
if(data->mf_id == *mf_id && data->dev_id == *dev_id) {
nand_drv.chip_data = data;
break;
for(size_t i = 0; i < nr_supported_nand_chips; ++i) {
const nand_chip* chip = &supported_nand_chips[i];
if(chip->mf_id == drv->mf_id && chip->dev_id == drv->dev_id) {
drv->chip = chip;
return true;
}
}
if(!nand_drv.chip_data) {
status = NAND_ERR_UNKNOWN_CHIP;
goto error;
return false;
}
static void setup_chip_data(nand_drv* drv)
{
drv->ppb = 1 << drv->chip->log2_ppb;
drv->fpage_size = drv->chip->page_size + drv->chip->oob_size;
}
static void setup_chip_commands(nand_drv* drv)
{
/* Select commands appropriate for the chip */
drv->cmd_page_read = NANDCMD_PAGE_READ(drv->chip->row_cycles);
drv->cmd_program_execute = NANDCMD_PROGRAM_EXECUTE(drv->chip->row_cycles);
drv->cmd_block_erase = NANDCMD_BLOCK_ERASE(drv->chip->row_cycles);
if(drv->chip->flags & NAND_CHIPFLAG_QUAD) {
drv->cmd_read_cache = NANDCMD_READ_CACHE_x4(drv->chip->col_cycles);
drv->cmd_program_load = NANDCMD_PROGRAM_LOAD_x4(drv->chip->col_cycles);
} else {
drv->cmd_read_cache = NANDCMD_READ_CACHE(drv->chip->col_cycles);
drv->cmd_program_load = NANDCMD_PROGRAM_LOAD(drv->chip->col_cycles);
}
}
static void setup_chip_registers(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);
}
/* Set parameters according to new chip data */
sfc_set_dev_conf(nand_drv.chip_data->dev_conf);
sfc_set_clock(nand_drv.chip_data->clock_freq);
status = NAND_SUCCESS;
/* Clear OTP bit to access the main data array */
nand_upd_reg(drv, FREG_CFG, FREG_CFG_OTP_ENABLE, 0);
error:
sfc_unlock();
return status;
/* Clear write protection bits */
nand_set_reg(drv, FREG_PROT, FREG_PROT_UNLOCK);
}
const nand_chip_data* nand_get_chip_data(void)
int nand_open(nand_drv* drv)
{
return nand_drv.chip_data;
}
extern int nand_enable_writes(bool en)
{
if(en == nand_drv.write_enabled)
if(drv->refcount > 0)
return NAND_SUCCESS;
int rc = nandop_set_write_protect(!en);
if(rc == NAND_SUCCESS)
nand_drv.write_enabled = en;
/* Initialize the controller */
sfc_open();
sfc_set_dev_conf(supported_nand_chips[0].dev_conf);
sfc_set_clock(supported_nand_chips[0].clock_freq);
return rc;
/* 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);
setup_chip_commands(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;
}
static int nand_rdwr(bool write, uint32_t addr, uint32_t size, uint8_t* buf)
void nand_close(nand_drv* drv)
{
const uint32_t page_size = (1 << nand_drv.chip_data->log2_page_size);
if(drv->refcount == 0)
return;
if(addr & (page_size - 1))
return NAND_ERR_UNALIGNED;
if(size & (page_size - 1))
return NAND_ERR_UNALIGNED;
if(size <= 0)
return NAND_SUCCESS;
if(write && !nand_drv.write_enabled)
return NAND_ERR_WRITE_PROTECT;
if((uint32_t)buf & (CACHEALIGN_SIZE - 1))
return NAND_ERR_UNALIGNED;
/* 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);
addr >>= nand_drv.chip_data->log2_page_size;
size >>= nand_drv.chip_data->log2_page_size;
int rc = NAND_SUCCESS;
sfc_lock();
for(; size > 0; --size, ++addr, buf += page_size) {
if(write)
rc = nandop_write_page(addr, buf);
else
rc = nandop_read_page(addr, buf);
if(rc)
break;
}
sfc_unlock();
return rc;
sfc_close();
drv->refcount--;
}
int nand_read(uint32_t addr, uint32_t size, uint8_t* buf)
static uint8_t nand_wait_busy(nand_drv* drv)
{
return nand_rdwr(false, addr, size, buf);
}
int nand_write(uint32_t addr, uint32_t size, const uint8_t* buf)
{
return nand_rdwr(true, addr, size, (uint8_t*)buf);
}
int nand_erase(uint32_t addr, uint32_t size)
{
const uint32_t page_size = 1 << nand_drv.chip_data->log2_page_size;
const uint32_t block_size = page_size << nand_drv.chip_data->log2_block_size;
const uint32_t pages_per_block = 1 << nand_drv.chip_data->log2_block_size;
if(addr & (block_size - 1))
return NAND_ERR_UNALIGNED;
if(size & (block_size - 1))
return NAND_ERR_UNALIGNED;
if(size <= 0)
return NAND_SUCCESS;
if(!nand_drv.write_enabled)
return NAND_ERR_WRITE_PROTECT;
addr >>= nand_drv.chip_data->log2_page_size;
size >>= nand_drv.chip_data->log2_page_size;
size >>= nand_drv.chip_data->log2_block_size;
int rc = NAND_SUCCESS;
sfc_lock();
for(; size > 0; --size, addr += pages_per_block)
if((rc = nandop_erase_block(addr)))
break;
sfc_unlock();
return rc;
}
/*
* NAND ops
*/
static int nandop_wait_status(int errbit)
{
int reg;
uint8_t reg;
do {
reg = nandcmd_get_feature(NAND_FREG_STATUS);
if(reg < 0)
return reg;
} while(reg & NAND_FREG_STATUS_OIP);
if(reg & errbit)
return NAND_ERR_COMMAND;
reg = nand_get_reg(drv, FREG_STATUS);
} while(reg & FREG_STATUS_BUSY);
return reg;
}
static int nandop_read_page(uint32_t row_addr, uint8_t* buf)
int nand_block_erase(nand_drv* drv, nand_block_t block)
{
int status;
sfc_exec(NANDCMD_WR_EN, 0, NULL, 0);
sfc_exec(drv->cmd_block_erase, block, NULL, 0);
if((status = nandcmd_page_read_to_cache(row_addr)) < 0)
return status;
if((status = nandop_wait_status(0)) < 0)
return status;
if((status = nandcmd_read_from_cache(buf)) < 0)
return status;
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(nand_drv* drv, nand_page_t page, const void* buffer)
{
sfc_exec(NANDCMD_WR_EN, 0, NULL, 0);
sfc_exec(drv->cmd_program_load, 0, (void*)buffer, drv->fpage_size|SFC_WRITE);
sfc_exec(drv->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(nand_drv* drv, nand_page_t page, void* buffer)
{
sfc_exec(drv->cmd_page_read, page, NULL, 0);
nand_wait_busy(drv);
sfc_exec(drv->cmd_read_cache, 0, buffer, drv->fpage_size|SFC_READ);
return NAND_SUCCESS;
}
static int nandop_write_page(uint32_t row_addr, const uint8_t* buf)
int nand_read_bytes(nand_drv* drv, uint32_t byte_addr, uint32_t byte_len, void* buffer)
{
int status;
if(byte_len == 0)
return NAND_SUCCESS;
if((status = nandcmd_write_enable()) < 0)
return status;
if((status = nandcmd_program_load(buf)) < 0)
return status;
if((status = nandcmd_program_execute(row_addr)) < 0)
return status;
if((status = nandop_wait_status(NAND_FREG_STATUS_P_FAIL)) < 0)
return status;
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;
return NAND_SUCCESS;
}
memcpy(buffer, &drv->page_buf[offset], MIN(pg_size, byte_len));
static int nandop_erase_block(uint32_t block_addr)
{
int status;
if(byte_len <= pg_size)
break;
if((status = nandcmd_write_enable()) < 0)
return status;
if((status = nandcmd_block_erase(block_addr)) < 0)
return status;
if((status = nandop_wait_status(NAND_FREG_STATUS_E_FAIL)) < 0)
return status;
return NAND_SUCCESS;
}
static int nandop_set_write_protect(bool en)
{
int val = nandcmd_get_feature(NAND_FREG_PROTECTION);
if(val < 0)
return val;
if(en) {
val |= NAND_FREG_PROTECTION_ALLBP;
if(nand_drv.chip_data->flags & NANDCHIP_FLAG_USE_BRWD)
val |= NAND_FREG_PROTECTION_BRWD;
} else {
val &= ~NAND_FREG_PROTECTION_ALLBP;
if(nand_drv.chip_data->flags & NANDCHIP_FLAG_USE_BRWD)
val &= ~NAND_FREG_PROTECTION_BRWD;
offset = 0;
byte_len -= pg_size;
buffer += pg_size;
page++;
}
/* NOTE: The WP pin typically only protects changes to the protection
* register -- it doesn't actually prevent writing to the chip. That's
* why it should be re-enabled after setting the new protection status.
*/
sfc_set_wp_enable(false);
int status = nandcmd_set_feature(NAND_FREG_PROTECTION, val);
sfc_set_wp_enable(true);
return NAND_SUCCESS;
}
if(status < 0)
return status;
int nand_write_bytes(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;
}
/*
* Low-level NAND commands
/* 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.
*/
static int nandcmd_read_id(int* mf_id, int* dev_id)
{
sfc_op op = {0};
op.command = NAND_CMD_READ_ID;
op.flags = SFC_FLAG_READ;
op.addr_bytes = 1;
op.addr_lo = 0;
op.data_bytes = 2;
op.buffer = nand_auxbuf;
if(sfc_exec(&op))
return NAND_ERR_CONTROLLER;
*mf_id = nand_auxbuf[0];
*dev_id = nand_auxbuf[1];
return NAND_SUCCESS;
}
static int nandcmd_write_enable(void)
{
sfc_op op = {0};
op.command = NAND_CMD_WRITE_ENABLE;
if(sfc_exec(&op))
return NAND_ERR_CONTROLLER;
return NAND_SUCCESS;
}
static int nandcmd_get_feature(uint8_t reg)
{
sfc_op op = {0};
op.command = NAND_CMD_GET_FEATURE;
op.flags = SFC_FLAG_READ;
op.addr_bytes = 1;
op.addr_lo = reg;
op.data_bytes = 1;
op.buffer = nand_auxbuf;
if(sfc_exec(&op))
return NAND_ERR_CONTROLLER;
return nand_auxbuf[0];
}
static int nandcmd_set_feature(uint8_t reg, uint8_t val)
{
sfc_op op = {0};
op.command = NAND_CMD_SET_FEATURE;
op.flags = SFC_FLAG_WRITE;
op.addr_bytes = 1;
op.addr_lo = reg;
op.data_bytes = 1;
op.buffer = nand_auxbuf;
nand_auxbuf[0] = val;
if(sfc_exec(&op))
return NAND_ERR_CONTROLLER;
return NAND_SUCCESS;
}
static int nandcmd_page_read_to_cache(uint32_t row_addr)
{
sfc_op op = {0};
op.command = NAND_CMD_PAGE_READ_TO_CACHE;
op.addr_bytes = nand_drv.chip_data->rowaddr_width;
op.addr_lo = row_addr;
if(sfc_exec(&op))
return NAND_ERR_CONTROLLER;
return NAND_SUCCESS;
}
static int nandcmd_read_from_cache(uint8_t* buf)
{
sfc_op op = {0};
if(nand_drv.chip_data->flags & NANDCHIP_FLAG_QUAD) {
op.command = NAND_CMD_READ_FROM_CACHEx4;
op.mode = SFC_MODE_QUAD_IO;
} else {
op.command = NAND_CMD_READ_FROM_CACHE;
op.mode = SFC_MODE_STANDARD;
}
op.flags = SFC_FLAG_READ;
op.addr_bytes = nand_drv.chip_data->coladdr_width;
op.addr_lo = 0;
op.dummy_bits = 8; // NOTE: this may need a chip_data parameter
op.data_bytes = (1 << nand_drv.chip_data->log2_page_size);
op.buffer = buf;
if(sfc_exec(&op))
return NAND_ERR_CONTROLLER;
return NAND_SUCCESS;
}
static int nandcmd_program_load(const uint8_t* buf)
{
sfc_op op = {0};
if(nand_drv.chip_data->flags & NANDCHIP_FLAG_QUAD) {
op.command = NAND_CMD_PROGRAM_LOADx4;
op.mode = SFC_MODE_QUAD_IO;
} else {
op.command = NAND_CMD_PROGRAM_LOAD;
op.mode = SFC_MODE_STANDARD;
}
op.flags = SFC_FLAG_WRITE;
op.addr_bytes = nand_drv.chip_data->coladdr_width;
op.addr_lo = 0;
op.data_bytes = (1 << nand_drv.chip_data->log2_page_size);
op.buffer = (void*)buf;
if(sfc_exec(&op))
return NAND_ERR_CONTROLLER;
return NAND_SUCCESS;
}
static int nandcmd_program_execute(uint32_t row_addr)
{
sfc_op op = {0};
op.command = NAND_CMD_PROGRAM_EXECUTE;
op.addr_bytes = nand_drv.chip_data->rowaddr_width;
op.addr_lo = row_addr;
if(sfc_exec(&op))
return NAND_ERR_CONTROLLER;
return NAND_SUCCESS;
}
static int nandcmd_block_erase(uint32_t block_addr)
{
sfc_op op = {0};
op.command = NAND_CMD_BLOCK_ERASE;
op.addr_bytes = nand_drv.chip_data->rowaddr_width;
op.addr_lo = block_addr;
if(sfc_exec(&op))
return NAND_ERR_CONTROLLER;
return NAND_SUCCESS;
}

View File

@ -22,86 +22,161 @@
#ifndef __NAND_X1000_H__
#define __NAND_X1000_H__
/* NOTE: this is a very minimal API designed only to support a bootloader.
* Not suitable for general data storage. It doesn't have proper support for
* partial page writes, access to spare area, etc, which are all necessary
* for an effective flash translation layer.
*
* There's no ECC support. This can be added if necessary, but it's unlikely
* the boot area on any X1000 device uses software ECC as Ingenic's SPL simply
* doesn't have much room for more code (theirs programmed to work on multiple
* hardware configurations, so it's bigger than ours).
*/
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdbool.h>
#include "kernel.h"
/* Error codes which can be returned by the NAND API */
#define NAND_SUCCESS 0
#define NAND_ERR_UNKNOWN_CHIP (-1)
#define NAND_ERR_UNALIGNED (-2)
#define NAND_ERR_WRITE_PROTECT (-3)
#define NAND_ERR_CONTROLLER (-4)
#define NAND_ERR_COMMAND (-5)
#define NAND_SUCCESS 0
#define NAND_ERR_UNKNOWN_CHIP (-1)
#define NAND_ERR_PROGRAM_FAIL (-2)
#define NAND_ERR_ERASE_FAIL (-3)
#define NAND_ERR_UNALIGNED (-4)
/* Chip supports quad I/O for page read/write */
#define NANDCHIP_FLAG_QUAD 0x01
/* keep max page size in sync with the NAND chip table in the .c file */
#define NAND_DRV_SCRATCHSIZE 32
#define NAND_DRV_MAXPAGESIZE 2112
/* Set/clear the BRWD bit when enabling/disabling write protection */
#define NANDCHIP_FLAG_USE_BRWD 0x02
/* Quad I/O support bit */
#define NAND_CHIPFLAG_QUAD 0x0001
/* Chip requires QE bit set to enable quad I/O mode */
#define NAND_CHIPFLAG_HAS_QE_BIT 0x0002
typedef struct nand_chip_data {
/* Chip manufacturer / device ID */
uint8_t mf_id;
uint8_t dev_id;
/* Types to distinguish between block & page addresses in the API.
*
* BIT 31 log2_ppb bits
* +-------------------------------+---------------+
* nand_page_t = | block nr | page nr |
* +-------------------------------+---------------+
* BIT 0
*
* The page address is split into block and page numbers. Page numbers occupy
* the lower log2_ppb bits, and the block number occupies the upper bits.
*
* Block addresses are structured the same as page addresses, but with a page
* number of 0. So block number N has address N << log2_ppb.
*/
typedef uint32_t nand_block_t;
typedef uint32_t nand_page_t;
/* Width of row/column addresses in bytes */
uint8_t rowaddr_width;
uint8_t coladdr_width;
typedef struct nand_chip {
/* Manufacturer and device ID bytes */
uint8_t mf_id;
uint8_t dev_id;
/* SFC dev conf and clock frequency to use for this device */
uint32_t dev_conf;
/* Row/column address width */
uint8_t row_cycles;
uint8_t col_cycles;
/* Base2 logarithm of the number of pages per block */
unsigned log2_ppb;
/* Size of a page's main / oob areas, in bytes. */
unsigned page_size;
unsigned oob_size;
/* Total number of blocks in the chip */
unsigned nr_blocks;
/* Clock frequency to use */
uint32_t clock_freq;
/* Page size in bytes = 1 << log2_page_size */
uint32_t log2_page_size;
/* Value of sfc_dev_conf */
uint32_t dev_conf;
/* Block size in number of pages = 1 << log2_block_size */
uint32_t log2_block_size;
/* Chip flags */
/* Chip specific flags */
uint32_t flags;
} nand_chip_data;
} nand_chip;
/* Open or close the NAND driver. The NAND driver takes control of the SFC,
* so that driver must be in the closed state before opening the NAND driver.
typedef struct nand_drv {
/* NAND access lock. Needs to be held during any operations. */
struct mutex mutex;
/* Reference count for open/close operations */
unsigned refcount;
/* Scratch and page buffers. Both need to be cacheline-aligned and are
* provided externally by the caller prior to nand_open().
*
* - The scratch buffer is NAND_DRV_SCRATCHSIZE bytes long and is used
* for small data transfers associated with commands. It must not be
* disturbed while any NAND operation is in progress.
*
* - The page buffer is used by certain functions like nand_read_bytes(),
* but it's main purpose is to provide a common temporary buffer for
* driver users to perform I/O with. Must be fpage_size bytes long.
*/
uint8_t* scratch_buf;
uint8_t* page_buf;
/* Pointer to the chip data. */
const nand_chip* chip;
/* Pages per block = 1 << chip->log2_ppb */
unsigned ppb;
/* Full page size = chip->page_size + chip->oob_size */
unsigned fpage_size;
/* Probed mf_id / dev_id for debugging, in case identification fails. */
uint8_t mf_id;
uint8_t dev_id;
/* SFC commands used for I/O, these are set based on chip data */
uint32_t cmd_page_read;
uint32_t cmd_read_cache;
uint32_t cmd_program_load;
uint32_t cmd_program_execute;
uint32_t cmd_block_erase;
} nand_drv;
extern const nand_chip supported_nand_chips[];
extern const size_t nr_supported_nand_chips;
/* Return the static NAND driver instance.
*
* ALL normal Rockbox code should use this instance. The SPL does not
* use it, because it needs to manually place buffers in external RAM.
*/
extern int nand_open(void);
extern void nand_close(void);
extern nand_drv* nand_init(void);
/* Identify the NAND chip. This must be done after opening the driver and
* prior to any data access, in order to set the chip parameters. */
extern int nand_identify(int* mf_id, int* dev_id);
static inline void nand_lock(nand_drv* drv)
{
mutex_lock(&drv->mutex);
}
/* Return the chip data for the identified NAND chip.
* Returns NULL if the chip is not identified. */
const nand_chip_data* nand_get_chip_data(void);
static inline void nand_unlock(nand_drv* drv)
{
mutex_unlock(&drv->mutex);
}
/* Controls the chip's write protect features. The driver also keeps track of
* this flag and refuses to perform write or erase operations unless you have
* enabled writes. Writes should be disabled again when you finish writing. */
extern int nand_enable_writes(bool en);
/* Open or close the NAND driver
*
* The NAND driver is reference counted, and opening / closing it will
* increment and decrement the reference count. The hardware is only
* controlled when the reference count rises above or falls to 0, else
* these functions are no-ops which always succeed.
*
* These functions require the lock to be held.
*/
extern int nand_open(nand_drv* drv);
extern void nand_close(nand_drv* drv);
/* Reading and writing operates on whole pages at a time. If the address or
* size is not aligned to a multiple of the page size, no data will be read
* or written and an error code is returned. */
extern int nand_read(uint32_t addr, uint32_t size, uint8_t* buf);
extern int nand_write(uint32_t addr, uint32_t size, const uint8_t* buf);
/* Read / program / erase operations. Buffer needs to be cache-aligned for DMA.
* Read and program operate on full page data, ie. including OOB data areas.
*
* NOTE: ECC is not implemented. If it ever needs to be, these functions will
* probably use ECC transparently. All code should be written to expect this.
*/
extern int nand_block_erase(nand_drv* drv, nand_block_t block);
extern int nand_page_program(nand_drv* drv, nand_page_t page, const void* buffer);
extern int nand_page_read(nand_drv* drv, nand_page_t page, void* buffer);
/* Erase operates on whole blocks. Like the page read/write operations,
* the address and size must be aligned to a multiple of the block size.
* If not, no blocks are erased and an error code is returned. */
extern int nand_erase(uint32_t addr, uint32_t size);
/* Wrappers to read/write bytes. For simple access to the main data area only.
* The write address / length must align to a block boundary. Reads do not have
* any alignment requirement. OOB data is never read, and is written as 0xff.
*/
extern int nand_read_bytes(nand_drv* drv, uint32_t byte_addr, uint32_t byte_len, void* buffer);
extern int nand_write_bytes(nand_drv* drv, uint32_t byte_addr, uint32_t byte_len, const void* buffer);
#endif /* __NAND_X1000_H__ */

View File

@ -21,86 +21,71 @@
#include "system.h"
#include "kernel.h"
#include "panic.h"
#include "sfc-x1000.h"
#include "clk-x1000.h"
#include "irq-x1000.h"
#include "x1000/sfc.h"
#include "x1000/cpm.h"
/* DMA works, but not in the SPL due to some hardware not being set up right.
* Only the SPL and bootloader actually require flash access, so to keep it
* simple, DMA is unconditionally disabled. */
//#define NEED_SFC_DMA
/* #define USE_DMA */
#define FIFO_THRESH 31
#define SFC_STATUS_PENDING (-1)
static void sfc_poll_wait(void);
#ifdef USE_DMA
static void sfc_irq_wait(void);
#ifdef NEED_SFC_DMA
static struct mutex sfc_mutex;
static struct semaphore sfc_sema;
static struct timeout sfc_lockup_tmo;
static bool sfc_inited = false;
static volatile int sfc_status;
#else
# define sfc_status SFC_STATUS_OK
#endif
void sfc_init(void)
{
#ifdef NEED_SFC_DMA
if(sfc_inited)
return;
mutex_init(&sfc_mutex);
semaphore_init(&sfc_sema, 1, 0);
sfc_inited = true;
/* This function pointer thing is a hack for the SPL, since it has to use
* the NAND driver directly and we can't afford to drag in the whole kernel
* just to wait on a semaphore. */
static void(*sfc_wait)(void) = sfc_poll_wait;
#endif
}
void sfc_lock(void)
{
#ifdef NEED_SFC_DMA
mutex_lock(&sfc_mutex);
#endif
}
void sfc_unlock(void)
{
#ifdef NEED_SFC_DMA
mutex_unlock(&sfc_mutex);
#endif
}
void sfc_open(void)
{
jz_writef(CPM_CLKGR, SFC(0));
#ifdef USE_DMA
jz_writef(SFC_GLB, OP_MODE_V(DMA), BURST_MD_V(INCR32),
PHASE_NUM(1), THRESHOLD(FIFO_THRESH), WP_EN(1));
#else
jz_writef(SFC_GLB, OP_MODE_V(SLAVE), PHASE_NUM(1),
THRESHOLD(FIFO_THRESH), WP_EN(1));
#endif
REG_SFC_CGE = 0;
REG_SFC_INTC = 0x1f;
REG_SFC_MEM_ADDR = 0;
#ifdef NEED_SFC_DMA
jz_writef(SFC_GLB, OP_MODE_V(DMA), BURST_MD_V(INCR32));
system_enable_irq(IRQ_SFC);
#endif
}
void sfc_close(void)
{
#ifdef NEED_SFC_DMA
system_disable_irq(IRQ_SFC);
#endif
REG_SFC_CGE = 0x1f;
jz_writef(CPM_CLKGR, SFC(1));
}
void sfc_irq_begin(void)
{
#ifdef USE_DMA
static bool inited = false;
if(!inited) {
semaphore_init(&sfc_sema, 1, 0);
inited = true;
}
system_enable_irq(IRQ_SFC);
sfc_wait = sfc_irq_wait;
#endif
}
void sfc_irq_end(void)
{
#ifdef USE_DMA
system_disable_irq(IRQ_SFC);
sfc_wait = sfc_poll_wait;
#endif
}
void sfc_set_clock(uint32_t freq)
{
/* TODO: This is a hack so we can use MPLL in the SPL.
* There must be a better way to do this... */
/* FIXME: Get rid of this hack & allow defining a real clock tree... */
x1000_clk_t clksrc = X1000_CLK_MPLL;
uint32_t in_freq = clk_get(clksrc);
if(in_freq < freq) {
@ -115,170 +100,99 @@ void sfc_set_clock(uint32_t freq)
jz_writef(CPM_SSICDR, CE(0));
}
#ifdef NEED_SFC_DMA
static int sfc_lockup_tmo_cb(struct timeout* tmo)
#ifndef USE_DMA
static void sfc_fifo_rdwr(bool write, void* buffer, uint32_t data_bytes)
{
(void)tmo;
uint32_t* word_buf = (uint32_t*)buffer;
uint32_t sr_bit = write ? BM_SFC_SR_TREQ : BM_SFC_SR_RREQ;
uint32_t clr_bit = write ? BM_SFC_SCR_CLR_TREQ : BM_SFC_SCR_CLR_RREQ;
uint32_t data_words = (data_bytes + 3) / 4;
while(data_words > 0) {
if(REG_SFC_SR & sr_bit) {
REG_SFC_SCR = clr_bit;
int irq = disable_irq_save();
if(sfc_status == SFC_STATUS_PENDING) {
sfc_status = SFC_STATUS_LOCKUP;
jz_overwritef(SFC_TRIG, STOP(1));
semaphore_release(&sfc_sema);
/* We need to read/write in bursts equal to FIFO threshold amount
* X1000 PM, 10.8.5, SFC > software guidelines > slave mode */
uint32_t amount = MIN(data_words, FIFO_THRESH);
data_words -= amount;
uint32_t* endptr = word_buf + amount;
for(; word_buf != endptr; ++word_buf) {
if(write)
REG_SFC_DATA = *word_buf;
else
*word_buf = REG_SFC_DATA;
}
}
}
}
#endif
void sfc_exec(uint32_t cmd, uint32_t addr, void* data, uint32_t size)
{
/* Deal with transfer direction */
bool write = (size & SFC_WRITE) != 0;
uint32_t glb = REG_SFC_GLB;
if(data) {
if(write) {
jz_vwritef(glb, SFC_GLB, TRAN_DIR_V(WRITE));
size &= ~SFC_WRITE;
#ifdef USE_DMA
commit_dcache_range(data, size);
#endif
} else {
jz_vwritef(glb, SFC_GLB, TRAN_DIR_V(READ));
#ifdef USE_DMA
discard_dcache_range(data, size);
#endif
}
}
restore_irq(irq);
return 0;
/* Program transfer configuration */
REG_SFC_GLB = glb;
REG_SFC_TRAN_LENGTH = size;
#ifdef USE_DMA
REG_SFC_MEM_ADDR = PHYSADDR(data);
#endif
REG_SFC_TRAN_CONF(0) = cmd;
REG_SFC_DEV_ADDR(0) = addr;
REG_SFC_DEV_PLUS(0) = 0;
/* Clear old interrupts */
REG_SFC_SCR = 0x1f;
jz_writef(SFC_INTC, MSK_END(0));
/* Start the command */
jz_overwritef(SFC_TRIG, FLUSH(1));
jz_overwritef(SFC_TRIG, START(1));
/* Data transfer by PIO or DMA, and wait for completion */
#ifndef USE_DMA
sfc_fifo_rdwr(write, data, size);
sfc_poll_wait();
#else
sfc_wait();
#endif
}
static void sfc_wait_end(void)
static void sfc_poll_wait(void)
{
while(jz_readf(SFC_SR, END) == 0);
jz_overwritef(SFC_SCR, CLR_END(1));
}
#ifdef USE_DMA
static void sfc_irq_wait(void)
{
semaphore_wait(&sfc_sema, TIMEOUT_BLOCK);
}
void SFC(void)
{
unsigned sr = REG_SFC_SR & ~REG_SFC_INTC;
if(jz_vreadf(sr, SFC_SR, OVER)) {
jz_overwritef(SFC_SCR, CLR_OVER(1));
sfc_status = SFC_STATUS_OVERFLOW;
} else if(jz_vreadf(sr, SFC_SR, UNDER)) {
jz_overwritef(SFC_SCR, CLR_UNDER(1));
sfc_status = SFC_STATUS_UNDERFLOW;
} else if(jz_vreadf(sr, SFC_SR, END)) {
jz_overwritef(SFC_SCR, CLR_END(1));
sfc_status = SFC_STATUS_OK;
} else {
panicf("SFC IRQ bug");
return;
}
/* Not sure this is wholly correct */
if(sfc_status != SFC_STATUS_OK)
jz_overwritef(SFC_TRIG, STOP(1));
REG_SFC_INTC = 0x1f;
/* the only interrupt we use is END; errors are basically not
* possible with the SPI interface... */
semaphore_release(&sfc_sema);
jz_overwritef(SFC_SCR, CLR_END(1));
jz_writef(SFC_INTC, MSK_END(1));
}
#else
/* Note the X1000 is *very* picky about how the SFC FIFOs are accessed
* so please do NOT try to rearrange the code without testing it first!
*/
static void sfc_fifo_read(unsigned* buffer, int data_bytes)
{
int data_words = (data_bytes + 3) / 4;
while(data_words > 0) {
if(jz_readf(SFC_SR, RREQ)) {
jz_overwritef(SFC_SCR, CLR_RREQ(1));
int amount = data_words > FIFO_THRESH ? FIFO_THRESH : data_words;
data_words -= amount;
while(amount > 0) {
*buffer++ = REG_SFC_DATA;
amount -= 1;
}
}
}
}
static void sfc_fifo_write(const unsigned* buffer, int data_bytes)
{
int data_words = (data_bytes + 3) / 4;
while(data_words > 0) {
if(jz_readf(SFC_SR, TREQ)) {
jz_overwritef(SFC_SCR, CLR_TREQ(1));
int amount = data_words > FIFO_THRESH ? FIFO_THRESH : data_words;
data_words -= amount;
while(amount > 0) {
REG_SFC_DATA = *buffer++;
amount -= 1;
}
}
}
}
static void sfc_wait_end(void)
{
while(jz_readf(SFC_SR, END) == 0);
jz_overwritef(SFC_SCR, CLR_TREQ(1));
}
#endif /* NEED_SFC_DMA */
int sfc_exec(const sfc_op* op)
{
#ifdef NEED_SFC_DMA
uint32_t intc_clear = jz_orm(SFC_INTC, MSK_END);
#endif
if(op->flags & (SFC_FLAG_READ|SFC_FLAG_WRITE)) {
jz_writef(SFC_TRAN_CONF(0), DATA_EN(1));
REG_SFC_TRAN_LENGTH = op->data_bytes;
#ifdef NEED_SFC_DMA
REG_SFC_MEM_ADDR = PHYSADDR(op->buffer);
#endif
if(op->flags & SFC_FLAG_READ)
{
jz_writef(SFC_GLB, TRAN_DIR_V(READ));
#ifdef NEED_SFC_DMA
discard_dcache_range(op->buffer, op->data_bytes);
intc_clear |= jz_orm(SFC_INTC, MSK_OVER);
#endif
}
else
{
jz_writef(SFC_GLB, TRAN_DIR_V(WRITE));
#ifdef NEED_SFC_DMA
commit_dcache_range(op->buffer, op->data_bytes);
intc_clear |= jz_orm(SFC_INTC, MSK_UNDER);
#endif
}
} else {
jz_writef(SFC_TRAN_CONF(0), DATA_EN(0));
REG_SFC_TRAN_LENGTH = 0;
#ifdef NEED_SFC_DMA
REG_SFC_MEM_ADDR = 0;
#endif
}
bool dummy_first = (op->flags & SFC_FLAG_DUMMYFIRST) != 0;
jz_writef(SFC_TRAN_CONF(0),
MODE(op->mode), POLL_EN(0),
ADDR_WIDTH(op->addr_bytes),
PHASE_FMT(dummy_first ? 1 : 0),
DUMMY_BITS(op->dummy_bits),
COMMAND(op->command), CMD_EN(1));
REG_SFC_DEV_ADDR(0) = op->addr_lo;
REG_SFC_DEV_PLUS(0) = op->addr_hi;
#ifdef NEED_SFC_DMA
sfc_status = SFC_STATUS_PENDING;
timeout_register(&sfc_lockup_tmo, sfc_lockup_tmo_cb, 10*HZ, 0);
REG_SFC_SCR = 0x1f;
REG_SFC_INTC &= ~intc_clear;
#endif
jz_overwritef(SFC_TRIG, FLUSH(1));
jz_overwritef(SFC_TRIG, START(1));
#ifndef NEED_SFC_DMA
if(op->flags & SFC_FLAG_READ)
sfc_fifo_read((unsigned*)op->buffer, op->data_bytes);
if(op->flags & SFC_FLAG_WRITE)
sfc_fifo_write((const unsigned*)op->buffer, op->data_bytes);
#endif
sfc_wait_end();
#ifdef NEED_SFC_DMA
if(op->flags & SFC_FLAG_READ)
discard_dcache_range(op->buffer, op->data_bytes);
#endif
return sfc_status;
}

View File

@ -19,87 +19,107 @@
*
****************************************************************************/
#ifndef __SFC_X1000_H__
#define __SFC_X1000_H__
#include "x1000/sfc.h"
#include <stdint.h>
#include <stdbool.h>
#include "clk-x1000.h"
#include "x1000/sfc.h"
/* SPI flash controller interface -- this is a low-level driver upon which
* you can build NAND/NOR flash drivers. The main function is sfc_exec(),
* used to issue commands, transfer data, etc.
*/
#define SFC_FLAG_READ 0x01 /* Read data */
#define SFC_FLAG_WRITE 0x02 /* Write data */
#define SFC_FLAG_DUMMYFIRST 0x04 /* Do dummy bits before sending address.
* Default is dummy bits after address.
*/
/* SPI transfer mode. If in doubt, check with the X1000 manual and confirm
* the transfer format is what you expect.
*/
#define SFC_MODE_STANDARD 0
#define SFC_MODE_DUAL_IN_DUAL_OUT 1
#define SFC_MODE_DUAL_IO 2
#define SFC_MODE_FULL_DUAL_IO 3
#define SFC_MODE_QUAD_IN_QUAD_OUT 4
#define SFC_MODE_QUAD_IO 5
#define SFC_MODE_FULL_QUAD_IO 6
/* Return status codes for sfc_exec() */
#define SFC_STATUS_OK 0
#define SFC_STATUS_OVERFLOW 1
#define SFC_STATUS_UNDERFLOW 2
#define SFC_STATUS_LOCKUP 3
typedef struct sfc_op {
int command; /* Command number */
int mode; /* SPI transfer mode */
int flags; /* Flags for this op */
int addr_bytes; /* Number of address bytes */
int dummy_bits; /* Number of dummy bits (yes: bits, not bytes) */
uint32_t addr_lo; /* Lower 32 bits of address */
uint32_t addr_hi; /* Upper 32 bits of address */
int data_bytes; /* Number of data bytes to read/write */
void* buffer; /* Data buffer -- MUST be word-aligned */
} sfc_op;
/* One-time driver init for mutexes/etc needed for handling interrupts.
* This can be safely called multiple times; only the first call will
* actually perform the init.
*/
extern void sfc_init(void);
/* Controller mutex -- lock before touching the driver */
extern void sfc_lock(void);
extern void sfc_unlock(void);
/* Open/close the driver. The driver must be open in order to do operations.
* Closing the driver shuts off the hardware; the driver can be re-opened at
* a later time when it's needed again.
/* SPI transfer mode. SFC_TMODE_X_Y_Z means:
*
* After opening the driver, you must also program a valid device configuration
* and clock rate using sfc_set_dev_conf() and sfc_set_clock().
* - X lines for command phase
* - Y lines for address+dummy phase
* - Z lines for data phase
*/
#define SFC_TMODE_1_1_1 0
#define SFC_TMODE_1_1_2 1
#define SFC_TMODE_1_2_2 2
#define SFC_TMODE_2_2_2 3
#define SFC_TMODE_1_1_4 4
#define SFC_TMODE_1_4_4 5
#define SFC_TMODE_4_4_4 6
/* Phase format
* _____________________
* / SFC_PFMT_ADDR_FIRST \
* +-----+-------+-------+------+
* | cmd | addr | dummy | data |
* +-----+-------+-------+------+
* ______________________
* / SFC_PFMT_DUMMY_FIRST \
* +-----+-------+-------+------+
* | cmd | dummy | addr | data |
* +-----+-------+-------+------+
*/
#define SFC_PFMT_ADDR_FIRST 0
#define SFC_PFMT_DUMMY_FIRST 1
/* Direction of transfer flag */
#define SFC_READ 0
#define SFC_WRITE (1 << 31)
/** \brief Macro to generate an SFC command for use with sfc_exec()
* \param cmd Command number (up to 16 bits)
* \param tmode SPI transfer mode
* \param awidth Number of address bytes
* \param dwidth Number of dummy cycles (1 cycle = 1 bit)
* \param pfmt Phase format (address first or dummy first)
* \param data_en 1 to enable data phase, 0 to omit it
*/
#define SFC_CMD(cmd, tmode, awidth, dwidth, pfmt, data_en) \
jz_orf(SFC_TRAN_CONF, COMMAND(cmd), CMD_EN(1), \
MODE(tmode), ADDR_WIDTH(awidth), DUMMY_BITS(dwidth), \
PHASE_FMT(pfmt), DATA_EN(data_en))
/* Open/close SFC hardware */
extern void sfc_open(void);
extern void sfc_close(void);
/* These functions can be called at any time while the driver is open, but
* must not be called while there is an operation in progress. It's the
* caller's job to ensure the configuration will work with the device and
* be capable of reading back data correctly.
*
* - sfc_set_dev_conf() writes its argument to the SFC_DEV_CONF register.
* - sfc_set_wp_enable() sets the state of the write-protect pin (WP).
* - sfc_set_clock() sets the controller clock frequency (in Hz).
*/
#define sfc_set_dev_conf(dev_conf) \
do { REG_SFC_DEV_CONF = (dev_conf); } while(0)
#define sfc_set_wp_enable(en) \
jz_writef(SFC_GLB, WP_EN((en) ? 1 : 0))
/* Enable IRQ mode, instead of busy waiting for operations to complete.
* Needs to be called separately after sfc_open(), because the SPL has to
* use busy waiting, but we cannot #ifdef it for the SPL due to limitations
* of the build system. */
extern void sfc_irq_begin(void);
extern void sfc_irq_end(void);
/* Change the SFC clock frequency */
extern void sfc_set_clock(uint32_t freq);
/* Execute an operation. Returns zero on success, nonzero on failure. */
extern int sfc_exec(const sfc_op* op);
/* Set the device configuration register */
inline void sfc_set_dev_conf(uint32_t conf)
{
REG_SFC_DEV_CONF = conf;
}
/* Control the state of the write protect pin */
inline void sfc_set_wp_enable(bool en)
{
jz_writef(SFC_GLB, WP_EN(en ? 1 : 0));
}
/** \brief Execute a command
* \param cmd Command encoded by `SFC_CMD` macro.
* \param addr Address up to 32 bits; pass 0 if the command doesn't need it
* \param data Buffer for data transfer commands, must be cache-aligned
* \param size Number of data bytes / direction of transfer flag
* \returns SFC status code: 0 on success and < 0 on failure.
*
* - Non-data commands must pass `data = NULL` and `size = 0` in order to
* get correct results.
*
* - Data commands must specify a direction of transfer using the high bit
* of the `size` argument by OR'ing in `SFC_READ` or `SFC_WRITE`.
*/
extern void sfc_exec(uint32_t cmd, uint32_t addr, void* data, uint32_t size);
/* NOTE: the above will need to be changed if we need better performance
* The hardware can do multiple commands in a sequence, including polling,
* and emit an interrupt only at the end.
*
* Also, some chips need more than 4 address bytes even though the block
* and page numbers would still fit in 32 bits; the current API cannot
* handle this.
*/
#endif /* __SFC_X1000_H__ */