| /* |
| * (C) Copyright 2009 |
| * Magnus Lilja <lilja.magnus@gmail.com> |
| * |
| * (C) Copyright 2008 |
| * Maxim Artamonov, <scn1874 at yandex.ru> |
| * |
| * (C) Copyright 2006-2008 |
| * Stefan Roese, DENX Software Engineering, sr at denx.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 |
| */ |
| |
| #include <common.h> |
| #include <nand.h> |
| #ifdef CONFIG_MX31 |
| #include <asm/arch/mx31-regs.h> |
| #else |
| #include <asm/arch/imx-regs.h> |
| #endif |
| #include <asm/io.h> |
| #include <fsl_nfc.h> |
| |
| struct fsl_nfc_regs *nfc; |
| |
| static void nfc_wait_ready(void) |
| { |
| uint32_t tmp; |
| |
| while (!(readw(&nfc->nand_flash_config2) & NFC_INT)) |
| ; |
| |
| /* Reset interrupt flag */ |
| tmp = readw(&nfc->nand_flash_config2); |
| tmp &= ~NFC_INT; |
| writew(tmp, &nfc->nand_flash_config2); |
| } |
| |
| void nfc_nand_init(void) |
| { |
| #if defined(MXC_NFC_V1_1) |
| int ecc_per_page = CONFIG_SYS_NAND_PAGE_SIZE / 512; |
| int config1; |
| |
| writew(CONFIG_SYS_NAND_SPARE_SIZE / 2, &nfc->spare_area_size); |
| |
| /* unlocking RAM Buff */ |
| writew(0x2, &nfc->configuration); |
| |
| /* hardware ECC checking and correct */ |
| config1 = readw(&nfc->nand_flash_config1) | NFC_ECC_EN | 0x800; |
| /* |
| * if spare size is larger that 16 bytes per 512 byte hunk |
| * then use 8 symbol correction instead of 4 |
| */ |
| if ((CONFIG_SYS_NAND_SPARE_SIZE / ecc_per_page) > 16) |
| config1 &= ~NFC_4_8N_ECC; |
| else |
| config1 |= NFC_4_8N_ECC; |
| writew(config1, &nfc->nand_flash_config1); |
| #elif defined(MXC_NFC_V1) |
| /* unlocking RAM Buff */ |
| writew(0x2, &nfc->configuration); |
| |
| /* hardware ECC checking and correct */ |
| writew(NFC_ECC_EN, &nfc->nand_flash_config1); |
| #endif |
| } |
| |
| static void nfc_nand_command(unsigned short command) |
| { |
| writew(command, &nfc->flash_cmd); |
| writew(NFC_CMD, &nfc->nand_flash_config2); |
| nfc_wait_ready(); |
| } |
| |
| static void nfc_nand_page_address(unsigned int page_address) |
| { |
| unsigned int page_count; |
| |
| writew(0x00, &nfc->flash_add); |
| writew(NFC_ADDR, &nfc->nand_flash_config2); |
| nfc_wait_ready(); |
| |
| /* code only for large page flash */ |
| if (CONFIG_SYS_NAND_PAGE_SIZE > 512) { |
| writew(0x00, &nfc->flash_add); |
| writew(NFC_ADDR, &nfc->nand_flash_config2); |
| nfc_wait_ready(); |
| } |
| |
| page_count = CONFIG_SYS_NAND_SIZE / CONFIG_SYS_NAND_PAGE_SIZE; |
| |
| if (page_address <= page_count) { |
| page_count--; /* transform 0x01000000 to 0x00ffffff */ |
| do { |
| writew(page_address & 0xff, &nfc->flash_add); |
| writew(NFC_ADDR, &nfc->nand_flash_config2); |
| nfc_wait_ready(); |
| page_address = page_address >> 8; |
| page_count = page_count >> 8; |
| } while (page_count); |
| } |
| |
| writew(0x00, &nfc->flash_add); |
| writew(NFC_ADDR, &nfc->nand_flash_config2); |
| nfc_wait_ready(); |
| } |
| |
| static void nfc_nand_data_output(void) |
| { |
| int config1 = readw(&nfc->nand_flash_config1); |
| #ifdef NAND_MXC_2K_MULTI_CYCLE |
| int i; |
| #endif |
| |
| config1 |= NFC_ECC_EN | NFC_INT_MSK; |
| writew(config1, &nfc->nand_flash_config1); |
| writew(0, &nfc->buffer_address); |
| writew(NFC_OUTPUT, &nfc->nand_flash_config2); |
| nfc_wait_ready(); |
| #ifdef NAND_MXC_2K_MULTI_CYCLE |
| /* |
| * This NAND controller requires multiple input commands |
| * for pages larger than 512 bytes. |
| */ |
| for (i = 1; i < (CONFIG_SYS_NAND_PAGE_SIZE / 512); i++) { |
| config1 = readw(&nfc->nand_flash_config1); |
| config1 |= NFC_ECC_EN | NFC_INT_MSK; |
| writew(config1, &nfc->nand_flash_config1); |
| writew(i, &nfc->buffer_address); |
| writew(NFC_OUTPUT, &nfc->nand_flash_config2); |
| nfc_wait_ready(); |
| } |
| #endif |
| } |
| |
| static int nfc_nand_check_ecc(void) |
| { |
| return readw(&nfc->ecc_status_result); |
| } |
| |
| static int nfc_read_page(unsigned int page_address, unsigned char *buf) |
| { |
| int i; |
| u32 *src; |
| u32 *dst; |
| |
| writew(0, &nfc->buffer_address); /* read in first 0 buffer */ |
| nfc_nand_command(NAND_CMD_READ0); |
| nfc_nand_page_address(page_address); |
| |
| if (CONFIG_SYS_NAND_PAGE_SIZE > 512) |
| nfc_nand_command(NAND_CMD_READSTART); |
| |
| nfc_nand_data_output(); /* fill the main buffer 0 */ |
| |
| if (nfc_nand_check_ecc()) |
| return -1; |
| |
| src = &nfc->main_area[0][0]; |
| dst = (u32 *)buf; |
| |
| /* main copy loop from NAND-buffer to SDRAM memory */ |
| for (i = 0; i < (CONFIG_SYS_NAND_PAGE_SIZE / 4); i++) { |
| writel(readl(src), dst); |
| src++; |
| dst++; |
| } |
| |
| return 0; |
| } |
| |
| static int is_badblock(int pagenumber) |
| { |
| int page = pagenumber; |
| u32 badblock; |
| u32 *src; |
| |
| /* Check the first two pages for bad block markers */ |
| for (page = pagenumber; page < pagenumber + 2; page++) { |
| writew(0, &nfc->buffer_address); /* read in first 0 buffer */ |
| nfc_nand_command(NAND_CMD_READ0); |
| nfc_nand_page_address(page); |
| |
| if (CONFIG_SYS_NAND_PAGE_SIZE > 512) |
| nfc_nand_command(NAND_CMD_READSTART); |
| |
| nfc_nand_data_output(); /* fill the main buffer 0 */ |
| |
| src = &nfc->spare_area[0][0]; |
| |
| /* |
| * IMPORTANT NOTE: The nand flash controller uses a non- |
| * standard layout for large page devices. This can |
| * affect the position of the bad block marker. |
| */ |
| /* Get the bad block marker */ |
| badblock = readl(&src[CONFIG_SYS_NAND_BAD_BLOCK_POS / 4]); |
| badblock >>= 8 * (CONFIG_SYS_NAND_BAD_BLOCK_POS % 4); |
| badblock &= 0xff; |
| |
| /* bad block marker verify */ |
| if (badblock != 0xff) |
| return 1; /* potential bad block */ |
| } |
| |
| return 0; |
| } |
| |
| static int nand_load(unsigned int from, unsigned int size, unsigned char *buf) |
| { |
| int i; |
| unsigned int page; |
| unsigned int maxpages = CONFIG_SYS_NAND_SIZE / |
| CONFIG_SYS_NAND_PAGE_SIZE; |
| |
| nfc = (void *)NFC_BASE_ADDR; |
| |
| nfc_nand_init(); |
| |
| /* Convert to page number */ |
| page = from / CONFIG_SYS_NAND_PAGE_SIZE; |
| i = 0; |
| |
| while (i < (size / CONFIG_SYS_NAND_PAGE_SIZE)) { |
| if (nfc_read_page(page, buf) < 0) |
| return -1; |
| |
| page++; |
| i++; |
| buf = buf + CONFIG_SYS_NAND_PAGE_SIZE; |
| |
| /* |
| * Check if we have crossed a block boundary, and if so |
| * check for bad block. |
| */ |
| if (!(page % CONFIG_SYS_NAND_PAGE_COUNT)) { |
| /* |
| * Yes, new block. See if this block is good. If not, |
| * loop until we find a good block. |
| */ |
| while (is_badblock(page)) { |
| page = page + CONFIG_SYS_NAND_PAGE_COUNT; |
| /* Check i we've reached the end of flash. */ |
| if (page >= maxpages) |
| return -1; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * The main entry for NAND booting. It's necessary that SDRAM is already |
| * configured and available since this code loads the main U-Boot image |
| * from NAND into SDRAM and starts it from there. |
| */ |
| void nand_boot(void) |
| { |
| __attribute__((noreturn)) void (*uboot)(void); |
| |
| nfc = (void *)NFC_BASE_ADDR; |
| |
| /* |
| * CONFIG_SYS_NAND_U_BOOT_OFFS and CONFIG_SYS_NAND_U_BOOT_SIZE must |
| * be aligned to full pages |
| */ |
| if (!nand_load(CONFIG_SYS_NAND_U_BOOT_OFFS, CONFIG_SYS_NAND_U_BOOT_SIZE, |
| (uchar *)CONFIG_SYS_NAND_U_BOOT_DST)) { |
| /* Copy from NAND successful, start U-boot */ |
| uboot = (void *)CONFIG_SYS_NAND_U_BOOT_START; |
| uboot(); |
| } else { |
| /* Unrecoverable error when copying from NAND */ |
| hang(); |
| } |
| } |
| |
| /* |
| * Called in case of an exception. |
| */ |
| void hang(void) |
| { |
| /* Loop forever */ |
| while (1) ; |
| } |