mirror of
https://github.com/ARM-software/arm-trusted-firmware.git
synced 2025-04-17 10:04:26 +00:00
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>
This commit is contained in:
parent
45cc606ea7
commit
b114abb609
7 changed files with 1115 additions and 1 deletions
248
drivers/io/io_mtd.c
Normal file
248
drivers/io/io_mtd.c
Normal file
|
@ -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;
|
||||
}
|
118
drivers/mtd/nand/core.c
Normal file
118
drivers/mtd/nand/core.c
Normal file
|
@ -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;
|
||||
}
|
446
drivers/mtd/nand/raw_nand.c
Normal file
446
drivers/mtd/nand/raw_nand.c
Normal file
|
@ -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;
|
||||
}
|
59
include/drivers/io/io_mtd.h
Normal file
59
include/drivers/io/io_mtd.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2019, ARM Limited and Contributors. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef IO_MTD_H
|
||||
#define IO_MTD_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <drivers/io/io_storage.h>
|
||||
|
||||
/* MTD devices ops */
|
||||
typedef struct io_mtd_ops {
|
||||
/*
|
||||
* Initialize MTD framework and retrieve device information.
|
||||
*
|
||||
* @size: [out] MTD device size in bytes.
|
||||
* @erase_size: [out] MTD erase size in bytes.
|
||||
* Return 0 on success, a negative error code otherwise.
|
||||
*/
|
||||
int (*init)(unsigned long long *size, unsigned int *erase_size);
|
||||
|
||||
/*
|
||||
* Execute a read memory operation.
|
||||
*
|
||||
* @offset: Offset in bytes to start read operation.
|
||||
* @buffer: [out] Buffer to store read data.
|
||||
* @length: Required length to be read in bytes.
|
||||
* @out_length: [out] Length read in bytes.
|
||||
* Return 0 on success, a negative error code otherwise.
|
||||
*/
|
||||
int (*read)(unsigned int offset, uintptr_t buffer, size_t length,
|
||||
size_t *out_length);
|
||||
|
||||
/*
|
||||
* Execute a write memory operation.
|
||||
*
|
||||
* @offset: Offset in bytes to start write operation.
|
||||
* @buffer: Buffer to be written in device.
|
||||
* @length: Required length to be written in bytes.
|
||||
* Return 0 on success, a negative error code otherwise.
|
||||
*/
|
||||
int (*write)(unsigned int offset, uintptr_t buffer, size_t length);
|
||||
} io_mtd_ops_t;
|
||||
|
||||
typedef struct io_mtd_dev_spec {
|
||||
unsigned long long device_size;
|
||||
unsigned int erase_size;
|
||||
io_mtd_ops_t ops;
|
||||
} io_mtd_dev_spec_t;
|
||||
|
||||
struct io_dev_connector;
|
||||
|
||||
int register_io_dev_mtd(const struct io_dev_connector **dev_con);
|
||||
|
||||
#endif /* IO_MTD_H */
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2014, ARM Limited and Contributors. All rights reserved.
|
||||
* Copyright (c) 2014-2019, ARM Limited and Contributors. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
@ -22,6 +22,7 @@ typedef enum {
|
|||
IO_TYPE_DUMMY,
|
||||
IO_TYPE_FIRMWARE_IMAGE_PACKAGE,
|
||||
IO_TYPE_BLOCK,
|
||||
IO_TYPE_MTD,
|
||||
IO_TYPE_MMC,
|
||||
IO_TYPE_STM32IMAGE,
|
||||
IO_TYPE_MAX
|
||||
|
|
55
include/drivers/nand.h
Normal file
55
include/drivers/nand.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2019, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef DRIVERS_NAND_H
|
||||
#define DRIVERS_NAND_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <lib/utils_def.h>
|
||||
|
||||
#define PSEC_TO_MSEC(x) div_round_up((x), 1000000000ULL)
|
||||
|
||||
struct ecc {
|
||||
unsigned int mode; /* ECC mode NAND_ECC_MODE_{NONE|HW|ONDIE} */
|
||||
unsigned int size; /* Data byte per ECC step */
|
||||
unsigned int bytes; /* ECC bytes per step */
|
||||
unsigned int max_bit_corr; /* Max correctible bits per ECC steps */
|
||||
};
|
||||
|
||||
struct nand_device {
|
||||
unsigned int block_size;
|
||||
unsigned int page_size;
|
||||
unsigned long long size;
|
||||
unsigned int nb_planes;
|
||||
unsigned int buswidth;
|
||||
struct ecc ecc;
|
||||
int (*mtd_block_is_bad)(unsigned int block);
|
||||
int (*mtd_read_page)(struct nand_device *nand, unsigned int page,
|
||||
uintptr_t buffer);
|
||||
};
|
||||
|
||||
/*
|
||||
* Read bytes from NAND device
|
||||
*
|
||||
* @offset: Byte offset to read from in device
|
||||
* @buffer: [out] Bytes read from device
|
||||
* @length: Number of bytes to read
|
||||
* @length_read: [out] Number of bytes read from device
|
||||
* Return: 0 on success, a negative errno on failure
|
||||
*/
|
||||
int nand_read(unsigned int offset, uintptr_t buffer, size_t length,
|
||||
size_t *length_read);
|
||||
|
||||
/*
|
||||
* Get NAND device instance
|
||||
*
|
||||
* Return: NAND device instance reference
|
||||
*/
|
||||
struct nand_device *get_nand_device(void);
|
||||
|
||||
#endif /* DRIVERS_NAND_H */
|
187
include/drivers/raw_nand.h
Normal file
187
include/drivers/raw_nand.h
Normal file
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* Copyright (c) 2019, STMicroelectronics - All Rights Reserved
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef DRIVERS_RAW_NAND_H
|
||||
#define DRIVERS_RAW_NAND_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <drivers/nand.h>
|
||||
|
||||
/* NAND ONFI default value mode 0 in picosecond */
|
||||
#define NAND_TADL_MIN 400000UL
|
||||
#define NAND_TALH_MIN 20000UL
|
||||
#define NAND_TALS_MIN 50000UL
|
||||
#define NAND_TAR_MIN 25000UL
|
||||
#define NAND_TCCS_MIN 500000UL
|
||||
#define NAND_TCEA_MIN 100000UL
|
||||
#define NAND_TCEH_MIN 20000UL
|
||||
#define NAND_TCH_MIN 20000UL
|
||||
#define NAND_TCHZ_MAX 100000UL
|
||||
#define NAND_TCLH_MIN 20000UL
|
||||
#define NAND_TCLR_MIN 20000UL
|
||||
#define NAND_TCLS_MIN 50000UL
|
||||
#define NAND_TCOH_MIN 0UL
|
||||
#define NAND_TCS_MIN 70000UL
|
||||
#define NAND_TDH_MIN 20000UL
|
||||
#define NAND_TDS_MIN 40000UL
|
||||
#define NAND_TFEAT_MAX 1000000UL
|
||||
#define NAND_TIR_MIN 10000UL
|
||||
#define NAND_TITC_MIN 1000000UL
|
||||
#define NAND_TR_MAX 200000000UL
|
||||
#define NAND_TRC_MIN 100000UL
|
||||
#define NAND_TREA_MAX 40000UL
|
||||
#define NAND_TREH_MIN 30000UL
|
||||
#define NAND_TRHOH_MIN 0UL
|
||||
#define NAND_TRHW_MIN 200000UL
|
||||
#define NAND_TRHZ_MAX 200000UL
|
||||
#define NAND_TRLOH_MIN 0UL
|
||||
#define NAND_TRP_MIN 50000UL
|
||||
#define NAND_TRR_MIN 40000UL
|
||||
#define NAND_TRST_MAX 250000000000ULL
|
||||
#define NAND_TWB_MAX 200000UL
|
||||
#define NAND_TWC_MIN 100000UL
|
||||
#define NAND_TWH_MIN 30000UL
|
||||
#define NAND_TWHR_MIN 120000UL
|
||||
#define NAND_TWP_MIN 50000UL
|
||||
#define NAND_TWW_MIN 100000UL
|
||||
|
||||
/* NAND request types */
|
||||
#define NAND_REQ_CMD 0x0000U
|
||||
#define NAND_REQ_ADDR 0x1000U
|
||||
#define NAND_REQ_DATAIN 0x2000U
|
||||
#define NAND_REQ_DATAOUT 0x3000U
|
||||
#define NAND_REQ_WAIT 0x4000U
|
||||
#define NAND_REQ_MASK GENMASK(14, 12)
|
||||
#define NAND_REQ_BUS_WIDTH_8 BIT(15)
|
||||
|
||||
#define PARAM_PAGE_SIZE 256
|
||||
|
||||
/* NAND ONFI commands */
|
||||
#define NAND_CMD_READ_1ST 0x00U
|
||||
#define NAND_CMD_CHANGE_1ST 0x05U
|
||||
#define NAND_CMD_READID_SIG_ADDR 0x20U
|
||||
#define NAND_CMD_READ_2ND 0x30U
|
||||
#define NAND_CMD_STATUS 0x70U
|
||||
#define NAND_CMD_READID 0x90U
|
||||
#define NAND_CMD_CHANGE_2ND 0xE0U
|
||||
#define NAND_CMD_READ_PARAM_PAGE 0xECU
|
||||
#define NAND_CMD_RESET 0xFFU
|
||||
|
||||
#define ONFI_REV_21 BIT(3)
|
||||
#define ONFI_FEAT_BUS_WIDTH_16 BIT(0)
|
||||
#define ONFI_FEAT_EXTENDED_PARAM BIT(7)
|
||||
|
||||
/* NAND ECC type */
|
||||
#define NAND_ECC_NONE U(0)
|
||||
#define NAND_ECC_HW U(1)
|
||||
#define NAND_ECC_ONDIE U(2)
|
||||
|
||||
/* NAND bus width */
|
||||
#define NAND_BUS_WIDTH_8 U(0)
|
||||
#define NAND_BUS_WIDTH_16 U(1)
|
||||
|
||||
struct nand_req {
|
||||
struct nand_device *nand;
|
||||
uint16_t type;
|
||||
uint8_t *addr;
|
||||
unsigned int length;
|
||||
unsigned int delay_ms;
|
||||
unsigned int inst_delay;
|
||||
};
|
||||
|
||||
struct nand_param_page {
|
||||
/* Rev information and feature block */
|
||||
uint32_t page_sig;
|
||||
uint16_t rev;
|
||||
uint16_t features;
|
||||
uint16_t opt_cmd;
|
||||
uint8_t jtg;
|
||||
uint8_t train_cmd;
|
||||
uint16_t ext_param_length;
|
||||
uint8_t nb_param_pages;
|
||||
uint8_t reserved1[17];
|
||||
/* Manufacturer information */
|
||||
uint8_t manufacturer[12];
|
||||
uint8_t model[20];
|
||||
uint8_t manufacturer_id;
|
||||
uint16_t data_code;
|
||||
uint8_t reserved2[13];
|
||||
/* Memory organization */
|
||||
uint32_t bytes_per_page;
|
||||
uint16_t spare_per_page;
|
||||
uint32_t bytes_per_partial;
|
||||
uint16_t spare_per_partial;
|
||||
uint32_t num_pages_per_blk;
|
||||
uint32_t num_blk_in_lun;
|
||||
uint8_t num_lun;
|
||||
uint8_t num_addr_cycles;
|
||||
uint8_t bit_per_cell;
|
||||
uint16_t max_bb_per_lun;
|
||||
uint16_t blk_endur;
|
||||
uint8_t valid_blk_begin;
|
||||
uint16_t blk_enbur_valid;
|
||||
uint8_t nb_prog_page;
|
||||
uint8_t partial_prog_attr;
|
||||
uint8_t nb_ecc_bits;
|
||||
uint8_t plane_addr;
|
||||
uint8_t mplanes_ops;
|
||||
uint8_t ez_nand;
|
||||
uint8_t reserved3[12];
|
||||
/* Electrical parameters */
|
||||
uint8_t io_pin_cap_max;
|
||||
uint16_t sdr_timing_mode;
|
||||
uint16_t sdr_prog_cache_timing;
|
||||
uint16_t tprog;
|
||||
uint16_t tbers;
|
||||
uint16_t tr;
|
||||
uint16_t tccs;
|
||||
uint8_t nvddr_timing_mode;
|
||||
uint8_t nvddr2_timing_mode;
|
||||
uint8_t nvddr_features;
|
||||
uint16_t clk_input_cap_typ;
|
||||
uint16_t io_pin_cap_typ;
|
||||
uint16_t input_pin_cap_typ;
|
||||
uint8_t input_pin_cap_max;
|
||||
uint8_t drv_strength_support;
|
||||
uint16_t tr_max;
|
||||
uint16_t tadl;
|
||||
uint16_t tr_typ;
|
||||
uint8_t reserved4[6];
|
||||
/* Vendor block */
|
||||
uint16_t vendor_revision;
|
||||
uint8_t vendor[88];
|
||||
uint16_t crc16;
|
||||
} __packed;
|
||||
|
||||
struct nand_ctrl_ops {
|
||||
int (*exec)(struct nand_req *req);
|
||||
void (*setup)(struct nand_device *nand);
|
||||
};
|
||||
|
||||
struct rawnand_device {
|
||||
struct nand_device *nand_dev;
|
||||
const struct nand_ctrl_ops *ops;
|
||||
};
|
||||
|
||||
int nand_raw_init(unsigned long long *size, unsigned int *erase_size);
|
||||
int nand_wait_ready(unsigned long delay);
|
||||
int nand_read_page_cmd(unsigned int page, unsigned int offset,
|
||||
uintptr_t buffer, unsigned int len);
|
||||
int nand_change_read_column_cmd(unsigned int offset, uintptr_t buffer,
|
||||
unsigned int len);
|
||||
void nand_raw_ctrl_init(const struct nand_ctrl_ops *ops);
|
||||
|
||||
/*
|
||||
* Platform can implement this to override default raw NAND instance
|
||||
* configuration.
|
||||
*
|
||||
* @device: target raw NAND instance.
|
||||
* Return 0 on success, negative value otherwise.
|
||||
*/
|
||||
int plat_get_raw_nand_data(struct rawnand_device *device);
|
||||
|
||||
#endif /* DRIVERS_RAW_NAND_H */
|
Loading…
Add table
Reference in a new issue