Browse Source
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>pull/1937/head
Lionel Debieve
5 years ago
7 changed files with 1115 additions and 1 deletions
@ -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; |
|||
} |
@ -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; |
|||
} |
@ -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; |
|||
} |
@ -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 */ |
@ -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 */ |
@ -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…
Reference in new issue