From 845034e6b217d5ff73de03213ede9b17646a91b1 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Fri, 3 Oct 2014 19:21:04 +0900 Subject: [PATCH] mtd: denali: add Denali NAND driver for SPL The SPL-mode driver for Denali(Cadence) NAND Flash Memory Controller IP. This driver requires two CONFIG macros: - CONFIG_SPL_NAND_DENALI Define to enable this driver. - CONFIG_SYS_NAND_BAD_BLOCK_POS Specify bad block mark position in the oob space. Typically 0. Signed-off-by: Masahiro Yamada Cc: Chin Liang See Cc: Scott Wood --- drivers/mtd/nand/Kconfig | 10 ++ drivers/mtd/nand/Makefile | 1 + drivers/mtd/nand/denali_spl.c | 231 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 242 insertions(+) create mode 100644 drivers/mtd/nand/denali_spl.c diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 1d9bf48..75c2c06 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -29,4 +29,14 @@ config NAND_DENALI_SPARE_AREA_SKIP_BYTES endif +if SPL_BUILD + +config SPL_NAND_DENALI + bool "Support Denali NAND controller for SPL" + help + This is a small implementation of the Denali NAND controller + for use on SPL. + +endif + endmenu diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index f298f84..47eb34f 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -12,6 +12,7 @@ NORMAL_DRIVERS=y endif obj-$(CONFIG_SPL_NAND_AM33XX_BCH) += am335x_spl_bch.o +obj-$(CONFIG_SPL_NAND_DENALI) += denali_spl.o obj-$(CONFIG_SPL_NAND_DOCG4) += docg4_spl.o obj-$(CONFIG_SPL_NAND_SIMPLE) += nand_spl_simple.o obj-$(CONFIG_SPL_NAND_LOAD) += nand_spl_load.o diff --git a/drivers/mtd/nand/denali_spl.c b/drivers/mtd/nand/denali_spl.c new file mode 100644 index 0000000..65fdde8 --- /dev/null +++ b/drivers/mtd/nand/denali_spl.c @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2014 Panasonic Corporation + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include "denali.h" + +#define SPARE_ACCESS 0x41 +#define MAIN_ACCESS 0x42 +#define PIPELINE_ACCESS 0x2000 + +#define BANK(x) ((x) << 24) + +static void __iomem *denali_flash_mem = + (void __iomem *)CONFIG_SYS_NAND_DATA_BASE; +static void __iomem *denali_flash_reg = + (void __iomem *)CONFIG_SYS_NAND_REGS_BASE; + +static const int flash_bank; +static uint8_t page_buffer[NAND_MAX_PAGESIZE]; +static int page_size, oob_size, pages_per_block; + +static void index_addr(uint32_t address, uint32_t data) +{ + writel(address, denali_flash_mem + INDEX_CTRL_REG); + writel(data, denali_flash_mem + INDEX_DATA_REG); +} + +static int wait_for_irq(uint32_t irq_mask) +{ + unsigned long timeout = 1000000; + uint32_t intr_status; + + do { + intr_status = readl(denali_flash_reg + INTR_STATUS(flash_bank)); + + if (intr_status & INTR_STATUS__ECC_UNCOR_ERR) { + debug("Uncorrected ECC detected\n"); + return -EIO; + } + + if (intr_status & irq_mask) + break; + + udelay(1); + timeout--; + } while (timeout); + + if (!timeout) { + debug("Timeout with interrupt status %08x\n", intr_status); + return -EIO; + } + + return 0; +} + +static void read_data_from_flash_mem(uint8_t *buf, int len) +{ + int i; + uint32_t *buf32; + + /* transfer the data from the flash */ + buf32 = (uint32_t *)buf; + + /* + * Let's take care of unaligned access although it rarely happens. + * Avoid put_unaligned() for the normal use cases since it leads to + * a bit performance regression. + */ + if ((unsigned long)buf32 % 4) { + for (i = 0; i < len / 4; i++) + put_unaligned(readl(denali_flash_mem + INDEX_DATA_REG), + buf32++); + } else { + for (i = 0; i < len / 4; i++) + *buf32++ = readl(denali_flash_mem + INDEX_DATA_REG); + } + + if (len % 4) { + u32 tmp; + + tmp = cpu_to_le32(readl(denali_flash_mem + INDEX_DATA_REG)); + buf = (uint8_t *)buf32; + for (i = 0; i < len % 4; i++) { + *buf++ = tmp; + tmp >>= 8; + } + } +} + +int denali_send_pipeline_cmd(int page, int ecc_en, int access_type) +{ + uint32_t addr, cmd; + static uint32_t page_count = 1; + + writel(ecc_en, denali_flash_reg + ECC_ENABLE); + + /* clear all bits of intr_status. */ + writel(0xffff, denali_flash_reg + INTR_STATUS(flash_bank)); + + addr = BANK(flash_bank) | page; + + /* setup the acccess type */ + cmd = MODE_10 | addr; + index_addr(cmd, access_type); + + /* setup the pipeline command */ + index_addr(cmd, PIPELINE_ACCESS | page_count); + + cmd = MODE_01 | addr; + writel(cmd, denali_flash_mem + INDEX_CTRL_REG); + + return wait_for_irq(INTR_STATUS__LOAD_COMP); +} + +static int nand_read_oob(void *buf, int page) +{ + int ret; + + ret = denali_send_pipeline_cmd(page, 0, SPARE_ACCESS); + if (ret < 0) + return ret; + + read_data_from_flash_mem(buf, oob_size); + + return 0; +} + +static int nand_read_page(void *buf, int page) +{ + int ret; + + ret = denali_send_pipeline_cmd(page, 1, MAIN_ACCESS); + if (ret < 0) + return ret; + + read_data_from_flash_mem(buf, page_size); + + return 0; +} + +static int nand_block_isbad(int block) +{ + int ret; + + ret = nand_read_oob(page_buffer, block * pages_per_block); + if (ret < 0) + return ret; + + return page_buffer[CONFIG_SYS_NAND_BAD_BLOCK_POS] != 0xff; +} + +/* nand_init() - initialize data to make nand usable by SPL */ +void nand_init(void) +{ + /* access to main area */ + writel(0, denali_flash_reg + TRANSFER_SPARE_REG); + + /* + * These registers are expected to be already set by the hardware + * or earlier boot code. So we read these values out. + */ + page_size = readl(denali_flash_reg + DEVICE_MAIN_AREA_SIZE); + oob_size = readl(denali_flash_reg + DEVICE_SPARE_AREA_SIZE); + pages_per_block = readl(denali_flash_reg + PAGES_PER_BLOCK); +} + +int nand_spl_load_image(uint32_t offs, unsigned int size, void *dst) +{ + int block, page, column, readlen; + int ret; + int force_bad_block_check = 1; + + page = offs / page_size; + column = offs % page_size; + + block = page / pages_per_block; + page = page % pages_per_block; + + while (size) { + if (force_bad_block_check || page == 0) { + ret = nand_block_isbad(block); + if (ret < 0) + return ret; + + if (ret) { + block++; + continue; + } + } + + force_bad_block_check = 0; + + if (unlikely(column || size < page_size)) { + /* Partial page read */ + ret = nand_read_page(page_buffer, + block * pages_per_block + page); + if (ret < 0) + return ret; + + readlen = min(page_size - column, size); + memcpy(dst, page_buffer, readlen); + + column = 0; + } else { + ret = nand_read_page(dst, + block * pages_per_block + page); + if (ret < 0) + return ret; + + readlen = page_size; + } + + size -= readlen; + dst += readlen; + page++; + if (page == pages_per_block) { + block++; + page = 0; + } + } + + return 0; +} + +void nand_deselect(void) {}