blob: b21996db84d730c8f19054f6b843926f3782edee (
plain) (
tree)
|
|
/**
* @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;
}
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;
}
|