summaryrefslogtreecommitdiff
path: root/firmware/target
diff options
context:
space:
mode:
authorAidan MacDonald <amachronic@protonmail.com>2021-06-19 17:48:13 +0100
committerAidan MacDonald <amachronic@protonmail.com>2021-06-27 19:09:03 +0100
commit9f950d8bbf25995d0ba71ce2cd6cc9c0d3b91a9c (patch)
tree4946622b4bb3d8862237ae6eeddb39e1c2bfcf67 /firmware/target
parent9246cbc65e0d71428f7ac8662efdb47f7b9cc174 (diff)
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
Diffstat (limited to 'firmware/target')
-rw-r--r--firmware/target/mips/ingenic_x1000/fiiom3k/installer-fiiom3k.c58
-rw-r--r--firmware/target/mips/ingenic_x1000/fiiom3k/spl-fiiom3k.c44
-rw-r--r--firmware/target/mips/ingenic_x1000/nand-x1000.c686
-rw-r--r--firmware/target/mips/ingenic_x1000/nand-x1000.h207
-rw-r--r--firmware/target/mips/ingenic_x1000/sfc-x1000.c294
-rw-r--r--firmware/target/mips/ingenic_x1000/sfc-x1000.h152
6 files changed, 675 insertions, 766 deletions
diff --git a/firmware/target/mips/ingenic_x1000/fiiom3k/installer-fiiom3k.c b/firmware/target/mips/ingenic_x1000/fiiom3k/installer-fiiom3k.c
index 10a58ace38..8ce73bf09e 100644
--- a/firmware/target/mips/ingenic_x1000/fiiom3k/installer-fiiom3k.c
+++ b/firmware/target/mips/ingenic_x1000/fiiom3k/installer-fiiom3k.c
@@ -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();
- if(rc < 0)
- goto error;
-
- rc = nand_enable_writes(true);
- if(rc < 0) {
- rc = INSTALL_ERR_FLASH(NAND_ENABLE_WRITES, rc);
- goto error;
- }
+ nand_drv* drv = nand_init();
+ nand_lock(drv);
- rc = nand_erase(0, IMAGE_SIZE);
- if(rc < 0) {
- rc = INSTALL_ERR_FLASH(NAND_ERASE, rc);
+ int rc = nand_open(drv);
+ if(rc < 0)
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;
}
diff --git a/firmware/target/mips/ingenic_x1000/fiiom3k/spl-fiiom3k.c b/firmware/target/mips/ingenic_x1000/fiiom3k/spl-fiiom3k.c
index 7c56e4ac19..5680b1e548 100644
--- a/firmware/target/mips/ingenic_x1000/fiiom3k/spl-fiiom3k.c
+++ b/firmware/target/mips/ingenic_x1000/fiiom3k/spl-fiiom3k.c
@@ -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();
}
diff --git a/firmware/target/mips/ingenic_x1000/nand-x1000.c b/firmware/target/mips/ingenic_x1000/nand-x1000.c
index 5a21b1f8c2..b76efe65e5 100644
--- a/firmware/target/mips/ingenic_x1000/nand-x1000.c
+++ b/firmware/target/mips/ingenic_x1000/nand-x1000.c
@@ -22,480 +22,394 @@
#include "nand-x1000.h"
#include "sfc-x1000.h"
#include "system.h"
-#include <stddef.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
-
-/* NAND device register addresses for GET_FEATURE / SET_FEATURE */
-#define NAND_FREG_PROTECTION 0xa0
-#define NAND_FREG_FEATURE 0xb0
-#define NAND_FREG_STATUS 0xc0
-
-/* 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
-
-/* Feature register bits */
-#define NAND_FREG_FEATURE_QE 0x01
-
-/* 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
+#include <string.h>
+
+/* 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)
+
+/* Feature registers are found in linux/mtd/spinand.h,
+ * apparently these are pretty standardized */
+#define FREG_PROT 0xa0
+#define FREG_PROT_UNLOCK 0x00
+
+#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)
+
+#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)
+
+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);
-
-/* 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);
-
-/* 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;
-};
+const size_t nr_supported_nand_chips =
+ sizeof(supported_nand_chips) / sizeof(nand_chip);
-static struct nand_drv nand_drv;
-static uint8_t nand_auxbuf[32] CACHEALIGN_ATTR;
+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;
-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();
-
- int status = nandcmd_read_id(mf_id, dev_id);
- if(status < 0)
- goto error;
+ uint8_t x = nand_get_reg(drv, reg);
+ x &= ~msk;
+ x |= val;
+ nand_set_reg(drv, reg, x);
+}
- 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;
+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 < 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;
- }
-
- /* 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;
+ return false;
+}
- error:
- sfc_unlock();
- return status;
+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;
}
-const nand_chip_data* nand_get_chip_data(void)
+static void setup_chip_commands(nand_drv* drv)
{
- return nand_drv.chip_data;
+ /* 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);
+ }
}
-extern int nand_enable_writes(bool en)
+static void setup_chip_registers(nand_drv* drv)
{
- if(en == nand_drv.write_enabled)
- return NAND_SUCCESS;
+ /* 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);
+ }
- int rc = nandop_set_write_protect(!en);
- if(rc == NAND_SUCCESS)
- nand_drv.write_enabled = en;
+ /* Clear OTP bit to access the main data array */
+ nand_upd_reg(drv, FREG_CFG, FREG_CFG_OTP_ENABLE, 0);
- return rc;
+ /* Clear write protection bits */
+ nand_set_reg(drv, FREG_PROT, FREG_PROT_UNLOCK);
}
-static int nand_rdwr(bool write, uint32_t addr, uint32_t size, uint8_t* buf)
+int nand_open(nand_drv* drv)
{
- const uint32_t page_size = (1 << nand_drv.chip_data->log2_page_size);
-
- if(addr & (page_size - 1))
- return NAND_ERR_UNALIGNED;
- if(size & (page_size - 1))
- return NAND_ERR_UNALIGNED;
- if(size <= 0)
+ if(drv->refcount > 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;
- addr >>= nand_drv.chip_data->log2_page_size;
- size >>= nand_drv.chip_data->log2_page_size;
+ /* Initialize the controller */
+ sfc_open();
+ sfc_set_dev_conf(supported_nand_chips[0].dev_conf);
+ sfc_set_clock(supported_nand_chips[0].clock_freq);
- int rc = NAND_SUCCESS;
- sfc_lock();
+ /* Send the software reset command */
+ sfc_exec(NANDCMD_RESET, 0, NULL, 0);
+ mdelay(10);
- for(; size > 0; --size, ++addr, buf += page_size) {
- if(write)
- rc = nandop_write_page(addr, buf);
- else
- rc = nandop_read_page(addr, buf);
+ /* Chip identification and setup */
+ if(!identify_chip(drv))
+ return NAND_ERR_UNKNOWN_CHIP;
- if(rc)
- break;
- }
+ setup_chip_data(drv);
+ setup_chip_commands(drv);
- sfc_unlock();
- return rc;
-}
+ /* Set new SFC parameters */
+ sfc_set_dev_conf(drv->chip->dev_conf);
+ sfc_set_clock(drv->chip->clock_freq);
-int nand_read(uint32_t addr, uint32_t size, uint8_t* buf)
-{
- return nand_rdwr(false, addr, size, buf);
-}
+ /* Enter normal operating mode */
+ setup_chip_registers(drv);
-int nand_write(uint32_t addr, uint32_t size, const uint8_t* buf)
-{
- return nand_rdwr(true, addr, size, (uint8_t*)buf);
+ drv->refcount++;
+ return NAND_SUCCESS;
}
-int nand_erase(uint32_t addr, uint32_t size)
+void nand_close(nand_drv* drv)
{
- 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(drv->refcount == 0)
+ return;
- 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;
+ /* 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);
- 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;
+ sfc_close();
+ drv->refcount--;
}
-/*
- * NAND ops
- */
-
-static int nandop_wait_status(int errbit)
+static uint8_t nand_wait_busy(nand_drv* drv)
{
- 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;
-
- 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;
+ sfc_exec(NANDCMD_WR_EN, 0, NULL, 0);
+ sfc_exec(drv->cmd_block_erase, block, NULL, 0);
- return NAND_SUCCESS;
+ uint8_t status = nand_wait_busy(drv);
+ if(status & FREG_STATUS_EFAIL)
+ return NAND_ERR_ERASE_FAIL;
+ else
+ return NAND_SUCCESS;
}
-static int nandop_write_page(uint32_t row_addr, const uint8_t* buf)
+int nand_page_program(nand_drv* drv, nand_page_t page, const void* buffer)
{
- int status;
-
- 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;
-
- return NAND_SUCCESS;
+ 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;
}
-static int nandop_erase_block(uint32_t block_addr)
+int nand_page_read(nand_drv* drv, nand_page_t page, void* buffer)
{
- int status;
-
- 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;
-
+ 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_set_write_protect(bool en)
+int nand_read_bytes(nand_drv* drv, uint32_t byte_addr, uint32_t byte_len, void* buffer)
{
- 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;
- }
-
- /* 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);
-
- if(status < 0)
- return status;
+ if(byte_len == 0)
+ return NAND_SUCCESS;
- return NAND_SUCCESS;
-}
+ int rc;
+ unsigned pg_size = drv->chip->page_size;
+ nand_page_t page = byte_addr / pg_size;
+ unsigned offset = byte_addr % pg_size;
+ while(1) {
+ rc = nand_page_read(drv, page, drv->page_buf);
+ if(rc < 0)
+ return rc;
-/*
- * Low-level NAND commands
- */
+ memcpy(buffer, &drv->page_buf[offset], MIN(pg_size, byte_len));
-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;
-}
+ if(byte_len <= pg_size)
+ break;
-static int nandcmd_write_enable(void)
-{
- sfc_op op = {0};
- op.command = NAND_CMD_WRITE_ENABLE;
- if(sfc_exec(&op))
- return NAND_ERR_CONTROLLER;
+ offset = 0;
+ byte_len -= pg_size;
+ buffer += pg_size;
+ page++;
+ }
return NAND_SUCCESS;
}
-static int nandcmd_get_feature(uint8_t reg)
+int nand_write_bytes(nand_drv* drv, uint32_t byte_addr, uint32_t byte_len, const void* buffer)
{
- 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;
+ if(byte_len == 0)
+ return NAND_SUCCESS;
- return NAND_SUCCESS;
-}
+ int rc;
+ unsigned pg_size = drv->chip->page_size;
+ unsigned blk_size = pg_size << drv->chip->log2_ppb;
-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;
+ if(byte_addr % blk_size != 0)
+ return NAND_ERR_UNALIGNED;
+ if(byte_len % blk_size != 0)
+ return NAND_ERR_UNALIGNED;
- return NAND_SUCCESS;
-}
+ nand_page_t page = byte_addr / pg_size;
+ nand_page_t end_page = page + (byte_len / pg_size);
-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;
+ for(nand_block_t blk = page; blk < end_page; blk += drv->ppb) {
+ rc = nand_block_erase(drv, blk);
+ if(rc < 0)
+ return rc;
}
- 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;
-}
+ 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;
-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;
+ rc = nand_page_program(drv, page, drv->page_buf);
+ if(rc < 0)
+ return rc;
}
- 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;
-}
+/* 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.
+ */
diff --git a/firmware/target/mips/ingenic_x1000/nand-x1000.h b/firmware/target/mips/ingenic_x1000/nand-x1000.h
index cc56b836f8..711bf190b5 100644
--- a/firmware/target/mips/ingenic_x1000/nand-x1000.h
+++ b/firmware/target/mips/ingenic_x1000/nand-x1000.h
@@ -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.
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include "kernel.h"
+
+#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)
+
+/* 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
+
+/* 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
+
+/* Types to distinguish between block & page addresses in the API.
*
- * 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).
+ * 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;
-#include <stdint.h>
-#include <stdbool.h>
-#include <stddef.h>
+typedef struct nand_chip {
+ /* Manufacturer and device ID bytes */
+ uint8_t mf_id;
+ uint8_t dev_id;
-/* 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)
+ /* Row/column address width */
+ uint8_t row_cycles;
+ uint8_t col_cycles;
-/* Chip supports quad I/O for page read/write */
-#define NANDCHIP_FLAG_QUAD 0x01
+ /* Base2 logarithm of the number of pages per block */
+ unsigned log2_ppb;
-/* Set/clear the BRWD bit when enabling/disabling write protection */
-#define NANDCHIP_FLAG_USE_BRWD 0x02
+ /* Size of a page's main / oob areas, in bytes. */
+ unsigned page_size;
+ unsigned oob_size;
-typedef struct nand_chip_data {
- /* Chip manufacturer / device ID */
- uint8_t mf_id;
- uint8_t dev_id;
+ /* Total number of blocks in the chip */
+ unsigned nr_blocks;
- /* Width of row/column addresses in bytes */
- uint8_t rowaddr_width;
- uint8_t coladdr_width;
+ /* Clock frequency to use */
+ uint32_t clock_freq;
- /* SFC dev conf and clock frequency to use for this device */
+ /* Value of sfc_dev_conf */
uint32_t dev_conf;
- uint32_t clock_freq;
- /* Page size in bytes = 1 << log2_page_size */
- uint32_t log2_page_size;
+ /* Chip specific flags */
+ uint32_t flags;
+} nand_chip;
+
+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 nand_drv* nand_init(void);
+
+static inline void nand_lock(nand_drv* drv)
+{
+ mutex_lock(&drv->mutex);
+}
- /* Block size in number of pages = 1 << log2_block_size */
- uint32_t log2_block_size;
+static inline void nand_unlock(nand_drv* drv)
+{
+ mutex_unlock(&drv->mutex);
+}
- /* Chip flags */
- uint32_t flags;
-} nand_chip_data;
+/* 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);
+
+/* 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);
-/* 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.
+/* 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_open(void);
-extern void nand_close(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);
-
-/* 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);
-
-/* 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);
-
-/* 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);
-
-/* 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);
+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__ */
diff --git a/firmware/target/mips/ingenic_x1000/sfc-x1000.c b/firmware/target/mips/ingenic_x1000/sfc-x1000.c
index c1fde89b70..5ade6bcc64 100644
--- a/firmware/target/mips/ingenic_x1000/sfc-x1000.c
+++ b/firmware/target/mips/ingenic_x1000/sfc-x1000.c
@@ -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;
-#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);
+/* 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_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;
+}
+
+void sfc_close(void)
+{
+ 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;
+ }
-#ifdef NEED_SFC_DMA
- jz_writef(SFC_GLB, OP_MODE_V(DMA), BURST_MD_V(INCR32));
system_enable_irq(IRQ_SFC);
+ sfc_wait = sfc_irq_wait;
#endif
}
-void sfc_close(void)
+void sfc_irq_end(void)
{
-#ifdef NEED_SFC_DMA
+#ifdef USE_DMA
system_disable_irq(IRQ_SFC);
+ sfc_wait = sfc_poll_wait;
#endif
-
- REG_SFC_CGE = 0x1f;
- jz_writef(CPM_CLKGR, SFC(1));
}
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)
-{
- (void)tmo;
-
- 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);
- }
-
- restore_irq(irq);
- return 0;
-}
-
-static void sfc_wait_end(void)
+#ifndef USE_DMA
+static void sfc_fifo_rdwr(bool write, void* buffer, uint32_t data_bytes)
{
- 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;
- semaphore_release(&sfc_sema);
-}
-#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;
+ 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(jz_readf(SFC_SR, RREQ)) {
- jz_overwritef(SFC_SCR, CLR_RREQ(1));
+ if(REG_SFC_SR & sr_bit) {
+ REG_SFC_SCR = clr_bit;
- int amount = data_words > FIFO_THRESH ? FIFO_THRESH : data_words;
+ /* 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;
- 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;
+ 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;
}
}
}
}
-
-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);
+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_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);
+ } else {
+ jz_vwritef(glb, SFC_GLB, TRAN_DIR_V(READ));
+#ifdef USE_DMA
+ discard_dcache_range(data, size);
#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;
+ /* 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;
-#ifdef NEED_SFC_DMA
- sfc_status = SFC_STATUS_PENDING;
- timeout_register(&sfc_lockup_tmo, sfc_lockup_tmo_cb, 10*HZ, 0);
+ /* Clear old interrupts */
REG_SFC_SCR = 0x1f;
- REG_SFC_INTC &= ~intc_clear;
-#endif
+ jz_writef(SFC_INTC, MSK_END(0));
+ /* Start the command */
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);
+ /* 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
+}
- sfc_wait_end();
+static void sfc_poll_wait(void)
+{
+ while(jz_readf(SFC_SR, END) == 0);
+ jz_overwritef(SFC_SCR, CLR_END(1));
+}
-#ifdef NEED_SFC_DMA
- if(op->flags & SFC_FLAG_READ)
- discard_dcache_range(op->buffer, op->data_bytes);
-#endif
+#ifdef USE_DMA
+static void sfc_irq_wait(void)
+{
+ semaphore_wait(&sfc_sema, TIMEOUT_BLOCK);
+}
- return sfc_status;
+void SFC(void)
+{
+ /* 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));
}
+#endif
diff --git a/firmware/target/mips/ingenic_x1000/sfc-x1000.h b/firmware/target/mips/ingenic_x1000/sfc-x1000.h
index 5784198b93..d28bcb6740 100644
--- a/firmware/target/mips/ingenic_x1000/sfc-x1000.h
+++ b/firmware/target/mips/ingenic_x1000/sfc-x1000.h
@@ -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.
+/* SPI transfer mode. SFC_TMODE_X_Y_Z means:
+ *
+ * - 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
-#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.
- */
+/* 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
-/* SPI transfer mode. If in doubt, check with the X1000 manual and confirm
- * the transfer format is what you expect.
+/* 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_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
+#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))
-/* 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
+/* Open/close SFC hardware */
+extern void sfc_open(void);
+extern void sfc_close(void);
-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;
+/* 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);
-/* 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);
+/* Change the SFC clock frequency */
+extern void sfc_set_clock(uint32_t freq);
+
+/* Set the device configuration register */
+inline void sfc_set_dev_conf(uint32_t conf)
+{
+ REG_SFC_DEV_CONF = conf;
+}
-/* Controller mutex -- lock before touching the driver */
-extern void sfc_lock(void);
-extern void sfc_unlock(void);
+/* 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));
+}
-/* 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.
+/** \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.
*
- * After opening the driver, you must also program a valid device configuration
- * and clock rate using sfc_set_dev_conf() and sfc_set_clock().
+ * - 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_open(void);
-extern void sfc_close(void);
+extern void sfc_exec(uint32_t cmd, uint32_t addr, void* data, uint32_t size);
-/* 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.
+/* 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.
*
- * - 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).
+ * 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.
*/
-#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))
-
-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);
+#endif /* __SFC_X1000_H__ */