| /* |
| * Copyright (c) 2017, ARM Limited and Contributors. All rights reserved. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| #include <arch_helpers.h> |
| #include <debug.h> |
| #include <io/io_block.h> |
| #include <mmio.h> |
| #include <platform_def.h> |
| #include <sys/types.h> |
| #include <utils_def.h> |
| |
| #include "uniphier.h" |
| |
| #define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) |
| |
| #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; |
| |
| /* save the result for future re-use */ |
| 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 = { |
| .buffer = { |
| .offset = UNIPHIER_BLOCK_BUF_BASE, |
| .length = UNIPHIER_BLOCK_BUF_SIZE, |
| }, |
| .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->host_base = 0x68000000; |
| nand->reg_base = 0x68100000; |
| |
| 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; |
| } |
| |
| int uniphier_nand_init(uintptr_t *block_dev_spec) |
| { |
| int ret; |
| |
| ret = uniphier_nand_hw_init(&uniphier_nand); |
| if (ret) |
| return ret; |
| |
| uniphier_nand_dev_spec.block_size = uniphier_nand.page_size; |
| |
| *block_dev_spec = (uintptr_t)&uniphier_nand_dev_spec; |
| |
| return 0; |
| } |