diff options
author | Bjarne Steinsbo <bsteinsbo@gmail.com> | 2011-02-15 20:23:40 +0100 |
---|---|---|
committer | Øyvind Harboe <oyvind.harboe@zylin.com> | 2011-02-15 20:23:40 +0100 |
commit | 3f4b9e334b867a16c35b1c6d9a1f9aefd35fd91b (patch) | |
tree | 3b599ba715c6f74ffdc07d93a22e9dc950f2a50f /src/flash | |
parent | fe0894015fd3d25593ce3a7211b1540ebfbab1f3 (diff) | |
download | openocd+libswd-3f4b9e334b867a16c35b1c6d9a1f9aefd35fd91b.tar.gz openocd+libswd-3f4b9e334b867a16c35b1c6d9a1f9aefd35fd91b.tar.bz2 openocd+libswd-3f4b9e334b867a16c35b1c6d9a1f9aefd35fd91b.tar.xz openocd+libswd-3f4b9e334b867a16c35b1c6d9a1f9aefd35fd91b.zip |
lpc32xx: Flash driver
Based on the lpc3180 driver, but released as a separate driver for two reasons:
1) I don't have an lpc3180 to test it against, so it might unintentionally break compatibility.
2) It's using a different OOB layout than lpc3180.
Rewritten so that it no longer borrows code from the NXP CDL library. Instead borrowing code from the u-boot port to lpc32xx, written by Kevin Wells.
Tested on lpc3250 (Hitex LPC3250-Stick). OOB layout is compatible with LPCLinux.
Diffstat (limited to 'src/flash')
-rw-r--r-- | src/flash/nand/Makefile.am | 2 | ||||
-rw-r--r-- | src/flash/nand/driver.c | 2 | ||||
-rw-r--r-- | src/flash/nand/ecc.c | 61 | ||||
-rw-r--r-- | src/flash/nand/lpc32xx.c | 1828 | ||||
-rw-r--r-- | src/flash/nand/lpc32xx.h | 39 |
5 files changed, 1932 insertions, 0 deletions
diff --git a/src/flash/nand/Makefile.am b/src/flash/nand/Makefile.am index 9aa0e69f..23c303bf 100644 --- a/src/flash/nand/Makefile.am +++ b/src/flash/nand/Makefile.am @@ -16,6 +16,7 @@ NAND_DRIVERS = \ nonce.c \ davinci.c \ lpc3180.c \ + lpc32xx.c \ mx2.c \ mx3.c \ orion.c \ @@ -35,6 +36,7 @@ noinst_HEADERS = \ fileio.h \ imp.h \ lpc3180.h \ + lpc32xx.h \ mx2.h \ mx3.h \ s3c24xx.h \ diff --git a/src/flash/nand/driver.c b/src/flash/nand/driver.c index 522dfa40..f34811ba 100644 --- a/src/flash/nand/driver.c +++ b/src/flash/nand/driver.c @@ -31,6 +31,7 @@ extern struct nand_flash_controller nonce_nand_controller; extern struct nand_flash_controller davinci_nand_controller; extern struct nand_flash_controller lpc3180_nand_controller; +extern struct nand_flash_controller lpc32xx_nand_controller; extern struct nand_flash_controller orion_nand_controller; extern struct nand_flash_controller s3c2410_nand_controller; extern struct nand_flash_controller s3c2412_nand_controller; @@ -49,6 +50,7 @@ static struct nand_flash_controller *nand_flash_controllers[] = &nonce_nand_controller, &davinci_nand_controller, &lpc3180_nand_controller, + &lpc32xx_nand_controller, &orion_nand_controller, &s3c2410_nand_controller, &s3c2412_nand_controller, diff --git a/src/flash/nand/ecc.c b/src/flash/nand/ecc.c index 2de12d42..b4039976 100644 --- a/src/flash/nand/ecc.c +++ b/src/flash/nand/ecc.c @@ -120,3 +120,64 @@ int nand_calculate_ecc(struct nand_device *nand, const uint8_t *dat, uint8_t *ec return 0; } + +static inline int countbits(uint32_t byte) +{ + int res = 0; + + for (;byte; byte >>= 1) + res += byte & 0x01; + return res; +} + +/** + * nand_correct_data - Detect and correct a 1 bit error for 256 byte block + */ +int nand_correct_data(struct nand_device *nand, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + uint8_t s0, s1, s2; + +#ifdef NAND_ECC_SMC + s0 = calc_ecc[0] ^ read_ecc[0]; + s1 = calc_ecc[1] ^ read_ecc[1]; + s2 = calc_ecc[2] ^ read_ecc[2]; +#else + s1 = calc_ecc[0] ^ read_ecc[0]; + s0 = calc_ecc[1] ^ read_ecc[1]; + s2 = calc_ecc[2] ^ read_ecc[2]; +#endif + if ((s0 | s1 | s2) == 0) + return 0; + + /* Check for a single bit error */ + if( ((s0 ^ (s0 >> 1)) & 0x55) == 0x55 && + ((s1 ^ (s1 >> 1)) & 0x55) == 0x55 && + ((s2 ^ (s2 >> 1)) & 0x54) == 0x54) { + + uint32_t byteoffs, bitnum; + + byteoffs = (s1 << 0) & 0x80; + byteoffs |= (s1 << 1) & 0x40; + byteoffs |= (s1 << 2) & 0x20; + byteoffs |= (s1 << 3) & 0x10; + + byteoffs |= (s0 >> 4) & 0x08; + byteoffs |= (s0 >> 3) & 0x04; + byteoffs |= (s0 >> 2) & 0x02; + byteoffs |= (s0 >> 1) & 0x01; + + bitnum = (s2 >> 5) & 0x04; + bitnum |= (s2 >> 4) & 0x02; + bitnum |= (s2 >> 3) & 0x01; + + dat[byteoffs] ^= (1 << bitnum); + + return 1; + } + + if(countbits(s0 | ((uint32_t)s1 << 8) | ((uint32_t)s2 <<16)) == 1) + return 1; + + return -1; +} diff --git a/src/flash/nand/lpc32xx.c b/src/flash/nand/lpc32xx.c new file mode 100644 index 00000000..5cde90c4 --- /dev/null +++ b/src/flash/nand/lpc32xx.c @@ -0,0 +1,1828 @@ +/*************************************************************************** + * Copyright (C) 2007 by Dominic Rath * + * Dominic.Rath@gmx.de * + * * + * Copyright (C) 2011 Bjarne Steinsbo <bsteinsbo@gmail.com> * + * Copyright (C) 2010 richard vegh <vegh.ricsi@gmail.com> * + * Copyright (C) 2010 Oyvind Harboe <oyvind.harboe@zylin.com> * + * * + * Based on a combination of the lpc3180 driver and code from * + * uboot-2009.03-lpc32xx by Kevin Wells. * + * Any bugs are mine. --BSt * + * * + * 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 program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "imp.h" +#include "lpc32xx.h" +#include <target/target.h> + +static int lpc32xx_reset(struct nand_device *nand); +static int lpc32xx_controller_ready(struct nand_device *nand, int timeout); +static int lpc32xx_tc_ready(struct nand_device *nand, int timeout); +extern int nand_correct_data(struct nand_device *nand, u_char *dat, + u_char *read_ecc, u_char *calc_ecc); + +/* These are offset with the working area in IRAM when using DMA to + * read/write data to the SLC controller. + * - DMA descriptors will be put at start of working area, + * - Hardware generated ECC will be stored at ECC_OFFS + * - OOB wil be read/written from/to SPARE_OFFS + * - Actual page data will be read from/to DATA_OFFS + * There are unused holes between the used areas. + */ +#define ECC_OFFS 0x120 +#define SPARE_OFFS 0x140 +#define DATA_OFFS 0x200 + +static int sp_ooblayout[] = { + 10, 11, 12, 13, 14, 15 +}; +static int lp_ooblayout[] = { + 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63 +}; + +typedef struct { + volatile uint32_t dma_src; + volatile uint32_t dma_dest; + volatile uint32_t next_lli; + volatile uint32_t next_ctrl; +} dmac_ll_t; + +static dmac_ll_t dmalist[(2048/256) * 2 + 1]; + +/* nand device lpc32xx <target#> <oscillator_frequency> + */ +NAND_DEVICE_COMMAND_HANDLER(lpc32xx_nand_device_command) +{ + if (CMD_ARGC < 3) { + LOG_WARNING("incomplete 'lpc32xx' nand flash configuration"); + return ERROR_FLASH_BANK_INVALID; + } + + uint32_t osc_freq; + COMMAND_PARSE_NUMBER(u32, CMD_ARGV[2], osc_freq); + + struct lpc32xx_nand_controller *lpc32xx_info; + lpc32xx_info = malloc(sizeof(struct lpc32xx_nand_controller)); + nand->controller_priv = lpc32xx_info; + + lpc32xx_info->osc_freq = osc_freq; + + if ((lpc32xx_info->osc_freq < 1000) || (lpc32xx_info->osc_freq > 20000)) + LOG_WARNING("LPC32xx oscillator frequency should be between " + "1000 and 20000 kHz, was %i", + lpc32xx_info->osc_freq); + + lpc32xx_info->selected_controller = LPC32xx_NO_CONTROLLER; + lpc32xx_info->sw_write_protection = 0; + lpc32xx_info->sw_wp_lower_bound = 0x0; + lpc32xx_info->sw_wp_upper_bound = 0x0; + + return ERROR_OK; +} + +static int lpc32xx_pll(int fclkin, uint32_t pll_ctrl) +{ + int bypass = (pll_ctrl & 0x8000) >> 15; + int direct = (pll_ctrl & 0x4000) >> 14; + int feedback = (pll_ctrl & 0x2000) >> 13; + int p = (1 << ((pll_ctrl & 0x1800) >> 11) * 2); + int n = ((pll_ctrl & 0x0600) >> 9) + 1; + int m = ((pll_ctrl & 0x01fe) >> 1) + 1; + int lock = (pll_ctrl & 0x1); + + if (!lock) + LOG_WARNING("PLL is not locked"); + + if (!bypass && direct) /* direct mode */ + return (m * fclkin) / n; + + if (bypass && !direct) /* bypass mode */ + return fclkin / (2 * p); + + if (bypass & direct) /* direct bypass mode */ + return fclkin; + + if (feedback) /* integer mode */ + return m * (fclkin / n); + else /* non-integer mode */ + return (m / (2 * p)) * (fclkin / n); +} + +static float lpc32xx_cycle_time(struct nand_device *nand) +{ + struct lpc32xx_nand_controller *lpc32xx_info = nand->controller_priv; + struct target *target = nand->target; + uint32_t sysclk_ctrl, pwr_ctrl, hclkdiv_ctrl, hclkpll_ctrl; + int sysclk; + int hclk; + int hclk_pll; + float cycle; + int retval; + + /* calculate timings */ + + /* determine current SYSCLK (13'MHz or main oscillator) */ + retval = target_read_u32(target, 0x40004050, &sysclk_ctrl); + if (ERROR_OK != retval) { + LOG_ERROR("could not read SYSCLK_CTRL"); + return ERROR_NAND_OPERATION_FAILED; + } + + if ((sysclk_ctrl & 1) == 0) + sysclk = lpc32xx_info->osc_freq; + else + sysclk = 13000; + + /* determine selected HCLK source */ + retval = target_read_u32(target, 0x40004044, &pwr_ctrl); + if (ERROR_OK != retval) { + LOG_ERROR("could not read HCLK_CTRL"); + return ERROR_NAND_OPERATION_FAILED; + } + + if ((pwr_ctrl & (1 << 2)) == 0) { /* DIRECT RUN mode */ + hclk = sysclk; + } else { + retval = target_read_u32(target, 0x40004058, &hclkpll_ctrl); + if (ERROR_OK != retval) { + LOG_ERROR("could not read HCLKPLL_CTRL"); + return ERROR_NAND_OPERATION_FAILED; + } + hclk_pll = lpc32xx_pll(sysclk, hclkpll_ctrl); + + retval = target_read_u32(target, 0x40004040, &hclkdiv_ctrl); + if (ERROR_OK != retval) { + LOG_ERROR("could not read CLKDIV_CTRL"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (pwr_ctrl & (1 << 10)) /* ARM_CLK and HCLK use PERIPH_CLK */ + hclk = hclk_pll / (((hclkdiv_ctrl & 0x7c) >> 2) + 1); + else /* HCLK uses HCLK_PLL */ + hclk = hclk_pll / (1 << (hclkdiv_ctrl & 0x3)); + } + + LOG_DEBUG("LPC32xx HCLK currently clocked at %i kHz", hclk); + + cycle = (1.0 / hclk) * 1000000.0; + + return cycle; +} + +static int lpc32xx_init(struct nand_device *nand) +{ + struct lpc32xx_nand_controller *lpc32xx_info = nand->controller_priv; + struct target *target = nand->target; + int bus_width = nand->bus_width ? : 8; + int address_cycles = nand->address_cycles ? : 3; + int page_size = nand->page_size ? : 512; + int retval; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("target must be halted to use LPC32xx " + "NAND flash controller"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* sanitize arguments */ + if (bus_width != 8) { + LOG_ERROR("LPC32xx doesn't support %i", bus_width); + return ERROR_NAND_OPERATION_NOT_SUPPORTED; + } + + /* inform calling code about selected bus width */ + nand->bus_width = bus_width; + + if ((address_cycles != 3) && (address_cycles != 4)) { + LOG_ERROR("LPC32xx driver doesn't support %i", address_cycles); + return ERROR_NAND_OPERATION_NOT_SUPPORTED; + } + + if ((page_size != 512) && (page_size != 2048)) { + LOG_ERROR("LPC32xx doesn't support page size %i", page_size); + return ERROR_NAND_OPERATION_NOT_SUPPORTED; + } + + /* select MLC controller if none is currently selected */ + if (lpc32xx_info->selected_controller == LPC32xx_NO_CONTROLLER) { + LOG_DEBUG("no LPC32xx NAND flash controller selected, " + "using default 'mlc'"); + lpc32xx_info->selected_controller = LPC32xx_MLC_CONTROLLER; + } + + if (lpc32xx_info->selected_controller == LPC32xx_MLC_CONTROLLER) { + uint32_t mlc_icr_value = 0x0; + float cycle; + int twp, twh, trp, treh, trhz, trbwb, tcea; + + /* FLASHCLK_CTRL = 0x22 (enable clk for MLC) */ + retval = target_write_u32(target, 0x400040c8, 0x22); + if (ERROR_OK != retval) { + LOG_ERROR("could not set FLASHCLK_CTRL"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* MLC_CEH = 0x0 (Force nCE assert) */ + retval = target_write_u32(target, 0x200b804c, 0x0); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_CEH"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* MLC_LOCK = 0xa25e (unlock protected registers) */ + retval = target_write_u32(target, 0x200b8044, 0xa25e); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_LOCK"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* MLC_ICR = configuration */ + if (lpc32xx_info->sw_write_protection) + mlc_icr_value |= 0x8; + if (page_size == 2048) + mlc_icr_value |= 0x4; + if (address_cycles == 4) + mlc_icr_value |= 0x2; + if (bus_width == 16) + mlc_icr_value |= 0x1; + retval = target_write_u32(target, 0x200b8030, mlc_icr_value); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ICR"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* calculate NAND controller timings */ + cycle = lpc32xx_cycle_time(nand); + + twp = ((40 / cycle) + 1); + twh = ((20 / cycle) + 1); + trp = ((30 / cycle) + 1); + treh = ((15 / cycle) + 1); + trhz = ((30 / cycle) + 1); + trbwb = ((100 / cycle) + 1); + tcea = ((45 / cycle) + 1); + + /* MLC_LOCK = 0xa25e (unlock protected registers) */ + retval = target_write_u32(target, 0x200b8044, 0xa25e); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_LOCK"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* MLC_TIME_REG */ + retval = target_write_u32(target, 0x200b8034, + (twp & 0xf) + | ((twh & 0xf) << 4) + | ((trp & 0xf) << 8) + | ((treh & 0xf) << 12) + | ((trhz & 0x7) << 16) + | ((trbwb & 0x1f) << 19) + | ((tcea & 0x3) << 24)); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_TIME_REG"); + return ERROR_NAND_OPERATION_FAILED; + } + + retval = lpc32xx_reset(nand); + if (ERROR_OK != retval) + return ERROR_NAND_OPERATION_FAILED; + } else if (lpc32xx_info->selected_controller == LPC32xx_SLC_CONTROLLER) { + float cycle; + int r_setup, r_hold, r_width, r_rdy; + int w_setup, w_hold, w_width, w_rdy; + + /* FLASHCLK_CTRL = 0x05 (enable clk for SLC) */ + retval = target_write_u32(target, 0x400040c8, 0x05); + if (ERROR_OK != retval) { + LOG_ERROR("could not set FLASHCLK_CTRL"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* after reset set other registers of SLC, + * so reset calling is here at the begining + */ + retval = lpc32xx_reset(nand); + if (ERROR_OK != retval) + return ERROR_NAND_OPERATION_FAILED; + + /* SLC_CFG = + Force nCE assert, + DMA ECC enabled, + ECC enabled, + DMA burst enabled, + DMA read from SLC, + WIDTH = bus_width) + */ + retval = target_write_u32(target, 0x20020014, + 0x3e | (bus_width == 16) ? 1 : 0); + if (ERROR_OK != retval) { + LOG_ERROR("could not set SLC_CFG"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* SLC_IEN = 3 (INT_RDY_EN = 1) ,(INT_TC_STAT = 1) */ + retval = target_write_u32(target, 0x20020020, 0x03); + if (ERROR_OK != retval) { + LOG_ERROR("could not set SLC_IEN"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* DMA configuration */ + + /* DMACLK_CTRL = 0x01 (enable clock for DMA controller) */ + retval = target_write_u32(target, 0x400040e8, 0x01); + if (ERROR_OK != retval) { + LOG_ERROR("could not set DMACLK_CTRL"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* DMACConfig = DMA enabled*/ + retval = target_write_u32(target, 0x31000030, 0x01); + if (ERROR_OK != retval) { + LOG_ERROR("could not set DMACConfig"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* calculate NAND controller timings */ + cycle = lpc32xx_cycle_time(nand); + + r_setup = w_setup = 0; + r_hold = w_hold = 10 / cycle; + r_width = 30 / cycle; + w_width = 40 / cycle; + r_rdy = w_rdy = 100 / cycle; + + /* SLC_TAC: SLC timing arcs register */ + retval = target_write_u32(target, 0x2002002c, + (r_setup & 0xf) + | ((r_hold & 0xf) << 4) + | ((r_width & 0xf) << 8) + | ((r_rdy & 0xf) << 12) + | ((w_setup & 0xf) << 16) + | ((w_hold & 0xf) << 20) + | ((w_width & 0xf) << 24) + | ((w_rdy & 0xf) << 28)); + if (ERROR_OK != retval) { + LOG_ERROR("could not set SLC_TAC"); + return ERROR_NAND_OPERATION_FAILED; + } + } + + return ERROR_OK; +} + +static int lpc32xx_reset(struct nand_device *nand) +{ + struct lpc32xx_nand_controller *lpc32xx_info = nand->controller_priv; + struct target *target = nand->target; + int retval; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("target must be halted to use " + "LPC32xx NAND flash controller"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (lpc32xx_info->selected_controller == LPC32xx_NO_CONTROLLER) { + LOG_ERROR("BUG: no LPC32xx NAND flash controller selected"); + return ERROR_NAND_OPERATION_FAILED; + } else if (lpc32xx_info->selected_controller == LPC32xx_MLC_CONTROLLER) { + /* MLC_CMD = 0xff (reset controller and NAND device) */ + retval = target_write_u32(target, 0x200b8000, 0xff); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_CMD"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (!lpc32xx_controller_ready(nand, 100)) { + LOG_ERROR("LPC32xx MLC NAND controller timed out " + "after reset"); + return ERROR_NAND_OPERATION_TIMEOUT; + } + } else if (lpc32xx_info->selected_controller == LPC32xx_SLC_CONTROLLER) { + /* SLC_CTRL = 0x6 (ECC_CLEAR, SW_RESET) */ + retval = target_write_u32(target, 0x20020010, 0x6); + if (ERROR_OK != retval) { + LOG_ERROR("could not set SLC_CTRL"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (!lpc32xx_controller_ready(nand, 100)) + { + LOG_ERROR("LPC32xx SLC NAND controller timed out " + "after reset"); + return ERROR_NAND_OPERATION_TIMEOUT; + } + } + + return ERROR_OK; +} + +static int lpc32xx_command(struct nand_device *nand, uint8_t command) +{ + struct lpc32xx_nand_controller *lpc32xx_info = nand->controller_priv; + struct target *target = nand->target; + int retval; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("target must be halted to use " + "LPC32xx NAND flash controller"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (lpc32xx_info->selected_controller == LPC32xx_NO_CONTROLLER) { + LOG_ERROR("BUG: no LPC32xx NAND flash controller selected"); + return ERROR_NAND_OPERATION_FAILED; + } else if (lpc32xx_info->selected_controller == LPC32xx_MLC_CONTROLLER) { + /* MLC_CMD = command */ + retval = target_write_u32(target, 0x200b8000, command); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_CMD"); + return ERROR_NAND_OPERATION_FAILED; + } + } else if (lpc32xx_info->selected_controller == LPC32xx_SLC_CONTROLLER) { + /* SLC_CMD = command */ + retval = target_write_u32(target, 0x20020008, command); + if (ERROR_OK != retval) { + LOG_ERROR("could not set SLC_CMD"); + return ERROR_NAND_OPERATION_FAILED; + } + } + + return ERROR_OK; +} + +static int lpc32xx_address(struct nand_device *nand, uint8_t address) +{ + struct lpc32xx_nand_controller *lpc32xx_info = nand->controller_priv; + struct target *target = nand->target; + int retval; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("target must be halted to use " + "LPC32xx NAND flash controller"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (lpc32xx_info->selected_controller == LPC32xx_NO_CONTROLLER) { + LOG_ERROR("BUG: no LPC32xx NAND flash controller selected"); + return ERROR_NAND_OPERATION_FAILED; + } else if (lpc32xx_info->selected_controller == LPC32xx_MLC_CONTROLLER) { + /* MLC_ADDR = address */ + retval = target_write_u32(target, 0x200b8004, address); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + } else if (lpc32xx_info->selected_controller == LPC32xx_SLC_CONTROLLER) { + /* SLC_ADDR = address */ + retval = target_write_u32(target, 0x20020004, address); + if (ERROR_OK != retval) { + LOG_ERROR("could not set SLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + } + + return ERROR_OK; +} + +static int lpc32xx_write_data(struct nand_device *nand, uint16_t data) +{ + struct lpc32xx_nand_controller *lpc32xx_info = nand->controller_priv; + struct target *target = nand->target; + int retval; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("target must be halted to use " + "LPC32xx NAND flash controller"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (lpc32xx_info->selected_controller == LPC32xx_NO_CONTROLLER) { + LOG_ERROR("BUG: no LPC32xx NAND flash controller selected"); + return ERROR_NAND_OPERATION_FAILED; + } else if (lpc32xx_info->selected_controller == LPC32xx_MLC_CONTROLLER) { + /* MLC_DATA = data */ + retval = target_write_u32(target, 0x200b0000, data); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_DATA"); + return ERROR_NAND_OPERATION_FAILED; + } + } else if (lpc32xx_info->selected_controller == LPC32xx_SLC_CONTROLLER) { + /* SLC_DATA = data */ + retval = target_write_u32(target, 0x20020000, data); + if (ERROR_OK != retval) { + LOG_ERROR("could not set SLC_DATA"); + return ERROR_NAND_OPERATION_FAILED; + } + } + + return ERROR_OK; +} + +static int lpc32xx_read_data(struct nand_device *nand, void *data) +{ + struct lpc32xx_nand_controller *lpc32xx_info = nand->controller_priv; + struct target *target = nand->target; + int retval; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("target must be halted to use LPC32xx " + "NAND flash controller"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (lpc32xx_info->selected_controller == LPC32xx_NO_CONTROLLER) { + LOG_ERROR("BUG: no LPC32xx NAND flash controller selected"); + return ERROR_NAND_OPERATION_FAILED; + } else if (lpc32xx_info->selected_controller == LPC32xx_MLC_CONTROLLER) { + /* data = MLC_DATA, use sized access */ + if (nand->bus_width == 8) { + uint8_t *data8 = data; + retval = target_read_u8(target, 0x200b0000, data8); + } else { + LOG_ERROR("BUG: bus_width neither 8 nor 16 bit"); + return ERROR_NAND_OPERATION_FAILED; + } + if (ERROR_OK != retval) { + LOG_ERROR("could not read MLC_DATA"); + return ERROR_NAND_OPERATION_FAILED; + } + } else if (lpc32xx_info->selected_controller == LPC32xx_SLC_CONTROLLER) { + uint32_t data32; + + /* data = SLC_DATA, must use 32-bit access */ + retval = target_read_u32(target, 0x20020000, &data32); + if (ERROR_OK != retval) { + LOG_ERROR("could not read SLC_DATA"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (nand->bus_width == 8) { + uint8_t *data8 = data; + *data8 = data32 & 0xff; + } else { + LOG_ERROR("BUG: bus_width neither 8 nor 16 bit"); + return ERROR_NAND_OPERATION_FAILED; + } + } + + return ERROR_OK; +} + +static int lpc32xx_write_page_mlc(struct nand_device *nand, uint32_t page, + uint8_t *data, uint32_t data_size, + uint8_t *oob, uint32_t oob_size) +{ + struct target *target = nand->target; + int retval; + uint8_t status; + static uint8_t page_buffer[512]; + static uint8_t oob_buffer[6]; + int quarter, num_quarters; + + /* MLC_CMD = sequential input */ + retval = target_write_u32(target, 0x200b8000, NAND_CMD_SEQIN); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_CMD"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (nand->page_size == 512) { + /* MLC_ADDR = 0x0 (one column cycle) */ + retval = target_write_u32(target, 0x200b8004, 0x0); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* MLC_ADDR = row */ + retval = target_write_u32(target, 0x200b8004, page & 0xff); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + retval = target_write_u32(target, 0x200b8004, + (page >> 8) & 0xff); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (nand->address_cycles == 4) { + retval = target_write_u32(target, 0x200b8004, + (page >> 16) & 0xff); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + } + } else { + /* MLC_ADDR = 0x0 (two column cycles) */ + retval = target_write_u32(target, 0x200b8004, 0x0); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + retval = target_write_u32(target, 0x200b8004, 0x0); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* MLC_ADDR = row */ + retval = target_write_u32(target, 0x200b8004, page & 0xff); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + retval = target_write_u32(target, 0x200b8004, + (page >> 8) & 0xff); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + } + + /* when using the MLC controller, we have to treat a large page device + * as being made out of four quarters, each the size of a small page + * device + */ + num_quarters = (nand->page_size == 2048) ? 4 : 1; + + for (quarter = 0; quarter < num_quarters; quarter++) { + int thisrun_data_size = (data_size > 512) ? 512 : data_size; + int thisrun_oob_size = (oob_size > 6) ? 6 : oob_size; + + memset(page_buffer, 0xff, 512); + if (data) { + memcpy(page_buffer, data, thisrun_data_size); + data_size -= thisrun_data_size; + data += thisrun_data_size; + } + + memset(oob_buffer, 0xff, 6); + if (oob) { + memcpy(oob_buffer, oob, thisrun_oob_size); + oob_size -= thisrun_oob_size; + oob += thisrun_oob_size; + } + + /* write MLC_ECC_ENC_REG to start encode cycle */ + retval = target_write_u32(target, 0x200b8008, 0x0); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ECC_ENC_REG"); + return ERROR_NAND_OPERATION_FAILED; + } + + retval = target_write_memory(target, 0x200a8000, + 4, 128, page_buffer); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_BUF (data)"); + return ERROR_NAND_OPERATION_FAILED; + } + retval = target_write_memory(target, 0x200a8000, + 1, 6, oob_buffer); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_BUF (oob)"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* write MLC_ECC_AUTO_ENC_REG to start auto encode */ + retval = target_write_u32(target, 0x200b8010, 0x0); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ECC_AUTO_ENC_REG"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (!lpc32xx_controller_ready(nand, 1000)) { + LOG_ERROR("timeout while waiting for " + "completion of auto encode cycle"); + return ERROR_NAND_OPERATION_FAILED; + } + } + + /* MLC_CMD = auto program command */ + retval = target_write_u32(target, 0x200b8000, NAND_CMD_PAGEPROG); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_CMD"); + return ERROR_NAND_OPERATION_FAILED; + } + + if ((retval = nand_read_status(nand, &status)) != ERROR_OK) { + LOG_ERROR("couldn't read status"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (status & NAND_STATUS_FAIL) { + LOG_ERROR("write operation didn't pass, status: 0x%2.2x", + status); + return ERROR_NAND_OPERATION_FAILED; + } + + return ERROR_OK; +} + +/* SLC controller in !raw mode will use target cpu to read/write nand from/to + * target internal memory. The transfer to/from flash is done by DMA. This + * function sets up the dma linked list in host memory for later transfer to + * target. + */ +static int lpc32xx_make_dma_list(uint32_t target_mem_base, uint32_t page_size, + int do_read) +{ + uint32_t i, dmasrc, ctrl, ecc_ctrl, oob_ctrl, dmadst; + + /* DMACCxControl = + TransferSize =64, + Source burst size =16, + Destination burst size = 16, + Source transfer width = 32 bit, + Destination transfer width = 32 bit, + Source AHB master select = M0, + Destination AHB master select = M0, + Source increment = 0, // set later + Destination increment = 0, // set later + Terminal count interrupt enable bit = 0 // set on last + */ /* + * Write Operation Sequence for Small Block NAND + * ---------------------------------------------------------- + * 1. X'fer 256 bytes of data from Memory to Flash. + * 2. Copy generated ECC data from Register to Spare Area + * 3. X'fer next 256 bytes of data from Memory to Flash. + * 4. Copy generated ECC data from Register to Spare Area. + * 5. X'fer 16 byets of Spare area from Memory to Flash. + * Read Operation Sequence for Small Block NAND + * ---------------------------------------------------------- + * 1. X'fer 256 bytes of data from Flash to Memory. + * 2. Copy generated ECC data from Register to ECC calc Buffer. + * 3. X'fer next 256 bytes of data from Flash to Memory. + * 4. Copy generated ECC data from Register to ECC calc Buffer. + * 5. X'fer 16 bytes of Spare area from Flash to Memory. + * Write Operation Sequence for Large Block NAND + * ---------------------------------------------------------- + * 1. Steps(1-4) of Write Operations repeate for four times + * which generates 16 DMA descriptors to X'fer 2048 bytes of + * data & 32 bytes of ECC data. + * 2. X'fer 64 bytes of Spare area from Memory to Flash. + * Read Operation Sequence for Large Block NAND + * ---------------------------------------------------------- + * 1. Steps(1-4) of Read Operations repeate for four times + * which generates 16 DMA descriptors to X'fer 2048 bytes of + * data & 32 bytes of ECC data. + * 2. X'fer 64 bytes of Spare area from Flash to Memory. + */ + + ctrl = (0x40 | 3 << 12 | 3 << 15 | 2 << 18 | 2 << 21 | 0 << 24 + | 0 << 25 | 0 << 26 | 0 << 27 | 0 << 31); + + /* DMACCxControl = + TransferSize =1, + Source burst size =4, + Destination burst size = 4, + Source transfer width = 32 bit, + Destination transfer width = 32 bit, + Source AHB master select = M0, + Destination AHB master select = M0, + Source increment = 0, + Destination increment = 1, + Terminal count interrupt enable bit = 0 + */ + ecc_ctrl = 0x01 | 1 << 12 | 1 << 15 | 2 << 18 | 2 << 21 | 0 << 24 + | 0 << 25 | 0 << 26 | 1 << 27 | 0 << 31; + + /* DMACCxControl = + TransferSize =16 for lp or 4 for sp, + Source burst size =16, + Destination burst size = 16, + Source transfer width = 32 bit, + Destination transfer width = 32 bit, + Source AHB master select = M0, + Destination AHB master select = M0, + Source increment = 0, // set later + Destination increment = 0, // set later + Terminal count interrupt enable bit = 1 // set on last + */ + oob_ctrl = (page_size == 2048 ? 0x10 : 0x04) + | 3 << 12 | 3 << 15 | 2 << 18 | 2 << 21 | 0 << 24 + | 0 << 25 | 0 << 26 | 0 << 27 | 1 << 31; + if (do_read) { + ctrl |= 1 << 27; /* Destination increment = 1 */ + oob_ctrl |= 1 << 27; /* Destination increment = 1 */ + dmasrc = 0x20020038; /* SLC_DMA_DATA */ + dmadst = target_mem_base + DATA_OFFS; + } else { + ctrl |= 1 << 26; /* Source increment = 1 */ + oob_ctrl |= 1 << 26; /* Source increment = 1 */ + dmasrc = target_mem_base + DATA_OFFS; + dmadst = 0x20020038; /* SLC_DMA_DATA */ + } + /* + * Write Operation Sequence for Small Block NAND + * ---------------------------------------------------------- + * 1. X'fer 256 bytes of data from Memory to Flash. + * 2. Copy generated ECC data from Register to Spare Area + * 3. X'fer next 256 bytes of data from Memory to Flash. + * 4. Copy generated ECC data from Register to Spare Area. + * 5. X'fer 16 byets of Spare area from Memory to Flash. + * Read Operation Sequence for Small Block NAND + * ---------------------------------------------------------- + * 1. X'fer 256 bytes of data from Flash to Memory. + * 2. Copy generated ECC data from Register to ECC calc Buffer. + * 3. X'fer next 256 bytes of data from Flash to Memory. + * 4. Copy generated ECC data from Register to ECC calc Buffer. + * 5. X'fer 16 bytes of Spare area from Flash to Memory. + * Write Operation Sequence for Large Block NAND + * ---------------------------------------------------------- + * 1. Steps(1-4) of Write Operations repeate for four times + * which generates 16 DMA descriptors to X'fer 2048 bytes of + * data & 32 bytes of ECC data. + * 2. X'fer 64 bytes of Spare area from Memory to Flash. + * Read Operation Sequence for Large Block NAND + * ---------------------------------------------------------- + * 1. Steps(1-4) of Read Operations repeate for four times + * which generates 16 DMA descriptors to X'fer 2048 bytes of + * data & 32 bytes of ECC data. + * 2. X'fer 64 bytes of Spare area from Flash to Memory. + */ + for (i = 0; i < page_size/0x100; i++) + { + dmalist[i*2].dma_src = (do_read ? dmasrc : (dmasrc + i * 256)); + dmalist[i*2].dma_dest = (do_read ? (dmadst + i * 256) : dmadst); + dmalist[i*2].next_lli = + target_mem_base + (i*2 + 1) * sizeof(dmac_ll_t); + dmalist[i*2].next_ctrl = ctrl; + + dmalist[(i*2) + 1].dma_src = 0x20020034; /* SLC_ECC */ + dmalist[(i*2) + 1].dma_dest = + target_mem_base + ECC_OFFS + i * 4; + dmalist[(i*2) + 1].next_lli = + target_mem_base + (i*2 + 2) * sizeof(dmac_ll_t); + dmalist[(i*2) + 1].next_ctrl = ecc_ctrl; + + } + if (do_read) + { + dmadst = target_mem_base + SPARE_OFFS; + } else { + dmasrc = target_mem_base + SPARE_OFFS; + dmalist[(i*2) - 1].next_lli = 0; /* last link = null on write */ + dmalist[(i*2) - 1].next_ctrl |= (1 << 31); /* Set TC enable */ + } + dmalist[i*2].dma_src = dmasrc; + dmalist[i*2].dma_dest = dmadst; + dmalist[i*2].next_lli = 0; + dmalist[i*2].next_ctrl = oob_ctrl; + + return (i*2 + 1); /* Number of descriptors */ +} + +static int lpc32xx_start_slc_dma(struct nand_device *nand, uint32_t count, + int do_wait) +{ + struct target *target = nand->target; + int retval; + + /* DMACIntTCClear = ch0 */ + retval = target_write_u32(target, 0x31000008, 1); + if (ERROR_OK != retval) { + LOG_ERROR("Could not set DMACIntTCClear"); + return retval; + } + + /* DMACIntErrClear = ch0 */ + retval = target_write_u32(target, 0x31000010, 1); + if (ERROR_OK != retval) { + LOG_ERROR("Could not set DMACIntErrClear"); + return retval; + } + + /* DMACCxConfig= + E=1, + SrcPeripheral = 1 (SLC), + DestPeripheral = 1 (SLC), + FlowCntrl = 2 (Pher -> Mem, DMA), + IE = 0, + ITC = 0, + L= 0, + H=0 + */ + retval = target_write_u32(target, 0x31000110, + 1 | 1<<1 | 1<<6 | 2<<11 | 0<<14 + | 0<<15 | 0<<16 | 0<<18); + if (ERROR_OK != retval) { + LOG_ERROR("Could not set DMACC0Config"); + return retval; + } + + /* SLC_CTRL = 3 (START DMA), ECC_CLEAR */ + retval = target_write_u32(target, 0x20020010, 0x3); + if (ERROR_OK != retval) { + LOG_ERROR("Could not set SLC_CTRL"); + return retval; + } + + /* SLC_ICR = 2, INT_TC_CLR, clear pending TC*/ + retval = target_write_u32(target, 0x20020028, 2); + if (ERROR_OK != retval) { + LOG_ERROR("Could not set SLC_ICR"); + return retval; + } + + /* SLC_TC */ + retval = target_write_u32(target, 0x20020030, count); + if (ERROR_OK != retval) { + LOG_ERROR("lpc32xx_start_slc_dma: Could not set SLC_TC"); + return retval; + } + + /* Wait finish */ + if (do_wait && !lpc32xx_tc_ready(nand, 100)) { + LOG_ERROR("timeout while waiting for completion of DMA"); + return ERROR_NAND_OPERATION_FAILED; + } + + return retval; +} + +static int lpc32xx_dma_ready(struct nand_device *nand, int timeout) +{ + struct target *target = nand->target; + + LOG_DEBUG("lpc32xx_dma_ready count start=%d", timeout); + + do { + uint32_t tc_stat; + uint32_t err_stat; + int retval; + + /* Read DMACRawIntTCStat */ + retval = target_read_u32(target, 0x31000014, &tc_stat); + if (ERROR_OK != retval) { + LOG_ERROR("Could not read DMACRawIntTCStat"); + return 0; + } + /* Read DMACRawIntErrStat */ + retval = target_read_u32(target, 0x31000018, &err_stat); + if (ERROR_OK != retval) { + LOG_ERROR("Could not read DMACRawIntErrStat"); + return 0; + } + if ((tc_stat | err_stat) & 1) { + LOG_DEBUG("lpc32xx_dma_ready count=%d", + timeout); + if (err_stat & 1) { + LOG_ERROR("lpc32xx_dma_ready " + "DMA error, aborted"); + return 0; + } else { + return 1; + } + } + + alive_sleep(1); + } while (timeout-- > 0); + + return 0; +} + +static uint32_t slc_ecc_copy_to_buffer(uint8_t * spare, + const uint32_t * ecc, int count) +{ + int i; + for (i = 0; i < (count * 3); i += 3) { + uint32_t ce = ecc[i/3]; + ce = ~(ce << 2) & 0xFFFFFF; + spare[i+2] = (uint8_t)(ce & 0xFF); ce >>= 8; + spare[i+1] = (uint8_t)(ce & 0xFF); ce >>= 8; + spare[i] = (uint8_t)(ce & 0xFF); + } + return 0; +} + +static void lpc32xx_dump_oob(uint8_t *oob, uint32_t oob_size) +{ + int addr = 0; + while (oob_size > 0) { + LOG_DEBUG("%02x: %02x %02x %02x %02x %02x %02x %02x %02x", addr, + oob[0], oob[1], oob[2], oob[3], + oob[4], oob[5], oob[6], oob[7]); + oob += 8; + addr += 8; + oob_size -= 8; + } +} + +static int lpc32xx_write_page_slc(struct nand_device *nand, + struct working_area *pworking_area, + uint32_t page, uint8_t *data, + uint32_t data_size, uint8_t *oob, + uint32_t oob_size) +{ + struct target *target = nand->target; + int retval; + uint32_t target_mem_base; + + LOG_DEBUG("SLC write page %x data=%d, oob=%d, " + "data_size=%d, oob_size=%d", + page, data != 0, oob != 0, data_size, oob_size); + + target_mem_base = pworking_area->address; + /* + * Skip writting page which has all 0xFF data as this will + * generate 0x0 value. + */ + if (data && !oob) { + uint32_t i, all_ff = 1; + for (i = 0; i < data_size; i++) + if (data[i] != 0xFF) { + all_ff = 0; + break; + } + if (all_ff) + return ERROR_OK; + } + /* Make the dma descriptors in local memory */ + int nll = lpc32xx_make_dma_list(target_mem_base, nand->page_size, 0); + /* Write them to target. + XXX: Assumes host and target have same byte sex. + */ + retval = target_write_memory(target, target_mem_base, 4, + nll * sizeof(dmac_ll_t) / 4, + (uint8_t *)dmalist); + if (ERROR_OK != retval) { + LOG_ERROR("Could not write DMA descriptors to IRAM"); + return retval; + } + + retval = nand_page_command(nand, page, NAND_CMD_SEQIN, !data); + if (ERROR_OK != retval) { + LOG_ERROR("NAND_CMD_SEQIN failed"); + return retval; + } + + /* SLC_CFG = + Force nCE assert, + DMA ECC enabled, + ECC enabled, + DMA burst enabled, + DMA write to SLC, + WIDTH = bus_width + */ + retval = target_write_u32(target, 0x20020014, 0x3c); + if (ERROR_OK != retval) { + LOG_ERROR("Could not set SLC_CFG"); + return retval; + } + if (data) { + /* Write data to target */ + static uint8_t fdata[2048]; + memset(fdata, 0xFF, nand->page_size); + memcpy(fdata, data, data_size); + retval = target_write_memory(target, + target_mem_base + DATA_OFFS, + 4, nand->page_size/4, fdata); + if (ERROR_OK != retval) { + LOG_ERROR("Could not write data to IRAM"); + return retval; + } + + /* Write first decriptor to DMA controller */ + retval = target_write_memory(target, 0x31000100, 4, + sizeof(dmac_ll_t) / 4, + (uint8_t *)dmalist); + if (ERROR_OK != retval) { + LOG_ERROR("Could not write DMA descriptor to DMAC"); + return retval; + } + + /* Start xfer of data from iram to flash using DMA */ + int tot_size = nand->page_size; + tot_size += tot_size == 2048 ? 64 : 16; + retval = lpc32xx_start_slc_dma(nand, tot_size, 0); + if (ERROR_OK != retval) { + LOG_ERROR("DMA failed"); + return retval; + } + + /* Wait for DMA to finish. SLC is not finished at this stage */ + if (!lpc32xx_dma_ready(nand, 100)) { + LOG_ERROR("Data DMA failed during write"); + return ERROR_FLASH_OPERATION_FAILED; + } + } /* data xfer */ + + /* Copy OOB to iram */ + static uint8_t foob[64]; + int foob_size = nand->page_size == 2048 ? 64 : 16; + memset(foob, 0xFF, foob_size); + if (oob) { /* Raw mode */ + memcpy(foob, oob, oob_size); + } else { + /* Get HW generated ECC, made while writing data */ + int ecc_count = nand->page_size == 2048 ? 8 : 2; + static uint32_t hw_ecc[8]; + retval = target_read_memory(target, target_mem_base + ECC_OFFS, + 4, ecc_count, (uint8_t *)hw_ecc); + if (ERROR_OK != retval) { + LOG_ERROR("Reading hw generated ECC from IRAM failed"); + return retval; + } + /* Copy to oob, at correct offsets */ + static uint8_t ecc[24]; + slc_ecc_copy_to_buffer(ecc, hw_ecc, ecc_count); + int *layout = nand->page_size == 2048 ? lp_ooblayout : sp_ooblayout; + int i; + for (i = 0; i < ecc_count * 3; i++) + foob[layout[i]] = ecc[i]; + lpc32xx_dump_oob(foob, foob_size); + } + retval = target_write_memory(target, target_mem_base + SPARE_OFFS, 4, + foob_size / 4, foob); + if (ERROR_OK != retval) { + LOG_ERROR("Writing OOB to IRAM failed"); + return retval; + } + + /* Write OOB decriptor to DMA controller */ + retval = target_write_memory(target, 0x31000100, 4, + sizeof(dmac_ll_t) / 4, + (uint8_t *)(&dmalist[nll-1])); + if (ERROR_OK != retval) { + LOG_ERROR("Could not write OOB DMA descriptor to DMAC"); + return retval; + } + if (data) { + /* Only restart DMA with last descriptor, + * don't setup SLC again */ + + /* DMACIntTCClear = ch0 */ + retval = target_write_u32(target, 0x31000008, 1); + if (ERROR_OK != retval) { + LOG_ERROR("Could not set DMACIntTCClear"); + return retval; + } + /* DMACCxConfig= + E=1, + SrcPeripheral = 1 (SLC), + DestPeripheral = 1 (SLC), + FlowCntrl = 2 (Pher -> Mem, DMA), + IE = 0, + ITC = 0, + L= 0, + H=0 + */ + retval = target_write_u32(target, 0x31000110, + 1 | 1<<1 | 1<<6 | 2<<11 | 0<<14 + | 0<<15 | 0<<16 | 0<<18); + if (ERROR_OK != retval) { + LOG_ERROR("Could not set DMACC0Config"); + return retval; + } + /* Wait finish */ + if (!lpc32xx_tc_ready(nand, 100)) { + LOG_ERROR("timeout while waiting for " + "completion of DMA"); + return ERROR_NAND_OPERATION_FAILED; + } + } else { + /* Start xfer of data from iram to flash using DMA */ + retval = lpc32xx_start_slc_dma(nand, foob_size, 1); + if (ERROR_OK != retval) { + LOG_ERROR("DMA OOB failed"); + return retval; + } + } + + /* Let NAND start actual writing */ + retval = nand_write_finish(nand); + if (ERROR_OK != retval) { + LOG_ERROR("nand_write_finish failed"); + return retval; + } + + return ERROR_OK; +} + +static int lpc32xx_write_page(struct nand_device *nand, uint32_t page, + uint8_t *data, uint32_t data_size, + uint8_t *oob, uint32_t oob_size) +{ + struct lpc32xx_nand_controller *lpc32xx_info = nand->controller_priv; + struct target *target = nand->target; + int retval = ERROR_OK; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("target must be halted to use LPC32xx " + "NAND flash controller"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (lpc32xx_info->selected_controller == LPC32xx_NO_CONTROLLER) { + LOG_ERROR("BUG: no LPC32xx NAND flash controller selected"); + return ERROR_NAND_OPERATION_FAILED; + } else if (lpc32xx_info->selected_controller == LPC32xx_MLC_CONTROLLER) { + if (!data && oob) { + LOG_ERROR("LPC32xx MLC controller can't write " + "OOB data only"); + return ERROR_NAND_OPERATION_NOT_SUPPORTED; + } + + if (oob && (oob_size > 24)) { + LOG_ERROR("LPC32xx MLC controller can't write more " + "than 6 bytes for each quarter's OOB data"); + return ERROR_NAND_OPERATION_NOT_SUPPORTED; + } + + if (data_size > (uint32_t)nand->page_size) { + LOG_ERROR("data size exceeds page size"); + return ERROR_NAND_OPERATION_NOT_SUPPORTED; + } + + retval = lpc32xx_write_page_mlc(nand, page, data, data_size, + oob, oob_size); + } else if (lpc32xx_info->selected_controller == LPC32xx_SLC_CONTROLLER) { + struct working_area *pworking_area; + if (!data && oob) { + /* + * if oob only mode is active original method is used + * as SLC controller hangs during DMA interworking. (?) + * Anyway the code supports the oob only mode below. + */ + return nand_write_page_raw(nand, page, data, + data_size, oob, oob_size); + } + retval = target_alloc_working_area(target, + nand->page_size + DATA_OFFS, + &pworking_area); + if (retval != ERROR_OK) { + LOG_ERROR("Can't allocate working area in " + "LPC internal RAM"); + return ERROR_FLASH_OPERATION_FAILED; + } + retval = lpc32xx_write_page_slc(nand, pworking_area, page, + data, data_size, oob, oob_size); + target_free_working_area(target, pworking_area); + } + + return retval; +} + +static int lpc32xx_read_page_mlc(struct nand_device *nand, uint32_t page, + uint8_t *data, uint32_t data_size, + uint8_t *oob, uint32_t oob_size) +{ + struct target *target = nand->target; + static uint8_t page_buffer[2048]; + static uint8_t oob_buffer[64]; + uint32_t page_bytes_done = 0; + uint32_t oob_bytes_done = 0; + uint32_t mlc_isr; + int retval; + + if (!data && oob) { + /* MLC_CMD = Read OOB + * we can use the READOOB command on both small and large page + * devices, as the controller translates the 0x50 command to + * a 0x0 with appropriate positioning of the serial buffer + * read pointer + */ + retval = target_write_u32(target, 0x200b8000, NAND_CMD_READOOB); + } else { + /* MLC_CMD = Read0 */ + retval = target_write_u32(target, 0x200b8000, NAND_CMD_READ0); + } + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_CMD"); + return ERROR_NAND_OPERATION_FAILED; + } + if (nand->page_size == 512) { + /* small page device */ + /* MLC_ADDR = 0x0 (one column cycle) */ + retval = target_write_u32(target, 0x200b8004, 0x0); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* MLC_ADDR = row */ + retval = target_write_u32(target, 0x200b8004, page & 0xff); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + retval = target_write_u32(target, 0x200b8004, + (page >> 8) & 0xff); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (nand->address_cycles == 4) { + retval = target_write_u32(target, 0x200b8004, + (page >> 16) & 0xff); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + } + } else { + /* large page device */ + /* MLC_ADDR = 0x0 (two column cycles) */ + retval = target_write_u32(target, 0x200b8004, 0x0); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + retval = target_write_u32(target, 0x200b8004, 0x0); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* MLC_ADDR = row */ + retval = target_write_u32(target, 0x200b8004, page & 0xff); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + retval = target_write_u32(target, 0x200b8004, + (page >> 8) & 0xff); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ADDR"); + return ERROR_NAND_OPERATION_FAILED; + } + + /* MLC_CMD = Read Start */ + retval = target_write_u32(target, 0x200b8000, + NAND_CMD_READSTART); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_CMD"); + return ERROR_NAND_OPERATION_FAILED; + } + } + + while (page_bytes_done < (uint32_t)nand->page_size) { + /* MLC_ECC_AUTO_DEC_REG = dummy */ + retval = target_write_u32(target, 0x200b8014, 0xaa55aa55); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_ECC_AUTO_DEC_REG"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (!lpc32xx_controller_ready(nand, 1000)) { + LOG_ERROR("timeout while waiting for " + "completion of auto decode cycle"); + return ERROR_NAND_OPERATION_FAILED; + } + + retval = target_read_u32(target, 0x200b8048, &mlc_isr); + if (ERROR_OK != retval) { + LOG_ERROR("could not read MLC_ISR"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (mlc_isr & 0x8) { + if (mlc_isr & 0x40) { + LOG_ERROR("uncorrectable error detected: " + "0x%2.2x", (unsigned)mlc_isr); + return ERROR_NAND_OPERATION_FAILED; + } + + LOG_WARNING("%i symbol error detected and corrected", + ((int)(((mlc_isr & 0x30) >> 4) + 1))); + } + + if (data) { + retval = target_read_memory(target, 0x200a8000, 4, 128, + page_buffer + page_bytes_done); + if (ERROR_OK != retval) { + LOG_ERROR("could not read MLC_BUF (data)"); + return ERROR_NAND_OPERATION_FAILED; + } + } + + if (oob) { + retval = target_read_memory(target, 0x200a8000, 4, 4, + oob_buffer + oob_bytes_done); + if (ERROR_OK != retval) { + LOG_ERROR("could not read MLC_BUF (oob)"); + return ERROR_NAND_OPERATION_FAILED; + } + } + + page_bytes_done += 512; + oob_bytes_done += 16; + } + + if (data) + memcpy(data, page_buffer, data_size); + + if (oob) + memcpy(oob, oob_buffer, oob_size); + + return ERROR_OK; +} + +static int lpc32xx_read_page_slc(struct nand_device *nand, + struct working_area *pworking_area, + uint32_t page, uint8_t *data, + uint32_t data_size, uint8_t *oob, + uint32_t oob_size) +{ + struct target *target = nand->target; + int retval; + uint32_t target_mem_base; + + LOG_DEBUG("SLC read page %x data=%d, oob=%d", + page, data_size, oob_size); + + target_mem_base = pworking_area->address; + + /* Make the dma descriptors in local memory */ + int nll = lpc32xx_make_dma_list(target_mem_base, nand->page_size, 1); + /* Write them to target. + XXX: Assumes host and target have same byte sex. + */ + retval = target_write_memory(target, target_mem_base, 4, + nll * sizeof(dmac_ll_t) / 4, + (uint8_t *)dmalist); + if (ERROR_OK != retval) { + LOG_ERROR("Could not write DMA descriptors to IRAM"); + return retval; + } + + retval = nand_page_command(nand, page, NAND_CMD_READ0, 0); + if (ERROR_OK != retval) { + LOG_ERROR("lpc32xx_read_page_slc: NAND_CMD_READ0 failed"); + return retval; + } + + /* SLC_CFG = + Force nCE assert, + DMA ECC enabled, + ECC enabled, + DMA burst enabled, + DMA read from SLC, + WIDTH = bus_width + */ + retval = target_write_u32(target, 0x20020014, 0x3e); + if (ERROR_OK != retval) { + LOG_ERROR("lpc32xx_read_page_slc: Could not set SLC_CFG"); + return retval; + } + + /* Write first decriptor to DMA controller */ + retval = target_write_memory(target, 0x31000100, 4, + sizeof(dmac_ll_t) / 4, (uint8_t *)dmalist); + if (ERROR_OK != retval) { + LOG_ERROR("Could not write DMA descriptor to DMAC"); + return retval; + } + + /* Start xfer of data from flash to iram using DMA */ + int tot_size = nand->page_size; + tot_size += nand->page_size == 2048 ? 64 : 16; + retval = lpc32xx_start_slc_dma(nand, tot_size, 1); + if (ERROR_OK != retval) { + LOG_ERROR("lpc32xx_read_page_slc: DMA read failed"); + return retval; + } + + /* Copy data from iram */ + if (data) { + retval = target_read_memory(target, target_mem_base + DATA_OFFS, + 4, data_size/4, data); + if (ERROR_OK != retval) { + LOG_ERROR("Could not read data from IRAM"); + return retval; + } + } + if (oob) { + /* No error correction, just return data as read from flash */ + retval = target_read_memory(target, + target_mem_base + SPARE_OFFS, 4, + oob_size/4, oob); + if (ERROR_OK != retval) { + LOG_ERROR("Could not read OOB from IRAM"); + return retval; + } + return ERROR_OK; + } + + /* Copy OOB from flash, stored in IRAM */ + static uint8_t foob[64]; + retval = target_read_memory(target, target_mem_base + SPARE_OFFS, + 4, nand->page_size == 2048 ? 16 : 4, foob); + lpc32xx_dump_oob(foob, nand->page_size == 2048 ? 64 : 16); + if (ERROR_OK != retval) { + LOG_ERROR("Could not read OOB from IRAM"); + return retval; + } + /* Copy ECC from HW, generated while reading */ + int ecc_count = nand->page_size == 2048 ? 8 : 2; + static uint32_t hw_ecc[8]; /* max size */ + retval = target_read_memory(target, target_mem_base + ECC_OFFS, 4, + ecc_count, (uint8_t *)hw_ecc); + if (ERROR_OK != retval) { + LOG_ERROR("Could not read hw generated ECC from IRAM"); + return retval; + } + static uint8_t ecc[24]; + slc_ecc_copy_to_buffer(ecc, hw_ecc, ecc_count); + /* Copy ECC from flash using correct layout */ + static uint8_t fecc[24]; /* max size */ + int *layout = nand->page_size == 2048 ? lp_ooblayout : sp_ooblayout; + int i; + for (i = 0; i < ecc_count * 3; i++) + fecc[i] = foob[layout[i]]; + /* Compare ECC and possibly correct data */ + for (i = 0; i < ecc_count; i++) { + retval = nand_correct_data(nand, data + 256*i, &fecc[i * 3], + &ecc[i * 3]); + if (retval > 0) + LOG_WARNING("error detected and corrected: %d/%d", + page, i); + if (retval < 0) + break; + } + if (i == ecc_count) + retval = ERROR_OK; + else { + LOG_ERROR("uncorrectable error detected: %d/%d", page, i); + retval = ERROR_NAND_OPERATION_FAILED; + } + return retval; +} + +static int lpc32xx_read_page(struct nand_device *nand, uint32_t page, + uint8_t *data, uint32_t data_size, + uint8_t *oob, uint32_t oob_size) +{ + struct lpc32xx_nand_controller *lpc32xx_info = nand->controller_priv; + struct target *target = nand->target; + int retval = ERROR_OK; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("target must be halted to use LPC32xx " + "NAND flash controller"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (lpc32xx_info->selected_controller == LPC32xx_NO_CONTROLLER) { + LOG_ERROR("BUG: no LPC32xx NAND flash controller selected"); + return ERROR_NAND_OPERATION_FAILED; + } else if (lpc32xx_info->selected_controller == LPC32xx_MLC_CONTROLLER) { + if (data_size > (uint32_t)nand->page_size) { + LOG_ERROR("data size exceeds page size"); + return ERROR_NAND_OPERATION_NOT_SUPPORTED; + } + retval = lpc32xx_read_page_mlc(nand, page, data, data_size, + oob, oob_size); + } else if (lpc32xx_info->selected_controller == LPC32xx_SLC_CONTROLLER) { + struct working_area *pworking_area; + + retval = target_alloc_working_area(target, + nand->page_size + 0x200, + &pworking_area); + if (retval != ERROR_OK) { + LOG_ERROR("Can't allocate working area in " + "LPC internal RAM"); + return ERROR_FLASH_OPERATION_FAILED; + } + retval = lpc32xx_read_page_slc(nand, pworking_area, page, + data, data_size, oob, oob_size); + target_free_working_area(target, pworking_area); + } + + return retval; +} + +static int lpc32xx_controller_ready(struct nand_device *nand, int timeout) +{ + struct lpc32xx_nand_controller *lpc32xx_info = nand->controller_priv; + struct target *target = nand->target; + int retval; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("target must be halted to use LPC32xx " + "NAND flash controller"); + return ERROR_NAND_OPERATION_FAILED; + } + + LOG_DEBUG("lpc32xx_controller_ready count start=%d", timeout); + + do { + if (lpc32xx_info->selected_controller == LPC32xx_MLC_CONTROLLER) { + uint8_t status; + + /* Read MLC_ISR, wait for controller to become ready */ + retval = target_read_u8(target, 0x200b8048, &status); + if (ERROR_OK != retval) { + LOG_ERROR("could not set MLC_STAT"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (status & 2) { + LOG_DEBUG("lpc32xx_controller_ready count=%d", + timeout); + return 1; + } + } else if (lpc32xx_info->selected_controller == LPC32xx_SLC_CONTROLLER) { + uint32_t status; + + /* Read SLC_STAT and check READY bit */ + retval = target_read_u32(target, 0x20020018, &status); + if (ERROR_OK != retval) { + LOG_ERROR("could not set SLC_STAT"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (status & 1) { + LOG_DEBUG("lpc32xx_controller_ready count=%d", + timeout); + return 1; + } + } + + alive_sleep(1); + } while (timeout-- > 0); + + return 0; +} + +static int lpc32xx_nand_ready(struct nand_device *nand, int timeout) +{ + struct lpc32xx_nand_controller *lpc32xx_info = nand->controller_priv; + struct target *target = nand->target; + int retval; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("target must be halted to use LPC32xx " + "NAND flash controller"); + return ERROR_NAND_OPERATION_FAILED; + } + + LOG_DEBUG("lpc32xx_nand_ready count start=%d", timeout); + + do { + if (lpc32xx_info->selected_controller == LPC32xx_MLC_CONTROLLER) { + uint8_t status = 0x0; + + /* Read MLC_ISR, wait for NAND flash device to + * become ready */ + retval = target_read_u8(target, 0x200b8048, &status); + if (ERROR_OK != retval) { + LOG_ERROR("could not read MLC_ISR"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (status & 1) { + LOG_DEBUG("lpc32xx_nand_ready count end=%d", + timeout); + return 1; + } + } else if (lpc32xx_info->selected_controller == LPC32xx_SLC_CONTROLLER) { + uint32_t status = 0x0; + + /* Read SLC_STAT and check READY bit */ + retval = target_read_u32(target, 0x20020018, &status); + if (ERROR_OK != retval) { + LOG_ERROR("could not read SLC_STAT"); + return ERROR_NAND_OPERATION_FAILED; + } + + if (status & 1) { + LOG_DEBUG("lpc32xx_nand_ready count end=%d", + timeout); + return 1; + } + } + + alive_sleep(1); + } while (timeout-- > 0); + + return 0; +} + +static int lpc32xx_tc_ready(struct nand_device *nand, int timeout) +{ + struct target *target = nand->target; + + LOG_DEBUG("lpc32xx_tc_ready count start=%d", timeout); + + do { + uint32_t status = 0x0; + int retval; + /* Read SLC_INT_STAT and check INT_TC_STAT bit */ + retval = target_read_u32(target, 0x2002001c, &status); + if (ERROR_OK != retval) { + LOG_ERROR("Could not read SLC_INT_STAT"); + return 0; + } + if (status & 2){ + LOG_DEBUG("lpc32xx_tc_ready count=%d", timeout); + return 1; + } + + alive_sleep(1); + } while (timeout-- > 0); + + return 0; +} + +COMMAND_HANDLER(handle_lpc32xx_select_command) +{ + struct lpc32xx_nand_controller *lpc32xx_info = NULL; + char *selected[] = { + "no", "mlc", "slc" + }; + + if ((CMD_ARGC < 1) || (CMD_ARGC > 3)) { + return ERROR_COMMAND_SYNTAX_ERROR; + } + + unsigned num; + COMMAND_PARSE_NUMBER(uint, CMD_ARGV[0], num); + struct nand_device *nand = get_nand_device_by_num(num); + if (!nand) { + command_print(CMD_CTX, "nand device '#%s' is out of bounds", + CMD_ARGV[0]); + return ERROR_OK; + } + + lpc32xx_info = nand->controller_priv; + + if (CMD_ARGC >= 2) { + if (strcmp(CMD_ARGV[1], "mlc") == 0) { + lpc32xx_info->selected_controller = + LPC32xx_MLC_CONTROLLER; + } else if (strcmp(CMD_ARGV[1], "slc") == 0) { + lpc32xx_info->selected_controller = + LPC32xx_SLC_CONTROLLER; + } else { + return ERROR_COMMAND_SYNTAX_ERROR; + } + } + + command_print(CMD_CTX, "%s controller selected", + selected[lpc32xx_info->selected_controller]); + + return ERROR_OK; +} + +static const struct command_registration lpc32xx_exec_command_handlers[] = { + { + .name = "select", + .handler = handle_lpc32xx_select_command, + .mode = COMMAND_EXEC, + .help = "select MLC or SLC controller (default is MLC)", + .usage = "bank_id ['mlc'|'slc' ]", + }, + COMMAND_REGISTRATION_DONE +}; +static const struct command_registration lpc32xx_command_handler[] = { + { + .name = "lpc32xx", + .mode = COMMAND_ANY, + .help = "LPC32xx NAND flash controller commands", + .chain = lpc32xx_exec_command_handlers, + }, + COMMAND_REGISTRATION_DONE +}; + +struct nand_flash_controller lpc32xx_nand_controller = { + .name = "lpc32xx", + .commands = lpc32xx_command_handler, + .nand_device_command = lpc32xx_nand_device_command, + .init = lpc32xx_init, + .reset = lpc32xx_reset, + .command = lpc32xx_command, + .address = lpc32xx_address, + .write_data = lpc32xx_write_data, + .read_data = lpc32xx_read_data, + .write_page = lpc32xx_write_page, + .read_page = lpc32xx_read_page, + .nand_ready = lpc32xx_nand_ready, +}; diff --git a/src/flash/nand/lpc32xx.h b/src/flash/nand/lpc32xx.h new file mode 100644 index 00000000..fd872459 --- /dev/null +++ b/src/flash/nand/lpc32xx.h @@ -0,0 +1,39 @@ +/*************************************************************************** + * Copyright (C) 2007 by Dominic Rath * + * Dominic.Rath@gmx.de * + * * + * 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 program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef LPC32xx_NAND_CONTROLLER_H +#define LPC32xx_NAND_CONTROLLER_H + +enum lpc32xx_selected_controller +{ + LPC32xx_NO_CONTROLLER, + LPC32xx_MLC_CONTROLLER, + LPC32xx_SLC_CONTROLLER, +}; + +struct lpc32xx_nand_controller +{ + int osc_freq; + enum lpc32xx_selected_controller selected_controller; + int sw_write_protection; + uint32_t sw_wp_lower_bound; + uint32_t sw_wp_upper_bound; +}; + +#endif /*LPC32xx_NAND_CONTROLLER_H */ |