aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorLionel Debieve <lionel.debieve@st.com>2019-09-09 20:13:34 +0200
committerLionel Debieve <lionel.debieve@st.com>2020-01-20 11:32:59 +0100
commitb114abb609d42a5e237a35f6e27852c9aa9ab963 (patch)
tree53856e8f52dede41db4301d30deb036fbcbc8f31 /drivers
parent45cc606ea7bbf8931be715a51c1819e7dd22e936 (diff)
downloadplatform_external_arm-trusted-firmware-b114abb609d42a5e237a35f6e27852c9aa9ab963.tar.gz
platform_external_arm-trusted-firmware-b114abb609d42a5e237a35f6e27852c9aa9ab963.tar.bz2
platform_external_arm-trusted-firmware-b114abb609d42a5e237a35f6e27852c9aa9ab963.zip
Add raw NAND framework
The raw NAND framework supports SLC NAND devices. It introduces a new high level interface (io_mtd) that defines operations a driver can register to the NAND framework. This interface will fill in the io_mtd device specification: - device_size - erase_size that could be used by the io_storage interface. NAND core source file integrates the standard read loop that performs NAND device read operations using a skip bad block strategy. A platform buffer must be defined in case of unaligned data. This buffer must fit to the maximum device page size defined by PLATFORM_MTD_MAX_PAGE_SIZE. The raw_nand.c source file embeds the specific NAND operations to read data. The read command is a raw page read without any ECC correction. This can be overridden by a low level driver. No generic support for write or erase command or software ECC correction. NAND ONFI detection is available and can be enabled using NAND_ONFI_DETECT=1. For non-ONFI NAND management, platform can define required information. Change-Id: Id80e9864456cf47f02b74938cf25d99261da8e82 Signed-off-by: Lionel Debieve <lionel.debieve@st.com> Signed-off-by: Christophe Kerello <christophe.kerello@st.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/io/io_mtd.c248
-rw-r--r--drivers/mtd/nand/core.c118
-rw-r--r--drivers/mtd/nand/raw_nand.c446
3 files changed, 812 insertions, 0 deletions
diff --git a/drivers/io/io_mtd.c b/drivers/io/io_mtd.c
new file mode 100644
index 000000000..7575fa250
--- /dev/null
+++ b/drivers/io/io_mtd.c
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2019, ARM Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+
+#include <platform_def.h>
+
+#include <common/debug.h>
+#include <drivers/io/io_driver.h>
+#include <drivers/io/io_mtd.h>
+#include <lib/utils.h>
+
+typedef struct {
+ io_mtd_dev_spec_t *dev_spec;
+ uintptr_t base;
+ unsigned long long offset; /* Offset in bytes */
+ unsigned long long size; /* Size of device in bytes */
+} mtd_dev_state_t;
+
+io_type_t device_type_mtd(void);
+
+static int mtd_open(io_dev_info_t *dev_info, const uintptr_t spec,
+ io_entity_t *entity);
+static int mtd_seek(io_entity_t *entity, int mode, signed long long offset);
+static int mtd_read(io_entity_t *entity, uintptr_t buffer, size_t length,
+ size_t *length_read);
+static int mtd_close(io_entity_t *entity);
+static int mtd_dev_open(const uintptr_t dev_spec, io_dev_info_t **dev_info);
+static int mtd_dev_close(io_dev_info_t *dev_info);
+
+static const io_dev_connector_t mtd_dev_connector = {
+ .dev_open = mtd_dev_open
+};
+
+static const io_dev_funcs_t mtd_dev_funcs = {
+ .type = device_type_mtd,
+ .open = mtd_open,
+ .seek = mtd_seek,
+ .read = mtd_read,
+ .close = mtd_close,
+ .dev_close = mtd_dev_close,
+};
+
+static mtd_dev_state_t state_pool[MAX_IO_MTD_DEVICES];
+static io_dev_info_t dev_info_pool[MAX_IO_MTD_DEVICES];
+
+io_type_t device_type_mtd(void)
+{
+ return IO_TYPE_MTD;
+}
+
+/* Locate a MTD state in the pool, specified by address */
+static int find_first_mtd_state(const io_mtd_dev_spec_t *dev_spec,
+ unsigned int *index_out)
+{
+ unsigned int index;
+ int result = -ENOENT;
+
+ for (index = 0U; index < MAX_IO_MTD_DEVICES; index++) {
+ /* dev_spec is used as identifier since it's unique */
+ if (state_pool[index].dev_spec == dev_spec) {
+ result = 0;
+ *index_out = index;
+ break;
+ }
+ }
+
+ return result;
+}
+
+/* Allocate a device info from the pool */
+static int allocate_dev_info(io_dev_info_t **dev_info)
+{
+ unsigned int index = 0U;
+ int result;
+
+ result = find_first_mtd_state(NULL, &index);
+ if (result != 0) {
+ return -ENOMEM;
+ }
+
+ dev_info_pool[index].funcs = &mtd_dev_funcs;
+ dev_info_pool[index].info = (uintptr_t)&state_pool[index];
+ *dev_info = &dev_info_pool[index];
+
+ return 0;
+}
+
+/* Release a device info from the pool */
+static int free_dev_info(io_dev_info_t *dev_info)
+{
+ int result;
+ unsigned int index = 0U;
+ mtd_dev_state_t *state;
+
+ state = (mtd_dev_state_t *)dev_info->info;
+ result = find_first_mtd_state(state->dev_spec, &index);
+ if (result != 0) {
+ return result;
+ }
+
+ zeromem(state, sizeof(mtd_dev_state_t));
+ zeromem(dev_info, sizeof(io_dev_info_t));
+
+ return 0;
+}
+
+static int mtd_open(io_dev_info_t *dev_info, const uintptr_t spec,
+ io_entity_t *entity)
+{
+ mtd_dev_state_t *cur;
+
+ assert((dev_info->info != 0UL) && (entity->info == 0UL));
+
+ cur = (mtd_dev_state_t *)dev_info->info;
+ entity->info = (uintptr_t)cur;
+ cur->offset = 0U;
+
+ return 0;
+}
+
+/* Seek to a specific position using offset */
+static int mtd_seek(io_entity_t *entity, int mode, signed long long offset)
+{
+ mtd_dev_state_t *cur;
+
+ assert((entity->info != (uintptr_t)NULL) && (offset >= 0));
+
+ cur = (mtd_dev_state_t *)entity->info;
+
+ switch (mode) {
+ case IO_SEEK_SET:
+ if ((offset >= 0) &&
+ ((unsigned long long)offset >= cur->size)) {
+ return -EINVAL;
+ }
+
+ cur->offset = offset;
+ break;
+ case IO_SEEK_CUR:
+ if (((cur->offset + (unsigned long long)offset) >=
+ cur->size) ||
+ ((cur->offset + (unsigned long long)offset) <
+ cur->offset)) {
+ return -EINVAL;
+ }
+
+ cur->offset += (unsigned long long)offset;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mtd_read(io_entity_t *entity, uintptr_t buffer, size_t length,
+ size_t *out_length)
+{
+ mtd_dev_state_t *cur;
+ io_mtd_ops_t *ops;
+ int ret;
+
+ assert(entity->info != (uintptr_t)NULL);
+ assert((length > 0U) && (buffer != (uintptr_t)NULL));
+
+ cur = (mtd_dev_state_t *)entity->info;
+ ops = &cur->dev_spec->ops;
+ assert(ops->read != NULL);
+
+ VERBOSE("Read at %llx into %lx, length %zi\n",
+ cur->offset, buffer, length);
+ if ((cur->offset + length) > cur->dev_spec->device_size) {
+ return -EINVAL;
+ }
+
+ ret = ops->read(cur->offset, buffer, length, out_length);
+ if (ret < 0) {
+ return ret;
+ }
+
+ assert(*out_length == length);
+ cur->offset += *out_length;
+
+ return 0;
+}
+
+static int mtd_close(io_entity_t *entity)
+{
+ entity->info = (uintptr_t)NULL;
+
+ return 0;
+}
+
+static int mtd_dev_open(const uintptr_t dev_spec, io_dev_info_t **dev_info)
+{
+ mtd_dev_state_t *cur;
+ io_dev_info_t *info;
+ io_mtd_ops_t *ops;
+ int result;
+
+ result = allocate_dev_info(&info);
+ if (result != 0) {
+ return -ENOENT;
+ }
+
+ cur = (mtd_dev_state_t *)info->info;
+ cur->dev_spec = (io_mtd_dev_spec_t *)dev_spec;
+ *dev_info = info;
+ ops = &(cur->dev_spec->ops);
+ if (ops->init != NULL) {
+ result = ops->init(&cur->dev_spec->device_size,
+ &cur->dev_spec->erase_size);
+ }
+
+ if (result == 0) {
+ cur->size = cur->dev_spec->device_size;
+ } else {
+ cur->size = 0ULL;
+ }
+
+ return result;
+}
+
+static int mtd_dev_close(io_dev_info_t *dev_info)
+{
+ return free_dev_info(dev_info);
+}
+
+/* Exported functions */
+
+/* Register the MTD driver in the IO abstraction */
+int register_io_dev_mtd(const io_dev_connector_t **dev_con)
+{
+ int result;
+
+ result = io_register_device(&dev_info_pool[0]);
+ if (result == 0) {
+ *dev_con = &mtd_dev_connector;
+ }
+
+ return result;
+}
diff --git a/drivers/mtd/nand/core.c b/drivers/mtd/nand/core.c
new file mode 100644
index 000000000..44b001e35
--- /dev/null
+++ b/drivers/mtd/nand/core.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2019, STMicroelectronics - All Rights Reserved
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stddef.h>
+
+#include <platform_def.h>
+
+#include <common/debug.h>
+#include <drivers/delay_timer.h>
+#include <drivers/nand.h>
+#include <lib/utils.h>
+
+/*
+ * Define a single nand_device used by specific NAND frameworks.
+ */
+static struct nand_device nand_dev;
+static uint8_t scratch_buff[PLATFORM_MTD_MAX_PAGE_SIZE];
+
+int nand_read(unsigned int offset, uintptr_t buffer, size_t length,
+ size_t *length_read)
+{
+ unsigned int block = offset / nand_dev.block_size;
+ unsigned int end_block = (offset + length - 1U) / nand_dev.block_size;
+ unsigned int page_start =
+ (offset % nand_dev.block_size) / nand_dev.page_size;
+ unsigned int nb_pages = nand_dev.block_size / nand_dev.page_size;
+ unsigned int start_offset = offset % nand_dev.page_size;
+ unsigned int page;
+ unsigned int bytes_read;
+ int is_bad;
+ int ret;
+
+ VERBOSE("Block %u - %u, page_start %u, nb %u, length %zu, offset %u\n",
+ block, end_block, page_start, nb_pages, length, offset);
+
+ *length_read = 0UL;
+
+ if (((start_offset != 0U) || (length % nand_dev.page_size) != 0U) &&
+ (sizeof(scratch_buff) < nand_dev.page_size)) {
+ return -EINVAL;
+ }
+
+ while (block <= end_block) {
+ is_bad = nand_dev.mtd_block_is_bad(block);
+ if (is_bad < 0) {
+ return is_bad;
+ }
+
+ if (is_bad == 1) {
+ /* Skip the block */
+ uint32_t max_block =
+ nand_dev.size / nand_dev.block_size;
+
+ block++;
+ end_block++;
+ if ((block < max_block) && (end_block < max_block)) {
+ continue;
+ }
+
+ return -EIO;
+ }
+
+ for (page = page_start; page < nb_pages; page++) {
+ if ((start_offset != 0U) ||
+ (length < nand_dev.page_size)) {
+ ret = nand_dev.mtd_read_page(
+ &nand_dev,
+ (block * nb_pages) + page,
+ (uintptr_t)scratch_buff);
+ if (ret != 0) {
+ return ret;
+ }
+
+ bytes_read = MIN((size_t)(nand_dev.page_size -
+ start_offset),
+ length);
+
+ memcpy((uint8_t *)buffer,
+ scratch_buff + start_offset,
+ bytes_read);
+
+ start_offset = 0U;
+ } else {
+ ret = nand_dev.mtd_read_page(&nand_dev,
+ (block * nb_pages) + page,
+ buffer);
+ if (ret != 0) {
+ return ret;
+ }
+
+ bytes_read = nand_dev.page_size;
+ }
+
+ length -= bytes_read;
+ buffer += bytes_read;
+ *length_read += bytes_read;
+
+ if (length == 0U) {
+ break;
+ }
+ }
+
+ page_start = 0U;
+ block++;
+ }
+
+ return 0;
+}
+
+struct nand_device *get_nand_device(void)
+{
+ return &nand_dev;
+}
diff --git a/drivers/mtd/nand/raw_nand.c b/drivers/mtd/nand/raw_nand.c
new file mode 100644
index 000000000..48131fcb2
--- /dev/null
+++ b/drivers/mtd/nand/raw_nand.c
@@ -0,0 +1,446 @@
+/*
+ * Copyright (c) 2019, STMicroelectronics - All Rights Reserved
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stddef.h>
+
+#include <platform_def.h>
+
+#include <common/debug.h>
+#include <drivers/delay_timer.h>
+#include <drivers/raw_nand.h>
+#include <lib/utils.h>
+
+#define ONFI_SIGNATURE_ADDR 0x20U
+
+/* CRC calculation */
+#define CRC_POLYNOM 0x8005U
+#define CRC_INIT_VALUE 0x4F4EU
+
+/* Status register */
+#define NAND_STATUS_READY BIT(6)
+
+#define SZ_128M 0x08000000U
+#define SZ_512 0x200U
+
+static struct rawnand_device rawnand_dev;
+
+#pragma weak plat_get_raw_nand_data
+int plat_get_raw_nand_data(struct rawnand_device *device)
+{
+ return 0;
+}
+
+static int nand_send_cmd(uint8_t cmd, unsigned int tim)
+{
+ struct nand_req req;
+
+ zeromem(&req, sizeof(struct nand_req));
+ req.nand = rawnand_dev.nand_dev;
+ req.type = NAND_REQ_CMD | cmd;
+ req.inst_delay = tim;
+
+ return rawnand_dev.ops->exec(&req);
+}
+
+static int nand_send_addr(uint8_t addr, unsigned int tim)
+{
+ struct nand_req req;
+
+ zeromem(&req, sizeof(struct nand_req));
+ req.nand = rawnand_dev.nand_dev;
+ req.type = NAND_REQ_ADDR;
+ req.addr = &addr;
+ req.inst_delay = tim;
+
+ return rawnand_dev.ops->exec(&req);
+}
+
+static int nand_send_wait(unsigned int delay, unsigned int tim)
+{
+ struct nand_req req;
+
+ zeromem(&req, sizeof(struct nand_req));
+ req.nand = rawnand_dev.nand_dev;
+ req.type = NAND_REQ_WAIT;
+ req.inst_delay = tim;
+ req.delay_ms = delay;
+
+ return rawnand_dev.ops->exec(&req);
+}
+
+
+static int nand_read_data(uint8_t *data, unsigned int length, bool use_8bit)
+{
+ struct nand_req req;
+
+ zeromem(&req, sizeof(struct nand_req));
+ req.nand = rawnand_dev.nand_dev;
+ req.type = NAND_REQ_DATAIN | (use_8bit ? NAND_REQ_BUS_WIDTH_8 : 0U);
+ req.addr = data;
+ req.length = length;
+
+ return rawnand_dev.ops->exec(&req);
+}
+
+int nand_change_read_column_cmd(unsigned int offset, uintptr_t buffer,
+ unsigned int len)
+{
+ int ret;
+ uint8_t addr[2];
+ unsigned int i;
+
+ ret = nand_send_cmd(NAND_CMD_CHANGE_1ST, 0U);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if (rawnand_dev.nand_dev->buswidth == NAND_BUS_WIDTH_16) {
+ offset /= 2U;
+ }
+
+ addr[0] = offset;
+ addr[1] = offset >> 8;
+
+ for (i = 0; i < 2U; i++) {
+ ret = nand_send_addr(addr[i], 0U);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ ret = nand_send_cmd(NAND_CMD_CHANGE_2ND, NAND_TCCS_MIN);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return nand_read_data((uint8_t *)buffer, len, false);
+}
+
+int nand_read_page_cmd(unsigned int page, unsigned int offset,
+ uintptr_t buffer, unsigned int len)
+{
+ uint8_t addr[5];
+ uint8_t i = 0U;
+ uint8_t j;
+ int ret;
+
+ VERBOSE(">%s page %u offset %u buffer 0x%lx\n", __func__, page, offset,
+ buffer);
+
+ if (rawnand_dev.nand_dev->buswidth == NAND_BUS_WIDTH_16) {
+ offset /= 2U;
+ }
+
+ addr[i++] = offset;
+ addr[i++] = offset >> 8;
+
+ addr[i++] = page;
+ addr[i++] = page >> 8;
+ if (rawnand_dev.nand_dev->size > SZ_128M) {
+ addr[i++] = page >> 16;
+ }
+
+ ret = nand_send_cmd(NAND_CMD_READ_1ST, 0U);
+ if (ret != 0) {
+ return ret;
+ }
+
+ for (j = 0U; j < i; j++) {
+ ret = nand_send_addr(addr[j], 0U);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ ret = nand_send_cmd(NAND_CMD_READ_2ND, NAND_TWB_MAX);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = nand_send_wait(PSEC_TO_MSEC(NAND_TR_MAX), NAND_TRR_MIN);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if (buffer != 0U) {
+ ret = nand_read_data((uint8_t *)buffer, len, false);
+ }
+
+ return ret;
+}
+
+static int nand_status(uint8_t *status)
+{
+ int ret;
+
+ ret = nand_send_cmd(NAND_CMD_STATUS, NAND_TWHR_MIN);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if (status != NULL) {
+ ret = nand_read_data(status, 1U, true);
+ }
+
+ return ret;
+}
+
+int nand_wait_ready(unsigned long delay)
+{
+ uint8_t status;
+ int ret;
+ uint64_t timeout;
+
+ /* Wait before reading status */
+ udelay(1);
+
+ ret = nand_status(NULL);
+ if (ret != 0) {
+ return ret;
+ }
+
+ timeout = timeout_init_us(delay);
+ while (!timeout_elapsed(timeout)) {
+ ret = nand_read_data(&status, 1U, true);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if ((status & NAND_STATUS_READY) != 0U) {
+ return nand_send_cmd(NAND_CMD_READ_1ST, 0U);
+ }
+
+ udelay(10);
+ }
+
+ return -ETIMEDOUT;
+}
+
+#if NAND_ONFI_DETECT
+static uint16_t nand_check_crc(uint16_t crc, uint8_t *data_in,
+ unsigned int data_len)
+{
+ uint32_t i;
+ uint32_t j;
+ uint32_t bit;
+
+ for (i = 0U; i < data_len; i++) {
+ uint8_t cur_param = *data_in++;
+
+ for (j = BIT(7); j != 0U; j >>= 1) {
+ bit = crc & BIT(15);
+ crc <<= 1;
+
+ if ((cur_param & j) != 0U) {
+ bit ^= BIT(15);
+ }
+
+ if (bit != 0U) {
+ crc ^= CRC_POLYNOM;
+ }
+ }
+
+ crc &= GENMASK(15, 0);
+ }
+
+ return crc;
+}
+
+static int nand_read_id(uint8_t addr, uint8_t *id, unsigned int size)
+{
+ int ret;
+
+ ret = nand_send_cmd(NAND_CMD_READID, 0U);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = nand_send_addr(addr, NAND_TWHR_MIN);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return nand_read_data(id, size, true);
+}
+
+static int nand_reset(void)
+{
+ int ret;
+
+ ret = nand_send_cmd(NAND_CMD_RESET, NAND_TWB_MAX);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return nand_send_wait(PSEC_TO_MSEC(NAND_TRST_MAX), 0U);
+}
+
+static int nand_read_param_page(void)
+{
+ struct nand_param_page page;
+ uint8_t addr = 0U;
+ int ret;
+
+ ret = nand_send_cmd(NAND_CMD_READ_PARAM_PAGE, 0U);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = nand_send_addr(addr, NAND_TWB_MAX);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = nand_send_wait(PSEC_TO_MSEC(NAND_TR_MAX), NAND_TRR_MIN);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = nand_read_data((uint8_t *)&page, sizeof(page), true);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if (strncmp((char *)&page.page_sig, "ONFI", 4) != 0) {
+ WARN("Error ONFI detection\n");
+ return -EINVAL;
+ }
+
+ if (nand_check_crc(CRC_INIT_VALUE, (uint8_t *)&page, 254U) !=
+ page.crc16) {
+ WARN("Error reading param\n");
+ return -EINVAL;
+ }
+
+ if ((page.features & ONFI_FEAT_BUS_WIDTH_16) != 0U) {
+ rawnand_dev.nand_dev->buswidth = NAND_BUS_WIDTH_16;
+ } else {
+ rawnand_dev.nand_dev->buswidth = NAND_BUS_WIDTH_8;
+ }
+
+ rawnand_dev.nand_dev->block_size = page.num_pages_per_blk *
+ page.bytes_per_page;
+ rawnand_dev.nand_dev->page_size = page.bytes_per_page;
+ rawnand_dev.nand_dev->size = page.num_pages_per_blk *
+ page.bytes_per_page *
+ page.num_blk_in_lun * page.num_lun;
+
+ if (page.nb_ecc_bits != GENMASK_32(7, 0)) {
+ rawnand_dev.nand_dev->ecc.max_bit_corr = page.nb_ecc_bits;
+ rawnand_dev.nand_dev->ecc.size = SZ_512;
+ }
+
+ VERBOSE("Page size %u, block_size %u, Size %llu, ecc %u, buswidth %u\n",
+ rawnand_dev.nand_dev->page_size,
+ rawnand_dev.nand_dev->block_size, rawnand_dev.nand_dev->size,
+ rawnand_dev.nand_dev->ecc.max_bit_corr,
+ rawnand_dev.nand_dev->buswidth);
+
+ return 0;
+}
+
+static int detect_onfi(void)
+{
+ int ret;
+ char id[4];
+
+ ret = nand_reset();
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = nand_read_id(ONFI_SIGNATURE_ADDR, (uint8_t *)id, sizeof(id));
+ if (ret != 0) {
+ return ret;
+ }
+
+ if (strncmp(id, "ONFI", sizeof(id)) != 0) {
+ WARN("NAND Non ONFI detected\n");
+ return -ENODEV;
+ }
+
+ return nand_read_param_page();
+}
+#endif
+
+static int nand_mtd_block_is_bad(unsigned int block)
+{
+ unsigned int nbpages_per_block = rawnand_dev.nand_dev->block_size /
+ rawnand_dev.nand_dev->page_size;
+ uint8_t bbm_marker[2];
+ uint8_t page;
+ int ret;
+
+ for (page = 0U; page < 2U; page++) {
+ ret = nand_read_page_cmd(block * nbpages_per_block,
+ rawnand_dev.nand_dev->page_size,
+ (uintptr_t)bbm_marker,
+ sizeof(bbm_marker));
+ if (ret != 0) {
+ return ret;
+ }
+
+ if ((bbm_marker[0] != GENMASK_32(7, 0)) ||
+ (bbm_marker[1] != GENMASK_32(7, 0))) {
+ WARN("Block %u is bad\n", block);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int nand_mtd_read_page_raw(struct nand_device *nand, unsigned int page,
+ uintptr_t buffer)
+{
+ return nand_read_page_cmd(page, 0U, buffer,
+ rawnand_dev.nand_dev->page_size);
+}
+
+void nand_raw_ctrl_init(const struct nand_ctrl_ops *ops)
+{
+ rawnand_dev.ops = ops;
+}
+
+int nand_raw_init(unsigned long long *size, unsigned int *erase_size)
+{
+ rawnand_dev.nand_dev = get_nand_device();
+ if (rawnand_dev.nand_dev == NULL) {
+ return -EINVAL;
+ }
+
+ rawnand_dev.nand_dev->mtd_block_is_bad = nand_mtd_block_is_bad;
+ rawnand_dev.nand_dev->mtd_read_page = nand_mtd_read_page_raw;
+ rawnand_dev.nand_dev->ecc.mode = NAND_ECC_NONE;
+
+ if ((rawnand_dev.ops->setup == NULL) ||
+ (rawnand_dev.ops->exec == NULL)) {
+ return -ENODEV;
+ }
+
+#if NAND_ONFI_DETECT
+ if (detect_onfi() != 0) {
+ WARN("Detect ONFI failed\n");
+ }
+#endif
+
+ if (plat_get_raw_nand_data(&rawnand_dev) != 0) {
+ return -EINVAL;
+ }
+
+ assert((rawnand_dev.nand_dev->page_size != 0U) &&
+ (rawnand_dev.nand_dev->block_size != 0U) &&
+ (rawnand_dev.nand_dev->size != 0U));
+
+ *size = rawnand_dev.nand_dev->size;
+ *erase_size = rawnand_dev.nand_dev->block_size;
+
+ rawnand_dev.ops->setup(rawnand_dev.nand_dev);
+
+ return 0;
+}