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:
parent
9246cbc65e
commit
9f950d8bbf
6 changed files with 711 additions and 802 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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__ */
|
||||
|
|
Loading…
Reference in a new issue