// ***************************************************************************** // // 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 **************************************