iBoot/drivers/apple/h2fmi/H2fmi_write.c

1215 lines
39 KiB
C

// *****************************************************************************
//
// File: H2fmi_write.c
//
// *****************************************************************************
//
// Notes:
//
// *****************************************************************************
//
// Copyright (C) 2008-2013 Apple, Inc. All rights reserved.
//
// This document is the property of Apple, 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"
/**
* Define the following to 'simulate' NAND programming errors.
*/
//??#define SIMULATE_WRITE_FAILURES
//??#define SIMULATE_WRITE_TIMEOUTS
//??#define SIMULATE_WRITE_LOCKUP
#if defined(SIMULATE_WRITE_TIMEOUTS)
#define WRITE_TIMEOUT_MAGIC_NUMBER ( 159871/2 )
static unsigned long ulWriteTimeoutCount = 0;
#endif
#if defined(SIMULATE_WRITE_FAILURES)
#define WRITE_FAILURE_MAGIC_NUMBER 159871
static unsigned long ulFailureCount = 0;
static unsigned long _gForcedFailure[H2FMI_MAX_NUM_BUS] = { 0, 0 };
#endif
#if defined(SIMULATE_WRITE_LOCKUP)
#define WRITE_LOCKUP_COUNT_TRIGGER 1000
static unsigned long ulWriteLockoutCount = 0;
#endif
// =============================================================================
// private implementation function declarations
// =============================================================================
static void h2fmi_start_nand_page_program(h2fmi_t* fmi, UInt32 page);
static void h2fmi_send_write_confirm(h2fmi_t* fmi);
static BOOL32 h2fmi_tx_raw_page(h2fmi_t* fmi, UInt8* data_buf, h2fmi_ce_t ce);
static BOOL32 h2fmi_tx_wait_page_done(h2fmi_t* fmi);
static BOOL32 h2fmi_tx_bootpage_pio(h2fmi_t* fmi, UInt8* data_buf, h2fmi_ce_t ce);
static void h2fmi_prepare_write_confirm(h2fmi_t* fmi);
static void h2fmi_write_Xfer_data_handler2(h2fmi_t * fmi);
/**
* Same as h2fmi_get_current_CE_state except also tests to
* ensure the write protect bit is off. Used for write
* mode as a sanity check as some NAND have been known to fail
* with 'stuck' write protects.
*
* @param fmi
*
* @return CE_STATE
*/
static CE_STATE h2fmi_get_current_writable_CE_state( h2fmi_t* fmi )
{
CE_STATE ceState = h2fmi_get_current_CE_state(fmi);
const UInt8 bNandStatus = (UInt8)h2fmi_rd(fmi, FMC_NAND_STATUS);
const UInt32 bWPDisabled = (bNandStatus & NAND_STATUS__WRITE_PROTECT);
WMR_ASSERT(bWPDisabled);
return ceState;
}
/**
* Called when a general failure has been detected. This is
* either a timeout (in which case the status pointer is NULL)
* or a valid pointer to a 'generic' status is passed in.
*
* It is up to this routine to report the correct failing CE
*
*/
static void h2fmi_recordFirstFailingCE( h2fmi_t *fmi )
{
if ( ((UInt32) ~0) == fmi->failureDetails.wFirstFailingCE )
{
/**
* Only record the FIRST failing CE ...
*/
fmi->failureDetails.wFirstFailingCE = fmi->stateMachine.currentCE;
}
}
// =============================================================================
// public implementation function definitions
// =============================================================================
BOOL32 h2fmi_write_page(h2fmi_t* fmi,
UInt16 ce,
UInt32 page,
UInt8* data_buf,
UInt8* meta_buf,
BOOL32* status_failed)
{
BOOL32 result = TRUE32;
struct dma_segment data_sgl;
struct dma_segment meta_sgl;
data_sgl.paddr = (UInt32)data_buf;
data_sgl.length = fmi->bytes_per_page;
meta_sgl.paddr = (UInt32)meta_buf;
meta_sgl.length = fmi->valid_bytes_per_meta;
result = h2fmi_write_multi(fmi, 1, &ce, &page, &data_sgl, &meta_sgl, status_failed, _kVS_NONE);
return result;
}
BOOL32 h2fmi_write_multi(h2fmi_t* fmi,
UInt32 page_count,
h2fmi_ce_t* chip_enable_array,
UInt32* page_number_array,
struct dma_segment* data_segment_array,
struct dma_segment* meta_segment_array,
BOOL32* write_failed,
UInt32 wVendorType)
{
BOOL32 result = TRUE32;
#if defined(SIMULATE_WRITE_LOCKUP)
if ( ++ulWriteLockoutCount >= WRITE_LOCKUP_COUNT_TRIGGER )
{
volatile int iHoldForLockup = 1;
while ( iHoldForLockup )
{
}
}
#endif
#if ( H2FMI_INSTRUMENT_BUS_1 )
h2fmi_instrument_bit_clear(0);
h2fmi_instrument_bit_clear(1);
h2fmi_instrument_bit_clear(2);
h2fmi_instrument_bit_clear(3);
#endif
WMR_ASSERT(fmi->stateMachine.currentMode == fmiNone);
#if ( H2FMI_INSTRUMENT_BUS_1 )
h2fmi_instrument_bit_set(0);
h2fmi_instrument_bit_clear(0);
h2fmi_instrument_bit_set(0);
h2fmi_instrument_bit_clear(0);
h2fmi_instrument_bit_set(0);
#endif
const UInt32 fmc_if_ctrl = h2fmi_rd(fmi, FMC_IF_CTRL);
WMR_ENTER_CRITICAL_SECTION();
{
fmi->stateMachine.chip_enable_array = chip_enable_array;
fmi->stateMachine.page_number_array = page_number_array;
fmi->stateMachine.data_segment_array = data_segment_array;
fmi->stateMachine.meta_segment_array = meta_segment_array;
fmi->stateMachine.state.wr = writeIdle;
fmi->stateMachine.page_count = page_count;
fmi->stateMachine.wVendorType = wVendorType;
h2fmi_reset(fmi);
// disable write protect
h2fmi_set_if_ctrl(fmi, FMC_IF_CTRL__WPB | fmc_if_ctrl);
fmi->stateMachine.currentMode = fmiWrite;
}
WMR_EXIT_CRITICAL_SECTION();
#if ( H2FMI_INSTRUMENT_BUS_1 )
h2fmi_instrument_bit_set(1);
#endif
#if (H2FMI_WAIT_USING_ISR)
static const utime_t clockDelay_uSec = 50000; // every 50 mSec
#endif
volatile WRITE_STATE* pState = &fmi->stateMachine.state.wr;
#if (H2FMI_WAIT_USING_ISR)
event_unsignal(&fmi->stateMachine.stateMachineDoneEvent);
#endif
do
{
if ( writeDone == (*pState) )
{
break;
}
WMR_ENTER_CRITICAL_SECTION();
h2fmi_handle_write_ISR_state_machine(fmi);
WMR_EXIT_CRITICAL_SECTION();
#if !(H2FMI_WAIT_USING_ISR)
WMR_YIELD();
}
while ( writeDone != (*pState) );
#else
}
while ( !event_wait_timeout(&fmi->stateMachine.stateMachineDoneEvent, clockDelay_uSec) );
/**
* Allow for early wakeup
*/
{
UInt32 iLoopCount = 0;
while ( writeDone != (*pState) )
{
WMR_YIELD();
if ( ++iLoopCount>H2FMI_EARLY_EXIT_ARBITRARY_LOOP_CONST )
{
iLoopCount = 0;
/**
* Allow the state machine a timeslice in case the HW gets
* stuck.
*/
WMR_ENTER_CRITICAL_SECTION();
h2fmi_handle_write_ISR_state_machine(fmi);
WMR_EXIT_CRITICAL_SECTION();
}
}
}
#endif
/**
* Finally check the outcome from our state machine
*/
result = fmi->stateMachine.fSuccessful;
if ( result )
{
/**
* ensure that system DMA is complete -- note that if we already
* are in an error state we skip this test as it adds an
* addition H2FMI_PAGE_TIMEOUT_MICROS to the overall operation.
*
* This wouldn't strictly be necessary on a write (since the DMA had to have completed
* for the FMC operation to complete), but we need to make sure the event is consumed
* otherwise it'll stay pending and casue the next DMA to appear to have completed
* immediately.
*/
// both dma streams should be complete now
if (!h2fmi_dma_wait(h2fmi_dma_data_chan(fmi), H2FMI_PAGE_TIMEOUT_MICROS) ||
!h2fmi_dma_wait(h2fmi_dma_meta_chan(fmi), H2FMI_PAGE_TIMEOUT_MICROS))
{
h2fmi_fail(result);
// If the write operation succeeded, but we never get a CDMA completion event,
// something is broken in the FMI or CDMA layers - no legit NAND problem should case this.
WMR_PANIC("Timeout waiting for CDMA during successful NAND write operation");
h2fmi_dma_cancel(h2fmi_dma_data_chan(fmi));
h2fmi_dma_cancel(h2fmi_dma_meta_chan(fmi));
fmi->failureDetails.wOverallOperationFailure = _kIOPFMI_STATUS_DMA_DONE_TIMEOUT;
}
}
else
{
h2fmi_dma_cancel(h2fmi_dma_data_chan(fmi));
h2fmi_dma_cancel(h2fmi_dma_meta_chan(fmi));
}
#if ( H2FMI_INSTRUMENT_BUS_1 )
h2fmi_instrument_bit_clear(1);
#endif
// return write protect to previous state
h2fmi_set_if_ctrl(fmi, fmc_if_ctrl);
/**
* Reset our current mode so that interrupts are not re-directed
* to our state machine.
*/
fmi->stateMachine.currentMode = fmiNone;
#if ( H2FMI_INSTRUMENT_BUS_1 )
h2fmi_instrument_bit_clear(0);
#endif
// presume all write operations will succeed
if (NULL != write_failed)
{
*write_failed = ( result ? FALSE32 : TRUE32 );
}
#if defined(SIMULATE_WRITE_FAILURES)
if ( _gForcedFailure[ fmi->bus_id ] )
{
/**
* Since our simulated failures may fail the dummy commit within
* a VS operation we need to reset our NAND back to a known good
* state... We could (should?) try to be more intelligent about
* this but for now we just do a reset any time we generate a
* failure.
*/
h2fmi_nand_reset_all(fmi);
_gForcedFailure[ fmi->bus_id ] = 0;
}
#endif
return result;
}
static BOOL32 h2fmi_tx_wait_page_done(h2fmi_t* fmi)
{
BOOL32 result = TRUE32;
#if H2FMI_WAIT_USING_ISR
if (!event_wait_timeout(&fmi->isr_event, H2FMI_PAGE_TIMEOUT_MICROS))
{
h2fmi_fail(result);
}
if (result && (!(fmi->isr_condition & FMI_INT_PEND__LAST_FMC_DONE)))
{
h2fmi_fail(result);
}
#else
result = h2fmi_wait_done(fmi, FMI_INT_PEND, FMI_INT_PEND__LAST_FMC_DONE, FMI_INT_PEND__LAST_FMC_DONE);
#endif
if (!result )
{
h2fmi_fmc_disable_all_ces(fmi);
}
return result;
}
UInt32 h2fmi_write_bootpage(h2fmi_t* fmi,
UInt32 ce,
UInt32 page,
UInt8* data_buf)
{
UInt32 fmc_if_ctrl = h2fmi_rd(fmi, FMC_IF_CTRL);
UInt8 nand_status;
UInt32 result = _kIOPFMI_STATUS_SUCCESS;
#if FMI_VERSION > 3
UInt32 sector;
#endif
// Enable specified NAND device
h2fmi_fmc_enable_ce(fmi, ce);
// Disable write protection
h2fmi_set_if_ctrl(fmi, FMC_IF_CTRL__WPB | fmc_if_ctrl);
// Prep for program (all steps prior to data transfer)
h2fmi_start_nand_page_program(fmi, page);
h2fmi_prepare_write_confirm(fmi);
h2fmi_set_bootpage_data_format(fmi);
#if FMI_VERSION > 3
// Set page format first to enable seed scrambling
h2fmi_wr(fmi, FMI_CONTROL, FMI_CONTROL__RESET_SEED);
// Fill randomizer seeds based on page address
for (sector = 0; sector < H2FMI_BOOT_SECTORS_PER_PAGE; sector++)
{
h2fmi_wr(fmi, FMI_SCRAMBLER_SEED_FIFO, page + sector);
}
#endif
// Transfer data for page to FMI
if (!h2fmi_tx_bootpage_pio(fmi, data_buf, ce))
{
result = _kIOPFMI_STATUS_FMC_DONE_TIMEOUT;
}
else
{
// End program operation
h2fmi_send_write_confirm(fmi);
// wait for the program to complete
if (!h2fmi_wait_status(fmi,
NAND_STATUS__DATA_CACHE_RB,
NAND_STATUS__DATA_CACHE_RB_READY,
&nand_status))
{
result = _kIOPFMI_STATUS_READY_BUSY_TIMEOUT;
}
else if ((nand_status & NAND_STATUS__CHIP_STATUS1) == NAND_STATUS__CHIP_STATUS1_FAIL)
{
// NAND reported Program Failed
result = _kIOPFMI_STATUS_PGM_ERROR;
}
}
// disable all FMI interrupt sources
h2fmi_wr(fmi, FMI_INT_EN, 0);
// restore write-protect
h2fmi_set_if_ctrl(fmi, fmc_if_ctrl);
// Disable NAND device
h2fmi_fmc_disable_ce(fmi, ce);
// Hardware still thinks there should be a meta DMA complete signal here somewhere.
// It'll never come, so reset the core to move on.
h2fmi_reset(fmi);
return result;
}
static BOOL32 h2fmi_tx_bootpage_pio(h2fmi_t* fmi,
UInt8* buf,
h2fmi_ce_t ce)
{
BOOL32 result = TRUE32;
UInt32 i;
UInt32 fmi_control = (FMI_CONTROL__MODE__WRITE |
FMI_CONTROL__START_BIT);
for (i = 0; i < H2FMI_BOOT_SECTORS_PER_PAGE; i++)
{
// Start FMI write
#if !WMR_BUILDING_EFI
h2fmi_wr(fmi, FMI_INT_EN, FMI_INT_EN__LAST_FMC_DONE);
#endif
h2fmi_wr(fmi, FMI_CONTROL, fmi_control);
h2fmi_pio_write_sector(fmi, buf, H2FMI_BYTES_PER_BOOT_SECTOR);
buf += H2FMI_BYTES_PER_BOOT_SECTOR;
// wait for the page to finish transferring
if (!h2fmi_tx_wait_page_done(fmi))
{
h2fmi_fail(result);
break;
}
}
return result;
}
UInt32 h2fmi_write_raw_page(h2fmi_t* fmi,
UInt32 ce,
UInt32 page,
UInt8* data_buf)
{
UInt32 result = _kIOPFMI_STATUS_SUCCESS;
UInt8 nand_status;
UInt32 fmc_if_ctrl = h2fmi_rd(fmi, FMC_IF_CTRL);
// If using PIO, make certain that the FMI subsystem gets reset
// between each write to read transition; might want to do this on
// read to write to just be safe, so I'm taking the most
// pessimistic option and doing it before each PIO-based
// operation).
//
// TODO: This workaround appears to also make dma-based write function correctly,
// which is unexpected and should be tracked down to root cause soon.
h2fmi_reset(fmi);
// enable specified nand device
h2fmi_fmc_enable_ce(fmi, ce);
// disable write protect
h2fmi_set_if_ctrl(fmi, FMC_IF_CTRL__WPB | fmc_if_ctrl);
h2fmi_wr(fmi, ECC_MASK, 0 );
// prep for program (all the step prior to xfer).
h2fmi_start_nand_page_program(fmi, page);
h2fmi_prepare_write_confirm(fmi);
// transfer data for page and meta to FMI
if (!h2fmi_tx_raw_page(fmi, data_buf, ce))
{
result = _kIOPFMI_STATUS_DMA_DONE_TIMEOUT;
h2fmi_fail(result);
}
else
{
// end program operation
h2fmi_send_write_confirm(fmi);
// wait for the device to come back
if (!h2fmi_wait_status(fmi,
NAND_STATUS__DATA_CACHE_RB,
NAND_STATUS__DATA_CACHE_RB_READY,
&nand_status))
{
result = _kIOPFMI_STATUS_READY_BUSY_TIMEOUT;
}
else if ((nand_status & NAND_STATUS__CHIP_STATUS1) == NAND_STATUS__CHIP_STATUS1_FAIL)
{
result = _kIOPFMI_STATUS_PGM_ERROR;
}
}
// disable all FMI interrupt sources
h2fmi_wr(fmi, FMI_INT_EN, 0);
// return write protect to previous state
h2fmi_set_if_ctrl(fmi, fmc_if_ctrl);
// disable all nand devices
h2fmi_fmc_disable_ce(fmi, ce);
if( result != _kIOPFMI_STATUS_SUCCESS)
{
h2fmi_fmc_disable_all_ces(fmi);
}
return result;
}
static BOOL32 h2fmi_tx_raw_page(h2fmi_t* fmi,
UInt8* buf,
h2fmi_ce_t ce)
{
BOOL32 result = TRUE32;
UInt32 i;
UInt32 spare = fmi->bytes_per_spare;
UInt32 fmi_control = (FMI_CONTROL__MODE__WRITE |
FMI_CONTROL__START_BIT);
for( i = 0; i < fmi->sectors_per_page; i++ )
{
const UInt32 spare_per_sector = WMR_MIN(spare, H2FMI_MAX_META_PER_ENVELOPE);
h2fmi_set_raw_write_data_format(fmi, spare_per_sector);
// start FMI write
h2fmi_wr(fmi, FMI_INT_EN, FMI_INT_EN__LAST_FMC_DONE);
h2fmi_wr(fmi, FMI_CONTROL, fmi_control);
h2fmi_dma_execute_cmd(DMA_CMD_DIR_TX,
h2fmi_dma_data_chan(fmi),
buf,
h2fmi_dma_data_fifo(fmi),
H2FMI_BYTES_PER_SECTOR,
sizeof(UInt32),
1,
NULL);
buf += H2FMI_BYTES_PER_SECTOR;
if( spare_per_sector )
{
h2fmi_dma_execute_cmd(DMA_CMD_DIR_TX,
h2fmi_dma_meta_chan(fmi),
buf,
h2fmi_dma_meta_fifo(fmi),
spare_per_sector,
sizeof(UInt8),
1,
NULL);
buf += spare_per_sector;
spare -= spare_per_sector;
}
// wait for the page to finish transferring
if ( !h2fmi_tx_wait_page_done(fmi) )
{
h2fmi_fail(result);
break;
}
}
if (!result)
{
h2fmi_fmc_disable_all_ces(fmi);
}
return result;
}
// =============================================================================
// private implementation function definitions
// =============================================================================
static void h2fmi_start_nand_page_program(h2fmi_t* fmi,
UInt32 page)
{
UInt32 wCmd1;
const UInt32 cmd1_addr_done = (FMC_STATUS__CMD1DONE |
FMC_STATUS__ADDRESSDONE);
// configure FMC for cmd1/addr sequence
h2fmi_config_page_addr(fmi, page);
if ( fmiNone == fmi->stateMachine.currentMode )
{
wCmd1 = NAND_CMD__WRITE;
}
else
{
switch ( fmi->stateMachine.wVendorType )
{
case _kVS_SAMSUNG_2P_2D:
wCmd1 = ( 0==(fmi->stateMachine.page_idx & 1) ? NAND_CMD__WRITE : NAND_CMD__WRITE2_SAMSUNG );
break;
case _kVS_HYNIX_2P:
wCmd1 = ( 0==(fmi->stateMachine.page_idx & 1) ? NAND_CMD__WRITE : NAND_CMD__WRITE2_HYNIX );
break;
case _kVS_TOSHIBA_2P:
case _kVS_MICRON_2P:
case _kVS_SAMSUNG_2D:
default:
wCmd1 = NAND_CMD__WRITE;
break;
}
}
h2fmi_wr(fmi, FMC_CMD, FMC_CMD__CMD1(wCmd1));
// start cmd1/addr/cmd2 sequence
h2fmi_wr(fmi, FMC_RW_CTRL, (FMC_RW_CTRL__CMD1_MODE |
FMC_RW_CTRL__ADDR_MODE));
// busy wait until cmd1/addr/cmd2 sequence completed
h2fmi_busy_wait(fmi, FMC_STATUS, cmd1_addr_done, cmd1_addr_done);
h2fmi_wr(fmi, FMC_STATUS, cmd1_addr_done);
}
static void h2fmi_incrementOutstandingOperations(h2fmi_t* fmi)
{
if ( (_kVS_NONE == fmi->stateMachine.wVendorType ) || ( 1 == ( fmi->stateMachine.page_idx & 1 ) ) )
{
const UInt32 maxQueueElements = sizeof(fmi->stateMachine.ceCommitQueue.previousCEIndex)/sizeof(fmi->stateMachine.ceCommitQueue.previousCEIndex[0]);
/**
* Only increment count on "terminal" commits -- we assume here
* that VS will only be 2P or 2D, not 2D+2P.
*/
fmi->stateMachine.wOutstandingCEWriteOperations++;
/**
* Save the working index in case we need it for holdoff
* processing.
*/
fmi->stateMachine.ceCommitQueue.previousCEIndex[ fmi->stateMachine.ceCommitQueue.head++ ] = fmi->stateMachine.page_idx;
if ( fmi->stateMachine.ceCommitQueue.head >= maxQueueElements )
{
fmi->stateMachine.ceCommitQueue.head = 0;
}
if ( fmi->stateMachine.ceCommitQueue.count < maxQueueElements )
{
fmi->stateMachine.ceCommitQueue.count++;
}
}
}
static void h2fmi_decrementOutstandingOperations(h2fmi_t* fmi)
{
const UInt32 lastIndex = fmi->stateMachine.lastPageIndex[ fmi->stateMachine.currentCE ];
if ( (_kVS_NONE == fmi->stateMachine.wVendorType ) || ( 0 == ( (lastIndex+1) & 1 ) ) )
{
/**
* Only decrement count on "terminal" waits -- we assume here
* that VS will only be 2P or 2D, not 2D+2P.
*/
fmi->stateMachine.wOutstandingCEWriteOperations--;
}
WMR_ASSERT( ( (Int32)fmi->stateMachine.wOutstandingCEWriteOperations ) >= 0 );
}
static void h2fmi_send_write_confirm(h2fmi_t* fmi)
{
// CMD1 should have been filled out already in h2fmi_prep_program_confirm().
// start cmd1
const UInt32 cmd2_done = FMC_STATUS__CMD2DONE;
h2fmi_wr(fmi, FMC_RW_CTRL, FMC_RW_CTRL__CMD2_MODE);
// wait until cmd1 sequence completed
h2fmi_busy_wait(fmi, FMC_STATUS, cmd2_done, cmd2_done);
h2fmi_wr(fmi, FMC_STATUS, cmd2_done);
}
// =============================================================================
// dma-only private implementation function definitions
// =============================================================================
BOOL32 h2fmi_do_write_setup( h2fmi_t* fmi )
{
BOOL32 fContinue = TRUE32;
fmi->stateMachine.currentCE = fmi->stateMachine.chip_enable_array[ fmi->stateMachine.page_idx ];
// enable specified nand device
h2fmi_fmc_enable_ce(fmi, fmi->stateMachine.currentCE);
if (fmi->stateMachine.dirty_ce_mask & (0x1U << fmi->stateMachine.currentCE))
{
// We must wait for the device to become ready as we have already started a write to it from
// a previous pass.
h2fmi_prepare_for_ready_busy_interrupt(fmi);
fContinue = FALSE32;
}
return fContinue ;
}
void h2fmi_ISR_state_machine_start_page( h2fmi_t* fmi )
{
UInt32 fmi_control = (FMI_CONTROL__MODE__WRITE |
FMI_CONTROL__START_BIT);
fmi->stateMachine.currentPage = fmi->stateMachine.page_number_array[ fmi->stateMachine.page_idx ];
// mark the current chip enable dirty so that it will be
// checked for ready before next use
fmi->stateMachine.dirty_ce_mask |= (0x1UL << fmi->stateMachine.currentCE);
// prep for program (all the step prior to xfer).
h2fmi_start_nand_page_program(fmi, fmi->stateMachine.currentPage);
// enable appropriate interrupt source
h2fmi_wr(fmi, FMI_INT_EN, FMI_INT_EN__LAST_FMC_DONE);
// start FMI write of next page
h2fmi_wr(fmi, FMI_CONTROL, fmi_control);
// Mark when we started out wait ...
fmi->stateMachine.waitStartTicks = WMR_CLOCK_TICKS();
// Increment the # of pages 'started' across the bus ...
fmi->failureDetails.wNumCE_Executed++;
h2fmi_prepare_write_confirm(fmi);
}
static void h2fmi_prepare_write_confirm(h2fmi_t *fmi)
{
UInt32 wCmd2;
if ( fmiNone == fmi->stateMachine.currentMode )
{
wCmd2 = NAND_CMD__WRITE_CONFIRM;
}
else
{
switch ( fmi->stateMachine.wVendorType )
{
case _kVS_HYNIX_2P:
wCmd2 = ( 0 == (fmi->stateMachine.page_idx & 1) ? NAND_CMD__DUMMY_CONFIRM_HYNIX : NAND_CMD__WRITE_CONFIRM );
break;
case _kVS_MICRON_2P:
wCmd2 = ( 0 == (fmi->stateMachine.page_idx & 1) ? NAND_CMD__DUMMY_CONFIRM_MICRON : NAND_CMD__WRITE_CONFIRM );
break;
case _kVS_SAMSUNG_2P_2D:
wCmd2 = ( 0 == (fmi->stateMachine.page_idx & 1) ? NAND_CMD__DUMMY_CONFIRM_SAMSUNG : NAND_CMD__WRITE_CONFIRM );
break;
case _kVS_TOSHIBA_2P:
wCmd2 = ( 0 == (fmi->stateMachine.page_idx & 1) ? NAND_CMD__DUMMY_CONFIRM_TOSHIBA : NAND_CMD__WRITE_CONFIRM );
break;
default:
wCmd2 = NAND_CMD__WRITE_CONFIRM;
break;
}
}
h2fmi_wr(fmi, FMC_CMD, FMC_CMD__CMD2(wCmd2));
}
static void h2fmi_write_done_handler( h2fmi_t* fmi )
{
h2fmi_clear_interrupts_and_reset_masks(fmi);
h2fmi_fmc_disable_all_ces( fmi );
#if (H2FMI_WAIT_USING_ISR)
event_signal( &fmi->stateMachine.stateMachineDoneEvent );
#endif
}
static void h2fmi_write_ending_write_handler( h2fmi_t* fmi );
static void h2fmi_write_wait_for_CE_handlerImpl( h2fmi_t* fmi, const BOOL32 fContinue )
{
CE_STATE ceState = h2fmi_get_current_writable_CE_state(fmi);
#if defined(SIMULATE_WRITE_TIMEOUTS)
if ( ++ulWriteTimeoutCount > WRITE_TIMEOUT_MAGIC_NUMBER )
{
ulWriteTimeoutCount = 0;
ceState = CE_TIMEOUT;
}
#endif
if ( CE_IDLE == ceState )
{
h2fmi_clear_interrupts_and_reset_masks(fmi);
const UInt8 bNandStatus = (UInt8)h2fmi_rd(fmi, FMC_NAND_STATUS);
UInt32 status1;
BOOL32 fSuccess;
switch ( fmi->stateMachine.wVendorType )
{
case _kVS_TOSHIBA_2P:
status1 = (bNandStatus & NAND_STATUS__TOS_CHIP_STATUS_MASK);
break;
default:
status1 = (bNandStatus & NAND_STATUS__CHIP_STATUS1);
break;
}
// clear FMC_RW_CTRL, thereby releasing REBHOLD and completing
// current cycle
h2fmi_wr(fmi, FMC_RW_CTRL, 0);
#if defined(SIMULATE_WRITE_FAILURES)
if ( (ulFailureCount++)>=WRITE_FAILURE_MAGIC_NUMBER )
{
ulFailureCount = 0;
status1 = ~NAND_STATUS__CHIP_STATUS1_PASS;
WMR_PRINT(ALWAYS,"Simulated write failure on ce %d, index: %d\n",
fmi->stateMachine.currentCE, fmi->stateMachine.page_idx);
_gForcedFailure[ fmi->bus_id ] = 1;
}
#endif
switch ( fmi->stateMachine.wVendorType )
{
default:
fSuccess = ( NAND_STATUS__CHIP_STATUS1_PASS == status1 );
break;
}
fmi->stateMachine.dirty_ce_mask &= ~(0x1U << fmi->stateMachine.currentCE);
if ( !fSuccess )
{
// Failed our write -- indicate such ...
fmi->stateMachine.fSuccessful = FALSE32;
// and now go to the exiting handler to wait for dirty CE's to complete.
fmi->stateMachine.state.wr = writeEndingWrite;
h2fmi_recordFirstFailingCE(fmi);
#if (H2FMI_WAIT_USING_ISR)
event_signal( &fmi->stateMachine.stateMachineDoneEvent );
#endif
h2fmi_write_ending_write_handler( fmi );
}
else
{
fmi->stateMachine.state.wr = writeXferData;
if ( fContinue )
{
h2fmi_ISR_state_machine_start_page(fmi);
}
}
h2fmi_decrementOutstandingOperations( fmi );
fmi->stateMachine.lastPageIndex[ fmi->stateMachine.currentCE ] = INVALID_CE_INDEX;
}
else if ( CE_TIMEOUT == ceState )
{
// We failed to get what we wanted -- signal error state and move to ending write stage to wait
// for additional CE's to complete. Also clear this CE to indicate that we are no longer waiting on
// it as we timed out.
fmi->stateMachine.fSuccessful = FALSE32;
fmi->stateMachine.state.wr = writeEndingWrite;
fmi->stateMachine.dirty_ce_mask &= ~(0x1U << fmi->stateMachine.currentCE);
h2fmi_recordFirstFailingCE(fmi);
h2fmi_decrementOutstandingOperations( fmi );
fmi->stateMachine.lastPageIndex[ fmi->stateMachine.currentCE ] = INVALID_CE_INDEX;
#if (H2FMI_WAIT_USING_ISR)
event_signal( &fmi->stateMachine.stateMachineDoneEvent );
#endif
h2fmi_write_ending_write_handler(fmi);
}
}
static void h2fmi_write_wait_for_CE_handler( h2fmi_t* fmi )
{
h2fmi_write_wait_for_CE_handlerImpl(fmi,TRUE32);
}
static void h2fmi_write_wait_for_CE_holdOff_handler( h2fmi_t* fmi )
{
fmi->stateMachine.wNumTimesHoldoffExecuted++;
h2fmi_write_wait_for_CE_handlerImpl(fmi,FALSE32);
if ( writeXferData == fmi->stateMachine.state.wr )
{
/**
* Return the HW to our current CE...
*/
h2fmi_fmc_enable_ce(fmi, fmi->stateMachine.savedCurrentCE);
h2fmi_prepare_write_confirm(fmi);
fmi->stateMachine.currentCE = fmi->stateMachine.savedCurrentCE;
h2fmi_write_Xfer_data_handler2(fmi);
}
}
static void h2fmi_write_ending_write_wait_CE_handler( h2fmi_t* fmi )
{
CE_STATE ceState = h2fmi_get_current_writable_CE_state(fmi);
if ( CE_IDLE == ceState )
{
h2fmi_clear_interrupts_and_reset_masks(fmi);
const UInt8 bNandStatus = (UInt8)h2fmi_rd(fmi, FMC_NAND_STATUS);
const UInt32 status1 = (bNandStatus & NAND_STATUS__CHIP_STATUS1);
if (NAND_STATUS__CHIP_STATUS1_PASS != status1)
{
fmi->stateMachine.fSuccessful = FALSE32;
// We will still wait for Ready/Busy CE's to complete ...
h2fmi_recordFirstFailingCE(fmi);
}
// clear FMC_RW_CTRL, thereby releasing REBHOLD and completing
// current cycle
h2fmi_wr(fmi, FMC_RW_CTRL, 0);
UInt32 wCEIndex = 0;
// Find the CE that was dirty ... (we only enter this state when dirty_ce_mask is non-zero)
while ( 0 == (fmi->stateMachine.dirty_ce_mask & (1 << wCEIndex)) )
{
wCEIndex++;
}
switch ( fmi->stateMachine.wVendorType )
{
case _kVS_SAMSUNG_2D:
case _kVS_SAMSUNG_2P_2D:
if ( 0 == fmi->stateMachine.currentWriteDie )
{
fmi->stateMachine.currentWriteDie++;
}
else
{
fmi->stateMachine.currentWriteDie = 0;
fmi->stateMachine.dirty_ce_mask &= ~(1 << wCEIndex);
}
fmi->stateMachine.page_idx++;
break;
default:
fmi->stateMachine.dirty_ce_mask &= ~(1 << wCEIndex);
break;
}
fmi->stateMachine.currentCE = wCEIndex;
fmi->stateMachine.state.wr = writeEndingWrite;
h2fmi_write_ending_write_handler(fmi);
}
else if ( CE_TIMEOUT == ceState )
{
// We failed to get what we wanted -- signal error state but continue to wait for any
// other CE's that have not been completed.
h2fmi_recordFirstFailingCE(fmi);
fmi->stateMachine.fSuccessful = FALSE32;
fmi->stateMachine.dirty_ce_mask &= ~(0x1U << fmi->stateMachine.currentCE);
fmi->stateMachine.state.wr = writeEndingWrite;
h2fmi_write_ending_write_handler(fmi);
}
}
static void h2fmi_write_ending_write_handler( h2fmi_t* fmi )
{
if (0 == fmi->stateMachine.dirty_ce_mask)
{
/**
* We're done waiting -- indicate that we're done and break.
*/
h2fmi_fmc_disable_all_ces(fmi);
fmi->stateMachine.state.wr = writeDone;
}
else
{
UInt32 wCEIndex = 0;
// Find the CE that is dirty ... (we only enter this state when dirty_ce_mask is non-zero)
while ( 0 == (fmi->stateMachine.dirty_ce_mask & (1 << wCEIndex)) )
{
wCEIndex++;
}
h2fmi_fmc_enable_ce(fmi, wCEIndex);
fmi->stateMachine.currentCE = wCEIndex;
switch ( fmi->stateMachine.wVendorType )
{
case _kVS_SAMSUNG_2P_2D:
case _kVS_SAMSUNG_2D:
fmi->stateMachine.page_idx++;
break;
default:
break;
}
h2fmi_prepare_for_ready_busy_interrupt(fmi);
fmi->stateMachine.waitStartTicks = WMR_CLOCK_TICKS();
fmi->stateMachine.state.wr = writeEndingWriteWaitCE;
// *should* execute writeEndingWriteWaitCE state.
}
}
static void h2fmi_write_Xfer_data_handler2(h2fmi_t * fmi)
{
const BOOL32 contExec = (0 == fmi->wMaxOutstandingCEWriteOperations)
|| ( fmi->stateMachine.wOutstandingCEWriteOperations < fmi->stateMachine.wMaxOutstandingCEWriteOperations )
|| ( (_kVS_NONE != fmi->stateMachine.wVendorType) && ( 0 == ( fmi->stateMachine.page_idx & 1 ) ) );
if ( contExec )
{
h2fmi_send_write_confirm(fmi);
h2fmi_incrementOutstandingOperations(fmi);
WMR_ASSERT( fmi->stateMachine.currentCE == fmi->stateMachine.chip_enable_array[ fmi->stateMachine.page_idx ] );
WMR_ASSERT( fmi->stateMachine.currentCE < ( sizeof(fmi->stateMachine.lastPageIndex) / sizeof(fmi->stateMachine.lastPageIndex[0]) ) );
fmi->stateMachine.lastPageIndex[ fmi->stateMachine.currentCE ] = fmi->stateMachine.page_idx++;
if ( fmi->stateMachine.page_idx < fmi->stateMachine.page_count )
{
/**
* More to do -- loop back and do this again!
*/
if (!h2fmi_do_write_setup(fmi))
{
/**
* We have to wait for a dirty CE -- our state machine will
* continue to cycle thru in the writeWaitForCE state below when
* it gets called from the FMC_NSRBBDONE interrupt.
*/
fmi->stateMachine.state.wr = writeWaitForCE;
fmi->stateMachine.waitStartTicks = WMR_CLOCK_TICKS();
}
else
{
/**
* else start a page xfer and break, waiting for our
* LAST_FMC_DONE interrupt again in this state.
*/
h2fmi_ISR_state_machine_start_page(fmi);
}
}
else
{
/**
* We need to finish up waiting on any and all dirty CE's ...
* signal our main routine that we are finishing up so that it
* is (hopefully) ready by the time we really complete.
*
* Fall through to endingWrite stage.
*/
fmi->stateMachine.state.wr = writeEndingWrite;
#if (H2FMI_WAIT_USING_ISR)
event_signal( &fmi->stateMachine.stateMachineDoneEvent );
#endif
h2fmi_write_ending_write_handler(fmi);
}
}
else
{
const UInt32 maxQueueElements = sizeof(fmi->stateMachine.ceCommitQueue.previousCEIndex)/sizeof(fmi->stateMachine.ceCommitQueue.previousCEIndex[0]);
/**
* We need to hang out and wait on older CE's until enough
* finish to allow us to continue.
*/
fmi->stateMachine.savedCurrentCE = fmi->stateMachine.currentCE;
WMR_ASSERT( fmi->stateMachine.ceCommitQueue.count > 0 );
fmi->stateMachine.currentCE = fmi->stateMachine.chip_enable_array[ fmi->stateMachine.ceCommitQueue.previousCEIndex[ fmi->stateMachine.ceCommitQueue.tail++ ] ];
if ( fmi->stateMachine.ceCommitQueue.tail >= maxQueueElements )
{
fmi->stateMachine.ceCommitQueue.tail = 0;
}
fmi->stateMachine.ceCommitQueue.count--;
/**
* Enable oldest CE and set up to wait for status interrupt.
*/
h2fmi_fmc_enable_ce(fmi, fmi->stateMachine.currentCE);
h2fmi_prepare_for_ready_busy_interrupt(fmi);
fmi->stateMachine.state.wr = writeWaitingForNumCELimit;
fmi->stateMachine.waitStartTicks = WMR_CLOCK_TICKS();
}
}
static void h2fmi_write_Xfer_data_handler( h2fmi_t* fmi )
{
fmi->isr_condition = h2fmi_rd(fmi, FMI_INT_PEND);
if (FMI_INT_PEND__LAST_FMC_DONE != (fmi->isr_condition & FMI_INT_PEND__LAST_FMC_DONE))
{
BOOL32 fTimeout = WMR_HAS_TIME_ELAPSED_TICKS(fmi->stateMachine.waitStartTicks, fmi->stateMachine.wPageTimeoutTicks);
if ( fTimeout )
{
fmi->stateMachine.fSuccessful = FALSE32;
// We failed to get what we wanted -- goto error state and complete.
fmi->stateMachine.state.wr = writeEndingWrite;
h2fmi_write_ending_write_handler(fmi);
}
}
else
{
h2fmi_clear_interrupts_and_reset_masks(fmi);
h2fmi_write_Xfer_data_handler2(fmi);
}
}
static void h2fmi_write_setup_handler( h2fmi_t* fmi )
{
if (!h2fmi_do_write_setup(fmi))
{
/**
* We have to wait for a dirty CE -- break so that our state
* machine continues to cycle thru in the writeWaitForCE state
* below.
*/
fmi->stateMachine.state.wr = writeWaitForCE;
fmi->stateMachine.waitStartTicks = WMR_CLOCK_TICKS();
}
else
{
/**
* else start a page xfer and fall through to the xfer data
* stage below.
*/
h2fmi_ISR_state_machine_start_page(fmi);
fmi->stateMachine.state.wr = writeXferData;
h2fmi_write_Xfer_data_handler(fmi);
}
}
static void h2fmi_write_idle_handler( h2fmi_t* fmi )
{
UInt32 Idx = 0;
fmi->stateMachine.dirty_ce_mask = 0;
fmi->stateMachine.currentWriteDie = 0;
fmi->stateMachine.page_idx = 0;
fmi->stateMachine.fSuccessful = TRUE32;
fmi->stateMachine.wNumTimesHoldoffExecuted = 0;
fmi->stateMachine.wOutstandingCEWriteOperations = 0;
fmi->stateMachine.wMaxOutstandingCEWriteOperations = ( 0 == fmi->stateMachine.wVendorType ?
fmi->wMaxOutstandingCEWriteOperations : 1*fmi->wMaxOutstandingCEWriteOperations );
for(Idx = 0; Idx < H2FMI_MAX_CE_TOTAL; Idx++)
{
fmi->stateMachine.lastPageIndex[Idx] = INVALID_CE_INDEX;
}
fmi->stateMachine.ceCommitQueue.count = 0;
fmi->stateMachine.ceCommitQueue.head = 0;
fmi->stateMachine.ceCommitQueue.tail = 0;
BOOL32 bSuccess = h2fmi_common_idle_handler(fmi);
WMR_ASSERT(bSuccess == TRUE32);
// Enter write mode so DMA transfer can start early
#if FMI_VERSION >= 3
h2fmi_wr(fmi, FMI_CONTROL, FMI_CONTROL__MODE__WRITE);
#endif // FMI_VERSION >= 3
bSuccess = h2fmi_start_dma(fmi);
WMR_ASSERT(bSuccess == TRUE32);
fmi->stateMachine.state.wr = writeSetup;
h2fmi_write_setup_handler(fmi);
}
typedef void (*h2fmi_isrWriteStateHandler)( h2fmi_t* fmi );
#if (!H2FMI_READONLY)
static const h2fmi_isrWriteStateHandler writeStateHandlerTable[] = {
h2fmi_write_idle_handler, // writeIdle = 0,
h2fmi_write_setup_handler, // writeSetup,
h2fmi_write_wait_for_CE_handler, // writeWaitForCE,
h2fmi_write_Xfer_data_handler, // writeXferData,
h2fmi_write_ending_write_handler, // writeEndingWrite,
h2fmi_write_ending_write_wait_CE_handler, // writeEndingWriteWaitCE,
h2fmi_write_done_handler, // writeDone
h2fmi_write_wait_for_CE_holdOff_handler // writeWaitingForNumCELimit
};
void h2fmi_handle_write_ISR_state_machine( h2fmi_t* fmi )
{
WMR_ASSERT(fmi->stateMachine.currentMode == fmiWrite);
WMR_ASSERT(fmi->stateMachine.state.wr <= writeWaitingForNumCELimit);
#if ( H2FMI_INSTRUMENT_BUS_1 )
{
int k = (int)fmi->stateMachine.state.wr;
h2fmi_instrument_bit_set(2);
while (k-- > 0)
{
h2fmi_instrument_bit_clear(2);
h2fmi_instrument_bit_set(2);
}
}
#endif
writeStateHandlerTable[ fmi->stateMachine.state.wr ]( fmi );
#if ( H2FMI_INSTRUMENT_BUS_1 )
h2fmi_instrument_bit_clear(2);
#endif
}
#else
void h2fmi_handle_write_ISR_state_machine( h2fmi_t* fmi )
{
// Someone's trying to write in a read-only config...
WMR_ASSERT(0);
}
#endif /* !H2FMI_READONLY */
// ********************************** EOF **************************************