From 3ec66893e377b088c1284d2d23adb2aeea6d7965 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sat, 27 Feb 2021 22:08:58 +0000 Subject: New port: FiiO M3K on bare metal Change-Id: I7517e7d5459e129dcfc9465c6fbd708619888fbe --- firmware/target/mips/ingenic_x1000/nand-x1000.c | 500 ++++++++++++++++++++++++ 1 file changed, 500 insertions(+) create mode 100644 firmware/target/mips/ingenic_x1000/nand-x1000.c (limited to 'firmware/target/mips/ingenic_x1000/nand-x1000.c') diff --git a/firmware/target/mips/ingenic_x1000/nand-x1000.c b/firmware/target/mips/ingenic_x1000/nand-x1000.c new file mode 100644 index 0000000000..54a1d11d95 --- /dev/null +++ b/firmware/target/mips/ingenic_x1000/nand-x1000.c @@ -0,0 +1,500 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2021 Aidan MacDonald + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "nand-x1000.h" +#include "nand-target.h" +#include "sfc-x1000.h" +#include "system.h" +#include + +#if !defined(NAND_MAX_PAGE_SIZE) || \ + !defined(NAND_INIT_SFC_DEV_CONF) || \ + !defined(NAND_INIT_CLOCK_SPEED) +# error "Target needs to specify NAND driver parameters" +#endif + +/* Must be at least as big as a cacheline */ +#define NAND_AUX_BUFFER_SIZE CACHEALIGN_SIZE + +/* Writes have been enabled */ +#define NAND_DRV_FLAG_WRITEABLE 0x01 + +/* Defined by target */ +extern const nand_chip_desc target_nand_chip_descs[]; + +/* Globals for the driver + * TODO: get rid of pagebuffer in the SPL to save code size + */ +static unsigned char pagebuffer[NAND_MAX_PAGE_SIZE] CACHEALIGN_ATTR; +static unsigned char auxbuffer[NAND_AUX_BUFFER_SIZE] CACHEALIGN_ATTR; +static nand_drv nand_driver; + +static void nand_drv_reset(nand_drv* d) +{ + d->chip_ops = NULL; + d->chip_data = NULL; + d->pagebuf = &pagebuffer[0]; + d->auxbuf = &auxbuffer[0]; + d->raw_page_size = 0; + d->flags = 0; +} + +/* Driver code */ +int nand_open(void) +{ + sfc_init(); + sfc_lock(); + + /* Reset driver state */ + nand_drv_reset(&nand_driver); + + /* Init hardware */ + sfc_open(); + sfc_set_dev_conf(NAND_INIT_SFC_DEV_CONF); + sfc_set_clock(NAND_CLOCK_SOURCE, NAND_INIT_CLOCK_SPEED); + + /* Identify NAND chip */ + int status = 0; + int nandid = nandcmd_read_id(&nand_driver); + if(nandid < 0) { + status = -1; + goto _err; + } + + unsigned char mf_id = nandid >> 8; + unsigned char dev_id = nandid & 0xff; + const nand_chip_desc* desc = &target_nand_chip_descs[0]; + while(1) { + if(desc->data == NULL || desc->ops == NULL) { + status = -1; + goto _err; + } + + if(desc->data->mf_id == mf_id && desc->data->dev_id == dev_id) + break; + } + + /* Fill driver parameters */ + nand_driver.chip_ops = desc->ops; + nand_driver.chip_data = desc->data; + nand_driver.raw_page_size = desc->data->page_size + desc->data->spare_size; + + /* Configure hardware and run init op */ + sfc_set_dev_conf(desc->data->dev_conf); + sfc_set_clock(NAND_CLOCK_SOURCE, desc->data->clock_freq); + + if(desc->ops->open(&nand_driver) < 0) { + status = -1; + goto _err; + } + + _exit: + sfc_unlock(); + return status; + _err: + nand_drv_reset(&nand_driver); + sfc_close(); + goto _exit; +} + +void nand_close(void) +{ + sfc_lock(); + nand_driver.chip_ops->close(&nand_driver); + nand_drv_reset(&nand_driver); + sfc_close(); + sfc_unlock(); +} + +int nand_enable_writes(bool en) +{ + sfc_lock(); + + int st = nand_driver.chip_ops->set_wp_enable(&nand_driver, en); + if(st >= 0) { + if(en) + nand_driver.flags |= NAND_DRV_FLAG_WRITEABLE; + else + nand_driver.flags &= ~NAND_DRV_FLAG_WRITEABLE; + } + + sfc_unlock(); + return st; +} + +extern int nand_read_bytes(uint32_t byteaddr, int count, void* buf) +{ + if(count <= 0) + return 0; + + nand_drv* d = &nand_driver; + uint32_t rowaddr = byteaddr / d->chip_data->page_size; + uint32_t coladdr = byteaddr % d->chip_data->page_size; + unsigned char* dstbuf = (unsigned char*)buf; + int status = 0; + + sfc_lock(); + do { + if(d->chip_ops->read_page(d, rowaddr, d->pagebuf) < 0) { + status = -1; + goto _end; + } + + if(d->chip_ops->ecc_read(d, d->pagebuf) < 0) { + status = -1; + goto _end; + } + + int amount = d->chip_data->page_size - coladdr; + if(amount > count) + amount = count; + + memcpy(dstbuf, d->pagebuf, amount); + dstbuf += amount; + count -= amount; + rowaddr += 1; + coladdr = 0; + } while(count > 0); + + _end: + sfc_unlock(); + return status; +} + +int nand_write_bytes(uint32_t byteaddr, int count, const void* buf) +{ + nand_drv* d = &nand_driver; + + if((d->flags & NAND_DRV_FLAG_WRITEABLE) == 0) + return -1; + + if(count <= 0) + return 0; + + uint32_t rowaddr = byteaddr / d->chip_data->page_size; + uint32_t coladdr = byteaddr % d->chip_data->page_size; + + /* Only support whole page writes right now */ + if(coladdr != 0) + return -1; + if(count % d->chip_data->page_size) + return -1; + + const unsigned char* srcbuf = (const unsigned char*)buf; + int status = 0; + + sfc_lock(); + do { + memcpy(d->pagebuf, srcbuf, d->chip_data->page_size); + d->chip_ops->ecc_write(d, d->pagebuf); + + if(d->chip_ops->write_page(d, rowaddr, d->pagebuf) < 0) { + status = -1; + goto _end; + } + + rowaddr += 1; + srcbuf += d->chip_data->page_size; + count -= d->chip_data->page_size; + } while(count > 0); + + _end: + sfc_unlock(); + return status; +} + +int nand_erase_block(uint32_t byteaddr) +{ + nand_drv* d = &nand_driver; + + if((d->flags & NAND_DRV_FLAG_WRITEABLE) == 0) + return -1; + + /* Ensure address is aligned to a block boundary */ + uint32_t blockaddr = byteaddr / d->chip_data->page_size; + if(blockaddr % d->chip_data->block_size) + return -1; + + int status = 0; + sfc_lock(); + + if(d->chip_ops->erase_block(d, blockaddr)) { + status = -1; + goto _end; + } + + _end: + sfc_unlock(); + return status; +} + +int nandcmd_read_id(nand_drv* d) +{ + 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 = d->auxbuf; + if(sfc_exec(&op)) + return -1; + + return (d->auxbuf[0] << 8) | d->auxbuf[1]; +} + +int nandcmd_write_enable(nand_drv* d) +{ + (void)d; + + sfc_op op = {0}; + op.command = NAND_CMD_WRITE_ENABLE; + if(sfc_exec(&op)) + return -1; + + return 0; +} + +int nandcmd_get_feature(nand_drv* d, int reg) +{ + sfc_op op = {0}; + op.command = NAND_CMD_GET_FEATURE; + op.flags = SFC_FLAG_READ; + op.addr_bytes = 1; + op.addr_lo = reg & 0xff; + op.data_bytes = 1; + op.buffer = d->auxbuf; + if(sfc_exec(&op)) + return -1; + + return d->auxbuf[0]; +} + +int nandcmd_set_feature(nand_drv* d, int reg, int val) +{ + sfc_op op = {0}; + op.command = NAND_CMD_SET_FEATURE; + op.flags = SFC_FLAG_READ; + op.addr_bytes = 1; + op.addr_lo = reg & 0xff; + op.data_bytes = 1; + op.buffer = d->auxbuf; + d->auxbuf[0] = val & 0xff; + if(sfc_exec(&op)) + return -1; + + return 0; +} + +int nandcmd_page_read_to_cache(nand_drv* d, uint32_t rowaddr) +{ + sfc_op op = {0}; + op.command = NAND_CMD_PAGE_READ_TO_CACHE; + op.addr_bytes = d->chip_data->rowaddr_width; + op.addr_lo = rowaddr; + if(sfc_exec(&op)) + return -1; + + return 0; +} + +int nandcmd_read_from_cache(nand_drv* d, unsigned char* buf) +{ + sfc_op op = {0}; + if(d->chip_data->flags & NANDCHIP_FLAG_QUAD) { + op.command = NAND_CMD_READ_FROM_CACHEx4; + op.mode = SFC_MODE_QUAD_IO; + } else { + op.command = NAND_CMD_READ_FROM_CACHE; + op.mode = SFC_MODE_STANDARD; + } + + op.flags = SFC_FLAG_READ; + op.addr_bytes = d->chip_data->coladdr_width; + op.addr_lo = 0; + op.dummy_bits = 8; + op.data_bytes = d->raw_page_size; + op.buffer = buf; + if(sfc_exec(&op)) + return -1; + + return 0; +} + +int nandcmd_program_load(nand_drv* d, const unsigned char* buf) +{ + sfc_op op = {0}; + if(d->chip_data->flags & NANDCHIP_FLAG_QUAD) { + op.command = NAND_CMD_PROGRAM_LOADx4; + op.mode = SFC_MODE_QUAD_IO; + } else { + op.command = NAND_CMD_PROGRAM_LOAD; + op.mode = SFC_MODE_STANDARD; + } + + op.flags = SFC_FLAG_WRITE; + op.addr_bytes = d->chip_data->coladdr_width; + op.addr_lo = 0; + op.data_bytes = d->raw_page_size; + op.buffer = (void*)buf; + if(sfc_exec(&op)) + return -1; + + return 0; +} + +int nandcmd_program_execute(nand_drv* d, uint32_t rowaddr) +{ + sfc_op op = {0}; + op.command = NAND_CMD_PROGRAM_EXECUTE; + op.addr_bytes = d->chip_data->rowaddr_width; + op.addr_lo = rowaddr; + if(sfc_exec(&op)) + return -1; + + return 0; +} + +int nandcmd_block_erase(nand_drv* d, uint32_t blockaddr) +{ + sfc_op op = {0}; + op.command = NAND_CMD_BLOCK_ERASE; + op.addr_bytes = d->chip_data->rowaddr_width; + op.addr_lo = blockaddr; + if(sfc_exec(&op)) + return 01; + + return 0; +} + +const nand_chip_ops nand_chip_ops_std = { + .open = nandop_std_open, + .close = nandop_std_close, + .read_page = nandop_std_read_page, + .write_page = nandop_std_write_page, + .erase_block = nandop_std_erase_block, + .set_wp_enable = nandop_std_set_wp_enable, + .ecc_read = nandop_ecc_none_read, + .ecc_write = nandop_ecc_none_write, +}; + +/* Helper needed by other ops */ +static int nandop_std_wait_status(nand_drv* d, int errbit) +{ + int reg; + do { + reg = nandcmd_get_feature(d, NAND_FREG_STATUS); + if(reg < 0) + return -1; + } while(reg & NAND_FREG_STATUS_OIP); + + if(reg & errbit) + return -1; + + return reg; +} + +int nandop_std_open(nand_drv* d) +{ + (void)d; + return 0; +} + +void nandop_std_close(nand_drv* d) +{ + (void)d; +} + +int nandop_std_read_page(nand_drv* d, uint32_t rowaddr, unsigned char* buf) +{ + if(nandcmd_page_read_to_cache(d, rowaddr) < 0) + return -1; + if(nandop_std_wait_status(d, 0) < 0) + return -1; + if(nandcmd_read_from_cache(d, buf) < 0) + return -1; + + return 0; +} + +int nandop_std_write_page(nand_drv* d, uint32_t rowaddr, const unsigned char* buf) +{ + if(nandcmd_write_enable(d) < 0) + return -1; + if(nandcmd_program_load(d, buf) < 0) + return -1; + if(nandcmd_program_execute(d, rowaddr) < 0) + return -1; + if(nandop_std_wait_status(d, NAND_FREG_STATUS_P_FAIL) < 0) + return -1; + + return 0; +} + +int nandop_std_erase_block(nand_drv* d, uint32_t blockaddr) +{ + if(nandcmd_write_enable(d) < 0) + return -1; + if(nandcmd_block_erase(d, blockaddr) < 0) + return -1; + if(nandop_std_wait_status(d, NAND_FREG_STATUS_E_FAIL) < 0) + return -1; + + return 0; +} + +int nandop_std_set_wp_enable(nand_drv* d, bool en) +{ + int val = nandcmd_get_feature(d, NAND_FREG_PROTECTION); + if(val < 0) + return -1; + + if(en) { + val &= ~NAND_FREG_PROTECTION_ALLBP; + if(d->chip_data->flags & NANDCHIP_FLAG_USE_BRWD) + val &= ~NAND_FREG_PROTECTION_BRWD; + } else { + val |= NAND_FREG_PROTECTION_ALLBP; + if(d->chip_data->flags & NANDCHIP_FLAG_USE_BRWD) + val |= NAND_FREG_PROTECTION_BRWD; + } + + sfc_set_wp_enable(false); + int status = nandcmd_set_feature(d, NAND_FREG_PROTECTION, val); + sfc_set_wp_enable(true); + + if(status < 0) + return -1; + + return 0; +} + +int nandop_ecc_none_read(nand_drv* d, unsigned char* buf) +{ + (void)d; + (void)buf; + return 0; +} + +void nandop_ecc_none_write(nand_drv* d, unsigned char* buf) +{ + memset(&buf[d->chip_data->page_size], 0xff, d->chip_data->spare_size); +} -- cgit v1.2.3