/****************************************************************************** | |
* | |
* alt_nand.c - API for the Altera SoC FPGA NAND device. | |
* | |
******************************************************************************/ | |
/****************************************************************************** | |
* | |
* Copyright 2013 Altera Corporation. All Rights Reserved. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions are met: | |
* | |
* 1. Redistributions of source code must retain the above copyright notice, | |
* this list of conditions and the following disclaimer. | |
* | |
* 2. Redistributions in binary form must reproduce the above copyright notice, | |
* this list of conditions and the following disclaimer in the documentation | |
* and/or other materials provided with the distribution. | |
* | |
* 3. The name of the author may not be used to endorse or promote products | |
* derived from this software without specific prior written permission. | |
* | |
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR | |
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. IN NO | |
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT | |
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING | |
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY | |
* OF SUCH DAMAGE. | |
* | |
******************************************************************************/ | |
#include "hwlib.h" | |
#include "alt_clock_manager.h" | |
#include "alt_nand_flash.h" | |
#include "socal/alt_nand.h" | |
#include "socal/alt_rstmgr.h" | |
#include "socal/alt_sysmgr.h" | |
#include "socal/hps.h" | |
#include "socal/socal.h" | |
#include "alt_nand_private.h" | |
//#include "denali_flash_regs.h" | |
#include <stdbool.h> | |
#include <string.h> | |
#include <stdio.h> | |
static int s_nand_is_interrupt_enabled = false; | |
static uint32_t g_nand_interrup_status_register_poll_counter_limit; | |
static nand_interrupt_handler_t s_nand_interrupt_handler = NULL; | |
uint32_t alt_nand_number_blocks_of_plane_get(void); | |
static __inline uint32_t alt_nand_compose_map01_cmd_addr(const uint32_t bank, const uint32_t block_addr, const uint32_t page_addr); | |
static __inline uint32_t alt_nand_get_interrupt_status_register_addr(const uint32_t bank); | |
static __inline uint32_t alt_nand_get_interrupt_enable_register_addr(const uint32_t bank); | |
static __inline uint32_t alt_nand_get_device_reset_register_bank(const uint32_t bank); | |
uint32_t nand_read_register(const uint32_t offset); | |
static uint32_t real_table[ALT_HHP_NAND_NUM_OF_BLOCK_TOTAL/32]; | |
alt_nand_bad_block_table_t nand_bad_block_table = real_table; | |
ALT_NAND_MGR_t nand_io = | |
{ | |
(ALT_NAND_CFG_raw_t*)ALT_NAND_CFG_ADDR, | |
(ALT_NAND_PARAM_raw_t*)ALT_NAND_PARAM_ADDR, | |
(ALT_NAND_STAT_raw_t*)ALT_NAND_STAT_ADDR, | |
(ALT_NAND_ECC_raw_t*)ALT_NAND_ECC_ADDR, | |
(ALT_NAND_DMA_raw_t*)ALT_NAND_DMA_ADDR, | |
(uint32_t *)ALT_NANDDATA_ADDR, /*control_address */ | |
ALT_CAST(uint32_t *, (ALT_CAST(char *, (ALT_NANDDATA_ADDR)) + 0x10)) | |
}; | |
ALT_NAND_MGR_t * nand = &nand_io; | |
FLASH_CHARACTERIZATION_t memory = | |
{ | |
/*manufacturer id */ ALT_HHP_NAND_MANUFACTURER_ID, | |
/*device_id */ ALT_HHP_NAND_DEVICE_ID, | |
/*device_param_0 */ 0, | |
/*device_param_1 */ 0, | |
/*device_param_2 */ 0, | |
/*page_size */ ALT_HHP_NAND_PAGE_SIZE, | |
/*spare_size */ ALT_HHP_NAND_SPARE_SIZE, | |
/*revision */ ALT_HHP_NAND_REVISION, | |
/*onfi_device_feature */ 0, | |
/*onfi_optional_commands */ 0, | |
/*onfi_timing_mode */ 0, | |
/*onfi_pgm_cache_timing_mode */ 0, | |
/*onfi_compliant */ 1, | |
/*onfi_device_no_of_luns */ 0, | |
/*onfi_device_no_of_blocks_per_lun */ 0, | |
/*features */ 0, | |
/*number_of_planes */ ALT_HHP_NAND_NUMBER_OF_PLANES, | |
/*pages_per_block */ ALT_HHP_NAND_PAGES_PER_BLOCK, | |
/*device_width */ ALT_HHP_NAND_DEVICE_WIDTH, | |
/*block_size */ ALT_HHP_NAND_PAGES_PER_BLOCK * ALT_HHP_NAND_PAGE_SIZE, | |
/*spare_area_skip_bytes */ ALT_HHP_NAND_SPARE_SKIP, | |
/*first block of next plane */ ALT_HHP_NAND_FIRST_BLOCK_OF_NEXT_PLANE, | |
/*page_size_in_32 */ ALT_HHP_NAND_PAGE_SIZE / sizeof(uint32_t), | |
/*block_shift */ ALT_HHP_NAND_BLOCK_SHIFT, | |
/*dma_burst_length */ 0, | |
/*ecc_correct */ ALT_HHP_NAND_ECC_CORRECT, | |
}; | |
FLASH_CHARACTERIZATION_t * flash = &memory; | |
ALT_STATUS_CODE alt_nand_flash_init(const bool load_block0_page0, | |
const bool page_size_512, | |
alt_nand_flash_custom_init_t custom_init, | |
void *user_arg) | |
{ | |
ALT_NAND_CFG_raw_t * cfg = (ALT_NAND_CFG_raw_t *)(nand->cfg); | |
ALT_NAND_PARAM_raw_t * param = (ALT_NAND_PARAM_raw_t *)(nand->param); | |
ALT_STATUS_CODE ret = ALT_E_SUCCESS; | |
uint32_t x; | |
alt_setbits_word(ALT_RSTMGR_PERMODRST_ADDR, ALT_RSTMGR_PERMODRST_NAND_SET_MSK); | |
alt_nand_set_sysmgr_bootstrap_value( ALT_NAND_BOOTSTRAP_INHIBIT_INIT_DISABLE, | |
load_block0_page0, | |
page_size_512, | |
ALT_NAND_BOOTSTRAP_TWO_ROW_ADDR_CYCLES_DISABLE | |
); | |
alt_clrbits_word(ALT_RSTMGR_PERMODRST_ADDR, ALT_RSTMGR_PERMODRST_NAND_SET_MSK); | |
g_nand_interrup_status_register_poll_counter_limit = (uint32_t)(-1); | |
ret = (*custom_init)(user_arg); | |
if (ret == ALT_E_RESERVED) // no custom initialization being done | |
{ | |
alt_nand_reset_bank(0); | |
} | |
// Read flash device characterization | |
flash->manufacturer_id = alt_read_word(¶m->manufacturer_id); | |
flash->device_id = alt_read_word(¶m->device_id); | |
flash->device_param_0 = alt_read_word(¶m->device_param_0); | |
flash->device_param_1 = alt_read_word(¶m->device_param_1); | |
flash->device_param_2 = alt_read_word(¶m->device_param_2); | |
flash->page_size = alt_read_word(&cfg->device_main_area_size); | |
flash->spare_size = alt_read_word(&cfg->device_spare_area_size); | |
flash->revision = alt_read_word(¶m->revision); | |
flash->onfi_device_features = alt_read_word(¶m->onfi_device_features); | |
flash->onfi_optional_commands = alt_read_word(¶m->onfi_optional_commands); | |
flash->onfi_timing_mode = alt_read_word(¶m->onfi_timing_mode); | |
flash->onfi_pgm_cache_timing_mode = alt_read_word(¶m->onfi_pgm_cache_timing_mode); | |
flash->onfi_compliant = alt_read_word(¶m->onfi_device_no_of_luns) >> 8; | |
flash->onfi_device_no_of_luns = alt_read_word(¶m->onfi_device_no_of_luns) & 0xff; | |
x = alt_read_word(¶m->onfi_device_no_of_blocks_per_lun_l); | |
flash->onfi_device_no_of_blocks_per_lun = (alt_read_word(¶m->onfi_device_no_of_blocks_per_lun_u) << 16) + x; | |
flash->features = alt_read_word(¶m->features); | |
x = alt_read_word(&cfg->number_of_planes); | |
switch (x) | |
{ | |
case 0: | |
flash->number_of_planes = 1; | |
break; | |
case 1: | |
flash->number_of_planes = 2; | |
break; | |
case 3: | |
flash->number_of_planes = 4; | |
break; | |
case 7: | |
flash->number_of_planes = 4; | |
break; | |
default: | |
flash->number_of_planes = 1; | |
break; | |
} | |
flash->pages_per_block = alt_read_word(&cfg->pages_per_block); | |
// Device Width register content should automatically update SystemManager:NandGrp:BootStrap:page512 or page512x16 bit | |
flash->device_width = alt_read_word(&cfg->device_width); | |
// Set the skip bytes and then read back the result. | |
alt_write_word(&cfg->spare_area_skip_bytes, flash->spare_area_skip_bytes); | |
flash->spare_area_skip_bytes = alt_read_word(&cfg->spare_area_skip_bytes); | |
flash->block_size = flash->pages_per_block * flash->page_size; | |
flash->first_block_of_next_plane = alt_read_word(&cfg->first_block_of_next_plane); | |
// Set flash config based on read config | |
flash->page_size_32 = flash->page_size / sizeof(uint32_t); | |
flash->page_shift = ffs32(flash->page_size); | |
flash->block_shift = ffs32(flash->pages_per_block); | |
alt_nand_rb_pin_mode_clear(ALT_NAND_CFG_RB_PIN_END_BANK0_SET_MSK); | |
alt_nand_flash_ecc_disable(); | |
alt_write_word(&cfg->first_block_of_next_plane,alt_nand_number_blocks_of_plane_get()); | |
flash->first_block_of_next_plane = alt_read_word(&cfg->first_block_of_next_plane); | |
return ALT_E_SUCCESS; | |
} | |
ALT_STATUS_CODE alt_nand_flash_uninit(void) | |
{ | |
alt_nand_flash_ecc_disable(); | |
alt_nand_dma_set_enabled( false ); | |
return ALT_E_SUCCESS; | |
} | |
#define ALT_NAND_INVALID_FLASH_ADDR 0xffffffff | |
uint32_t alt_nand_block_address_get(const uint32_t addr) | |
{ | |
return addr >> (flash->block_shift + flash->page_shift); | |
} | |
uint32_t alt_nand_page_address_get(const uint32_t addr) | |
{ | |
return addr >> flash->page_shift & ((1 << flash->block_shift) - 1); | |
} | |
uint32_t alt_nand_flash_addr_compose(const uint32_t block_num, | |
const uint32_t page_num) | |
{ | |
return (block_num << (flash->block_shift + flash->page_shift)) + (page_num << flash->page_shift); | |
} | |
ALT_STATUS_CODE alt_nand_flash_block_erase(const uint32_t block_addr, | |
alt_nand_callback_t completion_callback, | |
void *completion_arg) | |
{ | |
int32_t ret = ALT_E_SUCCESS; | |
int32_t res = -1; | |
uint32_t bank = alt_nand_bank_get(); | |
const uint32_t interrup_status_register = alt_nand_get_interrupt_status_register_addr( bank ); | |
const uint32_t addr = alt_nand_compose_map10_cmd_addr( bank, block_addr, 0 ); | |
alt_write_word(interrup_status_register, ALT_HHP_UINT32_MASK ); | |
alt_nand_write_indirect( addr, ALT_HHP_NAND_10_OP_ERASE_BLOCK ); | |
res = alt_nand_poll_for_interrupt_status_register(interrup_status_register, | |
ALT_NAND_INT_STATUS_TIME_OUT | | |
ALT_NAND_INT_STATUS_ERASE_COMP | | |
ALT_NAND_INT_STATUS_ERASE_FAIL); | |
if (!(res & ALT_NAND_STAT_INTR_STAT0_ERASE_COMP_SET_MSK)) | |
{ | |
//printf("FAIL: erasing not complete. (%08lX, %ld)\n", res, block_addr); | |
ret = ALT_E_ERROR; | |
} | |
return ret; | |
} | |
ALT_STATUS_CODE alt_nand_flash_page_read(const uint32_t page_addr, | |
const uint32_t num_pages, | |
void *dest, | |
const uint32_t dest_size) | |
{ | |
// currently only one page | |
uint32_t bank = alt_nand_bank_get(); | |
uint32_t page = alt_nand_page_address_get(page_addr); | |
uint32_t block = alt_nand_block_address_get(page_addr); | |
return alt_nand_full_page_read_with_map10( bank, block, page, (uint32_t *)dest); | |
} | |
ALT_STATUS_CODE alt_nand_flash_page_write(const uint32_t page_addr, | |
const uint32_t num_pages, | |
const void *src, | |
const uint32_t src_size) | |
{ | |
// currently only one page | |
uint32_t bank = alt_nand_bank_get(); | |
uint32_t page = alt_nand_page_address_get(page_addr); | |
uint32_t block = alt_nand_block_address_get(page_addr); | |
return alt_nand_full_page_write_with_map10( bank, block, page, (uint32_t *)src); | |
} | |
ALT_STATUS_CODE alt_nand_flash_page_dma_read(const uint32_t page_addr, | |
const uint32_t num_pages, | |
void *dest, | |
const uint32_t dest_size, | |
alt_nand_callback_t completion_callback, | |
void *completion_arg) | |
{ | |
ALT_STATUS_CODE status = ALT_E_SUCCESS; | |
uint32_t mem_addr = page_addr; | |
uint32_t dest_addr = (uint32_t)dest; | |
uint32_t buff_size = dest_size; | |
uint32_t bank = alt_nand_bank_get(); | |
uint32_t page; | |
uint32_t block; | |
for (int i = 0; (i < num_pages) && (buff_size >= flash->page_size); i++) { | |
page = alt_nand_page_address_get(mem_addr); | |
block = alt_nand_block_address_get(mem_addr); | |
status = alt_nand_dma_page_read(bank, block, page, dest_addr); | |
if (status == ALT_E_SUCCESS) | |
{ | |
mem_addr += flash->page_size; | |
dest_addr += flash->page_size; | |
buff_size -= flash->page_size; | |
} | |
else | |
break; | |
} | |
if (status == ALT_E_SUCCESS) | |
{ | |
(*completion_callback)(status, completion_arg); | |
} | |
return status; | |
} | |
ALT_STATUS_CODE alt_nand_flash_page_dma_write(const uint32_t page_addr, | |
const uint32_t num_pages, | |
const void *src, | |
const uint32_t src_size, | |
alt_nand_callback_t completion_callback, | |
void *completion_arg) | |
{ | |
ALT_STATUS_CODE status = ALT_E_SUCCESS; | |
uint32_t mem_addr = page_addr; | |
uint32_t src_addr = (uint32_t)src; | |
uint32_t buff_size = src_size; | |
uint32_t bank = alt_nand_bank_get(); | |
uint32_t page; | |
uint32_t block; | |
for (int i = 0; (i < num_pages) && (buff_size >= flash->page_size); i++) { | |
page = alt_nand_page_address_get(mem_addr); | |
block = alt_nand_block_address_get(mem_addr); | |
status = alt_nand_dma_page_write(bank, block, page, src_addr); | |
if (status == ALT_E_SUCCESS) | |
{ | |
mem_addr += flash->page_size; | |
src_addr += flash->page_size; | |
buff_size -= flash->page_size; | |
} | |
else | |
break; | |
} | |
if (status == ALT_E_SUCCESS) | |
{ | |
(*completion_callback)(status, completion_arg); | |
} | |
return status; | |
} | |
ALT_STATUS_CODE alt_nand_flash_ecc_enable(const ALT_NAND_ECC_CORRECTION_t ecc_correction) | |
{ | |
ALT_NAND_CFG_raw_t * cfg = (ALT_NAND_CFG_raw_t *)(nand->cfg); | |
alt_setbits_word(&cfg->ecc_enable, ALT_NAND_CFG_ECC_EN_FLAG_SET_MSK); | |
return ALT_E_SUCCESS; | |
} | |
ALT_STATUS_CODE alt_nand_flash_ecc_disable(void) | |
{ | |
ALT_NAND_CFG_raw_t * cfg = (ALT_NAND_CFG_raw_t *)(nand->cfg); | |
alt_clrbits_word(&cfg->ecc_enable, ALT_NAND_CFG_ECC_EN_FLAG_SET_MSK); | |
return ALT_E_SUCCESS; | |
} | |
ALT_STATUS_CODE alt_nand_flash_ecc_status_get(ALT_NAND_FLASH_ECC_STATUS_t *ecc_status) | |
{ | |
ALT_NAND_ECC_raw_t * ecc = (ALT_NAND_ECC_raw_t *)(nand->ecc); | |
uint32_t status; | |
status = alt_read_word(&ecc->ECCCorInfo_b01); | |
ecc_status->corrected_errors[0] = (status & ALT_NAND_ECC_ECCCORINFO_B01_MAX_ERRORS_B0_SET_MSK) >> ALT_NAND_ECC_ECCCORINFO_B01_MAX_ERRORS_B0_LSB; | |
ecc_status->corrected_errors[1] = (status & ALT_NAND_ECC_ECCCORINFO_B01_MAX_ERRORS_B1_SET_MSK) >> ALT_NAND_ECC_ECCCORINFO_B01_MAX_ERRORS_B1_LSB; | |
ecc_status->uncorrected_error[0] = (status & ALT_NAND_ECC_ECCCORINFO_B01_UNCOR_ERR_B0_SET_MSK) >> ALT_NAND_ECC_ECCCORINFO_B01_UNCOR_ERR_B0_LSB; | |
ecc_status->uncorrected_error[1] = (status & ALT_NAND_ECC_ECCCORINFO_B01_UNCOR_ERR_B1_SET_MSK) >> ALT_NAND_ECC_ECCCORINFO_B01_UNCOR_ERR_B1_LSB; | |
status = alt_read_word(&ecc->ECCCorInfo_b23); | |
ecc_status->corrected_errors[2] = (status & ALT_NAND_ECC_ECCCORINFO_B23_MAX_ERRORS_B2_SET_MSK) >> ALT_NAND_ECC_ECCCORINFO_B23_MAX_ERRORS_B2_LSB; | |
ecc_status->corrected_errors[3] = (status & ALT_NAND_ECC_ECCCORINFO_B23_MAX_ERRORS_B3_SET_MSK) >> ALT_NAND_ECC_ECCCORINFO_B23_MAX_ERRORS_B3_LSB; | |
ecc_status->uncorrected_error[2] = (status & ALT_NAND_ECC_ECCCORINFO_B23_UNCOR_ERR_B2_SET_MSK) >> ALT_NAND_ECC_ECCCORINFO_B23_UNCOR_ERR_B2_LSB; | |
ecc_status->uncorrected_error[3] = (status & ALT_NAND_ECC_ECCCORINFO_B23_UNCOR_ERR_B3_SET_MSK) >> ALT_NAND_ECC_ECCCORINFO_B23_UNCOR_ERR_B3_LSB; | |
return ALT_E_SUCCESS; | |
} | |
uint32_t alt_nand_int_status_get(void) | |
{ | |
uint32_t bank = alt_nand_bank_get(); | |
uint32_t reg = alt_nand_get_interrupt_status_register_addr(bank); | |
return alt_read_word(reg); | |
} | |
ALT_STATUS_CODE alt_nand_int_clear(const uint32_t mask) | |
{ | |
uint32_t bank = alt_nand_bank_get(); | |
uint32_t reg = alt_nand_get_interrupt_status_register_addr(bank); | |
alt_setbits_word(reg, mask); | |
return ALT_E_SUCCESS; | |
} | |
ALT_STATUS_CODE alt_nand_int_disable(const uint32_t mask) | |
{ | |
uint32_t bank = alt_nand_bank_get(); | |
uint32_t reg = alt_nand_get_interrupt_enable_register_addr(bank); | |
alt_clrbits_word(reg, mask); | |
return ALT_E_SUCCESS; | |
} | |
ALT_STATUS_CODE alt_nand_int_enable(const uint32_t mask) | |
{ | |
uint32_t bank = alt_nand_bank_get(); | |
uint32_t reg = alt_nand_get_interrupt_enable_register_addr(bank); | |
alt_setbits_word(reg, mask); | |
return ALT_E_SUCCESS; | |
} | |
uint32_t alt_nand_num_planes_get(void) | |
{ | |
return flash->number_of_planes; | |
} | |
uint32_t alt_nand_number_blocks_of_plane_get(void) | |
{ | |
return (flash->onfi_device_no_of_blocks_per_lun / flash->number_of_planes); | |
} | |
uint32_t alt_nand_first_block_of_next_plane(void) | |
{ | |
return flash->first_block_of_next_plane; | |
} | |
uint32_t alt_nand_num_blocks_get(void) | |
{ | |
return (alt_nand_num_planes_get() * alt_nand_number_blocks_of_plane_get()) ; | |
} | |
uint32_t alt_nand_num_pages_per_block_get(void) | |
{ | |
return flash->pages_per_block; | |
} | |
uint32_t alt_nand_sector_size_get(void) | |
{ | |
return ALT_HHP_NAND_SECTOR_SIZE; | |
} | |
uint32_t alt_nand_spare_size_get(void) | |
{ | |
return flash->spare_size; | |
} | |
bool alt_nand_block_is_bad(const uint32_t block_addr) | |
{ | |
return false; | |
} | |
ALT_STATUS_CODE alt_nand_bad_block_table_get(alt_nand_bad_block_table_t bad_block_table, | |
const uint32_t bad_block_table_len) | |
{ | |
alt_nand_callback_t dma_callback = &alt_nand_dma_page_callback; | |
uint32_t completion_arg = 0; | |
uint32_t total_blocks = (flash->onfi_device_no_of_luns * flash->onfi_device_no_of_blocks_per_lun); | |
uint32_t addr = (total_blocks - 1) << (flash->block_shift + flash->page_shift); // highest block is reseved for bad block table | |
uint32_t buff[ALT_HHP_NAND_PAGE_SIZE]; | |
alt_nand_flash_page_dma_read( addr, 1, buff, sizeof(buff), dma_callback, &completion_arg); | |
for (int i = 0; i <ALT_HHP_NAND_NUM_OF_BLOCK_TOTAL/32; i++) | |
{ | |
bad_block_table[i] = buff[i]; | |
} | |
return ALT_E_SUCCESS; | |
} | |
void alt_nand_rb_pin_mode_set(uint32_t mask) | |
{ | |
ALT_NAND_CFG_raw_t * cfg = (ALT_NAND_CFG_raw_t *)(nand->cfg); | |
alt_setbits_word(&cfg->rb_pin_enabled, mask); | |
} | |
void alt_nand_rb_pin_mode_clear(uint32_t mask) | |
{ | |
ALT_NAND_CFG_raw_t * cfg = (ALT_NAND_CFG_raw_t *)(nand->cfg); | |
alt_clrbits_word(&cfg->rb_pin_enabled, mask); | |
} | |
void alt_nand_reset_bank(uint32_t bank) | |
{ | |
ALT_NAND_CFG_raw_t * cfg = (ALT_NAND_CFG_raw_t *)(nand->cfg); | |
uint32_t interrup_status_register = alt_nand_get_interrupt_status_register_addr(bank); | |
uint32_t device_reset_bank = alt_nand_get_device_reset_register_bank(bank); | |
// Write on clear of all interrupt status | |
alt_write_word(interrup_status_register, ALT_HHP_UINT32_MASK); | |
// Controller sends a RESET command to device | |
alt_setbits_word(&cfg->device_reset, device_reset_bank); | |
alt_nand_poll_for_interrupt_status_register(interrup_status_register, ALT_NAND_STAT_INTR_STAT0_RST_COMP_SET_MSK); | |
// Write on clear of all interrupt status | |
alt_write_word(interrup_status_register, ALT_HHP_UINT32_MASK); | |
} | |
/* | |
* Count the consecutive zero bits (trailing) on the right in parallel | |
* | |
* Some bit fiddleing stuff. | |
* From... http://graphics.stanford.edu/~seander/bithacks.html | |
*/ | |
uint32_t ffs32(uint32_t v) | |
{ | |
uint32_t r = 0; | |
do | |
{ | |
if(v == 0) | |
break; | |
if(v & 0xFFFF0000){v >>= 16;r |= 16;} | |
if(v & 0x0000FF00){v >>= 8;r |= 8;} | |
if(v & 0x000000F0){v >>= 4;r |= 4;} | |
if(v & 0x0000000C){v >>= 2;r |= 2;} | |
if(v & 0x00000002){ ;r |= 1;} | |
} while(0); | |
return(r); | |
} | |
uint32_t alt_nand_poll_for_interrupt_status_register(uint32_t interrup_status_register, uint32_t interrup_status_mask ) | |
{ | |
uint32_t ret; | |
uint32_t i = 0; | |
if ( !s_nand_is_interrupt_enabled ) | |
{ | |
ret = alt_read_word( interrup_status_register ); | |
while( !( ret & (interrup_status_mask | ALT_NAND_INT_STATUS_UNSUP_CMD ) ) ) | |
{ | |
ret = alt_read_word( interrup_status_register ); | |
//printf("<alt_nand_poll for interrupt status register>->interrupt status reg = %08lX\n", ret); | |
if ( ++i == g_nand_interrup_status_register_poll_counter_limit ) | |
{ | |
break; | |
} | |
} | |
if ( ret & ALT_NAND_STAT_INTR_EN0_UNSUP_CMD_SET_MSK ) | |
{ | |
//printf( "Warning: Unsupported CMD interrupt INTR_STATUS_UNSUP_CMD is raised!!!!\n" ); | |
} | |
} | |
else | |
{ | |
ret = s_nand_interrupt_handler( interrup_status_register, interrup_status_mask ); | |
} | |
return ret; | |
} | |
int32_t alt_nand_full_page_read_with_map10(const uint32_t bank, const uint32_t block_addr, const uint32_t page_addr, uint32_t *buffer ) | |
{ | |
int32_t ret = ALT_E_SUCCESS; | |
int32_t res = -1; | |
const uint32_t PAGES_TO_READ = 1; // NOTE: Only 2 bits wide | |
const uint32_t interrup_status_register = alt_nand_get_interrupt_status_register_addr( bank ); | |
const uint32_t addr = alt_nand_compose_map10_cmd_addr( bank, block_addr, page_addr ); | |
alt_write_word( interrup_status_register, ALT_HHP_UINT32_MASK ); | |
// Sets up a pipeline read-ahead of for a read (0x2001) | |
alt_nand_write_indirect( addr, ALT_HHP_NAND_10_OP_READ_PIPE | PAGES_TO_READ ); | |
res = alt_nand_poll_for_interrupt_status_register( interrup_status_register, | |
ALT_NAND_INT_STATUS_TIME_OUT | | |
ALT_NAND_INT_STATUS_LOAD_COMP ); | |
if((res & ALT_NAND_INT_STATUS_LOAD_COMP) != 0) | |
{ | |
alt_nand_full_page_read_with_map10_post_read_with_map01( bank, block_addr, page_addr, buffer); | |
} | |
else | |
{ | |
//printf("FAIL: Timeout loading NAND page. (%08lX,%ld,%ld)\n", res, bank, page_addr); | |
ret = ALT_E_ERROR; | |
} | |
return ret; | |
} | |
uint32_t alt_nand_get_device_reset_register_bank(const uint32_t bank) | |
{ | |
uint32_t ret=0; | |
switch( bank ) | |
{ | |
case 0: | |
ret = ALT_NAND_CFG_DEVICE_RST_BANK0_SET_MSK; | |
break; | |
case 1: | |
ret = ALT_NAND_CFG_DEVICE_RST_BANK1_SET_MSK; | |
break; | |
case 2: | |
ret = ALT_NAND_CFG_DEVICE_RST_BANK2_SET_MSK; | |
break; | |
case 3: | |
ret = ALT_NAND_CFG_DEVICE_RST_BANK3_SET_MSK; | |
break; | |
default: | |
// info_assert(0, "Do not support more than 4 banks"); | |
break; | |
} | |
return ret; | |
} | |
uint32_t alt_nand_get_interrupt_status_register_addr(const uint32_t bank) | |
{ | |
ALT_NAND_STAT_raw_t * stat = (ALT_NAND_STAT_raw_t *)(nand->stat); | |
uint32_t ret=0; | |
switch( bank ) | |
{ | |
case 0: | |
ret = (uint32_t)(&stat->intr_status0); | |
break; | |
case 1: | |
ret = (uint32_t)(&stat->intr_status1); | |
break; | |
case 2: | |
ret = (uint32_t)(&stat->intr_status2); | |
break; | |
case 3: | |
ret = (uint32_t)(&stat->intr_status3); | |
break; | |
default: | |
// info_assert(0, "Do not support more than 4 banks"); | |
break; | |
} | |
return ret; | |
} | |
uint32_t alt_nand_get_interrupt_enable_register_addr(uint32_t bank) | |
{ | |
uint32_t interrup_enable_register=0; | |
ALT_NAND_STAT_raw_t * stat = (ALT_NAND_STAT_raw_t *)(nand->stat); | |
switch( bank ) | |
{ | |
case 0: | |
interrup_enable_register = (uint32_t)(&stat->intr_en0); | |
//printf("interrupt status register: %08lX \n", interrup_enable_register); | |
break; | |
case 1: | |
interrup_enable_register = (uint32_t)(&stat->intr_en1); | |
break; | |
case 2: | |
interrup_enable_register = (uint32_t)(&stat->intr_en2); | |
break; | |
case 3: | |
interrup_enable_register = (uint32_t)(&stat->intr_en3); | |
break; | |
default: | |
// info_assert(0, "Do not support more than 4 banks"); | |
break; | |
} | |
return interrup_enable_register; | |
} | |
uint32_t alt_nand_compose_map01_cmd_addr(const uint32_t bank, const uint32_t block_addr, const uint32_t page_addr) | |
{ | |
const uint32_t BANK_MASK = 0x3; | |
const uint32_t BLOCK_ADDR_MASK = (1 << (23 - flash->block_shift + 1)) - 1; | |
const uint32_t PAGE_ADDR_MASK = (1 << flash->block_shift) - 1; | |
const uint32_t ret = ALT_HHP_NAND_MODE_01 | | |
((bank & BANK_MASK) << ALT_HHP_NAND_ADDR_MAP_BANK_SEL_LSB_INDEX) | | |
((block_addr & BLOCK_ADDR_MASK) << flash->block_shift) | | |
(page_addr & PAGE_ADDR_MASK); | |
return ret; | |
} | |
uint32_t alt_nand_compose_map10_cmd_addr(const uint32_t bank, const uint32_t block_addr, const uint32_t page_addr) | |
{ | |
const uint32_t BANK_MASK = 0x3; | |
const uint32_t BLOCK_ADDR_MASK = (1 << (23 - flash->block_shift + 1)) - 1; | |
const uint32_t PAGE_ADDR_MASK = (1 << flash->block_shift) - 1; | |
const uint32_t ret = ALT_HHP_NAND_MODE_10 | | |
((bank & BANK_MASK) << ALT_HHP_NAND_ADDR_MAP_BANK_SEL_LSB_INDEX) | | |
((block_addr & BLOCK_ADDR_MASK) << flash->block_shift) | | |
(page_addr & PAGE_ADDR_MASK); | |
return ret; | |
} | |
void alt_nand_write_indirect(const uint32_t address, const uint32_t value) | |
{ | |
alt_write_word(nand->ctrl_addr, address); | |
alt_write_word(nand->data_addr, value); | |
} | |
uint32_t alt_nand_read_indirect(const uint32_t address) | |
{ | |
alt_write_word(nand->ctrl_addr, address); | |
return alt_read_word(nand->data_addr); | |
} | |
void alt_nand_full_page_read_with_map10_post_read_with_map01(const uint32_t bank, const uint32_t block_addr, const uint32_t page_addr, uint32_t *buffer) | |
{ | |
const uint32_t addr = alt_nand_compose_map01_cmd_addr( bank, block_addr, page_addr ); | |
uint32_t *cur = buffer; | |
uint32_t i; | |
for( i = 0; i < flash->page_size_32; ++i ) | |
{ | |
*cur++ = alt_nand_read_indirect( addr ); | |
} | |
} | |
int32_t alt_nand_full_page_write_with_map10(const uint32_t bank, const uint32_t block_addr, const uint32_t page_addr, const uint32_t *buffer) | |
{ | |
int32_t ret = 0; | |
int32_t res = -1; | |
const uint32_t PAGES_TO_READ = 1; // NOTE: Only 2 bits wide | |
const uint32_t interrup_status_register = alt_nand_get_interrupt_status_register_addr( bank ); | |
const uint32_t addr = alt_nand_compose_map10_cmd_addr( bank, block_addr, page_addr ); | |
alt_write_word( interrup_status_register, ALT_HHP_UINT32_MASK ); | |
// Sets up a pipeline read-ahead of “01” pages.“W” = 0 for a read (0x2001) | |
alt_nand_write_indirect( addr, ALT_HHP_NAND_10_OP_WRITE_PIPE | PAGES_TO_READ ); | |
// | |
// don't forget this one, it is a hardware bug | |
// | |
for (int i = 0; i < 10000; i++) ; | |
// Temporarily disable status check as RTL doesn't seem to raise any flag. | |
// But, it works. | |
//res = alt_nand_poll_for_interrupt_status_register( interrup_status_register, INTR_STATUS0__TIME_OUT | INTR_STATUS0__PIPE_CPYBCK_CMD_COMP ); | |
res = ALT_NAND_INT_STATUS_PIPE_CPYBCK_CMD_COMP; | |
if((res & ALT_NAND_INT_STATUS_PIPE_CPYBCK_CMD_COMP) != 0) | |
{ | |
alt_nand_full_page_write_with_map10_post_write_with_map01( bank, block_addr, page_addr, buffer ); | |
} | |
else | |
{ | |
//printf("FAIL: Timeout loading NAND page. (%08lX,%ld,%ld)\n", res, bank, page_addr); | |
ret = 1; | |
} | |
return ret; | |
} | |
void alt_nand_full_page_write_with_map10_post_write_with_map01(const uint32_t bank, const uint32_t block_addr, const uint32_t page_addr, const uint32_t *buffer) | |
{ | |
const uint32_t addr = alt_nand_compose_map01_cmd_addr( bank, block_addr, page_addr ); | |
const uint32_t *cur = buffer; | |
uint32_t i; | |
for( i = 0; i < flash->page_size_32; ++i ) | |
{ | |
alt_nand_write_indirect( addr, *cur++ ); | |
} | |
} | |
void alt_nand_set_sysmgr_bootstrap_value( uint32_t bootstrp_inhibit_init, | |
uint32_t bootstrp_inhibit_b0p0_load, | |
uint32_t bootstrp_512b_device, | |
uint32_t bootstrp_two_row_addr_cycles) | |
{ | |
uint32_t settings = ALT_SYSMGR_NAND_BOOTSTRAP_NOINIT_SET(bootstrp_inhibit_init) | | |
ALT_SYSMGR_NAND_BOOTSTRAP_PAGE512_SET(bootstrp_512b_device) | | |
ALT_SYSMGR_NAND_BOOTSTRAP_NOLDB0P0_SET(bootstrp_inhibit_b0p0_load) | | |
ALT_SYSMGR_NAND_BOOTSTRAP_TWOROWADDR_SET(bootstrp_two_row_addr_cycles); | |
alt_write_word(ALT_SYSMGR_NAND_BOOTSTRAP_ADDR, settings); | |
} | |
uint32_t alt_nand_bank_get(void) | |
{ | |
// on SOC, only bank 0 of physical memory is connected | |
return ALT_NAND_FLASH_MEM_BANK_0; | |
} | |
int32_t alt_nand_dma_page_write( const uint32_t bank, const uint32_t block_addr, const uint32_t page_addr, uint32_t mem_addr ) | |
{ | |
int32_t ret = 0; | |
uint32_t res; | |
const uint32_t interrup_status_register = alt_nand_get_interrupt_status_register_addr( bank ); | |
alt_write_word( interrup_status_register, ALT_HHP_UINT32_MASK ); | |
alt_nand_dma_set_enabled( true ); | |
alt_nand_dma_write_cmd_structure( bank, block_addr, page_addr, 1, mem_addr, false, 64); | |
res = alt_nand_poll_for_interrupt_status_register( interrup_status_register, | |
ALT_NAND_INT_STATUS_DMA_CMD_COMP | | |
ALT_NAND_INT_STATUS_PROGRAM_FAIL | | |
ALT_NAND_INT_STATUS_LOCKED_BLK ); | |
// 10.8. Order of interrupt status bits assertion 8. | |
if ( !(res & ALT_NAND_STAT_INTR_EN0_DMA_CMD_COMP_SET_MSK) ) | |
{ | |
//printf( "Error: DMA command is incomplete: 0x%lx\n", res ); | |
ret = res; | |
} | |
alt_nand_dma_set_enabled( false ); | |
return ret; | |
} | |
int32_t alt_nand_dma_page_read( const uint32_t bank, const uint32_t block_addr, const uint32_t page_addr, uint32_t mem_addr ) | |
{ | |
int32_t ret = 0; | |
uint32_t res; | |
const uint32_t interrup_status_register = alt_nand_get_interrupt_status_register_addr( bank ); | |
alt_write_word( interrup_status_register, ALT_HHP_UINT32_MASK ); | |
alt_nand_dma_set_enabled( true ); | |
alt_nand_dma_write_cmd_structure( bank, block_addr, page_addr, 1, mem_addr, true, 64 ); | |
res = alt_nand_poll_for_interrupt_status_register( interrup_status_register, | |
ALT_NAND_INT_STATUS_DMA_CMD_COMP | | |
ALT_NAND_INT_STATUS_ECC_UNCOR_ERR); | |
// 10.8. Order of interrupt status bits assertion 9. | |
if ( !(res & ALT_NAND_INT_STATUS_DMA_CMD_COMP) ) | |
{ | |
//printf( "Error: DMA command is incomplete: 0x%lx\n", res ); | |
ret = res; | |
} | |
alt_nand_dma_set_enabled( false ); | |
return ret; | |
} | |
void alt_nand_dma_set_enabled( int32_t is_enabled ) | |
{ | |
ALT_NAND_DMA_raw_t * dma = (ALT_NAND_DMA_raw_t *)(nand->dma); | |
alt_write_word(&dma->dma_enable, ( is_enabled ? ALT_NAND_DMA_DMA_EN_FLAG_SET_MSK : 0 ) ); | |
alt_read_word( &dma->dma_enable); | |
} | |
void alt_nand_dma_write_cmd_structure( const uint32_t bank, const uint32_t block_addr, const uint32_t page_addr, const uint32_t page_count, uint64_t mem_addr, int32_t is_read_op, const uint32_t burst_len ) | |
{ | |
const uint32_t MODE_BANK_MASK = 0xFF000000; | |
uint32_t addr = alt_nand_compose_map10_cmd_addr( bank, block_addr, page_addr ); | |
// Transaction 1 | |
// Table 7.2. Address Encoding | |
// Table 7.3. Data | |
alt_nand_write_indirect( addr, 0x2000 | (is_read_op ? 0x0 : 0x100) | page_count); | |
// Transaction 2 | |
// Table 7.4. Address Encoding | |
// Table 7.5. Data | |
addr &= MODE_BANK_MASK; | |
addr |= ((uint16_t)(mem_addr >> 16) << 8); | |
alt_nand_write_indirect( addr, 0x2200 ); | |
// Transaction 3 | |
// Table 7.6. Address Encoding | |
// Table 7.7. Data | |
addr &= MODE_BANK_MASK; | |
addr |= ((uint16_t)mem_addr << 8); | |
alt_nand_write_indirect( addr, 0x2300 ); | |
// Transaction 4 | |
// Table 7.8. Address Encoding | |
// Table 7.9. Data | |
addr &= MODE_BANK_MASK; | |
addr |= 0x10000 | burst_len << 8; // Enable INTR_STATUS__DMA_CMD_COMP always. | |
alt_nand_write_indirect( addr, 0x2400); | |
} | |
ALT_STATUS_CODE alt_nand_flash_init_manual(void *user_arg) | |
{ | |
//printf("Entered Custom Init Routine for NAND.\n"); | |
return ALT_E_RESERVED; | |
} | |
void alt_nand_erase_block_callback(ALT_STATUS_CODE status, void *callback_arg) | |
{ | |
//printf("NAND Block Erase Callback is successful.\n"); | |
return; | |
} | |
void alt_nand_dma_page_callback(ALT_STATUS_CODE status, void *callback_arg) | |
{ | |
//printf("NAND DMA read Callback is successful.\n"); | |
return; | |
} | |
uint32_t nand_read_register(const uint32_t offset) | |
{ | |
return alt_read_word(&nand->cfg + offset); | |
} | |