diff options
author | Aidan MacDonald <amachronic@protonmail.com> | 2021-06-19 17:48:13 +0100 |
---|---|---|
committer | Aidan MacDonald <amachronic@protonmail.com> | 2021-06-27 19:09:03 +0100 |
commit | 9f950d8bbf25995d0ba71ce2cd6cc9c0d3b91a9c (patch) | |
tree | 4946622b4bb3d8862237ae6eeddb39e1c2bfcf67 /firmware/target | |
parent | 9246cbc65e0d71428f7ac8662efdb47f7b9cc174 (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.c | 58 | ||||
-rw-r--r-- | firmware/target/mips/ingenic_x1000/fiiom3k/spl-fiiom3k.c | 44 | ||||
-rw-r--r-- | firmware/target/mips/ingenic_x1000/nand-x1000.c | 686 | ||||
-rw-r--r-- | firmware/target/mips/ingenic_x1000/nand-x1000.h | 207 | ||||
-rw-r--r-- | firmware/target/mips/ingenic_x1000/sfc-x1000.c | 294 | ||||
-rw-r--r-- | firmware/target/mips/ingenic_x1000/sfc-x1000.h | 152 |
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__ */ |