diff options
Diffstat (limited to 'bsps/arm/lpc32xx/nand/nand-mlc.c')
-rw-r--r-- | bsps/arm/lpc32xx/nand/nand-mlc.c | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/bsps/arm/lpc32xx/nand/nand-mlc.c b/bsps/arm/lpc32xx/nand/nand-mlc.c new file mode 100644 index 0000000000..f87c59b133 --- /dev/null +++ b/bsps/arm/lpc32xx/nand/nand-mlc.c @@ -0,0 +1,397 @@ +/** + * @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; +} |