985 lines
30 KiB
C
985 lines
30 KiB
C
|
// *****************************************************************************
|
||
|
//
|
||
|
// File: H2fmi_misc.c
|
||
|
//
|
||
|
// *****************************************************************************
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
// - Add a global for FMC_IF_CTRL values and avoid reading it on the
|
||
|
// fly to get the values.
|
||
|
//
|
||
|
// *****************************************************************************
|
||
|
//
|
||
|
// Copyright (C) 2008 Apple Computer, Inc. All rights reserved.
|
||
|
//
|
||
|
// This document is the property of Apple Computer, Inc.
|
||
|
// It is considered confidential and proprietary.
|
||
|
//
|
||
|
// This document may not be reproduced or transmitted in any form,
|
||
|
// in whole or in part, without the express written permission of
|
||
|
// Apple Computer, Inc.
|
||
|
//
|
||
|
// *****************************************************************************
|
||
|
|
||
|
#include "H2fmi_private.h"
|
||
|
#include "H2fmi_dma.h"
|
||
|
#if SUPPORT_PPN
|
||
|
#include "H2fmi_ppn.h"
|
||
|
#endif
|
||
|
|
||
|
#if (H2FMI_WAIT_USING_ISR)
|
||
|
#include <platform/soc/hwisr.h>
|
||
|
#include <platform/int.h>
|
||
|
#endif
|
||
|
|
||
|
|
||
|
// =============================================================================
|
||
|
// global variable declarations
|
||
|
// =============================================================================
|
||
|
|
||
|
//AES_KEY_SIZE_128
|
||
|
UInt8 nand_whitening_key[16] = {0x92, 0xa7, 0x42, 0xab, 0x08, 0xc9, 0x69, 0xbf, 0x00, 0x6c, 0x94, 0x12, 0xd3, 0xcc, 0x79, 0xa5};
|
||
|
|
||
|
// =============================================================================
|
||
|
// static implementation function declarations
|
||
|
// =============================================================================
|
||
|
|
||
|
#if (H2FMI_WAIT_USING_ISR)
|
||
|
static void h2fmi_init_isr(h2fmi_t* fmi);
|
||
|
static void h2fmi_isr_handler(void* arg);
|
||
|
#endif
|
||
|
|
||
|
// =============================================================================
|
||
|
// public implementation function definitions
|
||
|
// =============================================================================
|
||
|
|
||
|
void h2fmi_init_sys(h2fmi_t* fmi)
|
||
|
{
|
||
|
#if (H2FMI_WAIT_USING_ISR)
|
||
|
h2fmi_init_isr(fmi);
|
||
|
#endif
|
||
|
|
||
|
#if (H2FMI_WAIT_USING_ISR)
|
||
|
// init state machine event
|
||
|
event_init(&fmi->stateMachine.stateMachineDoneEvent, EVENT_FLAG_AUTO_UNSIGNAL, false);
|
||
|
#endif
|
||
|
|
||
|
// FMI manages ECC interaction, so make certain we aren't
|
||
|
// unmasking any interrupt sources
|
||
|
h2fmi_wr(fmi, ECC_MASK, 0x0);
|
||
|
|
||
|
fmi->h2fmi_ppn_panic_on_status_timeout = TRUE32;
|
||
|
}
|
||
|
|
||
|
void h2fmi_prepare_wait_status(h2fmi_t* fmi, UInt8 io_mask, UInt8 io_cond)
|
||
|
{
|
||
|
UInt32 wReadStatusCmd;
|
||
|
|
||
|
// turn off RBBEN and configure it to use look for mask value on I/O lines
|
||
|
h2fmi_set_if_ctrl(fmi, (~FMC_IF_CTRL__RBBEN & h2fmi_rd(fmi, FMC_IF_CTRL)));
|
||
|
h2fmi_wr(fmi, FMC_RBB_CONFIG, (FMC_RBB_CONFIG__POL(io_cond) |
|
||
|
FMC_RBB_CONFIG__POS(io_mask)));
|
||
|
|
||
|
if ( fmiWrite==fmi->stateMachine.currentMode )
|
||
|
{
|
||
|
switch ( fmi->stateMachine.wVendorType )
|
||
|
{
|
||
|
case _kVS_TOSHIBA_2P:
|
||
|
wReadStatusCmd = NAND_CMD__TOS_STATUS_71h;
|
||
|
break;
|
||
|
case _kVS_SAMSUNG_2P_2D:
|
||
|
wReadStatusCmd = ( 0==(fmi->stateMachine.page_idx&2) ? NAND_CMD__READ_SS_DIE1 : NAND_CMD__READ_SS_DIE2 );
|
||
|
break;
|
||
|
case _kVS_SAMSUNG_2D:
|
||
|
wReadStatusCmd = ( 0==(fmi->stateMachine.page_idx&1) ? NAND_CMD__READ_SS_DIE1 : NAND_CMD__READ_SS_DIE2 );
|
||
|
break;
|
||
|
case _kVS_NONE:
|
||
|
default:
|
||
|
wReadStatusCmd = NAND_CMD__READ_STATUS;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
wReadStatusCmd = NAND_CMD__READ_STATUS;
|
||
|
}
|
||
|
|
||
|
// exec Read Status command cycle
|
||
|
h2fmi_wr(fmi, FMC_CMD, FMC_CMD__CMD1(wReadStatusCmd));
|
||
|
h2fmi_wr(fmi, FMC_RW_CTRL, FMC_RW_CTRL__CMD1_MODE);
|
||
|
|
||
|
// busy wait until command cycle completed
|
||
|
h2fmi_busy_wait(fmi, FMC_STATUS, FMC_STATUS__CMD1DONE, FMC_STATUS__CMD1DONE);
|
||
|
h2fmi_wr(fmi, FMC_STATUS, FMC_STATUS__CMD1DONE);
|
||
|
|
||
|
// clear the FMC_STATUS__NSRBBDONE bit in FMC_STATUS
|
||
|
h2fmi_clear_interrupts_and_reset_masks(fmi);
|
||
|
|
||
|
// set up read cycle with REBHOLD to wait for ready bit to be
|
||
|
// set on I/O lines
|
||
|
h2fmi_wr(fmi, FMC_DATANUM, FMC_DATANUM__NUM(0));
|
||
|
h2fmi_wr(fmi, FMC_RW_CTRL, (FMC_RW_CTRL__REBHOLD | FMC_RW_CTRL__RD_MODE));
|
||
|
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_wait_status(h2fmi_t* fmi,
|
||
|
UInt8 io_mask,
|
||
|
UInt8 io_cond,
|
||
|
UInt8* status)
|
||
|
{
|
||
|
BOOL32 result = TRUE32;
|
||
|
|
||
|
h2fmi_prepare_wait_status(fmi, io_mask, io_cond);
|
||
|
|
||
|
#if (H2FMI_WAIT_USING_ISR)
|
||
|
{
|
||
|
// use NSRBBDone interrupt source to wait for I/O ready state
|
||
|
h2fmi_wr(fmi, FMC_INTMASK, FMC_INTMASK__NSRBBDONE);
|
||
|
h2fmi_wr(fmi, FMI_INT_EN, FMI_INT_EN__FMC_NSRBBDONE);
|
||
|
if (!event_wait_timeout(&fmi->isr_event, H2FMI_PAGE_TIMEOUT_MICROS))
|
||
|
{
|
||
|
h2fmi_fail(result);
|
||
|
}
|
||
|
else if (!(fmi->isr_condition & FMI_INT_PEND__FMC_NSRBBDONE))
|
||
|
{
|
||
|
h2fmi_fail(result);
|
||
|
}
|
||
|
}
|
||
|
#else
|
||
|
{
|
||
|
// wait until NSRBB done signaled
|
||
|
if (!h2fmi_wait_done(fmi, FMC_STATUS,
|
||
|
FMC_STATUS__NSRBBDONE,
|
||
|
FMC_STATUS__NSRBBDONE))
|
||
|
{
|
||
|
h2fmi_fail(result);
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// if status collection desired, grab it while REBHOLD still active
|
||
|
if (NULL != status)
|
||
|
{
|
||
|
*status = (UInt8)h2fmi_rd(fmi, FMC_NAND_STATUS);
|
||
|
}
|
||
|
|
||
|
// clear FMC_RW_CTRL, thereby releasing REBHOLD and completing
|
||
|
// current read cycle
|
||
|
h2fmi_wr(fmi, FMC_RW_CTRL, 0);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
void h2fmi_nand_read_chipid(h2fmi_t* fmi,
|
||
|
h2fmi_ce_t ce,
|
||
|
h2fmi_chipid_t* id)
|
||
|
{
|
||
|
UInt32 if_ctrl;
|
||
|
const UInt32 cmd1_addr_done = (FMC_STATUS__ADDRESSDONE |
|
||
|
FMC_STATUS__CMD1DONE);
|
||
|
|
||
|
if_ctrl = h2fmi_rd(fmi, FMC_IF_CTRL);
|
||
|
|
||
|
h2fmi_set_if_ctrl(fmi, (FMC_IF_CTRL__DCCYCLE(0) |
|
||
|
FMC_IF_CTRL__REB_SETUP(FMC_IF_CTRL__TIMING_MAX_CLOCKS) |
|
||
|
FMC_IF_CTRL__REB_HOLD(FMC_IF_CTRL__TIMING_MAX_CLOCKS) |
|
||
|
FMC_IF_CTRL__WEB_SETUP(FMC_IF_CTRL__TIMING_MAX_CLOCKS) |
|
||
|
FMC_IF_CTRL__WEB_HOLD(FMC_IF_CTRL__TIMING_MAX_CLOCKS)));
|
||
|
|
||
|
// enable specified nand device
|
||
|
h2fmi_fmc_enable_ce(fmi, ce);
|
||
|
|
||
|
// set up Read Id command and address cycles
|
||
|
h2fmi_wr(fmi, FMC_CMD, FMC_CMD__CMD1(NAND_CMD__READ_ID));
|
||
|
h2fmi_wr(fmi, FMC_ADDR0, FMC_ADDR0__SEQ0(0x00));
|
||
|
h2fmi_wr(fmi, FMC_ADDRNUM, FMC_ADDRNUM__NUM(0));
|
||
|
h2fmi_wr(fmi, FMC_RW_CTRL, (FMC_RW_CTRL__ADDR_MODE |
|
||
|
FMC_RW_CTRL__CMD1_MODE));
|
||
|
|
||
|
// wait until cmd & addr completion
|
||
|
h2fmi_busy_wait(fmi, FMC_STATUS, cmd1_addr_done, cmd1_addr_done);
|
||
|
h2fmi_wr(fmi, FMC_STATUS, cmd1_addr_done);
|
||
|
|
||
|
// read correct number of id bytes
|
||
|
h2fmi_fmc_read_data(fmi, H2FMI_NAND_ID_SIZE, (UInt8*)id);
|
||
|
|
||
|
// disable all nand devices
|
||
|
h2fmi_fmc_disable_all_ces(fmi);
|
||
|
|
||
|
h2fmi_set_if_ctrl(fmi, if_ctrl);
|
||
|
}
|
||
|
|
||
|
// Builds the valid_ces bitmask for all chip IDs that match id_filter
|
||
|
void h2fmi_build_ce_bitmask(h2fmi_t* fmi,
|
||
|
h2fmi_chipid_t* ids,
|
||
|
h2fmi_chipid_t* id_filter,
|
||
|
UInt8 addr)
|
||
|
{
|
||
|
UInt32 wIdx;
|
||
|
h2fmi_chipid_t all_zeros = {0};
|
||
|
|
||
|
// skip it if the filter is zeroes
|
||
|
if (!WMR_MEMCMP(all_zeros, id_filter, sizeof(h2fmi_chipid_t)))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
for (wIdx = 0; wIdx < H2FMI_MAX_CE_TOTAL; ++wIdx)
|
||
|
{
|
||
|
if (WMR_MEMCMP(ids[wIdx], *id_filter, H2FMI_NAND_ID_COMPARISON_SIZE) == 0)
|
||
|
{
|
||
|
// Chip ID matches
|
||
|
if(CHIPID_ADDR == addr)
|
||
|
{
|
||
|
_WMR_PRINT("[NAND] Found Chip ID %02X %02X %02X %02X %02X %02X on FMI%d:CE%d\n",
|
||
|
(UInt32)ids[wIdx][0],
|
||
|
(UInt32)ids[wIdx][1],
|
||
|
(UInt32)ids[wIdx][2],
|
||
|
(UInt32)ids[wIdx][3],
|
||
|
(UInt32)ids[wIdx][4],
|
||
|
(UInt32)ids[wIdx][5],
|
||
|
fmi->bus_id,
|
||
|
wIdx);
|
||
|
}
|
||
|
fmi->valid_ces |= (1UL << wIdx);
|
||
|
fmi->num_of_ce += 1;
|
||
|
}
|
||
|
else if(!h2fmi_is_chipid_invalid(&ids[wIdx]))
|
||
|
{
|
||
|
_WMR_PRINT("[NAND] Ignoring mismatched Chip ID %02X %02X %02X %02X %02X %02X on FMI%d:CE%d\n",
|
||
|
(UInt32)ids[wIdx][0],
|
||
|
(UInt32)ids[wIdx][1],
|
||
|
(UInt32)ids[wIdx][2],
|
||
|
(UInt32)ids[wIdx][3],
|
||
|
(UInt32)ids[wIdx][4],
|
||
|
(UInt32)ids[wIdx][5],
|
||
|
fmi->bus_id,
|
||
|
wIdx);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_nand_read_id_all(h2fmi_t* fmi,
|
||
|
h2fmi_chipid_t* ids,
|
||
|
UInt8 addr)
|
||
|
{
|
||
|
UInt32 wIdx;
|
||
|
BOOL32 result = TRUE32;
|
||
|
|
||
|
WMR_MEMSET(ids, 0, (sizeof(h2fmi_chipid_t) * H2FMI_MAX_CE_TOTAL));
|
||
|
fmi->valid_ces = 0;
|
||
|
fmi->num_of_ce = 0;
|
||
|
|
||
|
for (wIdx = 0; wIdx < H2FMI_MAX_CE_TOTAL; wIdx++)
|
||
|
{
|
||
|
if (!h2fmi_nand_read_id(fmi, wIdx, &ids[wIdx], addr))
|
||
|
{
|
||
|
h2fmi_fail(result);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_reset_and_read_chipids(h2fmi_t* fmi,
|
||
|
h2fmi_chipid_t* ids,
|
||
|
UInt8 addr)
|
||
|
{
|
||
|
BOOL32 result = TRUE32;
|
||
|
|
||
|
if (!h2fmi_nand_reset_all(fmi))
|
||
|
{
|
||
|
h2fmi_fail(result);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result = h2fmi_nand_read_id_all(fmi, ids, addr);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_nand_reset_all(h2fmi_t* fmi)
|
||
|
{
|
||
|
BOOL32 result = TRUE32;
|
||
|
|
||
|
UInt32 ce_idx;
|
||
|
|
||
|
for (ce_idx = 0; ce_idx < H2FMI_MAX_CE_TOTAL; ce_idx++)
|
||
|
{
|
||
|
// reset next device
|
||
|
if (!h2fmi_nand_reset(fmi, ce_idx))
|
||
|
{
|
||
|
// fail, but continue on to next device regardless
|
||
|
h2fmi_fail(result);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// <rdar://problem/8023322> Fix wait periods for initial reset
|
||
|
// wait 50 ms to allow nand to reset
|
||
|
WMR_SLEEP_US(H2FMI_MAX_RESET_DELAY);
|
||
|
|
||
|
#if SUPPORT_PPN
|
||
|
#if !H2FMI_IOP
|
||
|
if (fmi->is_ppn)
|
||
|
{
|
||
|
fmi->if_ctrl = H2FMI_IF_CTRL_LOW_POWER;
|
||
|
restoreTimingRegs(fmi);
|
||
|
}
|
||
|
#endif // !H2FMI_IOP
|
||
|
#endif // SUPPORT_PPN
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
#if FMI_VERSION == 0
|
||
|
UInt32 h2fmi_config_sectors_to_page_size(h2fmi_t* fmi)
|
||
|
{
|
||
|
UInt32 fmi_config = 0xFFFFFFFF;
|
||
|
|
||
|
// configure page size based device id table
|
||
|
switch (fmi->sectors_per_page)
|
||
|
{
|
||
|
case (4):
|
||
|
fmi_config = FMI_CONFIG__PAGE_SIZE__2K;
|
||
|
break;
|
||
|
|
||
|
case (8):
|
||
|
fmi_config = FMI_CONFIG__PAGE_SIZE__4K;
|
||
|
break;
|
||
|
|
||
|
case (16):
|
||
|
fmi_config = FMI_CONFIG__PAGE_SIZE__8K;
|
||
|
break;
|
||
|
|
||
|
case (1):
|
||
|
fmi_config = FMI_CONFIG__PAGE_SIZE__512;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
WMR_PRINT(ERROR,
|
||
|
"[FIL:ERR] (512 x %d)-byte page size unsupported\n",
|
||
|
fmi->sectors_per_page);
|
||
|
}
|
||
|
|
||
|
return fmi_config;
|
||
|
}
|
||
|
#endif // S5L8920X
|
||
|
|
||
|
void h2fmi_set_raw_write_data_format(h2fmi_t* fmi, UInt32 spare_per_sector)
|
||
|
{
|
||
|
#if FMI_VERSION == 0
|
||
|
// Use 512 bytes per page when writing. The H2P FMI_CONFIG register doesn't give us enough
|
||
|
// bits in the META_PER_PAGE field to do an entire 4KB page's worth of spare so we'll
|
||
|
// do four 512 byte reads in a row instead.
|
||
|
//
|
||
|
// If we have no spare, use LBA_AB rather than BYPASS_ECC. Otherwise FMI will hang waiting
|
||
|
// for a DMA operation on the meta channel that'll never come.
|
||
|
h2fmi_wr(fmi, FMI_CONFIG, ((spare_per_sector ? FMI_CONFIG__LBA_MODE__BYPASS_ECC : FMI_CONFIG__LBA_MODE__LBA_TYPE_AB) |
|
||
|
FMI_CONFIG__META_PER_ENVELOPE(spare_per_sector) |
|
||
|
FMI_CONFIG__META_PER_PAGE(spare_per_sector) |
|
||
|
FMI_CONFIG__PAGE_SIZE__512));
|
||
|
#else
|
||
|
// With H2A and later we have arbitrary control over page and sector size. We can make our life easier
|
||
|
// by just setting the sector size to be sector_data_size + sector_spare_size and pusing
|
||
|
// everything through the data FIFO.
|
||
|
h2fmi_wr(fmi, FMI_CONFIG, FMI_CONFIG__ECC_CORRECTABLE_BITS( 0 ) | FMI_CONFIG__DMA_BURST__32_CYCLES);
|
||
|
h2fmi_wr(fmi, FMI_DATA_SIZE, (FMI_DATA_SIZE__META_BYTES_PER_SECTOR( spare_per_sector ) |
|
||
|
FMI_DATA_SIZE__META_BYTES_PER_PAGE( spare_per_sector ) |
|
||
|
FMI_DATA_SIZE__BYTES_PER_SECTOR( 1024 ) |
|
||
|
FMI_DATA_SIZE__SECTORS_PER_PAGE( 1 )));
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void h2fmi_set_bootpage_data_format(h2fmi_t* fmi)
|
||
|
{
|
||
|
#if FMI_VERSION == 0
|
||
|
h2fmi_wr(fmi, FMI_CONFIG, (FMI_CONFIG__LBA_MODE__NORMAL |
|
||
|
FMI_CONFIG__META_PER_ENVELOPE(0) |
|
||
|
FMI_CONFIG__META_PER_PAGE(0) |
|
||
|
FMI_CONFIG__PAGE_SIZE__512 |
|
||
|
FMI_CONFIG__ECC_MODE__16BIT |
|
||
|
FMI_CONFIG__DMA_BURST__32_CYCLES));
|
||
|
#elif FMI_VERSION <= 2
|
||
|
h2fmi_wr(fmi, FMI_CONFIG, FMI_CONFIG__ECC_CORRECTABLE_BITS( MAX_ECC_CORRECTION ));
|
||
|
h2fmi_wr(fmi, FMI_DATA_SIZE, (FMI_DATA_SIZE__META_BYTES_PER_SECTOR(0) |
|
||
|
FMI_DATA_SIZE__META_BYTES_PER_PAGE(0) |
|
||
|
FMI_DATA_SIZE__BYTES_PER_SECTOR(512) |
|
||
|
FMI_DATA_SIZE__SECTORS_PER_PAGE(1)));
|
||
|
#elif FMI_VERSION <= 3
|
||
|
h2fmi_wr(fmi, FMI_CONFIG, FMI_CONFIG__ECC_CORRECTABLE_BITS( MAX_ECC_CORRECTION ));
|
||
|
h2fmi_wr(fmi, FMI_DATA_SIZE, (FMI_DATA_SIZE__META_BYTES_PER_SECTOR(0) |
|
||
|
FMI_DATA_SIZE__META_BYTES_PER_PAGE(0) |
|
||
|
FMI_DATA_SIZE__BYTES_PER_SECTOR(512) |
|
||
|
FMI_DATA_SIZE__SECTORS_PER_PAGE(1)));
|
||
|
// Do not use written-mark
|
||
|
h2fmi_wr(fmi, ECC_CON1, (ECC_CON1__ERROR_ALERT_LEVEL(0) |
|
||
|
ECC_CON1__INT_ENABLE(0) |
|
||
|
ECC_CON1__ALLOWED_STUCK_BIT_IN_FP(MAX_ECC_CORRECTION)));
|
||
|
#else
|
||
|
// Use randomizer
|
||
|
h2fmi_wr(fmi, FMI_CONFIG, FMI_CONFIG__ECC_CORRECTABLE_BITS( MAX_ECC_CORRECTION ) |
|
||
|
FMI_CONFIG__SCRAMBLE_SEED |
|
||
|
FMI_CONFIG__ENABLE_WHITENING);
|
||
|
|
||
|
h2fmi_wr(fmi, FMI_DATA_SIZE, (FMI_DATA_SIZE__META_BYTES_PER_SECTOR(0) |
|
||
|
FMI_DATA_SIZE__META_BYTES_PER_PAGE(0) |
|
||
|
FMI_DATA_SIZE__BYTES_PER_SECTOR(512) |
|
||
|
FMI_DATA_SIZE__SECTORS_PER_PAGE(1)));
|
||
|
// Fewer stuck bits are allowed
|
||
|
h2fmi_wr(fmi, ECC_CON1, (ECC_CON1__ERROR_ALERT_LEVEL(0) |
|
||
|
ECC_CON1__INT_ENABLE(0) |
|
||
|
ECC_CON1__ALLOWED_STUCK_BIT_IN_FP(2)));
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
void h2fmi_set_page_format(h2fmi_t* fmi)
|
||
|
{
|
||
|
h2fmi_wr(fmi, FMI_CONFIG, fmi->fmi_config_reg);
|
||
|
# if FMI_VERSION > 0
|
||
|
h2fmi_wr(fmi, FMI_DATA_SIZE, fmi->fmi_data_size_reg);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
UInt32 eccBytesPerSector;
|
||
|
UInt32 eccCorrectableBits;
|
||
|
} h2fmi_spare_layout;
|
||
|
|
||
|
#if FMI_VERSION > 0
|
||
|
// H2A, H3P
|
||
|
static const h2fmi_spare_layout _h2fmi_spare_layout [] =
|
||
|
{
|
||
|
{ 53, 30 },
|
||
|
{ 51, 29 },
|
||
|
{ 44, 25 },
|
||
|
{ 28, 16 },
|
||
|
{ 27, 15 },
|
||
|
{ 0, 0 }
|
||
|
};
|
||
|
#else
|
||
|
// H2P
|
||
|
static const h2fmi_spare_layout _h2fmi_spare_layout [] =
|
||
|
{
|
||
|
{ 26, 16 },
|
||
|
{ 13, 8 },
|
||
|
{ 0, 0 }
|
||
|
};
|
||
|
#endif
|
||
|
|
||
|
UInt32 h2fmi_calculate_ecc_bits(h2fmi_t* fmi)
|
||
|
{
|
||
|
UInt32 eccMode = 0;
|
||
|
const h2fmi_spare_layout* layout = _h2fmi_spare_layout;
|
||
|
const UInt32 bytesForEccPerSector = ((fmi->bytes_per_spare - fmi->valid_bytes_per_meta) /
|
||
|
(fmi->bytes_per_page / H2FMI_BYTES_PER_SECTOR));
|
||
|
for( ; layout->eccBytesPerSector != 0; layout++ )
|
||
|
{
|
||
|
if( layout->eccBytesPerSector <= bytesForEccPerSector )
|
||
|
{
|
||
|
eccMode = layout->eccCorrectableBits;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
WMR_ASSERT(eccMode != 0);
|
||
|
|
||
|
return eccMode;
|
||
|
|
||
|
}
|
||
|
|
||
|
UInt32 h2fmi_calculate_ecc_output_bytes(h2fmi_t* fmi)
|
||
|
{
|
||
|
const h2fmi_spare_layout* layout = _h2fmi_spare_layout;
|
||
|
const UInt32 bytesForEccPerSector = ((fmi->bytes_per_spare - fmi->valid_bytes_per_meta) /
|
||
|
(fmi->bytes_per_page / H2FMI_BYTES_PER_SECTOR));
|
||
|
for( ; layout->eccBytesPerSector != 0; layout++ )
|
||
|
{
|
||
|
if( layout->eccBytesPerSector <= bytesForEccPerSector )
|
||
|
{
|
||
|
return layout->eccBytesPerSector;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
UInt32 h2fmi_calculate_fmi_config(h2fmi_t* fmi)
|
||
|
{
|
||
|
UInt32 ret;
|
||
|
|
||
|
#if FMI_VERSION == 0
|
||
|
UInt32 eccMode;
|
||
|
eccMode = (fmi->correctable_bits == 16) ? FMI_CONFIG__ECC_MODE__16BIT :
|
||
|
FMI_CONFIG__ECC_MODE__8BIT;
|
||
|
|
||
|
ret = (FMI_CONFIG__LBA_MODE__NORMAL |
|
||
|
FMI_CONFIG__META_PER_ENVELOPE(fmi->valid_bytes_per_meta) |
|
||
|
FMI_CONFIG__META_PER_PAGE(fmi->valid_bytes_per_meta) |
|
||
|
h2fmi_config_sectors_to_page_size(fmi) |
|
||
|
FMI_CONFIG__DMA_BURST__8_CYCLES |
|
||
|
eccMode);
|
||
|
#else
|
||
|
ret = (FMI_CONFIG__ECC_CORRECTABLE_BITS( fmi->correctable_bits ) |
|
||
|
FMI_CONFIG__DMA_BURST__32_CYCLES);
|
||
|
#endif
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#if FMI_VERSION > 0
|
||
|
UInt32 h2fmi_calculate_fmi_data_size(h2fmi_t* fmi)
|
||
|
{
|
||
|
UInt32 ret;
|
||
|
|
||
|
ret = (FMI_DATA_SIZE__META_BYTES_PER_SECTOR( fmi->valid_bytes_per_meta ) |
|
||
|
FMI_DATA_SIZE__META_BYTES_PER_PAGE( fmi->valid_bytes_per_meta ) |
|
||
|
FMI_DATA_SIZE__BYTES_PER_SECTOR( H2FMI_BYTES_PER_SECTOR ) |
|
||
|
FMI_DATA_SIZE__SECTORS_PER_PAGE( fmi->sectors_per_page ));
|
||
|
return ret;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
// =============================================================================
|
||
|
// isr-only static implementation function definitions
|
||
|
// =============================================================================
|
||
|
|
||
|
#if (H2FMI_WAIT_USING_ISR)
|
||
|
|
||
|
static void h2fmi_init_isr(h2fmi_t* fmi)
|
||
|
{
|
||
|
WMR_PRINT(IRQ, "initializing interrupts for FMI%d\n",
|
||
|
h2fmi_bus_index(fmi));
|
||
|
|
||
|
// lookup fmi interrupt vector to use based upon fmi block
|
||
|
UInt32 vector = h2fmi_select_by_bus(fmi, INT_FMI0, INT_FMI1);
|
||
|
|
||
|
// init isr event and condition
|
||
|
event_init(&fmi->isr_event, EVENT_FLAG_AUTO_UNSIGNAL, false);
|
||
|
fmi->isr_condition = 0;
|
||
|
#if FMISS_ENABLED
|
||
|
fmi->isr_handler = NULL;
|
||
|
#endif // FMISS_ENABLED
|
||
|
|
||
|
// clear out any preexisting interrupts and mask sources
|
||
|
h2fmi_clear_interrupts_and_reset_masks(fmi);
|
||
|
|
||
|
// install interrupt handler
|
||
|
set_int_type(vector, INT_TYPE_IRQ | INT_TYPE_LEVEL);
|
||
|
install_int_handler(vector, h2fmi_isr_handler, (void*)fmi);
|
||
|
unmask_int(vector);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void h2fmi_default_isr_handler(h2fmi_t* fmi)
|
||
|
{
|
||
|
// capture condition triggering isr
|
||
|
fmi->isr_condition = h2fmi_rd(fmi, FMI_INT_PEND);
|
||
|
|
||
|
// clear all interrupts and mask sources
|
||
|
h2fmi_clear_interrupts_and_reset_masks(fmi);
|
||
|
|
||
|
// signal isr event
|
||
|
event_signal(&fmi->isr_event);
|
||
|
}
|
||
|
|
||
|
typedef void (*h2fmi_dispatch_handler)(h2fmi_t* fmi);
|
||
|
|
||
|
static const h2fmi_dispatch_handler h2fmi_isr_dispatchTable[] = {
|
||
|
h2fmi_default_isr_handler, // fmiNone
|
||
|
h2fmi_handle_read_ISR_state_machine, // fmiRead
|
||
|
h2fmi_handle_write_ISR_state_machine // fmiWrite
|
||
|
};
|
||
|
|
||
|
static void h2fmi_isr_handler(void* arg)
|
||
|
{
|
||
|
h2fmi_t* fmi = (h2fmi_t*)arg;
|
||
|
|
||
|
#if FMISS_ENABLED
|
||
|
if (NULL != fmi->isr_handler)
|
||
|
{
|
||
|
fmi->isr_handler(fmi);
|
||
|
}
|
||
|
else
|
||
|
#endif // FMISS_ENABLED
|
||
|
{
|
||
|
WMR_ASSERT( fmi->stateMachine.currentMode <= fmiWrite );
|
||
|
h2fmi_isr_dispatchTable[ fmi->stateMachine.currentMode ]( fmi );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
void h2fmi_clear_interrupts_and_reset_masks(h2fmi_t* fmi)
|
||
|
{
|
||
|
// mask further interrupts
|
||
|
h2fmi_wr(fmi, FMC_INTMASK, 0x0);
|
||
|
h2fmi_wr(fmi, FMI_INT_EN, 0x0);
|
||
|
|
||
|
// clear interrupt source flags
|
||
|
h2fmi_wr(fmi, FMC_STATUS, (FMC_STATUS__EMERGENCY2 |
|
||
|
FMC_STATUS__EMERGENCY01 |
|
||
|
FMC_STATUS__RDATADIRTY |
|
||
|
FMC_STATUS__RBBDONE7 |
|
||
|
FMC_STATUS__RBBDONE6 |
|
||
|
FMC_STATUS__RBBDONE5 |
|
||
|
FMC_STATUS__RBBDONE4 |
|
||
|
FMC_STATUS__RBBDONE3 |
|
||
|
FMC_STATUS__RBBDONE2 |
|
||
|
FMC_STATUS__RBBDONE1 |
|
||
|
FMC_STATUS__RBBDONE0 |
|
||
|
FMC_STATUS__TIMEOUT |
|
||
|
FMC_STATUS__NSERR |
|
||
|
FMC_STATUS__NSRBBDONE |
|
||
|
FMC_STATUS__TRANSDONE |
|
||
|
FMC_STATUS__ADDRESSDONE |
|
||
|
FMC_STATUS__CMD3DONE |
|
||
|
FMC_STATUS__CMD2DONE |
|
||
|
FMC_STATUS__CMD1DONE));
|
||
|
h2fmi_wr(fmi, FMI_INT_PEND, (FMI_INT_PEND__META_FIFO_OVERFLOW |
|
||
|
FMI_INT_PEND__META_FIFO_UNDERFLOW |
|
||
|
FMI_INT_PEND__LAST_FMC_DONE |
|
||
|
FMI_INT_PEND__TRANSACTION_COMPLETE));
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Maps the absolute CE to the required register bit # and
|
||
|
* provides which bus number that CE is on.
|
||
|
*
|
||
|
* Note that CE's are absolute on our system -- CE0 is ALWAYS
|
||
|
* from FMC0, regardless of which bus we are dealing with. CE8
|
||
|
* is ALWAYS bit zero of FMC1 as well.
|
||
|
*
|
||
|
* @param ce - desired absolute CE #
|
||
|
* @param p_wTargetBus pointer to store translated bus number.
|
||
|
* @param p_targetCE - pointer to store translated CE bit.
|
||
|
*/
|
||
|
static inline void h2fmi_calc_bus_ce(h2fmi_ce_t ce,
|
||
|
UInt32* p_wTargetBus,
|
||
|
h2fmi_ce_t* p_targetCE)
|
||
|
{
|
||
|
*p_wTargetBus = ( ce >> H2FMI_MAX_CE_PER_BUS_POW2 );
|
||
|
*p_targetCE = ( ce & (H2FMI_MAX_CE_PER_BUS - 1) );
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Enable the specified CE while ensuring that only one CE is
|
||
|
* enabled at a time on the given bus.
|
||
|
*
|
||
|
* @param fmi - context
|
||
|
* @param ce - Absolute CE #
|
||
|
*/
|
||
|
void h2fmi_fmc_enable_ce(h2fmi_t* fmi, h2fmi_ce_t ce)
|
||
|
{
|
||
|
h2fmi_ce_t targetCE;
|
||
|
UInt32 wTargetBus;
|
||
|
|
||
|
h2fmi_calc_bus_ce(ce, &wTargetBus, &targetCE);
|
||
|
|
||
|
if(wTargetBus == fmi->bus_id)
|
||
|
{
|
||
|
h2fmi_wr(fmi, FMC_CE_CTRL, FMC_CE_CTRL__CEB(targetCE));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
h2fmi_fmc_disable_all_ces(fmi);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Disable ONLY the specified CE (using read/modify/write).
|
||
|
*
|
||
|
* @param ce - Absolute CE #
|
||
|
*/
|
||
|
void h2fmi_fmc_disable_ce(h2fmi_t* fmi, h2fmi_ce_t ce)
|
||
|
{
|
||
|
h2fmi_ce_t targetCE;
|
||
|
UInt32 wTargetBus;
|
||
|
|
||
|
h2fmi_calc_bus_ce(ce, &wTargetBus, &targetCE);
|
||
|
|
||
|
if(wTargetBus == fmi->bus_id)
|
||
|
{
|
||
|
UInt32 val = h2fmi_rd(fmi, FMC_CE_CTRL);
|
||
|
h2fmi_wr(fmi, FMC_CE_CTRL, val & ~FMC_CE_CTRL__CEB(targetCE));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void h2fmi_fmc_disable_all_ces(h2fmi_t* fmi)
|
||
|
{
|
||
|
h2fmi_wr(fmi, FMC_CE_CTRL, 0);
|
||
|
}
|
||
|
|
||
|
void h2fmi_prepare_for_ready_busy_interrupt(h2fmi_t* fmi)
|
||
|
{
|
||
|
h2fmi_prepare_wait_status(fmi,
|
||
|
NAND_STATUS__DATA_CACHE_RB,
|
||
|
NAND_STATUS__DATA_CACHE_RB_READY);
|
||
|
|
||
|
// use NSRBBDone interrupt source to wait for I/O ready state
|
||
|
h2fmi_wr(fmi, FMC_INTMASK, FMC_INTMASK__NSRBBDONE);
|
||
|
h2fmi_wr(fmi, FMI_INT_EN, FMI_INT_EN__FMC_NSRBBDONE);
|
||
|
|
||
|
}
|
||
|
|
||
|
void h2fmi_set_page_format_and_ECC_level(h2fmi_t* fmi, UInt32 wErrorAlertLevel)
|
||
|
{
|
||
|
// configure FMI with page format
|
||
|
h2fmi_set_page_format(fmi);
|
||
|
|
||
|
#if FMI_VERSION == 0
|
||
|
h2fmi_wr(fmi, ECC_CON1, (ECC_CON1__ERROR_ALERT_LEVEL(wErrorAlertLevel) |
|
||
|
ECC_CON1__INT_ENABLE(0)));
|
||
|
#elif FMI_VERSION <= 2
|
||
|
h2fmi_wr(fmi, ECC_CON1, (ECC_CON1__ERROR_ALERT_LEVEL(wErrorAlertLevel) |
|
||
|
ECC_CON1__INT_ENABLE(0) |
|
||
|
ECC_CON1__ALLOWED_STUCK_BIT_IN_FP(fmi->correctable_bits)));
|
||
|
#else
|
||
|
h2fmi_wr(fmi, ECC_CON1, (ECC_CON1__ERROR_ALERT_LEVEL(wErrorAlertLevel) |
|
||
|
ECC_CON1__INT_ENABLE(0) |
|
||
|
ECC_CON1__ALLOWED_STUCK_BIT_IN_FP(fmi->correctable_bits) |
|
||
|
ECC_CON1__IMPLICIT_WRITE_MARK));
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Checks the current state of the (previously selected) CE,
|
||
|
* looking to see if it is ready or busy. Uses the
|
||
|
* stateMachine's "waitStartTick" and "wPageTimeoutTicks" to
|
||
|
* determine if the wait has timed out. Note that both of these
|
||
|
* fields must be initialized before calling this function.
|
||
|
*
|
||
|
* @param fmi
|
||
|
*
|
||
|
* @return CE_STATE
|
||
|
*/
|
||
|
CE_STATE h2fmi_get_current_CE_state( h2fmi_t* fmi )
|
||
|
{
|
||
|
CE_STATE ceState = CE_BUSY;
|
||
|
|
||
|
if (!(h2fmi_rd(fmi, FMI_INT_PEND) & FMI_INT_PEND__FMC_NSRBBDONE))
|
||
|
{
|
||
|
BOOL32 fTimeout = WMR_HAS_TIME_ELAPSED_TICKS( fmi->stateMachine.waitStartTicks, fmi->stateMachine.wPageTimeoutTicks );
|
||
|
|
||
|
if ( fTimeout )
|
||
|
{
|
||
|
ceState = CE_TIMEOUT;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ceState = CE_IDLE;
|
||
|
}
|
||
|
|
||
|
return ceState ;
|
||
|
}
|
||
|
|
||
|
#if ( H2FMI_INSTRUMENT_BUS_1 )
|
||
|
/**
|
||
|
* Sets the specified CE on FMI1.
|
||
|
*
|
||
|
* @param iBit - CE, zero is the first.
|
||
|
*/
|
||
|
void h2fmi_instrument_bit_set(int iBit)
|
||
|
{
|
||
|
iBit &= 0x03;
|
||
|
volatile UInt32* wFMCValue = ( 0 == (iBit & 0x02) ? FMI0 : FMI1 );
|
||
|
iBit = ( 0 == (iBit & 0x01) ? 2 : 3 );
|
||
|
UInt32 wCurrentValue = h2fmc_rd(wFMCValue, FMC_CE_CTRL);
|
||
|
wCurrentValue &= ~( 1 << iBit );
|
||
|
h2fmc_wr(wFMCValue, FMC_CE_CTRL, wCurrentValue );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Clears the specified CE on FMI1.
|
||
|
*
|
||
|
* @param iBit - CE, zero is the first.
|
||
|
*/
|
||
|
void h2fmi_instrument_bit_clear(int iBit)
|
||
|
{
|
||
|
iBit &= 0x03;
|
||
|
volatile UInt32* wFMCValue = ( 0 == (iBit & 0x02) ? FMI0 : FMI1 );
|
||
|
iBit = ( 0 == (iBit & 0x01) ? 2 : 3 );
|
||
|
UInt32 wCurrentValue = h2fmc_rd(wFMCValue, FMC_CE_CTRL);
|
||
|
wCurrentValue |= ( 1 << iBit );
|
||
|
h2fmc_wr(wFMCValue, FMC_CE_CTRL, wCurrentValue );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static void h2fmi_rx_prep(h2fmi_t* fmi)
|
||
|
{
|
||
|
h2fmi_set_page_format_and_ECC_level(fmi, 15);
|
||
|
}
|
||
|
|
||
|
static void h2fmi_tx_prep(h2fmi_t* fmi)
|
||
|
{
|
||
|
// undocumented silicon "feature": on write, program error alert
|
||
|
// level greater than bit correction capability to avoid spurious
|
||
|
// ECC error
|
||
|
// regardless, I was advised to disable interrupt to FMI from ECC
|
||
|
h2fmi_set_page_format_and_ECC_level(fmi, fmi->correctable_bits + 1);
|
||
|
// clean up any ECC block state before starting transfer
|
||
|
h2fmi_clean_ecc(fmi);
|
||
|
|
||
|
#if (H2FMI_WAIT_USING_ISR)
|
||
|
// enable appropriate interrupt source
|
||
|
h2fmi_wr(fmi, FMI_INT_EN, FMI_INT_EN__LAST_FMC_DONE);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL32 h2fmi_common_idle_handler( h2fmi_t* fmi )
|
||
|
{
|
||
|
/**
|
||
|
* Called initially from client in non-interrupt context to let
|
||
|
* us set everything up and start the process.
|
||
|
*/
|
||
|
BOOL32 bSuccessful = TRUE32;
|
||
|
|
||
|
fmi->failureDetails.wNumCE_Executed = 0;
|
||
|
fmi->failureDetails.wOverallOperationFailure = 0;
|
||
|
|
||
|
fmi->failureDetails.wFirstFailingCE = ~0;
|
||
|
|
||
|
fmi->stateMachine.wPageTimeoutTicks = H2FMI_PAGE_TIMEOUT_MICROS * WMR_GET_TICKS_PER_US();
|
||
|
fmi->stateMachine.wQuarterPageTimeoutTicks = fmi->stateMachine.wPageTimeoutTicks / 4;
|
||
|
|
||
|
if ( fmiRead == fmi->stateMachine.currentMode)
|
||
|
{
|
||
|
h2fmi_rx_prep(fmi);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
h2fmi_tx_prep(fmi);
|
||
|
}
|
||
|
|
||
|
return bSuccessful;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_start_dma( h2fmi_t* fmi )
|
||
|
{
|
||
|
BOOL32 bSuccessful = TRUE32;
|
||
|
|
||
|
bSuccessful = h2fmi_dma_execute_async(
|
||
|
( fmiRead == fmi->stateMachine.currentMode ? DMA_CMD_DIR_RX : DMA_CMD_DIR_TX ),
|
||
|
h2fmi_dma_data_chan(fmi),
|
||
|
fmi->stateMachine.data_segment_array,
|
||
|
h2fmi_dma_data_fifo(fmi),
|
||
|
fmi->bytes_per_page * fmi->stateMachine.page_count,
|
||
|
sizeof(UInt32),
|
||
|
8,
|
||
|
fmi->current_aes_cxt);
|
||
|
|
||
|
if (bSuccessful)
|
||
|
{
|
||
|
// use CDMA to feed meta buffer to FMI
|
||
|
bSuccessful = h2fmi_dma_execute_async(
|
||
|
( fmiRead == fmi->stateMachine.currentMode ? DMA_CMD_DIR_RX : DMA_CMD_DIR_TX ),
|
||
|
h2fmi_dma_meta_chan(fmi),
|
||
|
fmi->stateMachine.meta_segment_array,
|
||
|
h2fmi_dma_meta_fifo(fmi),
|
||
|
fmi->valid_bytes_per_meta * fmi->stateMachine.page_count,
|
||
|
sizeof(UInt8),
|
||
|
1,
|
||
|
NULL);
|
||
|
}
|
||
|
|
||
|
return bSuccessful;
|
||
|
}
|
||
|
|
||
|
#define LFSR32(seed) ((seed) & 1) ? ((seed) >> 1) ^ 0x80000061 : ((seed) >> 1)
|
||
|
void h2fmi_aes_iv(void* arg, UInt32 chunk_index, void* iv_buffer)
|
||
|
{
|
||
|
const h2fmi_t* fmi = (h2fmi_t*)arg;
|
||
|
if (fmi->current_aes_iv_array)
|
||
|
{
|
||
|
const UInt8* chunk_iv = fmi->current_aes_iv_array[chunk_index].aes_iv_bytes;
|
||
|
|
||
|
WMR_MEMCPY(iv_buffer, chunk_iv, 16);
|
||
|
WMR_PRINT(CRYPTO, "IV: 0x%08x 0x%08x 0x%08x 0x%08x\n",
|
||
|
((UInt32*)chunk_iv)[0],
|
||
|
((UInt32*)chunk_iv)[1],
|
||
|
((UInt32*)chunk_iv)[2],
|
||
|
((UInt32*)chunk_iv)[3]);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
const UInt32 pPage = fmi->iv_seed_array[chunk_index];
|
||
|
UInt32* iv = (UInt32*)iv_buffer;
|
||
|
WMR_PRINT(CRYPTO, "Whitening chunk %d\n", chunk_index);
|
||
|
|
||
|
iv[0] = LFSR32(pPage);
|
||
|
iv[1] = LFSR32(iv[0]);
|
||
|
iv[2] = LFSR32(iv[1]);
|
||
|
iv[3] = LFSR32(iv[2]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void h2fmi_set_if_ctrl(h2fmi_t* fmi, UInt32 if_ctrl)
|
||
|
{
|
||
|
#if FMI_VERSION == 4
|
||
|
// there are currently limitations on timings that can be used in H4G,
|
||
|
// some of which can be over come by disabling streaming (<rdar://problem/9033500>)
|
||
|
#if SUPPORT_TOGGLE_NAND
|
||
|
if (fmi->is_toggle)
|
||
|
{
|
||
|
const UInt32 reb_setup = FMC_IF_CTRL__GET_REB_SETUP(if_ctrl);
|
||
|
|
||
|
fmi->read_stream_disable = (3 < reb_setup) ? TRUE32 : FALSE32;
|
||
|
}
|
||
|
else
|
||
|
#endif // SUPPORT_TOGGLE_NAND
|
||
|
{
|
||
|
fmi->read_stream_disable = TRUE32;
|
||
|
}
|
||
|
#endif // FMI_VERSION == 4
|
||
|
|
||
|
#if FMI_VERSION >= 5
|
||
|
#if SUPPORT_TOGGLE_NAND
|
||
|
if (fmi->is_toggle)
|
||
|
{
|
||
|
fmi->read_tx_page_delay = 0;
|
||
|
}
|
||
|
else
|
||
|
#endif // SUPPORT_TOGGLE_NAND
|
||
|
{
|
||
|
const UInt32 dccycle = FMC_IF_CTRL__GET_DCCYCLE(if_ctrl);
|
||
|
const UInt32 reb_hold = FMC_IF_CTRL__GET_REB_HOLD(if_ctrl);
|
||
|
|
||
|
// <rdar://problem/10987609> Timeout during flash data transfer in SDR mode with slow timings
|
||
|
fmi->read_tx_page_delay = (reb_hold > (dccycle + 4) ? reb_hold - (dccycle + 4) : 0);
|
||
|
}
|
||
|
#endif // FMI_VERSION >= 5
|
||
|
|
||
|
h2fmi_wr(fmi, FMC_IF_CTRL, if_ctrl);
|
||
|
}
|
||
|
|
||
|
void restoreTimingRegs(h2fmi_t* fmi)
|
||
|
{
|
||
|
#if SUPPORT_TOGGLE_NAND
|
||
|
if (fmi->is_toggle)
|
||
|
{
|
||
|
h2fmi_wr(fmi, FMC_DQS_TIMING_CTRL, fmi->dqs_ctrl);
|
||
|
h2fmi_wr(fmi, FMC_TOGGLE_CTRL_1, fmi->timing_ctrl_1);
|
||
|
h2fmi_wr(fmi, FMC_TOGGLE_CTRL_2, fmi->timing_ctrl_2);
|
||
|
h2fmi_set_if_ctrl(fmi, fmi->toggle_if_ctrl);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
#endif
|
||
|
h2fmi_set_if_ctrl(fmi, fmi->if_ctrl);
|
||
|
#if SUPPORT_TOGGLE_NAND
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
// ********************************** EOF **************************************
|