// ***************************************************************************** // // File: fmiss_ppn.c // // Copyright (C) 2010 Apple 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 Inc. // // ***************************************************************************** #include "H2fmi_private.h" #include "H2fmi_dma.h" #include "H2fmi_ppn.h" #include #include #if FMISS_ENABLED #define FMISS_TIMEOUT_VALUE(f) (1 * 1000 * f->clock_speed_khz) #define FMISS_NS_TO_CLKS(f, t) (t * f->clock_speed_khz / (1000 * 1000)) // sequencer "wait_for_ready" command interrupt codes #define SEQ_WFR_CODE_UNUSED (0x00) #define SEQ_WFR_CODE_WRITE_STATUS (0x01) #define SEQ_WFR_CODE_INVALID (0xFF) // sequencer "send_interrupt" command interrupt codes #define SEQ_INT_CODE_UNUSED (0xFF0000) #define SEQ_INT_CODE_DRAIN_STATUS (0xFF0001) #define MACRO_LENGTH(s) \ (sizeof((UInt32[]){s})/sizeof(UInt32)) #if FMI_VERSION <= 3 #define GET_OPERATION_STATUS_SEQUENCE \ CMD_COMMAND(NAND_CMD__GET_NEXT_OPERATION_STATUS), \ CMD_COMMAND(NAND_CMD__OPERATION_STATUS), \ CMD_TIMED_WAIT(0), \ CMD_WAIT_FOR_READY(SEQ_WFR_CODE_UNUSED, 0x40, 0x40), \ CMD_SEND_INTERRUPT(SEQ_INT_CODE_UNUSED), \ CMD_PAUSE #else // FMI_VERSION <= 3 #define GET_OPERATION_STATUS_SEQUENCE \ CMD_COMMAND(NAND_CMD__GET_NEXT_OPERATION_STATUS), \ CMD_COMMAND(NAND_CMD__OPERATION_STATUS), \ CMD_TIMED_WAIT(0), \ CMD_WAIT_FOR_READY(SEQ_WFR_CODE_UNUSED, 0x40, 0x40), \ CMD_STORE_TO_FIFO(FMC_NAND_STATUS) #define STORE_CONTROLLER_STATUS_SEQUENCE \ CMD_COMMAND(NAND_CMD__CONTROLLER_STATUS), \ CMD_TIMED_WAIT(0), \ CMD_WAIT_FOR_READY(SEQ_WFR_CODE_WRITE_STATUS, 0x51, 0x40), \ CMD_STORE_TO_FIFO(FMC_NAND_STATUS) #endif // FMI_VERSION <= 3 #define GET_CONTROLLER_STATUS_SEQUENCE \ CMD_COMMAND(NAND_CMD__CONTROLLER_STATUS), \ CMD_TIMED_WAIT(0), \ CMD_WAIT_FOR_READY(SEQ_WFR_CODE_UNUSED, 0x40, 0x40), \ CMD_SEND_INTERRUPT(SEQ_INT_CODE_UNUSED), \ CMD_PAUSE #define PREP_READ_SEQUENCE \ CMD_LOAD_FROM_FIFO(FMC_ADDRNUM), \ CMD_LOAD_FROM_FIFO(FMC_ADDR0), \ CMD_LOAD_FROM_FIFO(FMC_ADDR1), \ CMD_LOAD_FROM_FIFO(FMC_CMD), \ CMD_LOAD_NEXT_WORD(FMC_RW_CTRL), \ FMC_RW_CTRL__CMD1_MODE | FMC_RW_CTRL__ADDR_MODE | FMC_RW_CTRL__CMD2_MODE, \ CMD_POLL(FMC_STATUS), \ FMC_STATUS__ADDRESSDONE | FMC_STATUS__CMD1DONE | FMC_STATUS__CMD2DONE, \ FMC_STATUS__ADDRESSDONE | FMC_STATUS__CMD1DONE | FMC_STATUS__CMD2DONE, \ CMD_LOAD_NEXT_WORD(FMC_STATUS), \ FMC_STATUS__ADDRESSDONE | FMC_STATUS__CMD1DONE | FMC_STATUS__CMD2DONE, \ CMD_TIMED_WAIT(1) #define START_WRITE_PAGE_SEQUENCE \ CMD_LOAD_FROM_FIFO(FMC_ADDR0), \ CMD_LOAD_FROM_FIFO(FMC_CMD), \ CMD_LOAD_NEXT_WORD(FMC_RW_CTRL), \ FMC_RW_CTRL__CMD1_MODE | FMC_RW_CTRL__ADDR_MODE, \ CMD_POLL(FMC_STATUS), \ FMC_STATUS__CMD1DONE | FMC_STATUS__ADDRESSDONE, \ FMC_STATUS__CMD1DONE | FMC_STATUS__ADDRESSDONE, \ CMD_LOAD_NEXT_WORD(FMC_STATUS), \ FMC_STATUS__CMD1DONE | FMC_STATUS__ADDRESSDONE #define PREP_WRITE_SEQUENCE \ CMD_LOAD_NEXT_WORD(FMC_CMD), \ NAND_CMD__MULTIPAGE_PREP, \ CMD_LOAD_FROM_FIFO(FMC_ADDR0), \ CMD_LOAD_NEXT_WORD(FMC_ADDRNUM), \ 2, \ CMD_LOAD_NEXT_WORD(FMC_RW_CTRL), \ FMC_RW_CTRL__CMD1_MODE | FMC_RW_CTRL__ADDR_MODE, \ CMD_POLL(FMC_STATUS), \ FMC_STATUS__ADDRESSDONE | FMC_STATUS__CMD1DONE, \ FMC_STATUS__ADDRESSDONE | FMC_STATUS__CMD1DONE, \ CMD_LOAD_NEXT_WORD(FMC_STATUS), \ FMC_STATUS__ADDRESSDONE | FMC_STATUS__CMD1DONE, // fmiss_put_* APIs implemented as macros so "value" can be a conditional statement evaluated at push-time #define fmiss_put_command(fmi, value, level, timeout) \ do \ { \ BOOL32 first = TRUE32; \ \ while ((COMMAND_FIFO_SIZE <= *(level)) && (!*(timeout)) && \ ((NULL == (fmi)->fmiss_cxt) || _kIOPFMI_STATUS_SUCCESS == *((fmiss_cxt_t *)(fmi)->fmiss_cxt)->fmi_status)) \ { \ \ if (!first) \ { \ if (!fmiss_yield(fmi)) \ { \ *(timeout) = TRUE32; \ break; \ } \ } \ else \ { \ first = FALSE32; \ } \ *(level) = COMMAND_FIFO_PTR__LEVEL(h2fmi_rd(fmi, COMMAND_FIFO_PTR)); \ } \ \ if (COMMAND_FIFO_SIZE > *(level)) \ { \ h2fmi_wr(fmi, COMMAND_FIFO, value); \ (*(level))++; \ } \ \ } \ while(0) #define fmiss_put_operand(fmi, value, level, timeout) \ do \ { \ BOOL32 first = TRUE32; \ \ while ((OPERAND_FIFO_SIZE <= *(level)) && (!*(timeout)) && \ ((NULL == (fmi)->fmiss_cxt) || _kIOPFMI_STATUS_SUCCESS == *((fmiss_cxt_t *)(fmi)->fmiss_cxt)->fmi_status)) \ { \ \ if (!first) \ { \ if (!fmiss_yield(fmi)) \ { \ *(timeout) = TRUE32; \ break; \ } \ } \ else \ { \ first = FALSE32; \ } \ *(level) = OPERAND_FIFO_PTR__LEVEL(h2fmi_rd(fmi, OPERAND_FIFO_PTR)); \ } \ \ if (OPERAND_FIFO_SIZE > *(level)) \ { \ h2fmi_wr(fmi, OPERAND_FIFO, value); \ (*(level))++; \ } \ \ } \ while(0) static UInt32 sequencer_macros[] = { GET_OPERATION_STATUS_SEQUENCE, GET_CONTROLLER_STATUS_SEQUENCE, #if FMI_VERSION >= 4 STORE_CONTROLLER_STATUS_SEQUENCE, #endif // FMI_VERSION >= 4 PREP_READ_SEQUENCE, START_WRITE_PAGE_SEQUENCE, PREP_WRITE_SEQUENCE }; static const UInt32 sequencer_macro_count = sizeof(sequencer_macros) / sizeof(sequencer_macros[0]); static const UInt32 timed_wait_ns[] = { PPN_TIMING_MIN_TRHW_NS, PPN_TIMING_MIN_TWHR_NS, }; static const UInt32 timed_wait_count = sizeof(timed_wait_ns) / sizeof(timed_wait_ns[0]); typedef enum { kGetOperationStatusHead, kGetOperationStatusLength = MACRO_LENGTH(GET_OPERATION_STATUS_SEQUENCE), kGetOperationStatusTail = kGetOperationStatusHead + kGetOperationStatusLength - 1, kGetControllerStatusHead, kGetControllerStatusLength = MACRO_LENGTH(GET_CONTROLLER_STATUS_SEQUENCE), kGetControllerStatusTail = kGetControllerStatusHead + kGetControllerStatusLength - 1, #if FMI_VERSION >= 4 kStoreControllerStatusHead, kStoreControllerStatusLength = MACRO_LENGTH(STORE_CONTROLLER_STATUS_SEQUENCE), kStoreControllerStatusTail = kStoreControllerStatusHead + kStoreControllerStatusLength - 1, #endif // FMI_VERSION >= 4 kPrepReadHead, kPrepReadLength = MACRO_LENGTH(PREP_READ_SEQUENCE), kPrepReadTail = kPrepReadHead + kPrepReadLength - 1, kStartWritePageHead, kStartWritePageLength = MACRO_LENGTH(START_WRITE_PAGE_SEQUENCE), kStartWritePageTail = kStartWritePageHead + kStartWritePageLength - 1, kPrepWriteHead, kPrepWriteLength = MACRO_LENGTH(PREP_WRITE_SEQUENCE), kPrepWriteTail = kPrepWriteHead + kPrepWriteLength - 1 } sequence_entry_t; typedef struct { PPNCommandStruct *command; UInt32 *fmi_status; UInt8 *ppn_status; UInt32 *status_page; UInt32 *store_level; BOOL32 *error; } fmiss_cxt_t; static UInt8 fmiss_ce_index_to_physical(const PPNCommandStruct *ppnCommand, h2fmi_ce_t ceIndex); static UInt32 fmiss_ce_reg_to_logical(h2fmi_t *fmi, UInt32 ceCtrl); static UInt8 fmiss_get_controller_status(h2fmi_t *fmi, UInt32 *command_level, BOOL32 *timeout); #if FMI_VERSION <= 3 static UInt8 fmiss_get_next_operation_status(h2fmi_t *fmi, UInt32 *command_level, BOOL32 *timeout); #endif // FMI_VERSION <= 3 static BOOL32 fmiss_ppn_prep_write_multi(h2fmi_t *fmi, const PPNCommandStruct *ppnCommand, UInt32 *command_level, UInt32 *operand_level); void fmiss_init_sequences(h2fmi_t *fmi) { static BOOL32 initialized = FALSE32; UInt32 i, j; if (!initialized) { for (i = 0; i < sequencer_macro_count; i++) { if (CMD_TIMED_WAIT(0) != (COMMAND_FIFO__COMMAND(~0UL) & sequencer_macros[i])) { continue; } for (j = 0; j < timed_wait_count; j++) { if (CMD_TIMED_WAIT(j) == sequencer_macros[i]) { const UInt32 clks = FMISS_NS_TO_CLKS(fmi, timed_wait_ns[j]); sequencer_macros[i] = CMD_TIMED_WAIT(clks); break; } } } initialized = TRUE32; } for (i = 0, j = 0 ; i < sequencer_macro_count ; i++, j += sizeof(*sequencer_macros)) { h2fmi_wr(fmi, SEQUENCER_MACROS(j), sequencer_macros[i]); } } #if FMI_VERSION >= 4 static void fmiss_ppn_handle_drain_status(h2fmi_t *fmi, UInt32 count) { fmiss_cxt_t *cxt = fmi->fmiss_cxt; PPNCommandEntry *entry = &cxt->command->entry[*cxt->status_page]; *cxt->store_level = STORE_FIFO_PTR__LEVEL(h2fmi_rd(fmi, STORE_FIFO_PTR)); if ((0 == count) || (*cxt->store_level < count)) { count = *cxt->store_level; } while (((0 < count) || cxt->error[entry->ceIdx]) && (cxt->command->num_pages > *cxt->status_page)) { if (!cxt->error[entry->ceIdx]) { UInt32 status = h2fmi_rd(fmi, STORE_FIFO); (*cxt->store_level)--; count--; *cxt->ppn_status |= status; entry->status = status; } (*cxt->status_page)++; entry++; } } static void fmiss_ppn_handle_command_fixup(h2fmi_t *fmi) { const UInt32 fmc_ce_ctrl = h2fmi_rd(fmi, FMC_CE_CTRL); const UInt32 operand_fifo_ptr = h2fmi_rd(fmi, OPERAND_FIFO_PTR); const UInt32 command_fifo_ptr = h2fmi_rd(fmi, COMMAND_FIFO_PTR); const UInt32 operand_count = OPERAND_FIFO_PTR__LEVEL(operand_fifo_ptr); const UInt32 command_count = COMMAND_FIFO_PTR__LEVEL(command_fifo_ptr); UInt32 operand_list[OPERAND_FIFO_SIZE]; UInt32 command_list[COMMAND_FIFO_SIZE]; BOOL32 failingCe = TRUE32; UInt32 i; WMR_ASSERT(OPERAND_FIFO_SIZE >= operand_count); WMR_ASSERT(COMMAND_FIFO_SIZE >= command_count); for (i = 0 ; i < operand_count ; i++) { const UInt32 fifoIdx = (OPERAND_FIFO_PTR__READ(operand_fifo_ptr) + i) % OPERAND_FIFO_SIZE * sizeof(UInt32); operand_list[i] = h2fmi_rd(fmi, SEQUENCER_OPERANDS(fifoIdx)); } for (i = 0 ; i < command_count ; i++) { const UInt32 fifoIdx = (COMMAND_FIFO_PTR__READ(command_fifo_ptr) + i) % COMMAND_FIFO_SIZE * sizeof(UInt32); command_list[i] = h2fmi_rd(fmi, SEQUENCER_COMMANDS(fifoIdx)); } h2fmi_wr(fmi, FMI_CONTROL, FMI_CONTROL__RESET_SEQUENCER | FMI_CONTROL__ENABLE_SEQUENCER | h2fmi_rd(fmi, FMI_CONTROL)); // unfortunately, the sequencer reset does not propagate to the macro execution block: rdar://problem/10188242 // wait for and discard the pending status store operation while (0 == STORE_FIFO_PTR__LEVEL(h2fmi_rd(fmi, STORE_FIFO_PTR))) { WMR_YIELD(); } h2fmi_rd(fmi, STORE_FIFO); for (i = 0 ; i < operand_count ; i++) { h2fmi_wr(fmi, OPERAND_FIFO, operand_list[i]); } // fix-up outstanding "enable_chip" and "get_controller_status" commands for failing CE for (i = 0 ; i < command_count ; i++) { if (CMD_ENABLE_CHIP(fmc_ce_ctrl) == command_list[i]) { failingCe = TRUE32; command_list[i] = CMD_ENABLE_CHIP(0); } else if (CMD_ENABLE_CHIP(0) == (COMMAND_FIFO__COMMAND(~0UL) & command_list[i])) { failingCe = FALSE32; } else if (failingCe && (CMD_MACRO(kStoreControllerStatusHead, kStoreControllerStatusLength) == command_list[i])) { continue; } h2fmi_wr(fmi, COMMAND_FIFO, command_list[i]); } } static void fmiss_ppn_handle_write_error(h2fmi_t *fmi, UInt8 status) { fmiss_cxt_t *cxt = fmi->fmiss_cxt; PPNCommandEntry *entry; fmiss_ppn_handle_drain_status(fmi, 0); entry = &cxt->command->entry[*cxt->status_page]; *cxt->ppn_status |= status; entry->status = status; (*cxt->status_page)++; if (0 == status) { *cxt->fmi_status = _kIOPFMI_STATUS_READY_BUSY_TIMEOUT; } else if (0 != (status & PPN_CONTROLLER_STATUS__GENERAL_ERROR)) { fmi->ppn->general_error = TRUE32; fmi->ppn->general_error_ce = fmiss_ce_reg_to_logical(fmi, h2fmi_rd(fmi, FMC_CE_CTRL)); *cxt->fmi_status = _kIOPFMI_STATUS_PPN_GENERAL_ERROR; } else if (0 != (status & PPN_CONTROLLER_STATUS__PENDING_ERRORS)) { // we don't actually know which page on this CE failed yet entry->status = PPN_CONTROLLER_STATUS__READY; cxt->error[entry->ceIdx] = TRUE32; } else { WMR_PANIC("Unhandled NAND status: 0x%02x!\n", status); } if (_kIOPFMI_STATUS_SUCCESS == *cxt->fmi_status) { fmiss_ppn_handle_command_fixup(fmi); } return; } #endif // FMI_VERSION >= 4 static BOOL32 fmiss_yield(h2fmi_t *fmi) { BOOL32 ret = TRUE32; const UInt32 seq_int_pend = h2fmi_rd(fmi, SEQ_INT_PEND); if (0 != (SEQ_INT_PEND__TIMEOUT & seq_int_pend)) { #if H2FMI_IOP WMR_PANIC("Sequencer Timeout!"); #endif // H2FMI_IOP #if FMI_VERSION >= 4 fmiss_ppn_handle_write_error(fmi, 0); #endif // FMI_VERSION >= 4 ret = FALSE32; goto exit; } #if FMI_VERSION >= 4 if ((NULL != fmi->fmiss_cxt) && (0 != (SEQ_INT_PEND__SEQUENCER_SIGNAL & seq_int_pend))) { const UInt32 code = h2fmi_rd(fmi, COMMAND_INT_CODE); h2fmi_wr(fmi, SEQ_INT_PEND, SEQ_INT_PEND__SEQUENCER_SIGNAL); if (SEQ_WFR_CODE_INVALID != COMMAND_INT_CODE__WAIT_FOR_READY_CODE(code)) { switch (COMMAND_INT_CODE__WAIT_FOR_READY_CODE(code)) { case SEQ_WFR_CODE_WRITE_STATUS: fmiss_ppn_handle_write_error(fmi, COMMAND_INT_CODE__WAIT_FOR_READY_STATUS(code)); break; default: WMR_PANIC("Unexpected sequencer interrupt: 0x%08x!\n", code); } } else { switch (COMMAND_INT_CODE__READ(code)) { case SEQ_INT_CODE_DRAIN_STATUS: fmiss_ppn_handle_drain_status(fmi, 0); break; default: WMR_PANIC("Unexpected sequencer interrupt: 0x%08x!\n", code); } } } else if (NULL != fmi->fmiss_cxt) { fmiss_ppn_handle_drain_status(fmi, 1); } #endif // FMI_VERSION >= 4 WMR_YIELD(); exit: return ret; } #if FMI_VERSION >= 4 static UInt32 fmiss_get_store(h2fmi_t *fmi, UInt32 *level, BOOL32 *timeout) { UInt32 ret = 0; BOOL32 first = TRUE32; while ((0 >= *level) && (!*timeout) && ((NULL == fmi->fmiss_cxt) || _kIOPFMI_STATUS_SUCCESS == *((fmiss_cxt_t *)fmi->fmiss_cxt)->fmi_status)) { if (!first) { if (!fmiss_yield(fmi)) { *timeout = TRUE32; break; } } else { first = FALSE32; } *level = STORE_FIFO_PTR__LEVEL(h2fmi_rd(fmi, STORE_FIFO_PTR)); } if (0 < *level) { ret = h2fmi_rd(fmi, STORE_FIFO); (*level)--; } return ret; } #endif // FMI_VERSION >= 4 Int32 fmiss_ppn_read_multi(h2fmi_t *fmi, PPNCommandStruct *ppnCommand, struct dma_segment *data_segment_array, struct dma_segment *meta_segment_array) { const UInt32 page_count = ppnCommand->num_pages; const UInt32 read_queue_size = fmi->ppn->device_params.read_queue_size; UInt32 overall_status; UInt32 prep_page; UInt32 read_page; UInt32 i; UInt32 queue_depth[PPN_MAX_CES_PER_BUS] = {0}; UInt32 ce_page_count[PPN_MAX_CES_PER_BUS] = {0}; UInt32 iopfmiStatus; BOOL32 timeout; UInt32 command_level = 0; UInt32 operand_level = 0; #if FMI_VERSION >= 4 UInt32 status_page = 0; UInt32 store_level = 0; #endif // FMI_VERSION >= 4 UInt32 fmi_control = (FMI_CONTROL__RESET_SEQUENCER | FMI_CONTROL__ENABLE_SEQUENCER | FMI_CONTROL__SEQUENCER_TIMEOUT_ENABLE | FMI_CONTROL__MODE__READ); UInt8 op_status; #if FMI_VERSION >= 4 fmi_control |= FMI_CONTROL__PAUSE_WHEN_STORE_FIFO_FULL; #if FMI_VERSION == 4 if (fmi->read_stream_disable) { fmi_control |= FMI_CONTROL__DISABLE_STREAMING; } #endif // FMI_VERSION == 4 #endif // FMI_VERSION >= 4 timeout = FALSE32; overall_status = 0; prep_page = 0; read_page = 0; WMR_ASSERT(page_count > 0); h2fmi_wr(fmi, FMI_CONTROL, FMI_CONTROL__MODE__SOFTWARE_RESET); if(ppnCommand->options & PPN_OPTIONS_GET_PAGE_RMA_INFO) { h2fmi_ce_t ce = fmiss_ce_index_to_physical(ppnCommand, 0); WMR_PRINT(ALWAYS, "Attempting to pull RMA data for CE %d page 0x%08x\n", ce, ppnCommand->entry[0].addr.row); h2fmi_ppn_force_geb_address(fmi, ce, ppnCommand->entry[0].addr.row); return _kIOPFMI_STATUS_PPN_GENERAL_ERROR; } if (ppnCommand->options & PPN_OPTIONS_REPORT_HEALTH) { UInt32 feature = 1; for (i = 0; i < PPN_MAX_CES_PER_BUS; i++) { if (ppnCommand->ceInfo[i].pages > 0) { h2fmi_ce_t ce = ppnCommand->ceInfo[i].ce; if (FIL_SUCCESS != h2fmi_ppn_set_features(fmi, ce, PPN_FEATURE__ENABLE_BITFLIPS_DATA_COLLECTION, (UInt8 *)&feature, PPN_FEATURE_LENGTH_ENABLE_BITFLIPS_DATA_COLLECTION, FALSE32, NULL)) { WMR_PANIC("Failed to enable bitflip collection on CE index %d (physical %d)", i, ce); } } } } h2fmi_wr(fmi, FMI_CONFIG, fmi->fmi_config_reg); h2fmi_wr(fmi, FMI_DATA_SIZE, (FMI_DATA_SIZE__BYTES_PER_SECTOR(H2FMI_BYTES_PER_SECTOR) | FMI_DATA_SIZE__SECTORS_PER_PAGE(fmi->logical_page_size / H2FMI_BYTES_PER_SECTOR) | FMI_DATA_SIZE__META_BYTES_PER_SECTOR(fmi->valid_bytes_per_meta) | FMI_DATA_SIZE__META_BYTES_PER_PAGE(fmi->valid_bytes_per_meta))); h2fmi_wr(fmi, TIMEOUT_VALUE, FMISS_TIMEOUT_VALUE(fmi)); h2fmi_wr(fmi, FMI_CONTROL, fmi_control); h2fmi_wr(fmi, SEQ_INT_PEND, 0xFFFFFFFF); for (read_page = 0; read_page < page_count; read_page++) { const UInt8 read_page_ce_index = ppnCommand->entry[read_page].ceIdx; const h2fmi_ce_t read_page_phys_ce = fmiss_ce_index_to_physical(ppnCommand, read_page_ce_index); UInt32 lba; while ((queue_depth[ppnCommand->entry[prep_page].ceIdx] < read_queue_size) && (prep_page < page_count)) { const UInt8 ce_index = ppnCommand->entry[prep_page].ceIdx; const h2fmi_ce_t phys_ce = fmiss_ce_index_to_physical(ppnCommand, ce_index); UInt32 *page_addr = (UInt32 *)&ppnCommand->entry[prep_page].addr; if (ce_index > PPN_MAX_CES_PER_BUS) { WMR_PRINT(ERROR, "ce_index (%d) < PPN_MAX_CES_PER_BUS (%d)\n", ce_index, PPN_MAX_CES_PER_BUS); return _kIOPFMI_STATUS_PARAM_INVALID; } fmiss_put_command(fmi, CMD_ENABLE_CHIP(phys_ce), &command_level, &timeout); // in Async mode, we are currently meeting tCS requirements because the command FIFO // is empty on initial CE assertion and register writes from the IOP are relatively slow // if the location of the CE assertion command changes, this will have to be reevaluated if (fmi->logical_page_size == fmi->bytes_per_page) { fmiss_put_operand(fmi, fmi->ppn->bytes_per_row_address - 1, &operand_level, &timeout); fmiss_put_operand(fmi, ppnCommand->entry[prep_page].addr.row, &operand_level, &timeout); fmiss_put_operand(fmi, 0, &operand_level, &timeout); } else { fmiss_put_operand(fmi, fmi->ppn->bytes_per_full_address - 1, &operand_level, &timeout); fmiss_put_operand(fmi, page_addr[0], &operand_level, &timeout); fmiss_put_operand(fmi, page_addr[1], &operand_level, &timeout); } if (ce_page_count[ce_index] + 1 == ppnCommand->ceInfo[ce_index].pages) { fmiss_put_operand(fmi, (FMC_CMD__CMD1(NAND_CMD__MULTIPAGE_READ_LAST) | FMC_CMD__CMD2(NAND_CMD__MULTIPAGE_READ_CONFIRM)), &operand_level, &timeout); } else { fmiss_put_operand(fmi, (FMC_CMD__CMD1(NAND_CMD__MULTIPAGE_READ) | FMC_CMD__CMD2(NAND_CMD__MULTIPAGE_READ_CONFIRM)), &operand_level, &timeout); } fmiss_put_command(fmi, CMD_MACRO(kPrepReadHead, kPrepReadLength), &command_level, &timeout); if (timeout) { break; } ce_page_count[ce_index]++; prep_page++; queue_depth[ce_index]++; } if (0 == read_page) { h2fmi_dma_execute_async(DMA_CMD_DIR_RX, h2fmi_dma_data_chan(fmi), data_segment_array, h2fmi_dma_data_fifo(fmi), ppnCommand->lbas * fmi->logical_page_size, sizeof(UInt32), H2FMI_DMA_BURST_CYCLES, fmi->current_aes_cxt); h2fmi_dma_execute_async(DMA_CMD_DIR_RX, h2fmi_dma_meta_chan(fmi), meta_segment_array, h2fmi_dma_meta_fifo(fmi), ppnCommand->lbas * fmi->valid_bytes_per_meta, #if FMI_VERSION >= 4 sizeof(UInt32), 4, #else // FMI_VERSION < 4 sizeof(UInt8), 1, #endif // FMI_VERSION < 4 NULL); } fmiss_put_command(fmi, CMD_ENABLE_CHIP(read_page_phys_ce), &command_level, &timeout); #if FMI_VERSION <= 3 op_status = fmiss_get_next_operation_status(fmi, &command_level, &timeout); if (fmi->retire_on_invalid_refresh && ((PPN_OPERATION_STATUS__REFRESH | PPN_OPERATION_STATUS__ERROR) == (op_status & (PPN_OPERATION_STATUS__REFRESH | PPN_OPERATION_STATUS__ERROR)))) { // Remap 0x43 (refresh + invalid) to 0x45 (retire + invalid) op_status = (op_status & ~(PPN_OPERATION_STATUS__REFRESH)) | PPN_OPERATION_STATUS__RETIRE; } ppnCommand->entry[read_page].status = op_status; overall_status |= op_status; #else // FMI_VERSION <= 3 if (STORE_FIFO_SIZE <= read_page) { op_status = fmiss_get_store(fmi, &store_level, &timeout); if (fmi->retire_on_invalid_refresh && ((PPN_OPERATION_STATUS__REFRESH | PPN_OPERATION_STATUS__ERROR) == (op_status & (PPN_OPERATION_STATUS__REFRESH | PPN_OPERATION_STATUS__ERROR)))) { // Remap 0x43 (refresh + invalid) to 0x45 (retire + invalid) op_status = (op_status & ~(PPN_OPERATION_STATUS__REFRESH)) | PPN_OPERATION_STATUS__RETIRE; } ppnCommand->entry[status_page].status = op_status; overall_status |= op_status; if (0 != (overall_status & PPN_OPERATION_STATUS__GENERAL_ERROR)) { const PPNCommandEntry *entry = &ppnCommand->entry[status_page]; const PPNCommandCeInfo *ceInfo = &ppnCommand->ceInfo[entry->ceIdx]; fmi->ppn->general_error = TRUE32; fmi->ppn->general_error_ce = ceInfo->ce; } status_page++; } fmiss_put_command(fmi, CMD_MACRO(kGetOperationStatusHead, kGetOperationStatusLength), &command_level, &timeout); #endif // FMI_VERSION <= 3 if (overall_status & PPN_OPERATION_STATUS__GENERAL_ERROR) { WMR_PRINT(ERROR, "Aborting read due to general error\n"); break; } fmiss_put_command(fmi, CMD_TIMED_WAIT(FMISS_NS_TO_CLKS(fmi, PPN_TIMING_MIN_TRHW_NS)), &command_level, &timeout); fmiss_put_command(fmi, CMD_COMMAND(NAND_CMD__READ_SERIAL_OUTPUT), &command_level, &timeout); fmiss_put_command(fmi, CMD_TIMED_WAIT(FMISS_NS_TO_CLKS(fmi, PPN_TIMING_MIN_TWHR_NS)), &command_level, &timeout); for (lba = 0; lba < ppnCommand->entry[read_page].lbas; lba++) { #if FMI_VERSION >= 5 if ((0 < fmi->read_tx_page_delay) && (0 < lba)) { fmiss_put_command(fmi, CMD_TIMED_WAIT(fmi->read_tx_page_delay - 1), &command_level, &timeout); } #endif // FMI_VERSION >= 5 fmiss_put_command(fmi, CMD_TX_PAGE, &command_level, &timeout); } fmiss_put_command(fmi, CMD_TIMED_WAIT(FMISS_NS_TO_CLKS(fmi, PPN_TIMING_MIN_TRHW_NS)), &command_level, &timeout); if (timeout) { break; } queue_depth[read_page_ce_index]--; } for (i = 0; i < PPN_MAX_CES_PER_BUS; i++) { if (ppnCommand->ceInfo[i].pages > 0) { h2fmi_ce_t phys_ce = 1 << (ppnCommand->ceInfo[i].ce & (H2FMI_MAX_CE_PER_BUS -1)); fmiss_put_command(fmi, CMD_ENABLE_CHIP(phys_ce), &command_level, &timeout); fmiss_put_command(fmi, CMD_COMMAND(NAND_CMD__GET_NEXT_OPERATION_STATUS), &command_level, &timeout); } } fmiss_put_command(fmi, CMD_ENABLE_CHIP(0), &command_level, &timeout); #if FMI_VERSION >= 4 while (!fmi->ppn->general_error && (status_page < page_count)) { op_status = fmiss_get_store(fmi, &store_level, &timeout); if (fmi->retire_on_invalid_refresh && ((PPN_OPERATION_STATUS__REFRESH | PPN_OPERATION_STATUS__ERROR) == (op_status & (PPN_OPERATION_STATUS__REFRESH | PPN_OPERATION_STATUS__ERROR)))) { // Remap 0x43 (refresh + invalid) to 0x45 (retire + invalid) op_status = (op_status & ~(PPN_OPERATION_STATUS__REFRESH)) | PPN_OPERATION_STATUS__RETIRE; } ppnCommand->entry[status_page].status = op_status; if (timeout) { break; } overall_status |= op_status; if (0 != (overall_status & PPN_OPERATION_STATUS__GENERAL_ERROR)) { const PPNCommandEntry *entry = &ppnCommand->entry[status_page]; const PPNCommandCeInfo *ceInfo = &ppnCommand->ceInfo[entry->ceIdx]; fmi->ppn->general_error = TRUE32; fmi->ppn->general_error_ce = ceInfo->ce; } status_page++; } #endif // FMI_VERSION >= 4 ppnCommand->page_status_summary = overall_status; if (timeout) { iopfmiStatus = _kIOPFMI_STATUS_READY_BUSY_TIMEOUT; h2fmi_dma_cancel(h2fmi_dma_meta_chan(fmi)); h2fmi_dma_cancel(h2fmi_dma_data_chan(fmi)); } else if (overall_status & PPN_OPERATION_STATUS__GENERAL_ERROR) { iopfmiStatus = _kIOPFMI_STATUS_PPN_GENERAL_ERROR; h2fmi_dma_cancel(h2fmi_dma_meta_chan(fmi)); h2fmi_dma_cancel(h2fmi_dma_data_chan(fmi)); h2fmi_wr(fmi, FMI_CONTROL, (FMI_CONTROL__RESET_SEQUENCER | FMI_CONTROL__ENABLE_SEQUENCER)); WMR_PRINT(ERROR, "IOP returning kIOPFMI_STATUS_PPN_GENERAL_ERROR\n"); } else { iopfmiStatus = _kIOPFMI_STATUS_SUCCESS; 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)) { iopfmiStatus = _kIOPFMI_STATUS_DMA_DONE_TIMEOUT; h2fmi_dma_cancel(h2fmi_dma_meta_chan(fmi)); h2fmi_dma_cancel(h2fmi_dma_data_chan(fmi)); } } return iopfmiStatus; } #if !(defined(AND_READONLY)) || AND_SUPPORT_NVRAM Int32 fmiss_ppn_write_multi(h2fmi_t *fmi, PPNCommandStruct *ppnCommand, struct dma_segment *data_segment_array, struct dma_segment *meta_segment_array) { const UInt32 page_count = ppnCommand->num_pages; UInt32 pageIndex = 0; BOOL32 error[PPN_MAX_CES_PER_BUS] = {FALSE32}; UInt8 overall_status = 0; UInt32 ce_page_count[PPN_MAX_CES_PER_BUS] = {0}; UInt32 command_level = 0; UInt32 operand_level = 0; UInt32 fmi_control = (FMI_CONTROL__MODE__WRITE | FMI_CONTROL__ENABLE_SEQUENCER | FMI_CONTROL__SEQUENCER_TIMEOUT_ENABLE); UInt32 program_status = _kIOPFMI_STATUS_SUCCESS; BOOL32 timeout = FALSE32; UInt8 errorCeIndex; #if FMI_VERSION >= 4 UInt32 status_page = 0; UInt32 store_level = 0; fmiss_cxt_t cxt; #endif // FMI_VERSION >= 4 #if FMI_VERSION >= 4 fmi_control |= FMI_CONTROL__PAUSE_WHEN_STORE_FIFO_FULL; #endif // FMI_VERSION >= 4 WMR_ASSERT(page_count > 0); WMR_ASSERT(page_count <= fmi->ppn->device_params.prep_function_buffer_size); h2fmi_wr(fmi, FMI_CONTROL, FMI_CONTROL__MODE__SOFTWARE_RESET); h2fmi_wr(fmi, TIMEOUT_VALUE, FMISS_TIMEOUT_VALUE(fmi)); h2fmi_wr(fmi, FMI_CONTROL, FMI_CONTROL__ENABLE_SEQUENCER); h2fmi_wr(fmi, SEQ_INT_PEND, 0xFFFFFFFF); if (page_count > 1) { if (!fmiss_ppn_prep_write_multi(fmi, ppnCommand, &command_level, &operand_level)) { return _kIOPFMI_STATUS_READY_BUSY_TIMEOUT; } } #if FMI_VERSION >= 4 cxt.command = ppnCommand; cxt.fmi_status = &program_status; cxt.ppn_status = &overall_status; cxt.store_level = &store_level; cxt.status_page = &status_page; cxt.error = error; fmi->fmiss_cxt = &cxt; #endif // FMI_VERSION >= 4 h2fmi_wr(fmi, FMI_CONFIG, fmi->fmi_config_reg); h2fmi_wr(fmi, FMI_DATA_SIZE, (FMI_DATA_SIZE__BYTES_PER_SECTOR(H2FMI_BYTES_PER_SECTOR) | FMI_DATA_SIZE__SECTORS_PER_PAGE(fmi->logical_page_size / H2FMI_BYTES_PER_SECTOR) | FMI_DATA_SIZE__META_BYTES_PER_SECTOR(fmi->valid_bytes_per_meta) | FMI_DATA_SIZE__META_BYTES_PER_PAGE(fmi->valid_bytes_per_meta))); h2fmi_wr(fmi, FMC_ADDRNUM, fmi->ppn->bytes_per_row_address - 1); h2fmi_wr(fmi, FMI_CONTROL, fmi_control); h2fmi_dma_execute_async(DMA_CMD_DIR_TX, h2fmi_dma_data_chan(fmi), data_segment_array, h2fmi_dma_data_fifo(fmi), ppnCommand->lbas * fmi->logical_page_size, sizeof(UInt32), H2FMI_DMA_BURST_CYCLES, fmi->current_aes_cxt); h2fmi_dma_execute_async(DMA_CMD_DIR_TX, h2fmi_dma_meta_chan(fmi), meta_segment_array, h2fmi_dma_meta_fifo(fmi), ppnCommand->lbas * fmi->valid_bytes_per_meta, #if FMI_VERSION >= 4 sizeof(UInt32), 4, #else // FMI_VERSION < 4 sizeof(UInt8), 1, #endif // FMI_VERSION < 4 NULL); do { const UInt8 ceIndex = ppnCommand->entry[pageIndex].ceIdx; const h2fmi_ce_t physCe = fmiss_ce_index_to_physical(ppnCommand, ceIndex); UInt32 lba; fmiss_put_command(fmi, CMD_ENABLE_CHIP(error[ceIndex] ? 0 : physCe), &command_level, &timeout); // in Async mode, we are currently meeting tCS requirements because the command FIFO // is empty on initial CE assertion and register writes from the IOP are relatively slow // if the location of the CE assertion command changes, this will have to be reevaluated fmiss_put_operand(fmi, ppnCommand->entry[pageIndex].addr.row, &operand_level, &timeout); if (ce_page_count[ceIndex] + 1 == ppnCommand->ceInfo[ceIndex].pages) { fmiss_put_operand(fmi, FMC_CMD__CMD1(NAND_CMD__MULTIPAGE_PROGRAM_LAST), &operand_level, &timeout); } else { fmiss_put_operand(fmi, FMC_CMD__CMD1(NAND_CMD__MULTIPAGE_PROGRAM), &operand_level, &timeout); } fmiss_put_command(fmi, CMD_MACRO(kStartWritePageHead, kStartWritePageLength), &command_level, &timeout); for (lba = 0; lba < ppnCommand->entry[pageIndex].lbas; lba++) { fmiss_put_command(fmi, CMD_TX_PAGE, &command_level, &timeout); } fmiss_put_command(fmi, CMD_COMMAND(NAND_CMD__MULTIPAGE_PROGRAM_CONFIRM), &command_level, &timeout); #if FMI_VERSION < 4 if (!error[ceIndex]) { UInt8 pageStatus = fmiss_get_controller_status(fmi, &command_level, &timeout); if (timeout) { // Don't look at pageStatus at all if we timed out - it could have bogus bits set. pageStatus = 0; } // Always mark the program as good here - if there was a program error or if the page was // kicked off but not completed, we'll fix up the status when we pull the program error // lists. ppnCommand->entry[pageIndex].status = PPN_CONTROLLER_STATUS__READY; overall_status |= pageStatus; if (timeout) { // Timeout reading page status program_status = _kIOPFMI_STATUS_READY_BUSY_TIMEOUT; } else if (pageStatus & PPN_CONTROLLER_STATUS__GENERAL_ERROR) { program_status = _kIOPFMI_STATUS_PPN_GENERAL_ERROR; } else if (pageStatus & PPN_CONTROLLER_STATUS__PENDING_ERRORS) { error[ceIndex] = TRUE32; } } else { // If we've seen a program error on a particular CE, we don't want to issue any more commands to it. // But we need to keep the DMA moving so we can complete the programs on other CEs. So we kick off the // program op with the CE disabled and just don't pull status. ppnCommand->entry[pageIndex].status = 0; } #else // FMI_VERSION >= 4 if (!error[ceIndex]) { fmiss_put_command(fmi, error[ceIndex] ? CMD_TIMED_WAIT(0) : CMD_MACRO(kStoreControllerStatusHead, kStoreControllerStatusLength), &command_level, &timeout); } if (page_count - 1 == pageIndex) { fmiss_put_command(fmi, CMD_SEND_INTERRUPT(SEQ_INT_CODE_DRAIN_STATUS), &command_level, &timeout); } #endif // FMI_VERSION >= 4 ce_page_count[ceIndex]++; pageIndex++; } while ((program_status == _kIOPFMI_STATUS_SUCCESS) && (pageIndex < page_count)); fmiss_put_command(fmi, CMD_ENABLE_CHIP(0), &command_level, &timeout); #if FMI_VERSION >= 4 while ((program_status == _kIOPFMI_STATUS_SUCCESS) && (status_page < page_count)) { fmiss_yield(fmi); } #endif // FMI_VERSION >= 4 if ((program_status != _kIOPFMI_STATUS_SUCCESS) || timeout || !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_dma_cancel(h2fmi_dma_meta_chan(fmi)); h2fmi_dma_cancel(h2fmi_dma_data_chan(fmi)); } for (errorCeIndex = 0; errorCeIndex < PPN_MAX_CES_PER_BUS; errorCeIndex++) { UInt32 temp = 1; UInt8 status = 0; if (error[errorCeIndex]) { const h2fmi_ce_t physCe = ppnCommand->ceInfo[errorCeIndex].ce; program_status = _kIOPFMI_STATUS_PGM_ERROR; // Program terminated early - reset sequencer h2fmi_wr(fmi, FMI_CONTROL, (FMI_CONTROL__RESET_SEQUENCER | FMI_CONTROL__ENABLE_SEQUENCER)); h2fmi_reset(fmi); // Find failed pages... h2fmi_ppn_get_feature(fmi, physCe, PPN_FEATURE__PROGRAM_FAILED_PAGES, (UInt8 *)fmi->error_list, PPN_ERROR_LIST_SIZE, &status); WMR_PRINT(ERROR, "PPN device reports %d pages failed program\n", fmi->error_list[0]); h2fmi_ppn_process_error_list(fmi, ppnCommand, errorCeIndex, fmi->error_list, PPN_PROGRAM_STATUS_FAIL); // Find pending pages... h2fmi_ppn_get_feature(fmi, physCe, PPN_FEATURE__PROGRAM_IGNORED_PAGES, (UInt8 *)fmi->error_list, PPN_ERROR_LIST_SIZE, &status); WMR_PRINT(ERROR, "PPN device reports %d pages pending after program failure\n", fmi->error_list[0]); h2fmi_ppn_process_error_list(fmi, ppnCommand, errorCeIndex, fmi->error_list, PPN_PROGRAM_STATUS_NOT_PROGRAMMED); // Find pages to retire... h2fmi_ppn_get_feature(fmi, physCe, PPN_FEATURE__PROGRAM_RETIRED_PAGES, (UInt8 *)fmi->error_list, PPN_ERROR_LIST_SIZE, &status); WMR_PRINT(ERROR, "PPN device reports %d pages should be retired after program failure\n", fmi->error_list[0]); h2fmi_ppn_process_error_list(fmi, ppnCommand, errorCeIndex, fmi->error_list, PPN_PROGRAM_STATUS_FAIL); // Clear Program error lists h2fmi_ppn_set_features(fmi, physCe, PPN_FEATURE__CLEAR_PROGRAM_ERROR_LISTS, (UInt8 *)&temp, 4, FALSE32, NULL); } } ppnCommand->page_status_summary = overall_status; if (program_status == _kIOPFMI_STATUS_PPN_GENERAL_ERROR) { WMR_PRINT(ERROR, "Program aborted due to GEB - resetting sequencer\n"); h2fmi_wr(fmi, FMI_CONTROL, (FMI_CONTROL__RESET_SEQUENCER | FMI_CONTROL__ENABLE_SEQUENCER)); } #if FMI_VERSION >= 4 fmi->fmiss_cxt = NULL; #endif // FMI_VERSION >= 4 return program_status; } #endif // !(defined(AND_READONLY)) || AND_SUPPORT_NVRAM static BOOL32 fmiss_ppn_prep_write_multi(h2fmi_t *fmi, const PPNCommandStruct *ppnCommand, UInt32 *command_level, UInt32 *operand_level) { const UInt32 totalPages = ppnCommand->num_pages; UInt16 ceIndex; UInt32 pageIndex; struct dma_segment dma_segment; UInt32 fmi_control = (FMI_CONTROL__MODE__WRITE | FMI_CONTROL__ENABLE_SEQUENCER | FMI_CONTROL__SEQUENCER_TIMEOUT_ENABLE); BOOL32 timeout = FALSE32; #if FMI_VERSION >= 4 fmi_control |= FMI_CONTROL__PAUSE_WHEN_STORE_FIFO_FULL; #endif // FMI_VERSION >= 4 for (pageIndex = 0 ; pageIndex < totalPages ; pageIndex++) { const PPNCommandEntry *entry = &ppnCommand->entry[pageIndex]; *fmi->ppn->prep_buffer[entry->ceIdx]++ = entry->addr.row; } for (ceIndex = 0; ceIndex < PPN_MAX_CES_PER_BUS; ceIndex++) { const UInt32 numPages = ppnCommand->ceInfo[ceIndex].pages; UInt32 addrBytes = numPages * sizeof(UInt32); const h2fmi_ce_t physCe = fmiss_ce_index_to_physical(ppnCommand, ceIndex); UInt32 fullSectors; fmi->ppn->prep_buffer[ceIndex] -= numPages; if (numPages) { const UInt32 prepBuffer = (UInt32)fmi->ppn->prep_buffer[ceIndex]; WMR_PREPARE_WRITE_BUFFER(fmi->ppn->prep_buffer[ceIndex], numPages * sizeof(**fmi->ppn->prep_buffer)); #if WMR_BUILDING_IBOOT dma_segment.paddr = mem_static_map_physical(prepBuffer); #else dma_segment.paddr = prepBuffer; #endif dma_segment.length = numPages * sizeof(UInt32); h2fmi_dma_execute_async(DMA_CMD_DIR_TX, h2fmi_dma_data_chan(fmi), &dma_segment, h2fmi_dma_data_fifo(fmi), numPages * sizeof(UInt32), sizeof(UInt32), 32, NULL); h2fmi_wr(fmi, FMI_CONFIG, FMI_CONFIG__DMA_BURST__32_CYCLES); fmiss_put_command(fmi, CMD_ENABLE_CHIP(physCe), command_level, &timeout); // in Async mode, we are currently meeting tCS requirements because the command FIFO // is empty on initial CE assertion and register writes from the IOP are relatively slow // if the location of the CE assertion command changes, this will have to be reevaluated fmiss_put_operand(fmi, (numPages << 8) | 0x87, operand_level, &timeout); fmiss_put_command(fmi, CMD_MACRO(kPrepWriteHead, kPrepWriteLength), command_level, &timeout); fullSectors = addrBytes / H2FMI_BYTES_PER_SECTOR; if (fullSectors > 0) { fmiss_put_command(fmi, CMD_LOAD_NEXT_WORD(FMI_DATA_SIZE), command_level, &timeout); fmiss_put_command(fmi, (FMI_DATA_SIZE__BYTES_PER_SECTOR(H2FMI_BYTES_PER_SECTOR)| FMI_DATA_SIZE__SECTORS_PER_PAGE(fullSectors)), command_level, &timeout); fmiss_put_command(fmi, CMD_LOAD_FROM_FIFO(FMI_CONTROL), command_level, &timeout); fmiss_put_operand(fmi, fmi_control, operand_level, &timeout); // there must be one command spacer between FMI_CONTROL writes and CMD_TX_PAGE, // which writes the start bit in FMI_CONTROL () fmiss_put_command(fmi, CMD_TIMED_WAIT(0), command_level, &timeout); fmiss_put_command(fmi, CMD_TX_PAGE, command_level, &timeout); addrBytes -= fullSectors * H2FMI_BYTES_PER_SECTOR; } if (addrBytes > 0) { fmiss_put_command(fmi, CMD_LOAD_NEXT_WORD(FMI_DATA_SIZE), command_level, &timeout); fmiss_put_command(fmi, (FMI_DATA_SIZE__BYTES_PER_SECTOR(addrBytes) | FMI_DATA_SIZE__SECTORS_PER_PAGE(1)), command_level, &timeout); fmiss_put_command(fmi, CMD_LOAD_FROM_FIFO(FMI_CONTROL), command_level, &timeout); fmiss_put_operand(fmi, fmi_control, operand_level, &timeout); // there must be one command spacer between FMI_CONTROL writes and CMD_TX_PAGE, // which writes the start bit in FMI_CONTROL () fmiss_put_command(fmi, CMD_TIMED_WAIT(0), command_level, &timeout); fmiss_put_command(fmi, CMD_TX_PAGE, command_level, &timeout); } fmiss_put_command(fmi, CMD_COMMAND(NAND_CMD__MULTIPAGE_PREP__CONFIRM), command_level, &timeout); fmiss_get_controller_status(fmi, command_level, &timeout); if ((timeout) || !h2fmi_dma_wait(h2fmi_dma_data_chan(fmi), H2FMI_PAGE_TIMEOUT_MICROS)) { #if H2FMI_IOP WMR_PANIC("Timeout waiting for CDMA channel %d to complete multi prep: ce %d, %d pages", h2fmi_dma_data_chan(fmi), physCe, numPages); #endif // H2FMI_IOP break; } } } return !timeout; } static UInt8 fmiss_ce_index_to_physical(const PPNCommandStruct *ppnCommand, h2fmi_ce_t ceIndex) { UInt8 physCe = ppnCommand->ceInfo[ceIndex].ce; return 1 << (physCe & (H2FMI_MAX_CE_PER_BUS - 1)); } static UInt32 fmiss_ce_reg_to_logical(h2fmi_t *fmi, UInt32 ceCtrl) { return WMR_LOG2(ceCtrl) + (fmi->bus_id * H2FMI_MAX_CE_PER_BUS); } static UInt8 fmiss_get_controller_status(h2fmi_t *fmi, UInt32 *command_level, BOOL32 *timeout) { UInt32 seq_int; UInt8 status = 0; fmiss_put_command(fmi, CMD_MACRO(kGetControllerStatusHead, kGetControllerStatusLength), command_level, timeout); seq_int = h2fmi_rd(fmi, SEQ_INT_PEND) & (SEQ_INT_PEND__TIMEOUT | SEQ_INT_PEND__SEQUENCER_SIGNAL); while ((seq_int == 0) && !*timeout) { WMR_YIELD(); seq_int = h2fmi_rd(fmi, SEQ_INT_PEND) & (SEQ_INT_PEND__TIMEOUT | SEQ_INT_PEND__SEQUENCER_SIGNAL); } h2fmi_wr(fmi, SEQ_INT_PEND, seq_int); if (seq_int & SEQ_INT_PEND__SEQUENCER_SIGNAL) { status = h2fmi_rd(fmi, FMC_NAND_STATUS); if (status & PPN_CONTROLLER_STATUS__GENERAL_ERROR) { fmi->ppn->general_error = TRUE32; fmi->ppn->general_error_ce = fmiss_ce_reg_to_logical(fmi, h2fmi_rd(fmi, FMC_CE_CTRL)); } } else if (seq_int & SEQ_INT_PEND__TIMEOUT) { *timeout = TRUE32; #if H2FMI_IOP WMR_PANIC("Timeout reading controller status"); #endif // !H2FMI_IOP } if (!*timeout) { h2fmi_wr(fmi, FMI_CONTROL, h2fmi_rd(fmi, FMI_CONTROL) | FMI_CONTROL__ENABLE_SEQUENCER); } return status; } #if FMI_VERSION <= 3 static UInt8 fmiss_get_next_operation_status(h2fmi_t *fmi, UInt32 *command_level, BOOL32 *timeout) { UInt32 seq_int; UInt8 operation_status = 0; fmiss_put_command(fmi, CMD_MACRO(kGetOperationStatusHead, kGetOperationStatusLength), command_level, timeout); seq_int = h2fmi_rd(fmi, SEQ_INT_PEND) & (SEQ_INT_PEND__TIMEOUT | SEQ_INT_PEND__SEQUENCER_SIGNAL); while ((seq_int == 0) && !*timeout) { WMR_YIELD(); seq_int = h2fmi_rd(fmi, SEQ_INT_PEND) & (SEQ_INT_PEND__TIMEOUT | SEQ_INT_PEND__SEQUENCER_SIGNAL); } h2fmi_wr(fmi, SEQ_INT_PEND, seq_int); if (seq_int & SEQ_INT_PEND__SEQUENCER_SIGNAL) { operation_status = h2fmi_rd(fmi, FMC_NAND_STATUS); if (operation_status & PPN_OPERATION_STATUS__GENERAL_ERROR) { fmi->ppn->general_error = TRUE32; fmi->ppn->general_error_ce = fmiss_ce_reg_to_logical(fmi, h2fmi_rd(fmi, FMC_CE_CTRL)); } h2fmi_wr(fmi, FMI_CONTROL, h2fmi_rd(fmi, FMI_CONTROL) | FMI_CONTROL__ENABLE_SEQUENCER); } else if (seq_int & SEQ_INT_PEND__TIMEOUT) { *timeout = TRUE32; #if H2FMI_IOP WMR_PANIC("Timeout"); #endif // H2FMI_IOP } else { WMR_PANIC("Unexpected SEQ_INT status: 0x%08x\n", seq_int); } return operation_status; } #endif // FMI_VERSION <= 3 UInt32 *fmiss_ppn_macros(UInt32 *count) { if (NULL != count) { *count = sequencer_macro_count; } return sequencer_macros; } #endif /* FMISS_ENABLED */