forked from Imagelibrary/rtems
398 lines
8.6 KiB
C
398 lines
8.6 KiB
C
/**
|
|
* @file
|
|
*
|
|
* @ingroup lpc32xx_nand_mlc
|
|
*
|
|
* @brief NAND MLC controller implementation.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2010-2011 embedded brains GmbH. All rights reserved.
|
|
*
|
|
* embedded brains GmbH
|
|
* Obere Lagerstr. 30
|
|
* 82178 Puchheim
|
|
* Germany
|
|
* <rtems@embedded-brains.de>
|
|
*
|
|
* The license and distribution terms for this file may be
|
|
* found in the file LICENSE in this distribution or at
|
|
* http://www.rtems.org/license/LICENSE.
|
|
*/
|
|
|
|
#include <bsp/lpc32xx.h>
|
|
#include <bsp/nand-mlc.h>
|
|
|
|
static volatile lpc32xx_nand_mlc *const mlc = &lpc32xx.nand_mlc;
|
|
|
|
static uint32_t mlc_flags;
|
|
|
|
static uint32_t mlc_block_count;
|
|
|
|
static uint32_t mlc_page_count;
|
|
|
|
static bool mlc_small_pages(void)
|
|
{
|
|
return (mlc_flags & MLC_SMALL_PAGES) != 0;
|
|
}
|
|
|
|
static bool mlc_many_address_cycles(void)
|
|
{
|
|
return (mlc_flags & MLC_MANY_ADDRESS_CYCLES) != 0;
|
|
}
|
|
|
|
static bool mlc_normal_blocks(void)
|
|
{
|
|
return (mlc_flags & MLC_NORMAL_BLOCKS) != 0;
|
|
}
|
|
|
|
uint32_t lpc32xx_mlc_page_size(void)
|
|
{
|
|
if (mlc_small_pages()) {
|
|
return 512;
|
|
} else {
|
|
return 2048;
|
|
}
|
|
}
|
|
|
|
uint32_t lpc32xx_mlc_pages_per_block(void)
|
|
{
|
|
if (mlc_small_pages()) {
|
|
return 32;
|
|
} else {
|
|
if (mlc_normal_blocks()) {
|
|
return 64;
|
|
} else {
|
|
return 128;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t lpc32xx_mlc_block_count(void)
|
|
{
|
|
return mlc_block_count;
|
|
}
|
|
|
|
uint32_t lpc32xx_mlc_io_width(void)
|
|
{
|
|
if ((mlc_flags & MLC_IO_WIDTH_16_BIT) == 0) {
|
|
return 8;
|
|
} else {
|
|
return 16;
|
|
}
|
|
}
|
|
|
|
static void mlc_unlock(void)
|
|
{
|
|
mlc->lock_pr = MLC_UNLOCK_PROT;
|
|
}
|
|
|
|
static void mlc_wait(uint32_t flags)
|
|
{
|
|
while ((mlc->isr & flags) != flags) {
|
|
/* Wait */
|
|
}
|
|
}
|
|
|
|
static void mlc_wait_until_ready(void)
|
|
{
|
|
mlc_wait(MLC_ISR_CONTROLLER_READY | MLC_ISR_NAND_READY);
|
|
}
|
|
|
|
static void mlc_reset(void)
|
|
{
|
|
mlc->cmd = 0xff;
|
|
}
|
|
|
|
static uint32_t mlc_status(void)
|
|
{
|
|
mlc_wait_until_ready();
|
|
mlc->cmd = 0x70;
|
|
|
|
return mlc->data.w8;
|
|
}
|
|
|
|
static bool mlc_was_operation_successful(void)
|
|
{
|
|
return (mlc_status() & (NAND_STATUS_READY | NAND_STATUS_ERROR))
|
|
== NAND_STATUS_READY;
|
|
}
|
|
|
|
static void mlc_set_block_address(uint32_t block_index)
|
|
{
|
|
if (mlc_small_pages()) {
|
|
mlc->addr = (uint8_t) (block_index << 5);
|
|
mlc->addr = (uint8_t) (block_index >> 3);
|
|
if (mlc_many_address_cycles()) {
|
|
mlc->addr = (uint8_t) (block_index >> 11);
|
|
}
|
|
} else {
|
|
if (mlc_normal_blocks()) {
|
|
mlc->addr = (uint8_t) (block_index << 6);
|
|
mlc->addr = (uint8_t) (block_index >> 2);
|
|
if (mlc_many_address_cycles()) {
|
|
mlc->addr = (uint8_t) (block_index >> 10);
|
|
}
|
|
} else {
|
|
mlc->addr = (uint8_t) (block_index << 7);
|
|
mlc->addr = (uint8_t) (block_index >> 1);
|
|
if (mlc_many_address_cycles()) {
|
|
mlc->addr = (uint8_t) (block_index >> 9);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void mlc_set_page_address(uint32_t page_index)
|
|
{
|
|
mlc->addr = 0;
|
|
if (mlc_small_pages()) {
|
|
mlc->addr = (uint8_t) page_index;
|
|
mlc->addr = (uint8_t) (page_index >> 8);
|
|
if (mlc_many_address_cycles()) {
|
|
mlc->addr = (uint8_t) (page_index >> 16);
|
|
}
|
|
} else {
|
|
mlc->addr = 0;
|
|
mlc->addr = (uint8_t) page_index;
|
|
mlc->addr = (uint8_t) (page_index >> 8);
|
|
if (mlc_many_address_cycles()) {
|
|
mlc->addr = (uint8_t) (page_index >> 16);
|
|
}
|
|
}
|
|
}
|
|
|
|
void lpc32xx_mlc_init(const lpc32xx_mlc_config *cfg)
|
|
{
|
|
uint32_t icr = 0;
|
|
|
|
mlc_flags = cfg->flags;
|
|
mlc_block_count = cfg->block_count;
|
|
mlc_page_count = cfg->block_count * lpc32xx_mlc_pages_per_block();
|
|
|
|
/* Clock */
|
|
LPC32XX_FLASHCLK_CTRL = FLASHCLK_IRQ_MLC | FLASHCLK_MLC_CLK_ENABLE;
|
|
|
|
/* Timing settings */
|
|
mlc_unlock();
|
|
mlc->time = cfg->time;
|
|
|
|
/* Configuration */
|
|
if (!mlc_small_pages()) {
|
|
icr |= MLC_ICR_LARGE_PAGES;
|
|
}
|
|
if (mlc_many_address_cycles()) {
|
|
icr |= MLC_ICR_ADDR_WORD_COUNT_4_5;
|
|
}
|
|
mlc_unlock();
|
|
mlc->icr = icr;
|
|
|
|
mlc_reset();
|
|
}
|
|
|
|
void lpc32xx_mlc_write_protection(
|
|
uint32_t page_index_low,
|
|
uint32_t page_index_high
|
|
)
|
|
{
|
|
mlc_unlock();
|
|
mlc->sw_wp_add_low = page_index_low;
|
|
mlc_unlock();
|
|
mlc->sw_wp_add_hig = page_index_high;
|
|
mlc_unlock();
|
|
mlc->icr |= MLC_ICR_SOFT_WRITE_PROT;
|
|
}
|
|
|
|
static bool is_word_aligned(const void *data, const void *spare)
|
|
{
|
|
return (((uintptr_t) data) | ((uintptr_t) spare)) % 4 == 0;
|
|
}
|
|
|
|
rtems_status_code lpc32xx_mlc_read_page(
|
|
uint32_t page_index,
|
|
void *data,
|
|
void *spare,
|
|
uint32_t *symbol_error_count_ptr
|
|
)
|
|
{
|
|
rtems_status_code sc = RTEMS_SUCCESSFUL;
|
|
size_t small_pages_count = mlc_small_pages() ? 1 : MLC_SMALL_PAGES_PER_LARGE_PAGE;
|
|
size_t sp = 0;
|
|
size_t i = 0;
|
|
uint32_t isr = 0;
|
|
uint32_t symbol_error_count = 0xffffffff;
|
|
bool aligned = is_word_aligned(data, spare);
|
|
uint8_t *current_data = data;
|
|
uint8_t *current_spare = spare;
|
|
|
|
if (page_index >= mlc_page_count) {
|
|
return RTEMS_INVALID_ID;
|
|
}
|
|
|
|
mlc_wait_until_ready();
|
|
mlc->cmd = 0x00;
|
|
if (!mlc_small_pages()) {
|
|
mlc->cmd = 0x30;
|
|
}
|
|
mlc_set_page_address(page_index);
|
|
mlc_wait(MLC_ISR_NAND_READY);
|
|
|
|
for (sp = 0; sc == RTEMS_SUCCESSFUL && sp < small_pages_count; ++sp) {
|
|
uint32_t *aligned_data = (uint32_t *) current_data;
|
|
uint32_t *aligned_spare = (uint32_t *) current_spare;
|
|
|
|
mlc->ecc_dec = 0;
|
|
|
|
if (aligned) {
|
|
for (i = 0; i < MLC_SMALL_DATA_WORD_COUNT; ++i) {
|
|
aligned_data [i] = mlc->data.w32;
|
|
}
|
|
for (i = 0; i < MLC_SMALL_SPARE_WORD_COUNT; ++i) {
|
|
aligned_spare [i] = mlc->data.w32;
|
|
}
|
|
} else {
|
|
for (i = 0; i < MLC_SMALL_DATA_SIZE; ++i) {
|
|
current_data [i] = mlc->data.w8;
|
|
}
|
|
for (i = 0; i < MLC_SMALL_SPARE_SIZE; ++i) {
|
|
current_spare [i] = mlc->data.w8;
|
|
}
|
|
}
|
|
|
|
mlc_wait(MLC_ISR_ECC_READY);
|
|
|
|
isr = mlc->isr;
|
|
if ((isr & MLC_ISR_ERRORS_DETECTED) == 0) {
|
|
symbol_error_count = 0;
|
|
} else {
|
|
if ((isr & MLC_ISR_DECODER_FAILURE) == 0) {
|
|
symbol_error_count = MLC_ISR_SYMBOL_ERRORS(isr);
|
|
if (aligned) {
|
|
mlc->rubp = 0;
|
|
for (i = 0; i < MLC_SMALL_DATA_WORD_COUNT; ++i) {
|
|
aligned_data [i] = mlc->buff.w32;
|
|
}
|
|
mlc->robp = 0;
|
|
for (i = 0; i < MLC_SMALL_SPARE_WORD_COUNT; ++i) {
|
|
aligned_spare [i] = mlc->buff.w32;
|
|
}
|
|
} else {
|
|
mlc->rubp = 0;
|
|
for (i = 0; i < MLC_SMALL_DATA_SIZE; ++i) {
|
|
current_data [i] = mlc->buff.w8;
|
|
}
|
|
mlc->robp = 0;
|
|
for (i = 0; i < MLC_SMALL_SPARE_SIZE; ++i) {
|
|
current_spare [i] = mlc->buff.w8;
|
|
}
|
|
}
|
|
} else {
|
|
sc = RTEMS_IO_ERROR;
|
|
}
|
|
}
|
|
|
|
current_data += MLC_SMALL_DATA_SIZE;
|
|
current_spare += MLC_SMALL_SPARE_SIZE;
|
|
}
|
|
|
|
if (symbol_error_count_ptr != NULL) {
|
|
*symbol_error_count_ptr = symbol_error_count;
|
|
}
|
|
|
|
return sc;
|
|
}
|
|
|
|
void lpc32xx_mlc_read_id(uint8_t *id, size_t n)
|
|
{
|
|
size_t i = 0;
|
|
|
|
mlc_wait_until_ready();
|
|
mlc->cmd = 0x90;
|
|
mlc->addr = 0;
|
|
mlc_wait(MLC_ISR_NAND_READY);
|
|
|
|
for (i = 0; i < n; ++i) {
|
|
id [i] = mlc->data.w8;
|
|
}
|
|
}
|
|
|
|
rtems_status_code lpc32xx_mlc_erase_block(uint32_t block_index)
|
|
{
|
|
rtems_status_code sc = RTEMS_UNSATISFIED;
|
|
|
|
if (block_index >= mlc_block_count) {
|
|
return RTEMS_INVALID_ID;
|
|
}
|
|
|
|
mlc_wait_until_ready();
|
|
mlc->cmd = 0x60;
|
|
mlc_set_block_address(block_index);
|
|
mlc->cmd = 0xd0;
|
|
|
|
if (mlc_was_operation_successful()) {
|
|
sc = RTEMS_SUCCESSFUL;
|
|
}
|
|
|
|
return sc;
|
|
}
|
|
|
|
rtems_status_code lpc32xx_mlc_write_page_with_ecc(
|
|
uint32_t page_index,
|
|
const void *data,
|
|
const void *spare
|
|
)
|
|
{
|
|
rtems_status_code sc = RTEMS_IO_ERROR;
|
|
size_t small_pages_count = mlc_small_pages() ?
|
|
1 : MLC_SMALL_PAGES_PER_LARGE_PAGE;
|
|
size_t sp = 0;
|
|
size_t i = 0;
|
|
bool aligned = is_word_aligned(data, spare);
|
|
const uint8_t *current_data = data;
|
|
const uint8_t *current_spare = spare;
|
|
|
|
if (page_index >= mlc_page_count) {
|
|
return RTEMS_INVALID_ID;
|
|
}
|
|
|
|
mlc_wait_until_ready();
|
|
mlc->cmd = 0x80;
|
|
mlc_set_page_address(page_index);
|
|
|
|
for (sp = 0; sp < small_pages_count; ++sp) {
|
|
mlc->ecc_enc = 0;
|
|
|
|
if (aligned) {
|
|
const uint32_t *aligned_data = (const uint32_t *) current_data;
|
|
const uint32_t *aligned_spare = (const uint32_t *) current_spare;
|
|
|
|
for (i = 0; i < MLC_SMALL_DATA_WORD_COUNT; ++i) {
|
|
mlc->data.w32 = aligned_data [i];
|
|
}
|
|
mlc->data.w32 = aligned_spare [0];
|
|
mlc->data.w16 = (uint16_t) aligned_spare [1];
|
|
} else {
|
|
for (i = 0; i < MLC_SMALL_DATA_SIZE; ++i) {
|
|
mlc->data.w8 = current_data [i];
|
|
}
|
|
for (i = 0; i < MLC_SMALL_USER_SPARE_SIZE; ++i) {
|
|
mlc->data.w8 = current_spare [i];
|
|
}
|
|
}
|
|
mlc->wpr = 0;
|
|
|
|
mlc_wait(MLC_ISR_CONTROLLER_READY);
|
|
|
|
current_data += MLC_SMALL_DATA_SIZE;
|
|
current_spare += MLC_SMALL_SPARE_SIZE;
|
|
}
|
|
|
|
mlc->cmd = 0x10;
|
|
|
|
if (mlc_was_operation_successful()) {
|
|
sc = RTEMS_SUCCESSFUL;
|
|
}
|
|
|
|
return sc;
|
|
}
|