summaryrefslogtreecommitdiffstats
path: root/drivers/mtd/nand/sunxi_nand_spl.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd/nand/sunxi_nand_spl.c')
-rw-r--r--drivers/mtd/nand/sunxi_nand_spl.c268
1 files changed, 155 insertions, 113 deletions
diff --git a/drivers/mtd/nand/sunxi_nand_spl.c b/drivers/mtd/nand/sunxi_nand_spl.c
index ac5f56d066..bf9b1b1450 100644
--- a/drivers/mtd/nand/sunxi_nand_spl.c
+++ b/drivers/mtd/nand/sunxi_nand_spl.c
@@ -5,9 +5,10 @@
* SPDX-License-Identifier: GPL-2.0+
*/
+#include <asm/arch/clock.h>
+#include <asm/io.h>
#include <common.h>
#include <config.h>
-#include <asm/io.h>
#include <nand.h>
/* registers */
@@ -41,6 +42,8 @@
#define NFC_CTL_EN (1 << 0)
#define NFC_CTL_RESET (1 << 1)
#define NFC_CTL_RAM_METHOD (1 << 14)
+#define NFC_CTL_PAGE_SIZE_MASK (0xf << 8)
+#define NFC_CTL_PAGE_SIZE(a) ((fls(a) - 11) << 8)
#define NFC_ECC_EN (1 << 0)
@@ -64,7 +67,8 @@
#define NFC_SEND_CMD3 (1 << 28)
#define NFC_SEND_CMD4 (1 << 29)
-#define NFC_CMD_INT_FLAG (1 << 1)
+#define NFC_ST_CMD_INT_FLAG (1 << 1)
+#define NFC_ST_DMA_INT_FLAG (1 << 2)
#define NFC_READ_CMD_OFFSET 0
#define NFC_RANDOM_READ_CMD0_OFFSET 8
@@ -85,6 +89,7 @@
#define SUNXI_DMA_DDMA_CFG_REG_LOADING (1 << 31)
#define SUNXI_DMA_DDMA_CFG_REG_DMA_DEST_DATA_WIDTH_32 (2 << 25)
+#define SUNXI_DMA_DDMA_CFG_REG_DDMA_DST_DRQ_TYPE_DRAM (1 << 16)
#define SUNXI_DMA_DDMA_CFG_REG_DMA_SRC_DATA_WIDTH_32 (2 << 9)
#define SUNXI_DMA_DDMA_CFG_REG_DMA_SRC_ADDR_MODE_IO (1 << 5)
#define SUNXI_DMA_DDMA_CFG_REG_DDMA_SRC_DRQ_TYPE_NFC (3 << 0)
@@ -94,10 +99,6 @@
/* minimal "boot0" style NAND support for Allwinner A20 */
-/* temporary buffer in internal ram */
-unsigned char temp_buf[CONFIG_NAND_SUNXI_SPL_ECC_PAGE_SIZE]
- __aligned(0x10) __section(".text#");
-
/* random seed used by linux */
const uint16_t random_seed[128] = {
0x2b75, 0x0bd0, 0x5ca3, 0x62d1, 0x1c93, 0x07e9, 0x2162, 0x3a72,
@@ -156,6 +157,8 @@ void nand_init(void)
{
uint32_t val;
+ board_nand_init();
+
val = readl(SUNXI_NFC_BASE + NFC_CTL);
/* enable and reset CTL */
writel(val | NFC_CTL_EN | NFC_CTL_RESET,
@@ -165,86 +168,49 @@ void nand_init(void)
NFC_CTL_RESET, MAX_RETRIES)) {
printf("Couldn't initialize nand\n");
}
+
+ /* reset NAND */
+ writel(NFC_ST_CMD_INT_FLAG, SUNXI_NFC_BASE + NFC_ST);
+ writel(NFC_SEND_CMD1 | NFC_WAIT_FLAG | NAND_CMD_RESET,
+ SUNXI_NFC_BASE + NFC_CMD);
+
+ if (!check_value(SUNXI_NFC_BASE + NFC_ST, NFC_ST_CMD_INT_FLAG,
+ MAX_RETRIES)) {
+ printf("Error timeout waiting for nand reset\n");
+ return;
+ }
+ writel(NFC_ST_CMD_INT_FLAG, SUNXI_NFC_BASE + NFC_ST);
}
-static void nand_read_page(unsigned int real_addr, int syndrome,
- uint32_t *ecc_errors)
+static int nand_read_page(int page_size, int ecc_strength, int ecc_page_size,
+ int addr_cycles, uint32_t real_addr, dma_addr_t dst, int syndrome)
{
uint32_t val;
- int ecc_off = 0;
+ int i, ecc_off = 0;
uint16_t ecc_mode = 0;
uint16_t rand_seed;
uint32_t page;
uint16_t column;
- uint32_t oob_offset;
-
- switch (CONFIG_NAND_SUNXI_SPL_ECC_STRENGTH) {
- case 16:
- ecc_mode = 0;
- ecc_off = 0x20;
- break;
- case 24:
- ecc_mode = 1;
- ecc_off = 0x2e;
- break;
- case 28:
- ecc_mode = 2;
- ecc_off = 0x32;
- break;
- case 32:
- ecc_mode = 3;
- ecc_off = 0x3c;
- break;
- case 40:
- ecc_mode = 4;
- ecc_off = 0x4a;
- break;
- case 48:
- ecc_mode = 4;
- ecc_off = 0x52;
- break;
- case 56:
- ecc_mode = 4;
- ecc_off = 0x60;
- break;
- case 60:
- ecc_mode = 4;
- ecc_off = 0x0;
- break;
- case 64:
- ecc_mode = 4;
- ecc_off = 0x0;
- break;
- default:
- ecc_mode = 0;
- ecc_off = 0;
- }
+ static const u8 strengths[] = { 16, 24, 28, 32, 40, 48, 56, 60, 64 };
- if (ecc_off == 0) {
- printf("Unsupported ECC strength (%d)!\n",
- CONFIG_NAND_SUNXI_SPL_ECC_STRENGTH);
- return;
+ for (i = 0; i < ARRAY_SIZE(strengths); i++) {
+ if (ecc_strength == strengths[i]) {
+ ecc_mode = i;
+ break;
+ }
}
- /* clear temp_buf */
- memset(temp_buf, 0, CONFIG_NAND_SUNXI_SPL_ECC_PAGE_SIZE);
+ /* HW ECC always request ECC bytes for 1024 bytes blocks */
+ ecc_off = DIV_ROUND_UP(ecc_strength * fls(8 * 1024), 8);
+ /* HW ECC always work with even numbers of ECC bytes */
+ ecc_off += (ecc_off & 1);
+ ecc_off += 4; /* prepad */
- /* set CMD */
- writel(NFC_SEND_CMD1 | NFC_WAIT_FLAG | NAND_CMD_RESET,
- SUNXI_NFC_BASE + NFC_CMD);
-
- if (!check_value(SUNXI_NFC_BASE + NFC_ST, NFC_CMD_INT_FLAG,
- MAX_RETRIES)) {
- printf("Error while initilizing command interrupt\n");
- return;
- }
-
- page = real_addr / CONFIG_NAND_SUNXI_SPL_PAGE_SIZE;
- column = real_addr % CONFIG_NAND_SUNXI_SPL_PAGE_SIZE;
+ page = real_addr / page_size;
+ column = real_addr % page_size;
if (syndrome)
- column += (column / CONFIG_NAND_SUNXI_SPL_ECC_PAGE_SIZE)
- * ecc_off;
+ column += (column / ecc_page_size) * ecc_off;
/* clear ecc status */
writel(0, SUNXI_NFC_BASE + NFC_ECC_ST);
@@ -262,15 +228,11 @@ static void nand_read_page(unsigned int real_addr, int syndrome,
val = readl(SUNXI_NFC_BASE + NFC_CTL);
writel(val | NFC_CTL_RAM_METHOD, SUNXI_NFC_BASE + NFC_CTL);
- if (syndrome) {
- writel(CONFIG_NAND_SUNXI_SPL_ECC_PAGE_SIZE,
+ if (!syndrome)
+ writel(page_size + (column / ecc_page_size) * ecc_off,
SUNXI_NFC_BASE + NFC_SPARE_AREA);
- } else {
- oob_offset = CONFIG_NAND_SUNXI_SPL_PAGE_SIZE
- + (column / CONFIG_NAND_SUNXI_SPL_ECC_PAGE_SIZE)
- * ecc_off;
- writel(oob_offset, SUNXI_NFC_BASE + NFC_SPARE_AREA);
- }
+
+ flush_dcache_range(dst, ALIGN(dst + ecc_page_size, ARCH_DMA_MINALIGN));
/* SUNXI_DMA */
writel(0x0, SUNXI_DMA_BASE + SUNXI_DMA_CFG_REG0); /* clr dma cmd */
@@ -278,15 +240,15 @@ static void nand_read_page(unsigned int real_addr, int syndrome,
writel(SUNXI_NFC_BASE + NFC_IO_DATA,
SUNXI_DMA_BASE + SUNXI_DMA_SRC_START_ADDR_REG0);
/* read to RAM */
- writel((uint32_t)temp_buf,
- SUNXI_DMA_BASE + SUNXI_DMA_DEST_START_ADDRR_REG0);
+ writel(dst, SUNXI_DMA_BASE + SUNXI_DMA_DEST_START_ADDRR_REG0);
writel(SUNXI_DMA_DDMA_PARA_REG_SRC_WAIT_CYC
| SUNXI_DMA_DDMA_PARA_REG_SRC_BLK_SIZE,
SUNXI_DMA_BASE + SUNXI_DMA_DDMA_PARA_REG0);
- writel(CONFIG_NAND_SUNXI_SPL_ECC_PAGE_SIZE,
+ writel(ecc_page_size,
SUNXI_DMA_BASE + SUNXI_DMA_DDMA_BC_REG0); /* 1kB */
writel(SUNXI_DMA_DDMA_CFG_REG_LOADING
| SUNXI_DMA_DDMA_CFG_REG_DMA_DEST_DATA_WIDTH_32
+ | SUNXI_DMA_DDMA_CFG_REG_DDMA_DST_DRQ_TYPE_DRAM
| SUNXI_DMA_DDMA_CFG_REG_DMA_SRC_DATA_WIDTH_32
| SUNXI_DMA_DDMA_CFG_REG_DMA_SRC_ADDR_MODE_IO
| SUNXI_DMA_DDMA_CFG_REG_DDMA_SRC_DRQ_TYPE_NFC,
@@ -300,54 +262,134 @@ static void nand_read_page(unsigned int real_addr, int syndrome,
writel(((page & 0xFFFF) << 16) | column,
SUNXI_NFC_BASE + NFC_ADDR_LOW);
writel((page >> 16) & 0xFF, SUNXI_NFC_BASE + NFC_ADDR_HIGH);
+ writel(NFC_ST_DMA_INT_FLAG, SUNXI_NFC_BASE + NFC_ST);
writel(NFC_SEND_CMD1 | NFC_SEND_CMD2 | NFC_DATA_TRANS |
- NFC_PAGE_CMD | NFC_WAIT_FLAG | (4 << NFC_ADDR_NUM_OFFSET) |
+ NFC_PAGE_CMD | NFC_WAIT_FLAG |
+ ((addr_cycles - 1) << NFC_ADDR_NUM_OFFSET) |
NFC_SEND_ADR | NFC_DATA_SWAP_METHOD | (syndrome ? NFC_SEQ : 0),
SUNXI_NFC_BASE + NFC_CMD);
- if (!check_value(SUNXI_NFC_BASE + NFC_ST, (1 << 2),
+ if (!check_value(SUNXI_NFC_BASE + NFC_ST, NFC_ST_DMA_INT_FLAG,
MAX_RETRIES)) {
printf("Error while initializing dma interrupt\n");
- return;
+ return -1;
}
+ writel(NFC_ST_DMA_INT_FLAG, SUNXI_NFC_BASE + NFC_ST);
if (!check_value_negated(SUNXI_DMA_BASE + SUNXI_DMA_CFG_REG0,
SUNXI_DMA_DDMA_CFG_REG_LOADING, MAX_RETRIES)) {
printf("Error while waiting for dma transfer to finish\n");
- return;
+ return -1;
}
+ invalidate_dcache_range(dst,
+ ALIGN(dst + ecc_page_size, ARCH_DMA_MINALIGN));
+
if (readl(SUNXI_NFC_BASE + NFC_ECC_ST))
- (*ecc_errors)++;
+ return -1;
+
+ return 0;
+}
+
+static int nand_read_ecc(int page_size, int ecc_strength, int ecc_page_size,
+ int addr_cycles, uint32_t offs, uint32_t size, void *dest, int syndrome)
+{
+ void *end = dest + size;
+
+ clrsetbits_le32(SUNXI_NFC_BASE + NFC_CTL, NFC_CTL_PAGE_SIZE_MASK,
+ NFC_CTL_PAGE_SIZE(page_size));
+
+ for ( ;dest < end; dest += ecc_page_size, offs += ecc_page_size) {
+ if (nand_read_page(page_size, ecc_strength, ecc_page_size,
+ addr_cycles, offs, (dma_addr_t)dest,
+ syndrome))
+ return -1;
+ }
+
+ return 0;
+}
+
+static int nand_read_buffer(uint32_t offs, unsigned int size, void *dest,
+ int syndrome)
+{
+ const struct {
+ int page_size;
+ int ecc_strength;
+ int ecc_page_size;
+ int addr_cycles;
+ } nand_configs[] = {
+ { 8192, 40, 1024, 5 },
+ { 16384, 56, 1024, 5 },
+ { 8192, 24, 1024, 5 },
+ };
+ static int nand_config = -1;
+ int i;
+
+ if (nand_config == -1) {
+ for (i = 0; i < ARRAY_SIZE(nand_configs); i++) {
+ debug("nand: trying page %d ecc %d / %d addr %d: ",
+ nand_configs[i].page_size,
+ nand_configs[i].ecc_strength,
+ nand_configs[i].ecc_page_size,
+ nand_configs[i].addr_cycles);
+ if (nand_read_ecc(nand_configs[i].page_size,
+ nand_configs[i].ecc_strength,
+ nand_configs[i].ecc_page_size,
+ nand_configs[i].addr_cycles,
+ offs, size, dest, syndrome) == 0) {
+ debug("success\n");
+ nand_config = i;
+ return 0;
+ }
+ debug("failed\n");
+ }
+ return -1;
+ }
+
+ return nand_read_ecc(nand_configs[nand_config].page_size,
+ nand_configs[nand_config].ecc_strength,
+ nand_configs[nand_config].ecc_page_size,
+ nand_configs[nand_config].addr_cycles,
+ offs, size, dest, syndrome);
}
int nand_spl_load_image(uint32_t offs, unsigned int size, void *dest)
{
- void *current_dest;
- uint32_t count;
- uint32_t current_count;
- uint32_t ecc_errors = 0;
-
- memset(dest, 0x0, size); /* clean destination memory */
- for (current_dest = dest;
- current_dest < (dest + size);
- current_dest += CONFIG_NAND_SUNXI_SPL_ECC_PAGE_SIZE) {
- nand_read_page(offs, offs
- < CONFIG_NAND_SUNXI_SPL_SYNDROME_PARTITIONS_END,
- &ecc_errors);
- count = current_dest - dest;
-
- if (size - count > CONFIG_NAND_SUNXI_SPL_ECC_PAGE_SIZE)
- current_count = CONFIG_NAND_SUNXI_SPL_ECC_PAGE_SIZE;
- else
- current_count = size - count;
-
- memcpy(current_dest,
- temp_buf,
- current_count);
- offs += CONFIG_NAND_SUNXI_SPL_ECC_PAGE_SIZE;
+ const uint32_t boot_offsets[] = {
+ 0 * 1024 * 1024 + CONFIG_SYS_NAND_U_BOOT_OFFS,
+ 1 * 1024 * 1024 + CONFIG_SYS_NAND_U_BOOT_OFFS,
+ 2 * 1024 * 1024 + CONFIG_SYS_NAND_U_BOOT_OFFS,
+ 4 * 1024 * 1024 + CONFIG_SYS_NAND_U_BOOT_OFFS,
+ };
+ int i, syndrome;
+
+ if (CONFIG_SYS_NAND_U_BOOT_OFFS == CONFIG_SPL_PAD_TO)
+ syndrome = 1; /* u-boot-dtb.bin appended to SPL */
+ else
+ syndrome = 0; /* u-boot-dtb.bin on its own partition */
+
+ if (offs == CONFIG_SYS_NAND_U_BOOT_OFFS) {
+ for (i = 0; i < ARRAY_SIZE(boot_offsets); i++) {
+ if (nand_read_buffer(boot_offsets[i], size,
+ dest, syndrome) == 0)
+ return 0;
+ }
+ return -1;
}
- return ecc_errors ? -1 : 0;
+
+ return nand_read_buffer(offs, size, dest, syndrome);
}
-void nand_deselect(void) {}
+void nand_deselect(void)
+{
+ struct sunxi_ccm_reg *const ccm =
+ (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+
+ clrbits_le32(&ccm->ahb_gate0, (CLK_GATE_OPEN << AHB_GATE_OFFSET_NAND0));
+#ifdef CONFIG_MACH_SUN9I
+ clrbits_le32(&ccm->ahb_gate1, (1 << AHB_GATE_OFFSET_DMA));
+#else
+ clrbits_le32(&ccm->ahb_gate0, (1 << AHB_GATE_OFFSET_DMA));
+#endif
+ clrbits_le32(&ccm->nand0_clk_cfg, CCM_NAND_CTRL_ENABLE | AHB_DIV_1);
+}
OpenPOWER on IntegriCloud