summaryrefslogtreecommitdiffstats
path: root/plat/socionext/uniphier/uniphier_nand.c
diff options
context:
space:
mode:
Diffstat (limited to 'plat/socionext/uniphier/uniphier_nand.c')
-rw-r--r--plat/socionext/uniphier/uniphier_nand.c283
1 files changed, 283 insertions, 0 deletions
diff --git a/plat/socionext/uniphier/uniphier_nand.c b/plat/socionext/uniphier/uniphier_nand.c
new file mode 100644
index 0000000..71cb96c
--- /dev/null
+++ b/plat/socionext/uniphier/uniphier_nand.c
@@ -0,0 +1,283 @@
+/*
+ * Copyright (c) 2017-2020, ARM Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <stdint.h>
+
+#include <platform_def.h>
+
+#include <arch_helpers.h>
+#include <common/debug.h>
+#include <drivers/io/io_block.h>
+#include <lib/mmio.h>
+#include <lib/utils_def.h>
+
+#include "uniphier.h"
+
+#define NAND_CMD_READ0 0
+#define NAND_CMD_READSTART 0x30
+
+#define DENALI_ECC_ENABLE 0x0e0
+#define DENALI_PAGES_PER_BLOCK 0x150
+#define DENALI_DEVICE_MAIN_AREA_SIZE 0x170
+#define DENALI_DEVICE_SPARE_AREA_SIZE 0x180
+#define DENALI_TWO_ROW_ADDR_CYCLES 0x190
+#define DENALI_INTR_STATUS0 0x410
+#define DENALI_INTR_ECC_UNCOR_ERR BIT(1)
+#define DENALI_INTR_DMA_CMD_COMP BIT(2)
+#define DENALI_INTR_INT_ACT BIT(12)
+
+#define DENALI_DMA_ENABLE 0x700
+
+#define DENALI_HOST_ADDR 0x00
+#define DENALI_HOST_DATA 0x10
+
+#define DENALI_MAP01 (1 << 26)
+#define DENALI_MAP10 (2 << 26)
+#define DENALI_MAP11 (3 << 26)
+
+#define DENALI_MAP11_CMD ((DENALI_MAP11) | 0)
+#define DENALI_MAP11_ADDR ((DENALI_MAP11) | 1)
+#define DENALI_MAP11_DATA ((DENALI_MAP11) | 2)
+
+#define DENALI_ACCESS_DEFAULT_AREA 0x42
+
+#define UNIPHIER_NAND_BBT_UNKNOWN 0xff
+
+struct uniphier_nand {
+ uintptr_t host_base;
+ uintptr_t reg_base;
+ int pages_per_block;
+ int page_size;
+ int two_row_addr_cycles;
+ uint8_t bbt[16];
+};
+
+struct uniphier_nand uniphier_nand;
+
+static void uniphier_nand_host_write(struct uniphier_nand *nand,
+ uint32_t addr, uint32_t data)
+{
+ mmio_write_32(nand->host_base + DENALI_HOST_ADDR, addr);
+ mmio_write_32(nand->host_base + DENALI_HOST_DATA, data);
+}
+
+static uint32_t uniphier_nand_host_read(struct uniphier_nand *nand,
+ uint32_t addr)
+{
+ mmio_write_32(nand->host_base + DENALI_HOST_ADDR, addr);
+ return mmio_read_32(nand->host_base + DENALI_HOST_DATA);
+}
+
+static int uniphier_nand_block_isbad(struct uniphier_nand *nand, int block)
+{
+ int page = nand->pages_per_block * block;
+ int column = nand->page_size;
+ uint8_t bbm;
+ uint32_t status;
+ int is_bad;
+
+ /* use cache if available */
+ if (block < ARRAY_SIZE(nand->bbt) &&
+ nand->bbt[block] != UNIPHIER_NAND_BBT_UNKNOWN)
+ return nand->bbt[block];
+
+ mmio_write_32(nand->reg_base + DENALI_ECC_ENABLE, 0);
+
+ mmio_write_32(nand->reg_base + DENALI_INTR_STATUS0, -1);
+
+ uniphier_nand_host_write(nand, DENALI_MAP11_CMD, NAND_CMD_READ0);
+ uniphier_nand_host_write(nand, DENALI_MAP11_ADDR, column & 0xff);
+ uniphier_nand_host_write(nand, DENALI_MAP11_ADDR, (column >> 8) & 0xff);
+ uniphier_nand_host_write(nand, DENALI_MAP11_ADDR, page & 0xff);
+ uniphier_nand_host_write(nand, DENALI_MAP11_ADDR, (page >> 8) & 0xff);
+ if (!nand->two_row_addr_cycles)
+ uniphier_nand_host_write(nand, DENALI_MAP11_ADDR,
+ (page >> 16) & 0xff);
+ uniphier_nand_host_write(nand, DENALI_MAP11_CMD, NAND_CMD_READSTART);
+
+ do {
+ status = mmio_read_32(nand->reg_base + DENALI_INTR_STATUS0);
+ } while (!(status & DENALI_INTR_INT_ACT));
+
+ bbm = uniphier_nand_host_read(nand, DENALI_MAP11_DATA);
+
+ is_bad = bbm != 0xff;
+
+ /* if possible, save the result for future re-use */
+ if (block < ARRAY_SIZE(nand->bbt))
+ nand->bbt[block] = is_bad;
+
+ if (is_bad)
+ WARN("found bad block at %d. skip.\n", block);
+
+ return is_bad;
+}
+
+static int uniphier_nand_read_pages(struct uniphier_nand *nand, uintptr_t buf,
+ int page_start, int page_count)
+{
+ uint32_t status;
+
+ mmio_write_32(nand->reg_base + DENALI_ECC_ENABLE, 1);
+ mmio_write_32(nand->reg_base + DENALI_DMA_ENABLE, 1);
+
+ mmio_write_32(nand->reg_base + DENALI_INTR_STATUS0, -1);
+
+ /* use Data DMA (64bit) */
+ mmio_write_32(nand->host_base + DENALI_HOST_ADDR,
+ DENALI_MAP10 | page_start);
+
+ /*
+ * 1. setup transfer type, interrupt when complete,
+ * burst len = 64 bytes, the number of pages
+ */
+ mmio_write_32(nand->host_base + DENALI_HOST_DATA,
+ 0x01002000 | (64 << 16) | page_count);
+
+ /* 2. set memory low address */
+ mmio_write_32(nand->host_base + DENALI_HOST_DATA, buf);
+
+ /* 3. set memory high address */
+ mmio_write_32(nand->host_base + DENALI_HOST_DATA, buf >> 32);
+
+ do {
+ status = mmio_read_32(nand->reg_base + DENALI_INTR_STATUS0);
+ } while (!(status & DENALI_INTR_DMA_CMD_COMP));
+
+ mmio_write_32(nand->reg_base + DENALI_DMA_ENABLE, 0);
+
+ if (status & DENALI_INTR_ECC_UNCOR_ERR) {
+ ERROR("uncorrectable error in page range %d-%d",
+ page_start, page_start + page_count - 1);
+ return -EBADMSG;
+ }
+
+ return 0;
+}
+
+static size_t __uniphier_nand_read(struct uniphier_nand *nand, int lba,
+ uintptr_t buf, size_t size)
+{
+ int pages_per_block = nand->pages_per_block;
+ int page_size = nand->page_size;
+ int blocks_to_skip = lba / pages_per_block;
+ int pages_to_read = div_round_up(size, page_size);
+ int page = lba % pages_per_block;
+ int block = 0;
+ uintptr_t p = buf;
+ int page_count, ret;
+
+ while (blocks_to_skip) {
+ ret = uniphier_nand_block_isbad(nand, block);
+ if (ret < 0)
+ goto out;
+
+ if (!ret)
+ blocks_to_skip--;
+
+ block++;
+ }
+
+ while (pages_to_read) {
+ ret = uniphier_nand_block_isbad(nand, block);
+ if (ret < 0)
+ goto out;
+
+ if (ret) {
+ block++;
+ continue;
+ }
+
+ page_count = MIN(pages_per_block - page, pages_to_read);
+
+ ret = uniphier_nand_read_pages(nand, p,
+ block * pages_per_block + page,
+ page_count);
+ if (ret)
+ goto out;
+
+ block++;
+ page = 0;
+ p += page_size * page_count;
+ pages_to_read -= page_count;
+ }
+
+out:
+ /* number of read bytes */
+ return MIN(size, p - buf);
+}
+
+static size_t uniphier_nand_read(int lba, uintptr_t buf, size_t size)
+{
+ size_t count;
+
+ inv_dcache_range(buf, size);
+
+ count = __uniphier_nand_read(&uniphier_nand, lba, buf, size);
+
+ inv_dcache_range(buf, size);
+
+ return count;
+}
+
+static struct io_block_dev_spec uniphier_nand_dev_spec = {
+ .ops = {
+ .read = uniphier_nand_read,
+ },
+ /* fill .block_size at run-time */
+};
+
+static int uniphier_nand_hw_init(struct uniphier_nand *nand)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(nand->bbt); i++)
+ nand->bbt[i] = UNIPHIER_NAND_BBT_UNKNOWN;
+
+ nand->reg_base = nand->host_base + 0x100000;
+
+ nand->pages_per_block =
+ mmio_read_32(nand->reg_base + DENALI_PAGES_PER_BLOCK);
+
+ nand->page_size =
+ mmio_read_32(nand->reg_base + DENALI_DEVICE_MAIN_AREA_SIZE);
+
+ if (mmio_read_32(nand->reg_base + DENALI_TWO_ROW_ADDR_CYCLES) & BIT(0))
+ nand->two_row_addr_cycles = 1;
+
+ uniphier_nand_host_write(nand, DENALI_MAP10,
+ DENALI_ACCESS_DEFAULT_AREA);
+
+ return 0;
+}
+
+static const uintptr_t uniphier_nand_base[] = {
+ [UNIPHIER_SOC_LD11] = 0x68000000,
+ [UNIPHIER_SOC_LD20] = 0x68000000,
+ [UNIPHIER_SOC_PXS3] = 0x68000000,
+};
+
+int uniphier_nand_init(unsigned int soc,
+ struct io_block_dev_spec **block_dev_spec)
+{
+ int ret;
+
+ assert(soc < ARRAY_SIZE(uniphier_nand_base));
+ uniphier_nand.host_base = uniphier_nand_base[soc];
+ if (!uniphier_nand.host_base)
+ return -ENOTSUP;
+
+ ret = uniphier_nand_hw_init(&uniphier_nand);
+ if (ret)
+ return ret;
+
+ uniphier_nand_dev_spec.block_size = uniphier_nand.page_size;
+
+ *block_dev_spec = &uniphier_nand_dev_spec;
+
+ return 0;
+}