2726 lines
91 KiB
C
2726 lines
91 KiB
C
|
// *****************************************************************************
|
||
|
//
|
||
|
// File: H2fmi_ppn.c
|
||
|
//
|
||
|
// Copyright (C) 2009 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 <FIL.h>
|
||
|
#include <FILTypes.h>
|
||
|
#include "WMRFeatures.h"
|
||
|
|
||
|
#if WMR_BUILDING_IBOOT
|
||
|
#include <sys.h>
|
||
|
#if !APPLICATION_EMBEDDEDIOP
|
||
|
#include "nandid.h"
|
||
|
#endif /* !APPLICATION_EMBEDDEDIOP */
|
||
|
#else
|
||
|
#include "NandSpecTables.h"
|
||
|
#endif /* WMR_BUILDING_IBOOT */
|
||
|
|
||
|
#if FMI_VERSION > 0
|
||
|
|
||
|
#if H2FMI_PPN_VERIFY_SET_FEATURES
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
UInt16 feature;
|
||
|
UInt32 states;
|
||
|
} h2fmi_ppn_feature_states_t;
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
UInt32 count;
|
||
|
UInt32 state;
|
||
|
struct
|
||
|
{
|
||
|
UInt16 feature;
|
||
|
UInt32 length;;
|
||
|
UInt32 value;
|
||
|
} list[PPN_MAX_FEATURE_COUNT];
|
||
|
} h2fmi_ppn_feature_shadow_t;
|
||
|
|
||
|
static h2fmi_ppn_feature_states_t _ppn_feature_states[] =
|
||
|
{
|
||
|
{ PPN_FEATURE__VREF_ENABLE , (1UL << PPN_FEATURE__POWER_STATE__LOW_POWER) } ,
|
||
|
};
|
||
|
|
||
|
static h2fmi_ppn_feature_shadow_t _ppn_feature_shadow[PPN_MAX_CES_PER_BUS];
|
||
|
|
||
|
#endif // H2FMI_PPN_VERIFY_SET_FEATURES
|
||
|
|
||
|
static void h2fmi_send_cmd(h2fmi_t *fmi, UInt8 cmd);
|
||
|
static void h2fmi_send_cmd_addr(h2fmi_t *fmi, UInt8 cmd, UInt8 *address, UInt32 address_bytes);
|
||
|
static void h2fmi_ppn_set_format(h2fmi_t *fmi, UInt32 sector_len, UInt32 sector_count, BOOL32 bootpage);
|
||
|
static void h2fmi_ppn_get_page_data(h2fmi_t *fmi, UInt32 length);
|
||
|
static void h2fmi_send_cmd_addr_cmd(h2fmi_t *fmi, UInt8 cmd1, UInt8 cmd2, const UInt8 *address, UInt32 address_bytes);
|
||
|
static BOOL32 h2fmi_ppn_prep_read(h2fmi_t *fmi, const RowColLenAddressType *page, BOOL32 last);
|
||
|
static void h2fmi_ppn_prep_write_multi(h2fmi_t *fmi, const PPNCommandStruct *ppnCommand);
|
||
|
static void h2fmi_ppn_program_page(h2fmi_t *fmi, UInt32 page, BOOL32 lastPage, UInt32 lbas);
|
||
|
static BOOL32 h2fmi_get_nand_status(h2fmi_t *fmi, UInt8 *status);
|
||
|
static void h2fmi_ppn_read_serial_output(h2fmi_t *fmi, UInt8 *buf, UInt32 len);
|
||
|
static BOOL32 h2fmi_ppn_switch_ce(h2fmi_t *fmi, h2fmi_ce_t ce);
|
||
|
static void h2fmi_ppn_disable_all_ces(h2fmi_t *fmi);
|
||
|
static void h2fmi_ppn_start_fmi_write_and_wait(h2fmi_t *fmi);
|
||
|
static void h2fmi_ppn_start_fmi_read_and_wait(h2fmi_t *fmi);
|
||
|
static BOOL32 h2fmi_ppn_get_next_operation_status_with_addr(h2fmi_t *fmi, UInt8 *addr, UInt8 *operation_status);
|
||
|
static void h2fmi_prepare_for_fmi_interrupt(h2fmi_t *fmi, UInt32 condition);
|
||
|
static BOOL32 h2fmi_wait_for_fmi_interrupt(h2fmi_t *fmi, UInt32 condition);
|
||
|
static BOOL32 h2fmi_ppn_set_generic_debug_data(h2fmi_t *fmi, h2fmi_ce_t ce, UInt32 flags);
|
||
|
static BOOL32 _validate_device_parameters(h2fmi_ppn_t *ppn);
|
||
|
#if H2FMI_PPN_VERIFY_SET_FEATURES
|
||
|
static void _update_feature_shadow(h2fmi_t *fmi, UInt16 feature, UInt8 *value, UInt32 len);
|
||
|
#endif // H2FMI_PPN_VERIFY_SET_FEATURES
|
||
|
|
||
|
static h2fmi_ce_t h2fmi_ppn_ce_index_to_physical(const PPNCommandStruct *command, UInt32 index)
|
||
|
{
|
||
|
return command->ceInfo[index].ce;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn_get_uid(h2fmi_t *fmi, UInt8 ce, UInt8 die, UInt8 *buf)
|
||
|
{
|
||
|
UInt8 status;
|
||
|
|
||
|
h2fmi_reset(fmi);
|
||
|
|
||
|
return h2fmi_ppn_get_feature(fmi,
|
||
|
ce,
|
||
|
(PPN_FEATURE__NAND_DIE_UNIQUE_ID | (die << 8)),
|
||
|
buf,
|
||
|
NAND_UID_PPN_BYTES_TO_READ,
|
||
|
&status);
|
||
|
}
|
||
|
|
||
|
Int32 h2fmi_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;
|
||
|
UInt32 prepPage;
|
||
|
UInt32 readPage;
|
||
|
BOOL32 ret;
|
||
|
UInt32 queueDepth;
|
||
|
const UInt32 readQueueSize = fmi->ppn->device_params.read_queue_size;
|
||
|
UInt32 overallStatus;
|
||
|
UInt32 i;
|
||
|
UInt32 cePageCount[PPN_MAX_CES_PER_BUS] = {0};
|
||
|
UInt8 setFeaturesBuffer[PPN_FEATURE_LENGTH_ENABLE_BITFLIPS_DATA_COLLECTION];
|
||
|
UInt32 iopfmiStatus;
|
||
|
BOOL32 timeout;
|
||
|
UInt8 opStatus;
|
||
|
|
||
|
timeout = FALSE32;
|
||
|
prepPage = 0;
|
||
|
readPage = 0;
|
||
|
queueDepth = 0;
|
||
|
overallStatus = 0;
|
||
|
|
||
|
WMR_ASSERT(fmi->activeCe == (h2fmi_ce_t)~0);
|
||
|
WMR_ASSERT(page_count > 0);
|
||
|
|
||
|
h2fmi_reset(fmi);
|
||
|
|
||
|
if(ppnCommand->options & PPN_OPTIONS_GET_PAGE_RMA_INFO)
|
||
|
{
|
||
|
// h2fmi_ce_t ce = h2fmi_ppn_ce_index_to_physical(ppnCommand, 0);
|
||
|
|
||
|
WMR_PANIC("Get RMA data not yet implemented with new PPN_FIL");
|
||
|
// WMR_PRINT(ALWAYS, "Attempting to pull RMA data for CE %d\n", ce);
|
||
|
// h2fmi_ppn_force_geb_address(fmi, ce, ppnCommand->pages[0]);
|
||
|
return _kIOPFMI_STATUS_PPN_GENERAL_ERROR;
|
||
|
}
|
||
|
|
||
|
if(ppnCommand->options & PPN_OPTIONS_REPORT_HEALTH)
|
||
|
{
|
||
|
setFeaturesBuffer[0] = 1; // To enable Bitflips/1KB data collection feature
|
||
|
setFeaturesBuffer[1] = 0;
|
||
|
setFeaturesBuffer[2] = 0;
|
||
|
setFeaturesBuffer[3] = 0;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < PPN_MAX_CES_PER_BUS; i++)
|
||
|
{
|
||
|
if (ppnCommand->ceInfo[i].pages > 0)
|
||
|
{
|
||
|
h2fmi_ce_t ce = h2fmi_ppn_ce_index_to_physical(ppnCommand, i);
|
||
|
if(ppnCommand->options & PPN_OPTIONS_REPORT_HEALTH)
|
||
|
{
|
||
|
if (FIL_SUCCESS != h2fmi_ppn_set_features(fmi,
|
||
|
ce,
|
||
|
PPN_FEATURE__ENABLE_BITFLIPS_DATA_COLLECTION,
|
||
|
setFeaturesBuffer,
|
||
|
PPN_FEATURE_LENGTH_ENABLE_BITFLIPS_DATA_COLLECTION,
|
||
|
FALSE32,
|
||
|
NULL))
|
||
|
{
|
||
|
WMR_PANIC("Set Features failed on CE index %d (physical %d)", i, ce);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for( readPage = 0; (readPage < page_count) && !timeout; readPage++)
|
||
|
{
|
||
|
const UInt8 readPageCeIndex = ppnCommand->entry[readPage].ceIdx;
|
||
|
const h2fmi_ce_t readPagePhysCe = h2fmi_ppn_ce_index_to_physical(ppnCommand,
|
||
|
readPageCeIndex);
|
||
|
UInt32 lba;
|
||
|
|
||
|
while((queueDepth < readQueueSize) && (prepPage < page_count))
|
||
|
{
|
||
|
const UInt8 ceIndex = ppnCommand->entry[prepPage].ceIdx;
|
||
|
const h2fmi_ce_t physCe = h2fmi_ppn_ce_index_to_physical(ppnCommand,
|
||
|
ceIndex);
|
||
|
|
||
|
WMR_ASSERT(ceIndex < PPN_MAX_CES_PER_BUS);
|
||
|
h2fmi_ppn_switch_ce(fmi, physCe);
|
||
|
ret = h2fmi_ppn_prep_read(fmi,
|
||
|
&ppnCommand->entry[prepPage].addr,
|
||
|
cePageCount[ceIndex] + 1 == ppnCommand->ceInfo[ceIndex].pages);
|
||
|
|
||
|
WMR_ASSERT( ret == TRUE32);
|
||
|
cePageCount[ceIndex]++;
|
||
|
prepPage++;
|
||
|
queueDepth++;
|
||
|
}
|
||
|
|
||
|
if (0 == readPage) {
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
h2fmi_ppn_switch_ce(fmi, readPagePhysCe);
|
||
|
ret = h2fmi_ppn_get_next_operation_status(fmi, &opStatus);
|
||
|
if (!ret)
|
||
|
{
|
||
|
ppnCommand->entry[readPage].status = 0;
|
||
|
timeout = TRUE32;
|
||
|
break;
|
||
|
}
|
||
|
else if (fmi->retire_on_invalid_refresh &&
|
||
|
((PPN_OPERATION_STATUS__REFRESH | PPN_OPERATION_STATUS__ERROR) ==
|
||
|
(opStatus & (PPN_OPERATION_STATUS__REFRESH | PPN_OPERATION_STATUS__ERROR))))
|
||
|
{
|
||
|
// Remap 0x43 (refresh + invalid) to 0x45 (retire + invalid)
|
||
|
opStatus = (opStatus & ~(PPN_OPERATION_STATUS__REFRESH)) | PPN_OPERATION_STATUS__RETIRE;
|
||
|
}
|
||
|
|
||
|
ppnCommand->entry[readPage].status = opStatus;
|
||
|
overallStatus |= opStatus;
|
||
|
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__READ_SERIAL_OUTPUT);
|
||
|
for (lba = 0; lba < ppnCommand->entry[readPage].lbas; lba++)
|
||
|
{
|
||
|
h2fmi_ppn_get_page_data(fmi, fmi->logical_page_size);
|
||
|
}
|
||
|
queueDepth--;
|
||
|
}
|
||
|
|
||
|
if (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 (i = 0; i < PPN_MAX_CES_PER_BUS; i++)
|
||
|
{
|
||
|
if (ppnCommand->ceInfo[i].pages > 0)
|
||
|
{
|
||
|
h2fmi_ce_t physCe = ppnCommand->ceInfo[i].ce;
|
||
|
|
||
|
h2fmi_ppn_switch_ce(fmi, physCe);
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__GET_NEXT_OPERATION_STATUS);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
h2fmi_ppn_disable_all_ces(fmi);
|
||
|
|
||
|
ppnCommand->page_status_summary = overallStatus;
|
||
|
if (timeout)
|
||
|
{
|
||
|
iopfmiStatus = _kIOPFMI_STATUS_READY_BUSY_TIMEOUT;
|
||
|
}
|
||
|
else if (overallStatus & PPN_OPERATION_STATUS__GENERAL_ERROR)
|
||
|
{
|
||
|
iopfmiStatus = _kIOPFMI_STATUS_PPN_GENERAL_ERROR;
|
||
|
WMR_PRINT(ERROR, "IOP returning kIOPFMI_STATUS_PPN_GENERAL_ERROR\n");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
iopfmiStatus = _kIOPFMI_STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
return iopfmiStatus;
|
||
|
}
|
||
|
|
||
|
Int32 h2fmi_ppn_read_bootpage(h2fmi_t *fmi,
|
||
|
UInt16 ce,
|
||
|
UInt32 page,
|
||
|
UInt8 *buf,
|
||
|
UInt8 *max_corrected)
|
||
|
{
|
||
|
Int32 ret = _kIOPFMI_STATUS_SUCCESS;
|
||
|
UInt8 operationStatus;
|
||
|
UInt8 i;
|
||
|
UInt32 fmi_control = (FMI_CONTROL__MODE__READ |
|
||
|
FMI_CONTROL__START_BIT);
|
||
|
|
||
|
#if SUPPORT_TOGGLE_NAND && !APPLICATION_EMBEDDEDIOP
|
||
|
if (fmi->is_toggle_system)
|
||
|
{
|
||
|
transitionWorldFromDDR(PPN_FEATURE__POWER_STATE__ASYNC);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if FMI_VERSION == 4
|
||
|
if (fmi->read_stream_disable)
|
||
|
{
|
||
|
fmi_control |= FMI_CONTROL__DISABLE_STREAMING;
|
||
|
}
|
||
|
#endif // FMI_VERSION == 4
|
||
|
|
||
|
WMR_ASSERT(fmi->activeCe == (h2fmi_ce_t)~0);
|
||
|
|
||
|
h2fmi_ppn_switch_ce(fmi, ce);
|
||
|
|
||
|
h2fmi_send_cmd_addr_cmd(fmi,
|
||
|
NAND_CMD__MULTIPAGE_READ_LAST,
|
||
|
NAND_CMD__MULTIPAGE_READ_CONFIRM,
|
||
|
(UInt8 *)&page,
|
||
|
fmi->ppn->bytes_per_row_address);
|
||
|
|
||
|
ret = h2fmi_ppn_get_next_operation_status(fmi, &operationStatus);
|
||
|
if (!ret)
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Timeout reading operation status in Read Bootpage\n");
|
||
|
ret = _kIOPFMI_STATUS_READY_BUSY_TIMEOUT;
|
||
|
}
|
||
|
else if (operationStatus != PPN_OPERATION_STATUS__READY)
|
||
|
{
|
||
|
if (operationStatus & PPN_OPERATION_STATUS__GENERAL_ERROR)
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "General Error bit asserted during Read Boot Page\n");
|
||
|
ret = _kIOPFMI_STATUS_PPN_GENERAL_ERROR;
|
||
|
}
|
||
|
else if (operationStatus & PPN_OPERATION_STATUS__CLEAN)
|
||
|
{
|
||
|
ret = _kIOPFMI_STATUS_BLANK;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Error 0x%02x bus: %d ce: %d p:0x%08x\n", (UInt32)operationStatus, (UInt32) fmi->bus_id, (UInt32) ce, page);
|
||
|
ret = _kIOPFMI_STATUS_AT_LEAST_ONE_UECC;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (buf != NULL)
|
||
|
{
|
||
|
h2fmi_clean_ecc(fmi);
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__READ_SERIAL_OUTPUT);
|
||
|
|
||
|
h2fmi_ppn_set_format(fmi, H2FMI_BYTES_PER_BOOT_SECTOR, 1, TRUE32);
|
||
|
for (i = 0; i < H2FMI_BOOT_SECTORS_PER_PAGE; i++)
|
||
|
{
|
||
|
#if FMI_ECC_DEBUG
|
||
|
h2fmi_wr(fmi, ECC_PND, ~0UL);
|
||
|
#endif // FMI_ECC_DEBUG
|
||
|
|
||
|
#if FMI_VERSION > 3
|
||
|
h2fmi_wr(fmi, FMI_SCRAMBLER_SEED_FIFO, page + i);
|
||
|
#endif // FMI_VERSION > 3
|
||
|
h2fmi_wr(fmi, FMI_CONTROL, fmi_control);
|
||
|
if (!h2fmi_pio_read_sector(fmi, buf, H2FMI_BYTES_PER_BOOT_SECTOR))
|
||
|
{
|
||
|
#if FMI_ECC_DEBUG
|
||
|
WMR_PANIC("PIO on bus %lu of sector %lu failed - %lu previous successes\n",
|
||
|
fmi->bus_id, i, fmi->ppn->ecc_sectors_read);
|
||
|
#endif // FMI_ECC_DEBUG
|
||
|
ret = _kIOPFMI_STATUS_DMA_DONE_TIMEOUT;
|
||
|
break;
|
||
|
}
|
||
|
buf += H2FMI_BYTES_PER_BOOT_SECTOR;
|
||
|
|
||
|
#if FMI_ECC_DEBUG
|
||
|
fmi->ppn->ecc_sectors_read++;
|
||
|
#endif // FMI_ECC_DEBUG
|
||
|
}
|
||
|
|
||
|
if (_kIOPFMI_STATUS_SUCCESS == ret)
|
||
|
{
|
||
|
ret = h2fmi_rx_check_page_ecc(fmi, 0, NULL, NULL, H2FMI_BOOT_SECTORS_PER_PAGE);
|
||
|
if (_kIOPFMI_STATUS_SUCCESS != ret)
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "PPN status check reported success, but ECC status check reported 0x%08x!\n", ret);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__GET_NEXT_OPERATION_STATUS);
|
||
|
|
||
|
h2fmi_ppn_disable_all_ces(fmi);
|
||
|
h2fmi_reset(fmi);
|
||
|
|
||
|
#if SUPPORT_TOGGLE_NAND && !APPLICATION_EMBEDDEDIOP
|
||
|
if (fmi->is_toggle_system)
|
||
|
{
|
||
|
transitionWorldToDDR(PPN_FEATURE__POWER_STATE__ASYNC);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
Int32 h2fmi_ppn_read_cau_bbt(h2fmi_t *fmi,
|
||
|
PPNCommandStruct *ppnCommand,
|
||
|
UInt8 *buf)
|
||
|
{
|
||
|
UInt8 *status = &ppnCommand->entry[0].status;
|
||
|
const UInt16 ceIndex = ppnCommand->entry[0].ceIdx;
|
||
|
const UInt32 pageAddr = ppnCommand->entry[0].addr.row;
|
||
|
const h2fmi_ppn_device_params_t *dev = &fmi->ppn->device_params;
|
||
|
const UInt32 cau = (pageAddr >> (dev->page_address_bits + dev->block_bits)) & ((1 << dev->cau_bits) - 1);
|
||
|
const h2fmi_ce_t physCe = h2fmi_ppn_ce_index_to_physical(ppnCommand, ceIndex);
|
||
|
|
||
|
WMR_ASSERT(fmi->activeCe == (h2fmi_ce_t)~0);
|
||
|
WMR_ASSERT(cau < fmi->ppn->device_params.caus_per_channel);
|
||
|
|
||
|
//WMR_PRINT(ALWAYS, "Read CAU BBT: bus %d, ce %d, CAU 0x%08x\n", fmi->bus_id, physCe, cau);
|
||
|
|
||
|
h2fmi_ppn_get_feature(fmi,
|
||
|
physCe,
|
||
|
PPN_FEATURE__BAD_BLOCK_BITMAP_ARRAY | (cau << 8),
|
||
|
buf,
|
||
|
fmi->ppn->device_params.blocks_per_cau / 8,
|
||
|
status);
|
||
|
|
||
|
ppnCommand->page_status_summary = status[0];
|
||
|
|
||
|
if (ppnCommand->page_status_summary & PPN_OPERATION_STATUS__GENERAL_ERROR)
|
||
|
{
|
||
|
return _kIOPFMI_STATUS_PPN_GENERAL_ERROR;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return _kIOPFMI_STATUS_SUCCESS;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
UInt8 h2fmi_ppn_get_idle_counter(h2fmi_t *fmi,
|
||
|
h2fmi_ce_t ce)
|
||
|
{
|
||
|
UInt32 counter;
|
||
|
UInt8 status;
|
||
|
|
||
|
if (!h2fmi_ppn_get_feature(fmi,
|
||
|
ce,
|
||
|
PPN_FEATURE__IDLE_COUNTER,
|
||
|
(UInt8 *)&counter,
|
||
|
4,
|
||
|
&status))
|
||
|
{
|
||
|
WMR_PANIC("Unexpected status or timeout reading PPN idle counter");
|
||
|
}
|
||
|
|
||
|
h2fmi_reset(fmi);
|
||
|
|
||
|
return counter & 0xFF;
|
||
|
}
|
||
|
|
||
|
|
||
|
#if !(defined(AND_READONLY)) || AND_SUPPORT_NVRAM
|
||
|
Int32 h2fmi_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;
|
||
|
BOOL32 ret = TRUE32;
|
||
|
UInt32 pageIndex = 0;
|
||
|
BOOL32 error = FALSE32;
|
||
|
h2fmi_ce_t errorCe;
|
||
|
UInt8 overallStatus = 0;
|
||
|
UInt32 cePageCount[PPN_MAX_CES_PER_BUS] = {0};
|
||
|
BOOL32 timeout = FALSE32;
|
||
|
|
||
|
WMR_ASSERT(fmi->activeCe == (h2fmi_ce_t)~0);
|
||
|
WMR_ASSERT(page_count > 0);
|
||
|
WMR_ASSERT(page_count <= fmi->ppn->device_params.prep_function_buffer_size);
|
||
|
|
||
|
h2fmi_reset(fmi);
|
||
|
|
||
|
if (page_count > 1)
|
||
|
{
|
||
|
h2fmi_ppn_prep_write_multi(fmi, ppnCommand);
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
|
||
|
for (pageIndex = 0; (ret &&
|
||
|
(pageIndex < page_count) &&
|
||
|
!timeout &&
|
||
|
!error);
|
||
|
pageIndex++)
|
||
|
{
|
||
|
const UInt8 ceIndex = ppnCommand->entry[pageIndex].ceIdx;
|
||
|
const h2fmi_ce_t physCe = h2fmi_ppn_ce_index_to_physical(ppnCommand, ceIndex);
|
||
|
UInt8 status;
|
||
|
|
||
|
h2fmi_ppn_switch_ce(fmi, physCe);
|
||
|
h2fmi_ppn_program_page(fmi,
|
||
|
ppnCommand->entry[pageIndex].addr.row,
|
||
|
cePageCount[ceIndex] + 1 == ppnCommand->ceInfo[ceIndex].pages,
|
||
|
ppnCommand->entry[pageIndex].lbas);
|
||
|
|
||
|
ret = h2fmi_ppn_get_controller_status(fmi, &status);
|
||
|
if (!ret)
|
||
|
{
|
||
|
// Don't look at op status at all if we timed out - it could have bogus bits set.
|
||
|
status = 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;
|
||
|
overallStatus |= status;
|
||
|
|
||
|
if (!ret)
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Timeout waiting for controller status after multi write\n");
|
||
|
timeout = TRUE32;
|
||
|
}
|
||
|
else if (status & PPN_CONTROLLER_STATUS__PENDING_ERRORS)
|
||
|
{
|
||
|
error = TRUE32;
|
||
|
errorCe = ceIndex;
|
||
|
WMR_PRINT(ERROR, "Bailing out due to program error\n");
|
||
|
}
|
||
|
else if (status & PPN_CONTROLLER_STATUS__GENERAL_ERROR)
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Terminating program due to General Error\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
cePageCount[ceIndex]++;
|
||
|
}
|
||
|
h2fmi_ppn_disable_all_ces(fmi);
|
||
|
|
||
|
if (error || timeout || fmi->ppn->general_error ||
|
||
|
!h2fmi_dma_wait(h2fmi_dma_data_chan(fmi), H2FMI_PAGE_TIMEOUT_MICROS) ||
|
||
|
!h2fmi_dma_wait(h2fmi_dma_meta_chan(fmi), H2FMI_PAGE_TIMEOUT_MICROS))
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Cancelling DMA\n");
|
||
|
h2fmi_dma_cancel(h2fmi_dma_meta_chan(fmi));
|
||
|
h2fmi_dma_cancel(h2fmi_dma_data_chan(fmi));
|
||
|
h2fmi_reset(fmi);
|
||
|
ret = FALSE32;
|
||
|
}
|
||
|
|
||
|
if (!timeout && error && !fmi->ppn->general_error)
|
||
|
{
|
||
|
UInt32 temp = 1;
|
||
|
UInt8 status = 0;
|
||
|
h2fmi_ce_t physCe = h2fmi_ppn_ce_index_to_physical(ppnCommand, errorCe);
|
||
|
|
||
|
// 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, errorCe, 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, errorCe, 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, errorCe, 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 = overallStatus;
|
||
|
if (timeout)
|
||
|
{
|
||
|
return _kIOPFMI_STATUS_READY_BUSY_TIMEOUT;
|
||
|
}
|
||
|
else if (overallStatus & PPN_OPERATION_STATUS__GENERAL_ERROR)
|
||
|
{
|
||
|
return _kIOPFMI_STATUS_PPN_GENERAL_ERROR;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return _kIOPFMI_STATUS_SUCCESS;
|
||
|
}
|
||
|
}
|
||
|
#endif // !(defined(AND_READONLY)) || AND_SUPPORT_NVRAM
|
||
|
|
||
|
void h2fmi_ppn_process_error_list(h2fmi_t *fmi,
|
||
|
PPNCommandStruct *ppnCommand,
|
||
|
UInt8 ceIdx,
|
||
|
UInt32 *list,
|
||
|
UInt8 status)
|
||
|
{
|
||
|
const UInt32 count = list[0];
|
||
|
UInt32 listIdx;
|
||
|
|
||
|
if (!count)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for (listIdx = 1; listIdx <= count; listIdx++)
|
||
|
{
|
||
|
const UInt32 page = list[listIdx];
|
||
|
UInt16 cmdIdx;
|
||
|
|
||
|
for (cmdIdx = 0; cmdIdx < ppnCommand->num_pages; cmdIdx++)
|
||
|
{
|
||
|
if ((ppnCommand->entry[cmdIdx].addr.row == page) &&
|
||
|
(ppnCommand->entry[cmdIdx].ceIdx == ceIdx))
|
||
|
{
|
||
|
WMR_PRINT(ALWAYS, "Bus %d ceIdx %d setting index %d (page 0x%08x) status to 0x%02x\n",
|
||
|
fmi->bus_id, ceIdx, cmdIdx, page, status);
|
||
|
|
||
|
ppnCommand->entry[cmdIdx].status |= status;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (cmdIdx >= ppnCommand->num_pages)
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "CE 0x%x Page 0x%08x is not in the command structure!", ppnCommand->ceInfo[ceIdx].ce, page);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
Int32 h2fmi_ppn_write_bootpage(h2fmi_t *fmi,
|
||
|
UInt16 ce,
|
||
|
UInt32 dwPpn,
|
||
|
const UInt8 *pabData)
|
||
|
{
|
||
|
Int32 ret = _kIOPFMI_STATUS_SUCCESS;
|
||
|
UInt8 controller_status = 0;
|
||
|
struct dma_segment sgl;
|
||
|
BOOL32 timeout = FALSE32;
|
||
|
UInt8 i;
|
||
|
|
||
|
#if SUPPORT_TOGGLE_NAND && !APPLICATION_EMBEDDEDIOP
|
||
|
if (fmi->is_toggle_system)
|
||
|
{
|
||
|
transitionWorldFromDDR(PPN_FEATURE__POWER_STATE__ASYNC);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
WMR_ASSERT(fmi->activeCe == (h2fmi_ce_t)~0);
|
||
|
h2fmi_reset(fmi);
|
||
|
h2fmi_ppn_switch_ce(fmi, ce);
|
||
|
|
||
|
sgl.paddr = (UInt32)pabData;
|
||
|
sgl.length = H2FMI_BOOT_BYTES_PER_PAGE;
|
||
|
|
||
|
h2fmi_dma_execute_async(DMA_CMD_DIR_TX,
|
||
|
h2fmi_dma_data_chan(fmi),
|
||
|
&sgl,
|
||
|
h2fmi_dma_data_fifo(fmi),
|
||
|
H2FMI_BOOT_BYTES_PER_PAGE,
|
||
|
sizeof(UInt32),
|
||
|
32,
|
||
|
NULL);
|
||
|
|
||
|
h2fmi_send_cmd_addr(fmi,
|
||
|
NAND_CMD__MULTIPAGE_PROGRAM_LAST,
|
||
|
(UInt8 *)&dwPpn,
|
||
|
fmi->ppn->bytes_per_row_address);
|
||
|
|
||
|
h2fmi_ppn_set_format(fmi, H2FMI_BYTES_PER_BOOT_SECTOR, 1, TRUE32);
|
||
|
for (i = 0; i < H2FMI_BOOT_SECTORS_PER_PAGE; i++)
|
||
|
{
|
||
|
#if FMI_VERSION > 3
|
||
|
h2fmi_wr(fmi, FMI_SCRAMBLER_SEED_FIFO, dwPpn + i);
|
||
|
#endif // FMI_VERSION > 3
|
||
|
h2fmi_ppn_start_fmi_write_and_wait(fmi);
|
||
|
}
|
||
|
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__MULTIPAGE_PROGRAM_CONFIRM);
|
||
|
|
||
|
if (!h2fmi_dma_wait(h2fmi_dma_data_chan(fmi), H2FMI_PAGE_TIMEOUT_MICROS))
|
||
|
{
|
||
|
WMR_PANIC("Timeout waiting for CDMA channel %d to complete\n",
|
||
|
h2fmi_dma_data_chan(fmi));
|
||
|
h2fmi_dma_cancel(h2fmi_dma_data_chan(fmi));
|
||
|
}
|
||
|
|
||
|
ret = h2fmi_ppn_get_controller_status(fmi, &controller_status);
|
||
|
if (!ret)
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Timeout waiting for controller status in Write bootpage\n");
|
||
|
timeout = TRUE32;
|
||
|
}
|
||
|
|
||
|
h2fmi_ppn_disable_all_ces(fmi);
|
||
|
|
||
|
if (timeout)
|
||
|
{
|
||
|
ret = _kIOPFMI_STATUS_READY_BUSY_TIMEOUT;
|
||
|
}
|
||
|
else if (controller_status & PPN_CONTROLLER_STATUS__GENERAL_ERROR)
|
||
|
{
|
||
|
ret = _kIOPFMI_STATUS_PPN_GENERAL_ERROR;
|
||
|
}
|
||
|
else if (controller_status & PPN_CONTROLLER_STATUS__PENDING_ERRORS)
|
||
|
{
|
||
|
UInt32 temp = 1;
|
||
|
|
||
|
WMR_PRINT(ERROR, "PPN device reports program error (0x%02x) on write boot page 0x%08x\n",
|
||
|
controller_status, dwPpn);
|
||
|
|
||
|
h2fmi_ppn_set_features(fmi,
|
||
|
ce,
|
||
|
PPN_FEATURE__CLEAR_PROGRAM_ERROR_LISTS,
|
||
|
(UInt8 *)&temp,
|
||
|
4,
|
||
|
FALSE32,
|
||
|
NULL);
|
||
|
|
||
|
ret = _kIOPFMI_STATUS_PGM_ERROR;
|
||
|
}
|
||
|
|
||
|
#if SUPPORT_TOGGLE_NAND && !APPLICATION_EMBEDDEDIOP
|
||
|
if (fmi->is_toggle_system)
|
||
|
{
|
||
|
transitionWorldToDDR(PPN_FEATURE__POWER_STATE__ASYNC);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
Int32 h2fmi_ppn_erase_blocks(h2fmi_t *fmi, PPNCommandStruct *ppnCommand)
|
||
|
{
|
||
|
const UInt32 queueSize = fmi->ppn->device_params.erase_queue_size;
|
||
|
BOOL32 ret;
|
||
|
UInt16 ceIndex;
|
||
|
UInt8 overallStatus = 0;
|
||
|
BOOL32 timeout = FALSE32;
|
||
|
|
||
|
WMR_ASSERT(fmi->activeCe == (h2fmi_ce_t)~0);
|
||
|
|
||
|
for (ceIndex = 0; ceIndex < PPN_MAX_CES_PER_BUS; ceIndex++)
|
||
|
{
|
||
|
const h2fmi_ce_t physCe = h2fmi_ppn_ce_index_to_physical(ppnCommand, ceIndex);
|
||
|
const UInt32 numBlocks = ppnCommand->ceInfo[ceIndex].pages;
|
||
|
const UInt32 ceOffset = ppnCommand->ceInfo[ceIndex].offset;
|
||
|
UInt32 prepBlock = 0;
|
||
|
UInt32 eraseBlock = 0;
|
||
|
UInt8 controllerStatus = 0;
|
||
|
UInt32 currentQueueLevel = 0;
|
||
|
|
||
|
if (numBlocks == 0)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
h2fmi_ppn_switch_ce(fmi, physCe);
|
||
|
|
||
|
for (eraseBlock = 0; eraseBlock < numBlocks; eraseBlock++)
|
||
|
{
|
||
|
while((currentQueueLevel < queueSize) && (prepBlock < numBlocks))
|
||
|
{
|
||
|
UInt8 cmd;
|
||
|
|
||
|
cmd = (prepBlock == (numBlocks - 1)) ?
|
||
|
NAND_CMD__MULTIBLOCK_ERASE_LAST :
|
||
|
NAND_CMD__MULTIBLOCK_ERASE;
|
||
|
|
||
|
h2fmi_send_cmd_addr_cmd(fmi,
|
||
|
cmd,
|
||
|
NAND_CMD__MULTIBLOCK_ERASE_CONFIRM,
|
||
|
(UInt8 *)&ppnCommand->entry[ceOffset + prepBlock].addr.row,
|
||
|
fmi->ppn->bytes_per_row_address);
|
||
|
|
||
|
if (!h2fmi_ppn_get_controller_status(fmi, &controllerStatus))
|
||
|
{
|
||
|
timeout = TRUE32;
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
currentQueueLevel++;
|
||
|
prepBlock++;
|
||
|
}
|
||
|
|
||
|
// How do I find the right place for this op status if I'm striping across CEs...?
|
||
|
ret = h2fmi_ppn_get_next_operation_status(fmi,
|
||
|
&ppnCommand->entry[ceOffset + eraseBlock].status);
|
||
|
if (!ret)
|
||
|
{
|
||
|
timeout = TRUE32;
|
||
|
goto done;
|
||
|
}
|
||
|
currentQueueLevel--;
|
||
|
overallStatus |= ppnCommand->entry[ceOffset + eraseBlock].status;
|
||
|
}
|
||
|
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__GET_NEXT_OPERATION_STATUS);
|
||
|
}
|
||
|
|
||
|
done:
|
||
|
h2fmi_ppn_disable_all_ces(fmi);
|
||
|
ppnCommand->page_status_summary = overallStatus;
|
||
|
|
||
|
if (timeout)
|
||
|
{
|
||
|
return _kIOPFMI_STATUS_READY_BUSY_TIMEOUT;
|
||
|
}
|
||
|
else if (overallStatus & PPN_CONTROLLER_STATUS__GENERAL_ERROR)
|
||
|
{
|
||
|
return _kIOPFMI_STATUS_PPN_GENERAL_ERROR;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return _kIOPFMI_STATUS_SUCCESS;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Send a multipage read PPN command for a particular page
|
||
|
*
|
||
|
* Send a:
|
||
|
* MULTIPAGE_READ / page / MULTIPAGE_READ_CONFIRM
|
||
|
* CONTROLLER_STATUS / wait for 0x42
|
||
|
*
|
||
|
* sequence to the NAND device
|
||
|
*
|
||
|
* @param fmi Pointer to FMI bus sturucture
|
||
|
* @param page Value to send as NAND page address
|
||
|
* @param last TRUE32 if last page in operation
|
||
|
*
|
||
|
* @return
|
||
|
* - TRUE32 if operation completed with expected status
|
||
|
* - FALSE32 on timeout waiting for expected status
|
||
|
*/
|
||
|
static BOOL32 h2fmi_ppn_prep_read(h2fmi_t *fmi, const RowColLenAddressType *page, BOOL32 last)
|
||
|
{
|
||
|
UInt8 cmd = (last == TRUE32) ? NAND_CMD__MULTIPAGE_READ_LAST : NAND_CMD__MULTIPAGE_READ;
|
||
|
|
||
|
if (fmi->logical_page_size == fmi->bytes_per_page)
|
||
|
{
|
||
|
h2fmi_send_cmd_addr_cmd(fmi,
|
||
|
cmd,
|
||
|
NAND_CMD__MULTIPAGE_READ_CONFIRM,
|
||
|
(UInt8 *)&page->row,
|
||
|
fmi->ppn->bytes_per_row_address);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
h2fmi_send_cmd_addr_cmd(fmi,
|
||
|
cmd,
|
||
|
NAND_CMD__MULTIPAGE_READ_CONFIRM,
|
||
|
(UInt8 *)page,
|
||
|
fmi->ppn->bytes_per_full_address);
|
||
|
}
|
||
|
|
||
|
return TRUE32;
|
||
|
}
|
||
|
|
||
|
static void h2fmi_ppn_prep_pages(h2fmi_t *fmi,
|
||
|
UInt16 physCe,
|
||
|
UInt32 cePageCount)
|
||
|
{
|
||
|
UInt32 writePrepCount = (cePageCount << 8) | 0x87;
|
||
|
UInt32 fullSectors = 0;
|
||
|
UInt32 address_bytes = cePageCount * sizeof(UInt32);
|
||
|
|
||
|
h2fmi_ppn_switch_ce(fmi, physCe);
|
||
|
|
||
|
h2fmi_send_cmd_addr(fmi,
|
||
|
NAND_CMD__MULTIPAGE_PREP,
|
||
|
(UInt8 *)&writePrepCount,
|
||
|
3);
|
||
|
|
||
|
// First fill as many 1KB sectors as possible - note that it's the client's responsibility
|
||
|
// to have started a DMA for the page address buffer.
|
||
|
fullSectors = address_bytes / H2FMI_BYTES_PER_SECTOR;
|
||
|
if (fullSectors > 0)
|
||
|
{
|
||
|
h2fmi_ppn_set_format(fmi, H2FMI_BYTES_PER_SECTOR, fullSectors, FALSE32);
|
||
|
h2fmi_ppn_start_fmi_write_and_wait(fmi);
|
||
|
address_bytes -= (H2FMI_BYTES_PER_SECTOR * fullSectors);
|
||
|
}
|
||
|
|
||
|
if (address_bytes > 0)
|
||
|
{
|
||
|
h2fmi_ppn_set_format(fmi, address_bytes, 1, FALSE32);
|
||
|
h2fmi_ppn_start_fmi_write_and_wait(fmi);
|
||
|
}
|
||
|
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__MULTIPAGE_PREP__CONFIRM);
|
||
|
}
|
||
|
|
||
|
static void h2fmi_ppn_prep_write_multi(h2fmi_t *fmi,
|
||
|
const PPNCommandStruct *ppnCommand)
|
||
|
{
|
||
|
const UInt32 totalPages = ppnCommand->num_pages;
|
||
|
UInt16 ceIndex;
|
||
|
UInt32 pageIndex;
|
||
|
struct dma_segment dma_segment;
|
||
|
|
||
|
for (pageIndex = 0 ; pageIndex < totalPages ; pageIndex++)
|
||
|
{
|
||
|
const PPNCommandEntry *entry = &ppnCommand->entry[pageIndex];
|
||
|
|
||
|
*fmi->ppn->prep_buffer[entry->ceIdx]++ = entry->addr.row;
|
||
|
}
|
||
|
|
||
|
|
||
|
// If we ever get DMA chaining in EFI we could make a SGL out of this and use
|
||
|
// a single DMA for all CEs..
|
||
|
for (ceIndex = 0; ceIndex < PPN_MAX_CES_PER_BUS; ceIndex++)
|
||
|
{
|
||
|
const UInt32 numPages = ppnCommand->ceInfo[ceIndex].pages;
|
||
|
const h2fmi_ce_t physCe = h2fmi_ppn_ce_index_to_physical(ppnCommand, ceIndex);
|
||
|
|
||
|
fmi->ppn->prep_buffer[ceIndex] -= numPages;
|
||
|
|
||
|
if (numPages > 0)
|
||
|
{
|
||
|
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 /* WMR_BUILDING_IBOOT */
|
||
|
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),
|
||
|
1,
|
||
|
NULL);
|
||
|
|
||
|
h2fmi_ppn_prep_pages(fmi, physCe, numPages);
|
||
|
|
||
|
if (!h2fmi_dma_wait(h2fmi_dma_data_chan(fmi), H2FMI_PAGE_TIMEOUT_MICROS))
|
||
|
{
|
||
|
WMR_PANIC("Timeout waiting for CDMA channel %d to complete multi prep: ce %d, %d pages", h2fmi_dma_data_chan(fmi), physCe, numPages);
|
||
|
h2fmi_dma_cancel(h2fmi_dma_data_chan(fmi));
|
||
|
WMR_PRINT(ERROR, "Timeout waiting for CDMA to complete multi prep\n");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Int32 h2fmi_ppn_set_features(h2fmi_t *fmi, UInt32 ce, UInt16 feature, UInt8 *data, UInt32 len, BOOL32 optFeature, UInt8 *stat)
|
||
|
{
|
||
|
UInt8 operation_status = 0;
|
||
|
Int32 ret = FIL_SUCCESS;
|
||
|
UInt32 fmi_control = (FMI_CONTROL__MODE__WRITE |
|
||
|
FMI_CONTROL__START_BIT);
|
||
|
UInt32 pos;
|
||
|
UInt32 sector_len;
|
||
|
|
||
|
WMR_ASSERT(fmi->activeCe == (h2fmi_ce_t)~0);
|
||
|
h2fmi_ppn_switch_ce(fmi, ce);
|
||
|
h2fmi_wr(fmi, FMI_CONTROL, FMI_CONTROL__MODE__SOFTWARE_RESET);
|
||
|
|
||
|
h2fmi_send_cmd_addr(fmi, NAND_CMD__SET_FEATURES, (UInt8 *)&feature, 2);
|
||
|
h2fmi_clear_interrupts_and_reset_masks(fmi);
|
||
|
|
||
|
for(pos = 0; pos < len; pos += sector_len)
|
||
|
{
|
||
|
UInt32 bounce = 0;
|
||
|
void *cursor = &data[pos];
|
||
|
|
||
|
sector_len = WMR_MIN(H2FMI_BYTES_PER_SECTOR, len - pos);
|
||
|
if (sizeof(bounce) > sector_len)
|
||
|
{
|
||
|
WMR_MEMCPY(&bounce, cursor, sector_len);
|
||
|
cursor = &bounce;
|
||
|
sector_len = sizeof(bounce);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
sector_len = ROUNDDOWNTO(sector_len, sizeof(bounce));
|
||
|
}
|
||
|
|
||
|
h2fmi_ppn_set_format(fmi, sector_len, 1, FALSE32);
|
||
|
h2fmi_wr(fmi, FMI_CONTROL, fmi_control);
|
||
|
h2fmi_pio_write_sector(fmi, cursor, sector_len);
|
||
|
h2fmi_busy_wait(fmi, FMI_INT_PEND, FMI_INT_PEND__LAST_FMC_DONE, FMI_INT_PEND__LAST_FMC_DONE);
|
||
|
h2fmi_wr(fmi, FMI_INT_PEND, FMI_INT_PEND__LAST_FMC_DONE);
|
||
|
}
|
||
|
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__SET_GET_FEATURES_CONFIRM);
|
||
|
if (!h2fmi_ppn_get_next_operation_status(fmi, &operation_status) || (operation_status != PPN_FEAT_OPERATION_STATUS__SUCCESS))
|
||
|
{
|
||
|
if ((operation_status == PPN_FEAT_OPERATION_STATUS__UNSUPPORTED_BY_CH) || (operation_status == PPN_FEAT_OPERATION_STATUS__UNSUPPORTED))
|
||
|
{
|
||
|
if (!optFeature)
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "PPN feature 0x%04x is unsupported on CH %d: status = 0x%02x\n", feature, fmi->bus_id, operation_status);
|
||
|
}
|
||
|
ret = FIL_UNSUPPORTED_ERROR;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Unable to set PPN feature 0x%04x: status = 0x%02x\n", feature, operation_status);
|
||
|
ret = FIL_CRITICAL_ERROR;
|
||
|
}
|
||
|
}
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__GET_NEXT_OPERATION_STATUS);
|
||
|
h2fmi_wr(fmi, FMI_CONTROL, FMI_CONTROL__MODE__SOFTWARE_RESET);
|
||
|
h2fmi_ppn_disable_all_ces(fmi);
|
||
|
h2fmi_reset(fmi);
|
||
|
|
||
|
#if H2FMI_PPN_VERIFY_SET_FEATURES
|
||
|
if (FIL_SUCCESS == ret)
|
||
|
{
|
||
|
_update_feature_shadow(fmi, feature, data, len);
|
||
|
}
|
||
|
#endif // H2FMI_PPN_VERIFY_SET_FEATURES
|
||
|
|
||
|
if (stat)
|
||
|
{
|
||
|
*stat = operation_status;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn_get_feature(h2fmi_t *fmi,
|
||
|
h2fmi_ce_t ce,
|
||
|
UInt32 feature,
|
||
|
UInt8 *buffer,
|
||
|
UInt32 len,
|
||
|
UInt8 *status)
|
||
|
{
|
||
|
BOOL32 ret = TRUE32;
|
||
|
UInt32 fmi_control = (FMI_CONTROL__MODE__READ |
|
||
|
FMI_CONTROL__START_BIT);
|
||
|
UInt32 pos;
|
||
|
UInt32 sector_len;
|
||
|
UInt8 operation_status = 0;
|
||
|
|
||
|
#if FMI_VERSION == 4
|
||
|
if (fmi->read_stream_disable)
|
||
|
{
|
||
|
fmi_control |= FMI_CONTROL__DISABLE_STREAMING;
|
||
|
}
|
||
|
#endif // FMI_VERSION == 4
|
||
|
|
||
|
h2fmi_ppn_switch_ce(fmi, ce);
|
||
|
|
||
|
h2fmi_clear_interrupts_and_reset_masks(fmi);
|
||
|
|
||
|
h2fmi_send_cmd_addr_cmd(fmi,
|
||
|
NAND_CMD__GET_FEATURES,
|
||
|
NAND_CMD__SET_GET_FEATURES_CONFIRM,
|
||
|
(UInt8 *)&feature,
|
||
|
2);
|
||
|
if (!h2fmi_ppn_get_next_operation_status(fmi, &operation_status) || (operation_status != 0x40))
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Unable to get PPN feature 0x%04x: status = 0x%02x\n", feature, (UInt32) operation_status);
|
||
|
ret = FALSE32;
|
||
|
}
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__READ_SERIAL_OUTPUT);
|
||
|
|
||
|
for(pos = 0; pos < len; pos += sector_len)
|
||
|
{
|
||
|
UInt32 bounce = 0;
|
||
|
void *cursor = &buffer[pos];
|
||
|
|
||
|
sector_len = WMR_MIN(H2FMI_BYTES_PER_SECTOR, len - pos);
|
||
|
if (sizeof(bounce) > sector_len)
|
||
|
{
|
||
|
cursor = &bounce;
|
||
|
sector_len = sizeof(bounce);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
sector_len = ROUNDDOWNTO(sector_len, sizeof(bounce));
|
||
|
}
|
||
|
|
||
|
h2fmi_ppn_set_format(fmi, sector_len, 1, FALSE32);
|
||
|
h2fmi_wr(fmi, FMI_CONTROL, fmi_control);
|
||
|
h2fmi_pio_read_sector(fmi, cursor, sector_len);
|
||
|
|
||
|
if (&bounce == cursor)
|
||
|
{
|
||
|
WMR_MEMCPY(&buffer[pos], cursor, WMR_MIN(H2FMI_BYTES_PER_SECTOR, len - pos));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
h2fmi_wr(fmi, FMI_CONTROL, FMI_CONTROL__MODE__SOFTWARE_RESET);
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__GET_NEXT_OPERATION_STATUS);
|
||
|
h2fmi_ppn_disable_all_ces(fmi);
|
||
|
|
||
|
if (status)
|
||
|
{
|
||
|
*status = operation_status;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void h2fmi_ppn_set_format(h2fmi_t *fmi, UInt32 sector_len, UInt32 sector_count, BOOL32 bootpage)
|
||
|
{
|
||
|
UInt32 fmi_config;
|
||
|
|
||
|
if (bootpage)
|
||
|
{
|
||
|
fmi_config = FMI_CONFIG__ECC_CORRECTABLE_BITS(30) |
|
||
|
FMI_CONFIG__DMA_BURST__32_CYCLES;
|
||
|
#if FMI_VERSION > 3
|
||
|
fmi_config |= FMI_CONFIG__SCRAMBLE_SEED |
|
||
|
FMI_CONFIG__ENABLE_WHITENING;
|
||
|
#endif
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fmi_config = FMI_CONFIG__ECC_CORRECTABLE_BITS(0) |
|
||
|
FMI_CONFIG__DMA_BURST__32_CYCLES;
|
||
|
}
|
||
|
|
||
|
h2fmi_wr(fmi, FMI_CONFIG, fmi_config);
|
||
|
h2fmi_wr(fmi, FMI_DATA_SIZE, (FMI_DATA_SIZE__META_BYTES_PER_SECTOR(0) |
|
||
|
FMI_DATA_SIZE__META_BYTES_PER_PAGE(0) |
|
||
|
FMI_DATA_SIZE__BYTES_PER_SECTOR(sector_len) |
|
||
|
FMI_DATA_SIZE__SECTORS_PER_PAGE(sector_count)));
|
||
|
#if FMI_VERSION > 3
|
||
|
if (bootpage)
|
||
|
{
|
||
|
h2fmi_wr(fmi, FMI_CONTROL, FMI_CONTROL__RESET_SEED);
|
||
|
}
|
||
|
#endif // FMI_VERSION > 3
|
||
|
}
|
||
|
|
||
|
static void h2fmi_ppn_start_fmi_read_and_wait(h2fmi_t *fmi)
|
||
|
{
|
||
|
UInt32 fmi_control = (FMI_CONTROL__MODE__READ |
|
||
|
FMI_CONTROL__START_BIT);
|
||
|
|
||
|
#if FMI_VERSION == 4
|
||
|
if (fmi->read_stream_disable)
|
||
|
{
|
||
|
fmi_control |= FMI_CONTROL__DISABLE_STREAMING;
|
||
|
}
|
||
|
#endif // FMI_VERSION == 4
|
||
|
|
||
|
h2fmi_clear_interrupts_and_reset_masks(fmi);
|
||
|
h2fmi_wr(fmi, FMI_CONTROL, fmi_control);
|
||
|
h2fmi_busy_wait(fmi, FMI_INT_PEND, FMI_INT_PEND__LAST_FMC_DONE, FMI_INT_PEND__LAST_FMC_DONE);
|
||
|
h2fmi_wr(fmi, FMI_INT_PEND, FMI_INT_PEND__LAST_FMC_DONE);
|
||
|
}
|
||
|
|
||
|
static void h2fmi_ppn_start_fmi_write_and_wait(h2fmi_t *fmi)
|
||
|
{
|
||
|
UInt32 fmi_control = (FMI_CONTROL__MODE__WRITE |
|
||
|
FMI_CONTROL__START_BIT);
|
||
|
|
||
|
h2fmi_clear_interrupts_and_reset_masks(fmi);
|
||
|
h2fmi_wr(fmi, FMI_CONTROL, fmi_control);
|
||
|
h2fmi_busy_wait(fmi, FMI_INT_PEND, FMI_INT_PEND__LAST_FMC_DONE, FMI_INT_PEND__LAST_FMC_DONE);
|
||
|
h2fmi_wr(fmi, FMI_INT_PEND, FMI_INT_PEND__LAST_FMC_DONE);
|
||
|
}
|
||
|
|
||
|
static void h2fmi_ppn_get_page_data(h2fmi_t *fmi, UInt32 length)
|
||
|
{
|
||
|
UInt32 fmi_control = (FMI_CONTROL__MODE__READ |
|
||
|
FMI_CONTROL__START_BIT);
|
||
|
|
||
|
#if FMI_VERSION == 4
|
||
|
if (fmi->read_stream_disable)
|
||
|
{
|
||
|
fmi_control |= FMI_CONTROL__DISABLE_STREAMING;
|
||
|
}
|
||
|
#endif // FMI_VERSION == 4
|
||
|
|
||
|
h2fmi_prepare_for_fmi_interrupt(fmi, FMI_INT_EN__LAST_FMC_DONE);
|
||
|
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(length / 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, FMI_CONTROL, fmi_control);
|
||
|
if (!h2fmi_wait_for_fmi_interrupt(fmi, FMI_INT_PEND__LAST_FMC_DONE))
|
||
|
{
|
||
|
WMR_PANIC("Timeout waiting for LAST_FMC_DONE interrupt");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void h2fmi_ppn_program_page(h2fmi_t *fmi, UInt32 page, BOOL32 lastPage, UInt32 lbas)
|
||
|
{
|
||
|
UInt32 lba;
|
||
|
UInt32 fmi_control = (FMI_CONTROL__MODE__WRITE |
|
||
|
FMI_CONTROL__START_BIT);
|
||
|
|
||
|
h2fmi_send_cmd_addr(fmi,
|
||
|
lastPage ? NAND_CMD__MULTIPAGE_PROGRAM_LAST : NAND_CMD__MULTIPAGE_PROGRAM,
|
||
|
(UInt8 *)&page,
|
||
|
fmi->ppn->bytes_per_row_address);
|
||
|
|
||
|
for (lba = 0; lba < lbas; lba++)
|
||
|
{
|
||
|
h2fmi_prepare_for_fmi_interrupt(fmi, FMI_INT_EN__LAST_FMC_DONE);
|
||
|
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, FMI_CONTROL, fmi_control);
|
||
|
if (!h2fmi_wait_for_fmi_interrupt(fmi, FMI_INT_PEND__LAST_FMC_DONE))
|
||
|
{
|
||
|
WMR_PANIC("Timeout waiting for LAST_FMC_DONE interrupt");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__MULTIPAGE_PROGRAM_CONFIRM);
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn_get_debug_data(h2fmi_t *fmi, h2fmi_ce_t ce, UInt32 type, UInt8 *buffer, UInt32 len)
|
||
|
{
|
||
|
UInt8 operation_status = 0;
|
||
|
h2fmi_ppn_switch_ce(fmi, ce);
|
||
|
|
||
|
h2fmi_send_cmd_addr_cmd(fmi,
|
||
|
NAND_CMD__GET_DEBUG_DATA,
|
||
|
NAND_CMD__SET_GET_FEATURES_CONFIRM,
|
||
|
(UInt8 *)&type,
|
||
|
3);
|
||
|
|
||
|
WMR_SLEEP_US(20000);
|
||
|
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__OPERATION_STATUS);
|
||
|
h2fmi_get_nand_status(fmi, &operation_status);
|
||
|
//WMR_ASSERT(operation_status == 0x50);
|
||
|
h2fmi_ppn_read_serial_output(fmi, buffer, len);
|
||
|
h2fmi_ppn_disable_all_ces(fmi);
|
||
|
return TRUE32;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn_set_debug_data(h2fmi_t *fmi,
|
||
|
h2fmi_ce_t ce,
|
||
|
UInt32 type,
|
||
|
UInt8 *buffer,
|
||
|
UInt32 len)
|
||
|
{
|
||
|
UInt8 operationStatus = 0;
|
||
|
UInt32 fmi_control = (FMI_CONTROL__MODE__WRITE |
|
||
|
FMI_CONTROL__START_BIT);
|
||
|
|
||
|
h2fmi_ppn_switch_ce(fmi, ce);
|
||
|
|
||
|
h2fmi_send_cmd_addr(fmi,
|
||
|
NAND_CMD__SET_DEBUG_DATA,
|
||
|
(UInt8 *)&type,
|
||
|
3);
|
||
|
|
||
|
h2fmi_prepare_for_fmi_interrupt(fmi, FMI_INT_EN__LAST_FMC_DONE);
|
||
|
h2fmi_ppn_set_format(fmi, len, 1, FALSE32);
|
||
|
h2fmi_wr(fmi, FMI_CONTROL, fmi_control);
|
||
|
h2fmi_pio_write_sector(fmi, buffer, len);
|
||
|
if (!h2fmi_wait_for_fmi_interrupt(fmi, FMI_INT_PEND__LAST_FMC_DONE))
|
||
|
{
|
||
|
h2fmi_ppn_disable_all_ces(fmi);
|
||
|
WMR_PANIC("Timeout waiting for LAST_FMC_DONE interrupt");
|
||
|
}
|
||
|
|
||
|
h2fmi_wr(fmi, FMI_CONTROL, FMI_CONTROL__MODE__SOFTWARE_RESET);
|
||
|
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__SET_GET_FEATURES_CONFIRM);
|
||
|
|
||
|
WMR_SLEEP_US(10000);
|
||
|
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__OPERATION_STATUS);
|
||
|
if (!h2fmi_get_nand_status(fmi, &operationStatus))
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Timeout reading operation status in SetDebugData - don't trust any debug data you pull from here.\n");
|
||
|
}
|
||
|
|
||
|
h2fmi_ppn_disable_all_ces(fmi);
|
||
|
return TRUE32;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn_get_fw_version(h2fmi_t *fmi,
|
||
|
h2fmi_ce_t ce,
|
||
|
UInt8 *buffer)
|
||
|
{
|
||
|
BOOL32 ret;
|
||
|
UInt8 operationStatus = 0;
|
||
|
|
||
|
h2fmi_reset(fmi);
|
||
|
|
||
|
ret = h2fmi_ppn_get_feature(fmi,
|
||
|
ce,
|
||
|
PPN_FEATURE__FW_VERSION,
|
||
|
buffer,
|
||
|
NAND_DEV_PARAM_LEN_PPN,
|
||
|
&operationStatus);
|
||
|
|
||
|
if (!ret)
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "failed CE %d\n", ce);
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn_get_package_assembly_code(h2fmi_t *fmi,
|
||
|
h2fmi_ce_t ce,
|
||
|
UInt8 *buffer)
|
||
|
{
|
||
|
BOOL32 ret;
|
||
|
UInt8 operationStatus = 0;
|
||
|
|
||
|
h2fmi_reset(fmi);
|
||
|
ret = h2fmi_ppn_get_feature(fmi,
|
||
|
ce,
|
||
|
PPN_FEATURE__PACKAGE_ASSEMBLY_CODE,
|
||
|
buffer,
|
||
|
NAND_DEV_PARAM_LEN_PPN,
|
||
|
&operationStatus);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn_get_controller_unique_id(h2fmi_t *fmi,
|
||
|
h2fmi_ce_t ce,
|
||
|
UInt8 *buffer)
|
||
|
{
|
||
|
BOOL32 ret;
|
||
|
UInt8 operationStatus = 0;
|
||
|
|
||
|
h2fmi_reset(fmi);
|
||
|
|
||
|
ret = h2fmi_ppn_get_feature(fmi,
|
||
|
ce,
|
||
|
PPN_FEATURE__CONTROLLER_UNIQUE_ID,
|
||
|
buffer,
|
||
|
NAND_DEV_PARAM_LEN_PPN,
|
||
|
&operationStatus);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn_get_controller_hw_id(h2fmi_t *fmi,
|
||
|
h2fmi_ce_t ce,
|
||
|
UInt8 *buffer)
|
||
|
{
|
||
|
BOOL32 ret;
|
||
|
UInt8 operationStatus = 0;
|
||
|
|
||
|
h2fmi_reset(fmi);
|
||
|
|
||
|
ret = h2fmi_ppn_get_feature(fmi,
|
||
|
ce,
|
||
|
PPN_FEATURE__CONTROLLER_HW_ID,
|
||
|
buffer,
|
||
|
NAND_DEV_PARAM_LEN_PPN,
|
||
|
&operationStatus);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn_get_marketing_name(h2fmi_t *fmi,
|
||
|
h2fmi_ce_t ce,
|
||
|
UInt8 *buffer)
|
||
|
{
|
||
|
BOOL32 ret;
|
||
|
UInt8 operationStatus = 0;
|
||
|
|
||
|
h2fmi_reset(fmi);
|
||
|
|
||
|
ret = h2fmi_ppn_get_feature(fmi,
|
||
|
ce,
|
||
|
PPN_FEATURE__NAND_MARKETING_NAME,
|
||
|
buffer,
|
||
|
PPN_NAND_MARKETING_NAME_LENGTH,
|
||
|
&operationStatus);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn_get_manufacturer_id(h2fmi_t *fmi, h2fmi_ce_t ce, UInt8 *buffer)
|
||
|
{
|
||
|
UInt8 addr;
|
||
|
const UInt32 len = 8;
|
||
|
UInt32 fmi_control = (FMI_CONTROL__MODE__READ |
|
||
|
FMI_CONTROL__START_BIT);
|
||
|
|
||
|
#if SUPPORT_TOGGLE_NAND
|
||
|
// assert that SoC is in non-toggle (async) mode
|
||
|
WMR_ASSERT(!(fmi->is_toggle));
|
||
|
#endif
|
||
|
|
||
|
#if FMI_VERSION == 4
|
||
|
if (fmi->read_stream_disable)
|
||
|
{
|
||
|
fmi_control |= FMI_CONTROL__DISABLE_STREAMING;
|
||
|
}
|
||
|
#endif // FMI_VERSION == 4
|
||
|
|
||
|
h2fmi_reset(fmi);
|
||
|
|
||
|
h2fmi_ppn_switch_ce(fmi, ce);
|
||
|
|
||
|
addr = MfgID_ADDR;
|
||
|
|
||
|
h2fmi_send_cmd_addr(fmi,
|
||
|
NAND_CMD__READ_ID,
|
||
|
(UInt8 *)&addr,
|
||
|
1);
|
||
|
|
||
|
h2fmi_ppn_set_format(fmi, len, 1, FALSE32);
|
||
|
h2fmi_wr(fmi, FMI_CONTROL, fmi_control);
|
||
|
h2fmi_pio_read_sector(fmi, buffer, len);
|
||
|
|
||
|
h2fmi_ppn_disable_all_ces(fmi);
|
||
|
h2fmi_reset(fmi);
|
||
|
return TRUE32;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn_get_die_chip_id(h2fmi_t *fmi, h2fmi_ce_t ce, UInt32 die, UInt8 *buffer)
|
||
|
{
|
||
|
BOOL32 ret;
|
||
|
UInt8 operationStatus = 0;
|
||
|
|
||
|
h2fmi_reset(fmi);
|
||
|
|
||
|
ret = h2fmi_ppn_get_feature(fmi,
|
||
|
ce,
|
||
|
PPN_FEATURE__NAND_DIE_CHIP_ID | (die << 8),
|
||
|
buffer,
|
||
|
PPN_FEATURE_LENGTH_DIE_CHIP_ID,
|
||
|
&operationStatus);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL32 h2fmi_ppn_get_general_error_info(h2fmi_t *fmi,
|
||
|
h2fmi_ce_t ce,
|
||
|
h2fmi_ppn_failure_info_t *buffer,
|
||
|
UInt32 *dataLength,
|
||
|
struct dma_segment *sgl)
|
||
|
{
|
||
|
BOOL32 ret;
|
||
|
UInt32 structLength;
|
||
|
|
||
|
WMR_ASSERT(buffer != NULL);
|
||
|
WMR_ASSERT(dataLength != NULL);
|
||
|
|
||
|
h2fmi_reset(fmi);
|
||
|
|
||
|
if(fmi->ppn->bytes_per_row_address == PPN_1_5_ROW_ADDR_BYTES)
|
||
|
{
|
||
|
structLength = sizeof(h2fmi_ppn_failure_info_ppn1_5_t);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
structLength = sizeof(h2fmi_ppn_failure_info_ppn1_0_t);
|
||
|
}
|
||
|
ret = h2fmi_ppn_get_debug_data(fmi,
|
||
|
ce,
|
||
|
PPN_GET_DEBUG_DATA__GET_FAILURE_TYPE,
|
||
|
(UInt8 *)buffer,
|
||
|
structLength);
|
||
|
|
||
|
if (ret)
|
||
|
{
|
||
|
UInt32 failureInfoPage;
|
||
|
UInt32 failureInfoPageCount;
|
||
|
|
||
|
if(fmi->ppn->bytes_per_row_address == PPN_1_5_ROW_ADDR_BYTES)
|
||
|
{
|
||
|
h2fmi_ppn_failure_info_ppn1_5_t *tempBuffer = &(buffer->failure_info_1_5);
|
||
|
WMR_PRINT(ERROR, "General Failure Info - failure: 0x%04x page 0x%02x%02x%02x%02x count 0x%02x%02x%02x checksum %02x\n",
|
||
|
tempBuffer->type,
|
||
|
tempBuffer->startPage[3], tempBuffer->startPage[2], tempBuffer->startPage[1], tempBuffer->startPage[0],
|
||
|
tempBuffer->pageCount[2], tempBuffer->pageCount[1], tempBuffer->pageCount[0],
|
||
|
tempBuffer->checksum);
|
||
|
|
||
|
failureInfoPage = (tempBuffer->startPage[0] |
|
||
|
(tempBuffer->startPage[1] << 8) |
|
||
|
(tempBuffer->startPage[2] << 16) |
|
||
|
(tempBuffer->startPage[3] << 24));
|
||
|
|
||
|
failureInfoPageCount = (((tempBuffer->pageCount[0]) |
|
||
|
(tempBuffer->pageCount[1] << 8) |
|
||
|
(tempBuffer->pageCount[2] << 16)));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
h2fmi_ppn_failure_info_ppn1_0_t *tempBuffer = &(buffer->failure_info_1_0);
|
||
|
WMR_PRINT(ERROR, "General Failure Info - failure: 0x%04x page 0x%02x%02x%02x count 0x%02x%02x%02x checksum %02x\n",
|
||
|
tempBuffer->type,
|
||
|
tempBuffer->startPage[2], tempBuffer->startPage[1], tempBuffer->startPage[0],
|
||
|
tempBuffer->pageCount[2], tempBuffer->pageCount[1], tempBuffer->pageCount[0],
|
||
|
tempBuffer->checksum);
|
||
|
|
||
|
failureInfoPage = (tempBuffer->startPage[0] |
|
||
|
(tempBuffer->startPage[1] << 8) |
|
||
|
(tempBuffer->startPage[2] << 16));
|
||
|
|
||
|
failureInfoPageCount = (((tempBuffer->pageCount[0]) |
|
||
|
(tempBuffer->pageCount[1] << 8) |
|
||
|
(tempBuffer->pageCount[2] << 16)));
|
||
|
}
|
||
|
|
||
|
*dataLength = failureInfoPageCount * PPN_PERFECT_PAGE_SIZE;
|
||
|
|
||
|
if ((failureInfoPageCount > 0) &&
|
||
|
(NULL != sgl))
|
||
|
{
|
||
|
// Pull the full Debug Data
|
||
|
h2fmi_ppn_dma_debug_data_payload(fmi,
|
||
|
ce,
|
||
|
failureInfoPage,
|
||
|
failureInfoPageCount,
|
||
|
sgl,
|
||
|
TRUE32);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*dataLength = 0;
|
||
|
WMR_PRINT(ERROR, "Timeout reading Failure Info Debug Data\n");
|
||
|
}
|
||
|
|
||
|
h2fmi_ppn_disable_all_ces(fmi);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static Int32 h2fmi_ppn_update_fw_chunk(h2fmi_t *fmi,
|
||
|
UInt32 chunk,
|
||
|
UInt32 length)
|
||
|
{
|
||
|
Int32 ret;
|
||
|
UInt8 operation_status = 0;
|
||
|
|
||
|
h2fmi_send_cmd_addr(fmi, NAND_CMD__UPDATE_FW, (UInt8 *)&chunk, 2);
|
||
|
|
||
|
h2fmi_ppn_set_format(fmi, PPN_PERFECT_FMI_SECTOR_SIZE, PPN_PERFECT_FMI_SECTORS_PER_PERFECT_PAGE, FALSE32);
|
||
|
h2fmi_ppn_start_fmi_write_and_wait(fmi);
|
||
|
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__SET_GET_FEATURES_CONFIRM);
|
||
|
ret = h2fmi_ppn_get_next_operation_status_with_addr(fmi, (UInt8 *)&chunk, &operation_status);
|
||
|
if (!ret)
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Timeout reading operation status on firmware chunk %d\n", chunk);
|
||
|
ret = FIL_CRITICAL_ERROR;
|
||
|
}
|
||
|
else if (operation_status == PPN_OPERATION_STATUS__READY)
|
||
|
{
|
||
|
ret = FIL_SUCCESS;
|
||
|
}
|
||
|
else if (operation_status == (PPN_OPERATION_STATUS__READY |
|
||
|
PPN_OPERATION_STATUS__CLEAN |
|
||
|
PPN_OPERATION_STATUS__ERROR))
|
||
|
{
|
||
|
// 0x49 indicates channel not updateable
|
||
|
WMR_PRINT(ERROR, "Channel doesn't support firmware updates - skipping\n");
|
||
|
ret = FIL_UNSUPPORTED_ERROR;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Unexpected status 0x%02x on firmware chunk %d\n",
|
||
|
operation_status, chunk);
|
||
|
ret = FIL_CRITICAL_ERROR;
|
||
|
}
|
||
|
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__GET_NEXT_OPERATION_STATUS);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
Int32 h2fmi_ppn_fw_update(h2fmi_t *fmi,
|
||
|
h2fmi_ce_t ce,
|
||
|
struct dma_segment *sgl,
|
||
|
UInt32 fw_size,
|
||
|
h2fmi_ppn_fw_type fw_type)
|
||
|
{
|
||
|
UInt32 start_signature = PPN_FW_START_SIGNATURE;
|
||
|
UInt32 end_signature = PPN_FW_END_SIGNATURE;
|
||
|
UInt32 chunk = 0;
|
||
|
UInt32 max_chunk_size = PPN_PERFECT_PAGE_SIZE;
|
||
|
Int32 ret = FIL_SUCCESS;
|
||
|
UInt16 feature;
|
||
|
UInt8 operStat = 0;
|
||
|
|
||
|
if (fw_type == ppnFwTypeFw)
|
||
|
{
|
||
|
feature = PPN_FEATURE__FW_UPDATE;
|
||
|
}
|
||
|
else if (fw_type == ppnFwTypeFwa)
|
||
|
{
|
||
|
feature = PPN_FEATURE__FWA_UPDATE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WMR_PANIC("Invalid firmware type");
|
||
|
}
|
||
|
|
||
|
h2fmi_reset(fmi);
|
||
|
|
||
|
h2fmi_set_if_ctrl(fmi, (FMC_IF_CTRL__REB_SETUP(FMC_IF_CTRL__TIMING_MAX_CLOCKS) |
|
||
|
FMC_IF_CTRL__REB_HOLD(FMC_IF_CTRL__TIMING_MAX_CLOCKS) |
|
||
|
FMC_IF_CTRL__WEB_SETUP(FMC_IF_CTRL__TIMING_MAX_CLOCKS) |
|
||
|
FMC_IF_CTRL__WEB_HOLD(FMC_IF_CTRL__TIMING_MAX_CLOCKS) |
|
||
|
FMC_IF_CTRL__DCCYCLE(0)));
|
||
|
|
||
|
//rdar://problem/11286520 Allow timeout on FW update for channel 1
|
||
|
#if !APPLICATION_EMBEDDEDIOP
|
||
|
if (fmi->bus_id != 0)
|
||
|
{
|
||
|
fmi->h2fmi_ppn_panic_on_status_timeout = FALSE32;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if (FIL_SUCCESS != h2fmi_ppn_set_features(fmi, ce, feature, (UInt8 *)&start_signature, 4, FALSE32, &operStat))
|
||
|
{
|
||
|
if (operStat == (PPN_OPERATION_STATUS__READY | PPN_OPERATION_STATUS__CLEAN | PPN_OPERATION_STATUS__ERROR))
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "CH %d, CE %d doesn't support entering firmware updates!\n", fmi->bus_id, ce);
|
||
|
|
||
|
#if !APPLICATION_EMBEDDEDIOP
|
||
|
fmi->h2fmi_ppn_panic_on_status_timeout = TRUE32;
|
||
|
#endif
|
||
|
return FIL_UNSUPPORTED_ERROR;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Error entering PPN firmware update mode\n");
|
||
|
#if !APPLICATION_EMBEDDEDIOP
|
||
|
if (fmi->h2fmi_ppn_panic_on_status_timeout == FALSE32){
|
||
|
fmi->h2fmi_ppn_panic_on_status_timeout = TRUE32;
|
||
|
return FIL_UNSUPPORTED_ERROR;
|
||
|
}
|
||
|
#endif
|
||
|
return FIL_CRITICAL_ERROR;
|
||
|
}
|
||
|
}
|
||
|
#if !APPLICATION_EMBEDDEDIOP
|
||
|
else
|
||
|
{
|
||
|
fmi->h2fmi_ppn_panic_on_status_timeout = TRUE32;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
h2fmi_ppn_switch_ce(fmi, ce);
|
||
|
|
||
|
// We may be coming in here from a corrupted image, etc. Go as slow
|
||
|
// as possible through the update.
|
||
|
|
||
|
h2fmi_dma_execute_async(DMA_CMD_DIR_TX,
|
||
|
h2fmi_dma_data_chan(fmi),
|
||
|
sgl,
|
||
|
h2fmi_dma_data_fifo(fmi),
|
||
|
fw_size,
|
||
|
sizeof(UInt32),
|
||
|
1,
|
||
|
NULL);
|
||
|
|
||
|
while( fw_size )
|
||
|
{
|
||
|
const UInt32 chunk_len = WMR_MIN(max_chunk_size, fw_size);
|
||
|
Int32 status;
|
||
|
|
||
|
status = h2fmi_ppn_update_fw_chunk(fmi, chunk, chunk_len);
|
||
|
if (status != FIL_SUCCESS)
|
||
|
{
|
||
|
ret = status;
|
||
|
goto done;
|
||
|
}
|
||
|
chunk++;
|
||
|
fw_size -= chunk_len;
|
||
|
}
|
||
|
|
||
|
h2fmi_ppn_disable_all_ces(fmi);
|
||
|
|
||
|
operStat = 0;
|
||
|
if (FIL_SUCCESS != h2fmi_ppn_set_features(fmi, ce, feature, (UInt8 *)&end_signature, 4, FALSE32, &operStat))
|
||
|
{
|
||
|
if (operStat == (PPN_OPERATION_STATUS__READY | PPN_OPERATION_STATUS__CLEAN | PPN_OPERATION_STATUS__ERROR))
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "CH %d, CE %d doesn't support exiting firmware updates!\n", fmi->bus_id, ce);
|
||
|
ret = FIL_UNSUPPORTED_ERROR;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Error exiting PPN firmware update mode\n");
|
||
|
ret = FIL_CRITICAL_ERROR;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
done:
|
||
|
h2fmi_ppn_disable_all_ces(fmi);
|
||
|
|
||
|
if (ret == FIL_SUCCESS)
|
||
|
{
|
||
|
if (!h2fmi_dma_wait(h2fmi_dma_data_chan(fmi), H2FMI_PAGE_TIMEOUT_MICROS))
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Timeout waiting for CDMA channel %d to complete\n",
|
||
|
h2fmi_dma_data_chan(fmi));
|
||
|
h2fmi_dma_cancel(h2fmi_dma_data_chan(fmi));
|
||
|
ret = FIL_CRITICAL_ERROR;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
h2fmi_dma_cancel(h2fmi_dma_data_chan(fmi));
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
void h2fmi_ppn_poweron(h2fmi_t *fmi)
|
||
|
{
|
||
|
// Enable Vcc
|
||
|
// Enable VccQ
|
||
|
}
|
||
|
|
||
|
void h2fmi_ppn_poweroff(h2fmi_t *fmi)
|
||
|
{
|
||
|
// Will we ever even do this?
|
||
|
WMR_PANIC("Called h2fmi_ppn_poweroff");
|
||
|
}
|
||
|
|
||
|
void h2fmi_ppn_set_power_low_power(h2fmi_t *fmi)
|
||
|
{
|
||
|
// Only valid from NORMAL state - maybe STANDBY?
|
||
|
// From ReadyForReset, we should come in through h2fmi_ppn_reset()
|
||
|
|
||
|
// SetFeatures(PowerState, Low Power)
|
||
|
}
|
||
|
|
||
|
void h2fmi_ppn_set_power_standby(h2fmi_t *fmi)
|
||
|
{
|
||
|
// Disable Vcc, leave VccQ enabled
|
||
|
}
|
||
|
|
||
|
static void h2fmi_send_cmd(h2fmi_t *fmi, UInt8 cmd)
|
||
|
{
|
||
|
h2fmi_wr(fmi, FMC_CMD, FMC_CMD__CMD1(cmd));
|
||
|
h2fmi_wr(fmi, FMC_RW_CTRL, FMC_RW_CTRL__CMD1_MODE);
|
||
|
h2fmi_busy_wait(fmi, FMC_STATUS, FMC_STATUS__CMD1DONE, FMC_STATUS__CMD1DONE);
|
||
|
h2fmi_wr(fmi, FMC_STATUS, FMC_STATUS__CMD1DONE);
|
||
|
}
|
||
|
|
||
|
static void h2fmi_send_cmd_addr(h2fmi_t *fmi, UInt8 cmd, UInt8 *address, UInt32 address_bytes)
|
||
|
{
|
||
|
const UInt32 cmd1_addr_done = (FMC_STATUS__ADDRESSDONE |
|
||
|
FMC_STATUS__CMD1DONE);
|
||
|
h2fmi_wr(fmi, FMC_CMD, FMC_CMD__CMD1(cmd));
|
||
|
h2fmi_wr(fmi, FMC_ADDR0, (FMC_ADDR0__SEQ0(address[0]) |
|
||
|
FMC_ADDR0__SEQ1(address[1]) |
|
||
|
FMC_ADDR0__SEQ2(address[2]) |
|
||
|
FMC_ADDR0__SEQ3(address[3])));
|
||
|
h2fmi_wr(fmi, FMC_ADDRNUM, FMC_ADDRNUM__NUM(address_bytes - 1));
|
||
|
h2fmi_wr(fmi, FMC_RW_CTRL, (FMC_RW_CTRL__ADDR_MODE |
|
||
|
FMC_RW_CTRL__CMD1_MODE));
|
||
|
h2fmi_busy_wait(fmi, FMC_STATUS, cmd1_addr_done, cmd1_addr_done);
|
||
|
h2fmi_wr(fmi, FMC_STATUS, cmd1_addr_done);
|
||
|
}
|
||
|
|
||
|
static void h2fmi_send_cmd_addr_cmd(h2fmi_t *fmi, UInt8 cmd1, UInt8 cmd2, const UInt8 *address, UInt32 address_bytes)
|
||
|
{
|
||
|
const UInt32 cmd1_cmd2_addr_done = (FMC_STATUS__ADDRESSDONE |
|
||
|
FMC_STATUS__CMD1DONE |
|
||
|
FMC_STATUS__CMD2DONE);
|
||
|
|
||
|
h2fmi_wr(fmi, FMC_CMD, (FMC_CMD__CMD1(cmd1) |
|
||
|
FMC_CMD__CMD2(cmd2)));
|
||
|
h2fmi_wr(fmi, FMC_ADDR0, (FMC_ADDR0__SEQ0(address[0]) |
|
||
|
FMC_ADDR0__SEQ1(address[1]) |
|
||
|
FMC_ADDR0__SEQ2(address[2]) |
|
||
|
FMC_ADDR0__SEQ3(address[3])));
|
||
|
|
||
|
if (address_bytes > 4)
|
||
|
{
|
||
|
h2fmi_wr(fmi, FMC_ADDR1, (FMC_ADDR1__SEQ4(address[4]) |
|
||
|
FMC_ADDR1__SEQ5(address[5]) |
|
||
|
FMC_ADDR1__SEQ6(address[6]) |
|
||
|
FMC_ADDR1__SEQ7(address[7])));
|
||
|
}
|
||
|
|
||
|
h2fmi_wr(fmi, FMC_ADDRNUM, FMC_ADDRNUM__NUM(address_bytes - 1));
|
||
|
h2fmi_wr(fmi, FMC_RW_CTRL, (FMC_RW_CTRL__ADDR_MODE |
|
||
|
FMC_RW_CTRL__CMD1_MODE |
|
||
|
FMC_RW_CTRL__CMD2_MODE));
|
||
|
|
||
|
h2fmi_busy_wait(fmi, FMC_STATUS, cmd1_cmd2_addr_done, cmd1_cmd2_addr_done);
|
||
|
h2fmi_wr(fmi, FMC_STATUS, cmd1_cmd2_addr_done);
|
||
|
}
|
||
|
|
||
|
static void h2fmi_ppn_read_serial_output(h2fmi_t *fmi, UInt8 *buf, UInt32 len)
|
||
|
{
|
||
|
UInt32 fullSectors;
|
||
|
UInt32 fmi_control = (FMI_CONTROL__MODE__READ |
|
||
|
FMI_CONTROL__START_BIT);
|
||
|
|
||
|
#if FMI_VERSION == 4
|
||
|
if (fmi->read_stream_disable)
|
||
|
{
|
||
|
fmi_control |= FMI_CONTROL__DISABLE_STREAMING;
|
||
|
}
|
||
|
#endif // FMI_VERSION == 4
|
||
|
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__READ_SERIAL_OUTPUT);
|
||
|
|
||
|
fullSectors = len / H2FMI_BYTES_PER_SECTOR;
|
||
|
|
||
|
if (fullSectors > 0)
|
||
|
{
|
||
|
h2fmi_ppn_set_format(fmi, H2FMI_BYTES_PER_SECTOR, fullSectors, FALSE32);
|
||
|
h2fmi_wr(fmi, FMI_CONTROL, fmi_control);
|
||
|
|
||
|
while(fullSectors)
|
||
|
{
|
||
|
h2fmi_pio_read_sector(fmi, buf, H2FMI_BYTES_PER_SECTOR);
|
||
|
buf += H2FMI_BYTES_PER_SECTOR;
|
||
|
fullSectors--;
|
||
|
len -= H2FMI_BYTES_PER_SECTOR;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (len > 0)
|
||
|
{
|
||
|
h2fmi_ppn_set_format(fmi, len, 1, FALSE32);
|
||
|
h2fmi_wr(fmi, FMI_CONTROL, fmi_control);
|
||
|
h2fmi_pio_read_sector(fmi, buf, len);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
static BOOL32 h2fmi_get_nand_status(h2fmi_t *fmi, UInt8 *status)
|
||
|
{
|
||
|
BOOL32 ret;
|
||
|
|
||
|
h2fmi_prepare_for_fmi_interrupt(fmi, FMI_INT_EN__FMC_NSRBBDONE);
|
||
|
|
||
|
h2fmi_set_if_ctrl(fmi, (~FMC_IF_CTRL__RBBEN & h2fmi_rd(fmi, FMC_IF_CTRL)));
|
||
|
h2fmi_wr(fmi, FMC_RBB_CONFIG, (FMC_RBB_CONFIG__POL(0x40) |
|
||
|
FMC_RBB_CONFIG__POS(0x40)));
|
||
|
h2fmi_wr(fmi, FMC_DATANUM, FMC_DATANUM__NUM(0));
|
||
|
h2fmi_wr(fmi, FMC_INTMASK, FMC_INTMASK__NSRBBDONE);
|
||
|
h2fmi_wr(fmi, FMC_RW_CTRL, (FMC_RW_CTRL__REBHOLD | FMC_RW_CTRL__RD_MODE));
|
||
|
|
||
|
ret = h2fmi_wait_for_fmi_interrupt(fmi, FMI_INT_PEND__FMC_NSRBBDONE);
|
||
|
*status = (UInt8)h2fmi_rd(fmi, FMC_NAND_STATUS);
|
||
|
|
||
|
if (!ret)
|
||
|
{
|
||
|
|
||
|
//rdar://problem/11286520 Allow timeout on FW update for channel 1
|
||
|
#if !APPLICATION_EMBEDDEDIOP
|
||
|
if (!fmi->h2fmi_ppn_panic_on_status_timeout)
|
||
|
{
|
||
|
return FALSE32;
|
||
|
}
|
||
|
#endif
|
||
|
// Don't panic in iBSS - we want to at least get to a prompt on errors.
|
||
|
WMR_PANIC("Timeout waiting for NAND status: 0x%02x\n", *status);
|
||
|
}
|
||
|
else if (*status & PPN_OPERATION_STATUS__GENERAL_ERROR)
|
||
|
{
|
||
|
fmi->ppn->general_error = TRUE32;
|
||
|
}
|
||
|
|
||
|
h2fmi_wr(fmi, FMC_RW_CTRL, 0);
|
||
|
return TRUE32;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn_get_next_operation_status(h2fmi_t *fmi, UInt8 *operation_status)
|
||
|
{
|
||
|
BOOL32 ret;
|
||
|
const UInt32 cmd1_cmd2_done = (FMC_STATUS__CMD1DONE |
|
||
|
FMC_STATUS__CMD2DONE);
|
||
|
|
||
|
h2fmi_wr(fmi, FMC_CMD, (FMC_CMD__CMD1(NAND_CMD__GET_NEXT_OPERATION_STATUS) |
|
||
|
FMC_CMD__CMD2(NAND_CMD__OPERATION_STATUS)));
|
||
|
h2fmi_wr(fmi, FMC_RW_CTRL, (FMC_RW_CTRL__CMD1_MODE |
|
||
|
FMC_RW_CTRL__CMD2_MODE));
|
||
|
h2fmi_busy_wait(fmi, FMC_STATUS, cmd1_cmd2_done, cmd1_cmd2_done);
|
||
|
h2fmi_wr(fmi, FMC_STATUS, cmd1_cmd2_done);
|
||
|
|
||
|
ret = h2fmi_get_nand_status(fmi, operation_status);
|
||
|
if (ret && (*operation_status & PPN_OPERATION_STATUS__GENERAL_ERROR))
|
||
|
{
|
||
|
fmi->ppn->general_error = TRUE32;
|
||
|
fmi->ppn->general_error_ce = fmi->activeCe;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn_get_next_operation_status_with_addr(h2fmi_t *fmi, UInt8 *addr, UInt8 *operation_status)
|
||
|
{
|
||
|
h2fmi_send_cmd_addr_cmd(fmi,
|
||
|
NAND_CMD__GET_NEXT_OPERATION_STATUS,
|
||
|
NAND_CMD__OPERATION_STATUS,
|
||
|
addr,
|
||
|
3);
|
||
|
return h2fmi_get_nand_status(fmi, operation_status);
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL32 h2fmi_ppn_get_controller_status(h2fmi_t *fmi, UInt8 *controller_status)
|
||
|
{
|
||
|
BOOL32 ret;
|
||
|
|
||
|
h2fmi_wr(fmi, FMC_CMD, FMC_CMD__CMD1(NAND_CMD__CONTROLLER_STATUS));
|
||
|
h2fmi_wr(fmi, FMC_RW_CTRL, FMC_RW_CTRL__CMD1_MODE);
|
||
|
h2fmi_busy_wait(fmi, FMC_STATUS, FMC_STATUS__CMD1DONE, FMC_STATUS__CMD1DONE);
|
||
|
h2fmi_wr(fmi, FMC_STATUS, FMC_STATUS__CMD1DONE);
|
||
|
|
||
|
ret = h2fmi_get_nand_status(fmi, controller_status);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#if 0
|
||
|
#define DUMP_VAR(variable) _WMR_PRINT("%s : %u\n", #variable, (UInt32)(variable))
|
||
|
#else
|
||
|
#define DUMP_VAR(variable)
|
||
|
#endif
|
||
|
BOOL32 h2fmi_ppn_get_device_params(h2fmi_t *fmi, h2fmi_ce_t ce, h2fmi_ppn_device_params_t *params)
|
||
|
{
|
||
|
UInt8 address[3] = {0, 0, 0};
|
||
|
UInt8 operation_status = 0;
|
||
|
BOOL32 result = TRUE32;
|
||
|
|
||
|
WMR_ASSERT(fmi->activeCe == (h2fmi_ce_t)~0);
|
||
|
|
||
|
h2fmi_set_if_ctrl(fmi, (FMC_IF_CTRL__REB_SETUP(FMC_IF_CTRL__TIMING_MAX_CLOCKS) |
|
||
|
FMC_IF_CTRL__REB_HOLD(FMC_IF_CTRL__TIMING_MAX_CLOCKS) |
|
||
|
FMC_IF_CTRL__WEB_SETUP(FMC_IF_CTRL__TIMING_MAX_CLOCKS) |
|
||
|
FMC_IF_CTRL__WEB_HOLD(FMC_IF_CTRL__TIMING_MAX_CLOCKS) |
|
||
|
FMC_IF_CTRL__DCCYCLE(0)));
|
||
|
|
||
|
h2fmi_ppn_switch_ce(fmi, ce);
|
||
|
|
||
|
h2fmi_send_cmd_addr_cmd(fmi,
|
||
|
NAND_CMD__READ_DEVICE_PARAMETERS,
|
||
|
NAND_CMD__READ_DEVICE_PARAMETERS_CONFIRM,
|
||
|
address,
|
||
|
1);
|
||
|
|
||
|
if (!h2fmi_ppn_get_next_operation_status(fmi, &operation_status))
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Timeout waiting for operation status\n");
|
||
|
result = FALSE32;
|
||
|
}
|
||
|
|
||
|
if (operation_status != 0x40)
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Invalid operation status reading PPN device parameters: 0x%02x\n",
|
||
|
operation_status);
|
||
|
result = FALSE32;
|
||
|
}
|
||
|
|
||
|
if (result)
|
||
|
{
|
||
|
h2fmi_ppn_read_serial_output(fmi, (UInt8 *)params, 512);
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__GET_NEXT_OPERATION_STATUS);
|
||
|
}
|
||
|
|
||
|
h2fmi_ppn_disable_all_ces(fmi);
|
||
|
|
||
|
if (result)
|
||
|
{
|
||
|
DUMP_VAR(params->caus_per_channel);
|
||
|
DUMP_VAR(params->cau_bits);
|
||
|
DUMP_VAR(params->blocks_per_cau);
|
||
|
DUMP_VAR(params->block_bits);
|
||
|
DUMP_VAR(params->pages_per_block);
|
||
|
DUMP_VAR(params->pages_per_block_slc);
|
||
|
DUMP_VAR(params->page_address_bits);
|
||
|
DUMP_VAR(params->address_bits_bits_per_cell);
|
||
|
DUMP_VAR(params->default_bits_per_cell);
|
||
|
DUMP_VAR(params->page_size);
|
||
|
DUMP_VAR(params->dies_per_channel);
|
||
|
|
||
|
DUMP_VAR(params->tRC);
|
||
|
DUMP_VAR(params->tREA);
|
||
|
DUMP_VAR(params->tREH);
|
||
|
DUMP_VAR(params->tRHOH);
|
||
|
DUMP_VAR(params->tRHZ);
|
||
|
DUMP_VAR(params->tRLOH);
|
||
|
DUMP_VAR(params->tRP);
|
||
|
DUMP_VAR(params->tWC);
|
||
|
DUMP_VAR(params->tWH);
|
||
|
DUMP_VAR(params->tWP);
|
||
|
|
||
|
DUMP_VAR(params->read_queue_size);
|
||
|
DUMP_VAR(params->program_queue_size);
|
||
|
DUMP_VAR(params->erase_queue_size);
|
||
|
DUMP_VAR(params->prep_function_buffer_size);
|
||
|
|
||
|
DUMP_VAR(params->tRST_ms);
|
||
|
DUMP_VAR(params->tPURST_ms);
|
||
|
DUMP_VAR(params->tSCE_ms);
|
||
|
DUMP_VAR(params->tCERDY_us);
|
||
|
|
||
|
DUMP_VAR(params->cau_per_channel2);
|
||
|
DUMP_VAR(params->dies_per_channel2);
|
||
|
DUMP_VAR(params->ddr_tRC);
|
||
|
DUMP_VAR(params->ddr_tREH);
|
||
|
DUMP_VAR(params->ddr_tRP);
|
||
|
DUMP_VAR(params->tDQSL_ps);
|
||
|
DUMP_VAR(params->tDQSH_ps);
|
||
|
DUMP_VAR(params->tDSC_ps);
|
||
|
DUMP_VAR(params->tDQSRE_ps);
|
||
|
DUMP_VAR(params->tDQSQ_ps);
|
||
|
DUMP_VAR(params->tDVW_ps);
|
||
|
DUMP_VAR(params->max_interface_speed);
|
||
|
|
||
|
if (fmi->ppn->spec_version < PPN_VERSION_1_5_0)
|
||
|
{
|
||
|
// PPN 1.0.x device
|
||
|
fmi->ppn->bytes_per_row_address = (params->cau_bits + params->block_bits + params->page_address_bits +
|
||
|
params->address_bits_bits_per_cell + 8 - 1) / 8;
|
||
|
fmi->ppn->bytes_per_full_address = 7;
|
||
|
WMR_ASSERT(sizeof(h2fmi_ppn_failure_info_ppn1_0_t) == PPN_1_0_FAILURE_INFO_EXPCT_LEN);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fmi->ppn->bytes_per_row_address = 4;
|
||
|
fmi->ppn->bytes_per_full_address = 8;
|
||
|
WMR_ASSERT(sizeof(h2fmi_ppn_failure_info_ppn1_5_t) == PPN_1_5_FAILURE_INFO_EXPCT_LEN);
|
||
|
}
|
||
|
|
||
|
result = _validate_device_parameters(fmi->ppn);
|
||
|
}
|
||
|
|
||
|
h2fmi_set_if_ctrl(fmi, fmi->if_ctrl);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn_init_channel(h2fmi_t *fmi,
|
||
|
BOOL32 debug_flags_valid,
|
||
|
UInt32 debug_flags,
|
||
|
BOOL32 vs_debug_flags_valid,
|
||
|
UInt32 vs_debug_flags,
|
||
|
BOOL32 allow_saving_debug_data,
|
||
|
UInt32 spec_version)
|
||
|
{
|
||
|
h2fmi_ppn_t *ppn;
|
||
|
h2fmi_ce_t ce;
|
||
|
|
||
|
#if !APPLICATION_EMBEDDEDIOP
|
||
|
fmi->h2fmi_ppn_panic_on_status_timeout = TRUE32;
|
||
|
#endif
|
||
|
|
||
|
if (!fmi->ppn)
|
||
|
{
|
||
|
fmi->ppn = (h2fmi_ppn_t *)WMR_MALLOC(sizeof(h2fmi_ppn_t));
|
||
|
for (ce = 0 ; ce < PPN_MAX_CES_PER_BUS ; ce++)
|
||
|
{
|
||
|
fmi->ppn->prep_buffer[ce] = (UInt32*)WMR_MALLOC(PPN_MAX_PAGES_PER_CE * sizeof(*fmi->ppn->prep_buffer));
|
||
|
}
|
||
|
//initialize error list for ppn
|
||
|
fmi->error_list = (UInt32 *)WMR_MALLOC(PPN_ERROR_LIST_SIZE);
|
||
|
WMR_ASSERT(fmi->error_list != NULL);
|
||
|
}
|
||
|
ppn = fmi->ppn;
|
||
|
|
||
|
ppn->debug_flags_valid = debug_flags_valid;
|
||
|
ppn->debug_flags = debug_flags;
|
||
|
ppn->vs_debug_flags_valid = vs_debug_flags_valid;
|
||
|
ppn->vs_debug_flags = vs_debug_flags;
|
||
|
ppn->allow_saving_debug_data = allow_saving_debug_data;
|
||
|
ppn->spec_version = spec_version;
|
||
|
|
||
|
#if FMISS_ENABLED
|
||
|
fmiss_init_sequences(fmi);
|
||
|
#endif /* FMISS_ENABLED */
|
||
|
|
||
|
return TRUE32;
|
||
|
}
|
||
|
|
||
|
// validate the value argument is between (inclusive) min and max
|
||
|
#define _validate_item(value_param, min_param, max_param) \
|
||
|
{ \
|
||
|
if (((value_param) < (min_param)) || ((value_param) > (max_param))) \
|
||
|
{ \
|
||
|
WMR_PRINT(ERROR, "failure with %s = %d\n", #value_param , value_param); \
|
||
|
return FALSE32; \
|
||
|
} \
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL32 _validate_device_parameters(h2fmi_ppn_t *ppn)
|
||
|
{
|
||
|
// check the device params
|
||
|
_validate_item(ppn->device_params.caus_per_channel, 1, 64);
|
||
|
_validate_item(ppn->device_params.cau_bits, 1, 7);
|
||
|
_validate_item(ppn->device_params.blocks_per_cau, 1024, 8500);
|
||
|
_validate_item(ppn->device_params.block_bits, 10, 15);
|
||
|
_validate_item(ppn->device_params.pages_per_block, 64, 512);
|
||
|
_validate_item(ppn->device_params.pages_per_block_slc, 64, 256);
|
||
|
_validate_item(ppn->device_params.page_address_bits, 5, 9);
|
||
|
_validate_item(ppn->device_params.address_bits_bits_per_cell, 1, 2);
|
||
|
_validate_item(ppn->device_params.default_bits_per_cell, 1, 4);
|
||
|
_validate_item(ppn->device_params.page_size, (8 * 1028), (16 * 1028)); // page size includes meta area (4 bytes per KB)
|
||
|
_validate_item(ppn->device_params.tRC, 1, 254);
|
||
|
_validate_item(ppn->device_params.tREA, 1, 254);
|
||
|
_validate_item(ppn->device_params.tREH, 1, 254);
|
||
|
_validate_item(ppn->device_params.tRHOH, 1, 254);
|
||
|
_validate_item(ppn->device_params.tRHZ, 1, 254);
|
||
|
_validate_item(ppn->device_params.tRLOH, 1, 254);
|
||
|
_validate_item(ppn->device_params.tRP, 1, 254);
|
||
|
_validate_item(ppn->device_params.tWC, 1, 254);
|
||
|
_validate_item(ppn->device_params.tWH, 1, 254);
|
||
|
_validate_item(ppn->device_params.tWP, 1, 254);
|
||
|
_validate_item(ppn->device_params.read_queue_size, 4, (8 * 1024));
|
||
|
_validate_item(ppn->device_params.program_queue_size, 2, (8 * 1024));
|
||
|
_validate_item(ppn->device_params.erase_queue_size, 4, (8 * 1024));
|
||
|
_validate_item(ppn->device_params.prep_function_buffer_size, 4, (8 * 1024));
|
||
|
_validate_item(ppn->device_params.tRST_ms, 1, 50);
|
||
|
_validate_item(ppn->device_params.tPURST_ms, 1, 50);
|
||
|
_validate_item(ppn->device_params.tSCE_ms, 1, 200);
|
||
|
_validate_item(ppn->device_params.tCERDY_us, 1, 200);
|
||
|
_validate_item(ppn->bytes_per_row_address, 3, 4);
|
||
|
_validate_item(ppn->bytes_per_full_address, 7, 8);
|
||
|
|
||
|
if ((ppn->device_params.blocks_per_cau % 8) != 0)
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "%d blocks per cau - rdar://8426007\n", ppn->device_params.blocks_per_cau);
|
||
|
return FALSE32;
|
||
|
}
|
||
|
|
||
|
if ((UInt32)(1 << ppn->device_params.page_address_bits) != ppn->device_params.pages_per_block)
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "pages_per_block:%d != 1 << page_address_bits:%d\n",
|
||
|
ppn->device_params.pages_per_block, ppn->device_params.page_address_bits);
|
||
|
return FALSE32;
|
||
|
}
|
||
|
|
||
|
return TRUE32;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn_post_rst_pre_pwrstate_operations(h2fmi_t* fmi)
|
||
|
{
|
||
|
BOOL32 result = TRUE32;
|
||
|
UInt32 ce_idx;
|
||
|
|
||
|
for(ce_idx = 0; ce_idx < H2FMI_MAX_CE_TOTAL; ce_idx++)
|
||
|
{
|
||
|
if (fmi->valid_ces & (1 << ce_idx))
|
||
|
{
|
||
|
h2fmi_ppn_configure_debug_data(fmi, ce_idx);
|
||
|
|
||
|
#if SUPPORT_TOGGLE_NAND
|
||
|
if (fmi->is_toggle_system)
|
||
|
{
|
||
|
if (!h2fmi_ppn15_enable_optional_signals(fmi, ce_idx))
|
||
|
{
|
||
|
result = FALSE32;
|
||
|
}
|
||
|
}
|
||
|
#endif // SUPPORT_TOGGLE_NAND
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn_set_channel_power_state(h2fmi_t *fmi, UInt32 ps_tran)
|
||
|
{
|
||
|
h2fmi_ce_t ce;
|
||
|
Int32 result;
|
||
|
UInt32 ps;
|
||
|
|
||
|
#if SUPPORT_TOGGLE_NAND
|
||
|
if (!fmi->is_toggle_system)
|
||
|
{
|
||
|
#endif
|
||
|
WMR_ASSERT(ps_tran == PPN_PS_TRANS_LOW_POWER_TO_ASYNC);
|
||
|
#if SUPPORT_TOGGLE_NAND
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
ps = PS_TRANS_GET_TO(ps_tran);
|
||
|
|
||
|
for( ce = 0; ce < H2FMI_MAX_CE_TOTAL; ce++)
|
||
|
{
|
||
|
if (fmi->valid_ces & (1 << ce))
|
||
|
{
|
||
|
WMR_PRINT(MISC, "Setting PPN Power State: 0x%x\n", ps);
|
||
|
result = h2fmi_ppn_set_features(fmi, ce, PPN_FEATURE__POWER_STATE, (UInt8 *)&ps, 1, FALSE32, NULL);
|
||
|
|
||
|
#if APPLICATION_EMBEDDEDIOP
|
||
|
WMR_ASSERT(result == FIL_SUCCESS);
|
||
|
#endif
|
||
|
if (result != FIL_SUCCESS)
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "failed setting power state on ce %d\n", ce);
|
||
|
return FALSE32;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if !H2FMI_IOP
|
||
|
if (PPN_FEATURE__POWER_STATE__LOW_POWER == ps)
|
||
|
{
|
||
|
fmi->if_ctrl = H2FMI_IF_CTRL_LOW_POWER;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fmi->if_ctrl = fmi->if_ctrl_normal;
|
||
|
}
|
||
|
restoreTimingRegs(fmi);
|
||
|
#endif // !H2FMI_IOP
|
||
|
|
||
|
return TRUE32;
|
||
|
}
|
||
|
|
||
|
#if !APPLICATION_EMBEDDEDIOP
|
||
|
void h2fmi_ppn_fill_nandinfo(h2fmi_t *fmi, NandInfo *nandInfo, NandRequiredTimings *requiredTiming)
|
||
|
{
|
||
|
h2fmi_ppn_t *ppn = fmi->ppn;
|
||
|
h2fmi_ppn_device_params_t *params = &ppn->device_params;
|
||
|
NandPpn *nandPpn = &ppn->nandPpn;
|
||
|
|
||
|
nandPpn->blocksPerCau = params->blocks_per_cau;
|
||
|
nandPpn->causPerCe = params->caus_per_channel;
|
||
|
nandPpn->slcPagesPerBlock = params->pages_per_block_slc;
|
||
|
nandPpn->mlcPagesPerBlock = params->pages_per_block;
|
||
|
nandPpn->matchOddEvenCaus = params->block_pairing_scheme;
|
||
|
nandPpn->bitsPerCau = params->cau_bits;
|
||
|
nandPpn->bitsPerPage = params->page_address_bits;
|
||
|
nandPpn->bitsPerBlock = params->block_bits;
|
||
|
nandPpn->maxTransactionSize = params->prep_function_buffer_size;
|
||
|
nandPpn->specVersion = ppn->spec_version;
|
||
|
|
||
|
fmi->blocks_per_ce = params->blocks_per_cau * params->caus_per_channel;
|
||
|
fmi->banks_per_ce = params->caus_per_channel; // Banks are CAUs in PPN
|
||
|
fmi->bytes_per_page = 1 << WMR_LOG2(params->page_size);
|
||
|
fmi->sectors_per_page = fmi->bytes_per_page / H2FMI_BYTES_PER_SECTOR;
|
||
|
fmi->pages_per_block = params->pages_per_block;
|
||
|
fmi->bytes_per_spare = params->page_size - fmi->bytes_per_page;
|
||
|
fmi->dies_per_cs = params->dies_per_channel;
|
||
|
|
||
|
// Cache page config registers
|
||
|
fmi->correctable_bits = 30; // only used for Bonfire reporting
|
||
|
fmi->refresh_threshold_bits = 0;
|
||
|
|
||
|
WMR_MEMSET(requiredTiming, 0, sizeof(*requiredTiming));
|
||
|
|
||
|
requiredTiming->clock_hz = WMR_BUS_FREQ_HZ();
|
||
|
#if FMI_USE_WRITE_CYCLE_BALANCING
|
||
|
requiredTiming->balance_write_cycle = TRUE32;
|
||
|
#endif
|
||
|
|
||
|
requiredTiming->soc_tx_prop_ns = FMI_TX_PROP_DELAY_NS;
|
||
|
requiredTiming->soc_rx_prop_ns = FMI_RX_PROP_DELAY_NS;
|
||
|
|
||
|
requiredTiming->tRC_ns = params->tRC;
|
||
|
requiredTiming->tRP_ns = params->tRP;
|
||
|
requiredTiming->tREH_ns = params->tREH;
|
||
|
requiredTiming->tREA_ns = params->tREA;
|
||
|
requiredTiming->tRHOH_ns = params->tRHOH;
|
||
|
requiredTiming->tRLOH_ns = params->tRLOH;
|
||
|
|
||
|
requiredTiming->tWC_ns = params->tWC;
|
||
|
requiredTiming->tWP_ns = params->tWP;
|
||
|
requiredTiming->tWH_ns = params->tWH;
|
||
|
|
||
|
#if SUPPORT_TOGGLE_NAND
|
||
|
if (fmi->is_toggle_system)
|
||
|
{
|
||
|
requiredTiming->ddr_tRC_ps = params->ddr_tRC * PS_PER_NS;
|
||
|
requiredTiming->ddr_tREH_ps = params->ddr_tREH * PS_PER_NS;
|
||
|
requiredTiming->ddr_tRP_ps = params->ddr_tRP * PS_PER_NS;
|
||
|
|
||
|
requiredTiming->tDSC_ps = params->tDSC_ps;
|
||
|
requiredTiming->tDQSH_ps = params->tDQSH_ps;
|
||
|
requiredTiming->tDQSL_ps = params->tDQSL_ps;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
fmi->ppn->boardSupport.vsType = FIL_VS_UNKNOWN;
|
||
|
nandInfo->boardSupport = &fmi->ppn->boardSupport;
|
||
|
|
||
|
nandInfo->format = getPPNFormat();
|
||
|
|
||
|
nandInfo->geometry = &fmi->ppn->geometry;
|
||
|
|
||
|
fmi->ppn->geometry.blocksPerCS = fmi->blocks_per_ce;
|
||
|
fmi->ppn->geometry.pagesPerBlock = fmi->pages_per_block;
|
||
|
fmi->ppn->geometry.dataBytesPerPage = fmi->bytes_per_page;
|
||
|
fmi->ppn->geometry.spareBytesPerPage = fmi->bytes_per_spare;
|
||
|
fmi->ppn->geometry.eccPer512Bytes = 0;
|
||
|
fmi->ppn->geometry.initialBBType = INIT_BBT_PPN;
|
||
|
fmi->ppn->geometry.diesPerCS = fmi->dies_per_cs;
|
||
|
|
||
|
}
|
||
|
#endif /* !APPLICATION_EMBEDDEDIOP */
|
||
|
|
||
|
UInt32 h2fmi_ppn_calculate_fmi_config(h2fmi_t *fmi)
|
||
|
{
|
||
|
UInt32 fmi_config = FMI_CONFIG__ECC_CORRECTABLE_BITS(0) |
|
||
|
FMI_CONFIG__DMA_BURST(WMR_LOG2(H2FMI_DMA_BURST_CYCLES));
|
||
|
|
||
|
#if FMI_VERSION >= 4
|
||
|
fmi_config |= FMI_CONFIG__META_DMA_BURST__4_CYCLES |
|
||
|
FMI_CONFIG__META_DMA_BURST__4_BYTES;
|
||
|
#endif // FMI_VERSION >= 4
|
||
|
|
||
|
return fmi_config;
|
||
|
}
|
||
|
|
||
|
UInt32 h2fmi_ppn_calculate_fmi_data_size(h2fmi_t *fmi)
|
||
|
{
|
||
|
return (FMI_DATA_SIZE__META_BYTES_PER_SECTOR( fmi->valid_bytes_per_meta) |
|
||
|
FMI_DATA_SIZE__META_BYTES_PER_PAGE( fmi->valid_bytes_per_meta) |
|
||
|
FMI_DATA_SIZE__BYTES_PER_SECTOR( H2FMI_BYTES_PER_SECTOR) |
|
||
|
FMI_DATA_SIZE__SECTORS_PER_PAGE( fmi->sectors_per_page));
|
||
|
}
|
||
|
|
||
|
static BOOL32 h2fmi_ppn_switch_ce(h2fmi_t *fmi, h2fmi_ce_t ce)
|
||
|
{
|
||
|
if (fmi->activeCe != ce)
|
||
|
{
|
||
|
h2fmi_fmc_disable_all_ces(fmi);
|
||
|
h2fmi_fmc_enable_ce(fmi, ce);
|
||
|
fmi->activeCe = ce;
|
||
|
return TRUE32;
|
||
|
}
|
||
|
|
||
|
return FALSE32;
|
||
|
}
|
||
|
|
||
|
static void h2fmi_ppn_disable_all_ces(h2fmi_t *fmi)
|
||
|
{
|
||
|
h2fmi_fmc_disable_all_ces(fmi);
|
||
|
fmi->activeCe = (h2fmi_ce_t)~0;
|
||
|
}
|
||
|
|
||
|
static void h2fmi_prepare_for_fmi_interrupt(h2fmi_t *fmi, UInt32 condition)
|
||
|
{
|
||
|
h2fmi_clear_interrupts_and_reset_masks(fmi);
|
||
|
#if H2FMI_WAIT_USING_ISR
|
||
|
h2fmi_wr(fmi, FMI_INT_EN, condition);
|
||
|
#endif /* H2FMI_WAIT_USING_ISR */
|
||
|
}
|
||
|
|
||
|
static BOOL32 h2fmi_wait_for_fmi_interrupt(h2fmi_t *fmi, UInt32 condition)
|
||
|
{
|
||
|
#if H2FMI_WAIT_USING_ISR
|
||
|
if (!event_wait_timeout(&fmi->isr_event, H2FMI_PAGE_TIMEOUT_MICROS) ||
|
||
|
!(fmi->isr_condition & condition))
|
||
|
{
|
||
|
return FALSE32;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return TRUE32;
|
||
|
}
|
||
|
#else
|
||
|
BOOL32 ret;
|
||
|
|
||
|
ret = h2fmi_wait_done(fmi, FMI_INT_PEND, condition, condition);
|
||
|
h2fmi_wr(fmi, FMI_INT_PEND, condition);
|
||
|
return ret;
|
||
|
#endif /* H2FMI_WAIT_USING_ISR */
|
||
|
}
|
||
|
|
||
|
#if SUPPORT_TOGGLE_NAND
|
||
|
BOOL32 h2fmi_ppn15_get_temperature(h2fmi_t *fmi, h2fmi_ce_t ce, Int16 *temperature)
|
||
|
{
|
||
|
UInt8 status;
|
||
|
|
||
|
if (!h2fmi_ppn_get_feature(fmi, ce, PPN_FEATURE__GET_DEVICE_TEMPERATURE, (UInt8*)temperature, PPN_FEATURE_LENGTH_TEMPERATURE, &status))
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Get Temperature failed FMI %d CE %d, Status: 0x%x\n", fmi->bus_id, ce, status);
|
||
|
return FALSE32;
|
||
|
}
|
||
|
|
||
|
*temperature -= 273; // convert to Celsius
|
||
|
|
||
|
return TRUE32;
|
||
|
}
|
||
|
|
||
|
BOOL32 h2fmi_ppn15_enable_optional_signals(h2fmi_t *fmi, h2fmi_ce_t ce)
|
||
|
{
|
||
|
const UInt32 enable = 1;
|
||
|
BOOL32 ret = TRUE32;
|
||
|
|
||
|
if (fmi->useDiffDQS)
|
||
|
{
|
||
|
WMR_PRINT(ALWAYS, "Enabling Diff DQS on CE %d\n", ce);
|
||
|
if (FIL_SUCCESS != h2fmi_ppn_set_features(fmi, ce, PPN_FEATURE__DQS_COMPLEMENT_ENABLE, (UInt8 *)&enable, PPN_FEATURE_LENGTH_DEFAULT, FALSE32, NULL))
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Enabling Diff DQS failed on CE %d\n", ce);
|
||
|
ret = FALSE32;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (fmi->useDiffRE)
|
||
|
{
|
||
|
WMR_PRINT(ALWAYS, "Enabling Diff RE on CE %d\n", ce);
|
||
|
if (FIL_SUCCESS != h2fmi_ppn_set_features(fmi, ce, PPN_FEATURE__RE_COMPLEMENT_ENABLE, (UInt8 *)&enable, PPN_FEATURE_LENGTH_DEFAULT, FALSE32, NULL))
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Enabling Diff RE failed on CE %d\n", ce);
|
||
|
ret = FALSE32;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (fmi->useVREF)
|
||
|
{
|
||
|
Int32 ret;
|
||
|
WMR_PRINT(ALWAYS, "Enabling VREF on CE %d\n", ce);
|
||
|
if(FIL_SUCCESS != (ret = h2fmi_ppn_set_features(fmi, ce, PPN_FEATURE__VREF_ENABLE, (UInt8 *)&enable, PPN_FEATURE_LENGTH_DEFAULT, (fmi->bus_id == 1) ? TRUE32 : FALSE32, NULL)))
|
||
|
{
|
||
|
if(!((fmi->bus_id == 1) && (ret == FIL_UNSUPPORTED_ERROR)))
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Enabling VREF failed on CE %d\n", ce);
|
||
|
ret = FALSE32;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
Int32 h2fmi_ppn_configure_debug_data(h2fmi_t *fmi,
|
||
|
h2fmi_ce_t ce)
|
||
|
{
|
||
|
if (fmi->ppn->allow_saving_debug_data)
|
||
|
{
|
||
|
const UInt32 debugInfo = PPN_FEATURE__ALLOW_SAVING_DEBUG_DATA_ENABLE;
|
||
|
|
||
|
h2fmi_ppn_set_features(fmi,
|
||
|
ce,
|
||
|
PPN_FEATURE__ALLOW_SAVING_DEBUG_DATA,
|
||
|
(UInt8 *)&debugInfo,
|
||
|
PPN_FEATURE_LENGTH_DEFAULT,
|
||
|
FALSE32,
|
||
|
NULL);
|
||
|
}
|
||
|
|
||
|
if (fmi->ppn->debug_flags_valid)
|
||
|
{
|
||
|
WMR_PRINT(ALWAYS, "Enabling PPN generic debug flags 0x%08x on CE %d\n",
|
||
|
fmi->ppn->debug_flags, ce);
|
||
|
|
||
|
h2fmi_ppn_set_features(fmi,
|
||
|
ce,
|
||
|
PPN_FEATURE__SET_DEBUG_DATA_GENERIC_CONFIG,
|
||
|
(UInt8*) &fmi->ppn->debug_flags,
|
||
|
PPN_FEATURE_LENGTH_DEFAULT,
|
||
|
FALSE32,
|
||
|
NULL);
|
||
|
}
|
||
|
|
||
|
if (fmi->ppn->vs_debug_flags_valid)
|
||
|
{
|
||
|
WMR_PRINT(ALWAYS, "Enabling PPN vendor-specific debug flags 0x%08x on CE %d\n",
|
||
|
fmi->ppn->vs_debug_flags, ce);
|
||
|
|
||
|
h2fmi_ppn_set_features(fmi,
|
||
|
ce,
|
||
|
PPN_FEATURE__SET_DEBUG_DATA_VENDOR_SPECIFIC,
|
||
|
(UInt8 *)&fmi->ppn->vs_debug_flags,
|
||
|
PPN_FEATURE_LENGTH_DEFAULT,
|
||
|
FALSE32,
|
||
|
NULL);
|
||
|
}
|
||
|
|
||
|
return FIL_SUCCESS;
|
||
|
}
|
||
|
|
||
|
void h2fmi_ppn_dma_debug_data_payload(h2fmi_t *fmi,
|
||
|
h2fmi_ce_t ce,
|
||
|
UInt32 page,
|
||
|
UInt32 pageCount,
|
||
|
struct dma_segment *sgl,
|
||
|
BOOL32 waitFortGDD)
|
||
|
{
|
||
|
UInt32 i;
|
||
|
UInt8 status = 0;
|
||
|
|
||
|
h2fmi_reset(fmi);
|
||
|
|
||
|
h2fmi_ppn_switch_ce(fmi, ce);
|
||
|
|
||
|
/* Sometimes Hynix PPN parts need a reset (particularly when dealing with timeouts), but
|
||
|
generally we shouldn't need to.
|
||
|
|
||
|
h2fmi_ppn_reset(fmi);
|
||
|
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__LOW_POWER_READ_STATUS);
|
||
|
h2fmi_get_nand_status(fmi, &status);
|
||
|
WMR_ASSERT(status == (PPN_LOW_POWER_STATUS__WRITE_PROTECT_BAR | PPN_LOW_POWER_STATUS__READY));
|
||
|
*/
|
||
|
h2fmi_dma_execute_async(DMA_CMD_DIR_RX,
|
||
|
h2fmi_dma_data_chan(fmi),
|
||
|
sgl,
|
||
|
h2fmi_dma_data_fifo(fmi),
|
||
|
pageCount * PPN_PERFECT_PAGE_SIZE,
|
||
|
sizeof(UInt32),
|
||
|
32,
|
||
|
NULL);
|
||
|
|
||
|
for (i = page; i < page + pageCount; i++)
|
||
|
{
|
||
|
h2fmi_send_cmd_addr_cmd(fmi,
|
||
|
NAND_CMD__GET_DEBUG_DATA,
|
||
|
NAND_CMD__SET_GET_FEATURES_CONFIRM,
|
||
|
(UInt8 *)&i,
|
||
|
fmi->ppn->bytes_per_row_address);
|
||
|
|
||
|
if (waitFortGDD)
|
||
|
{
|
||
|
WMR_SLEEP_US(fmi->ppn->spec_version < PPN_VERSION_1_5_0 ? PPN_tGDD_1_0_US : PPN_tGDD_1_5_US);
|
||
|
}
|
||
|
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__OPERATION_STATUS);
|
||
|
if (!h2fmi_get_nand_status(fmi, &status))
|
||
|
{
|
||
|
WMR_PANIC("Timeout getting nand status");
|
||
|
}
|
||
|
|
||
|
h2fmi_send_cmd(fmi, NAND_CMD__READ_SERIAL_OUTPUT);
|
||
|
|
||
|
h2fmi_ppn_set_format(fmi, PPN_PERFECT_FMI_SECTOR_SIZE, PPN_PERFECT_FMI_SECTORS_PER_PERFECT_PAGE, FALSE32);
|
||
|
h2fmi_ppn_start_fmi_read_and_wait(fmi);
|
||
|
}
|
||
|
|
||
|
if (!h2fmi_dma_wait(h2fmi_dma_data_chan(fmi), H2FMI_PAGE_TIMEOUT_MICROS))
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Timeout waiting for CDMA channel %d to complete\n",
|
||
|
h2fmi_dma_data_chan(fmi));
|
||
|
h2fmi_dma_cancel(h2fmi_dma_data_chan(fmi));
|
||
|
}
|
||
|
|
||
|
h2fmi_ppn_disable_all_ces(fmi);
|
||
|
}
|
||
|
|
||
|
void h2fmi_ppn_force_geb_address(h2fmi_t *fmi, h2fmi_ce_t ce, PageAddressType addr)
|
||
|
{
|
||
|
h2fmi_reset(fmi);
|
||
|
|
||
|
WMR_PRINT(ERROR, "Sending SET_DEBUG_DATA to collect page on bus %d ce %d page 0x%08x\n",
|
||
|
fmi->bus_id, ce, addr);
|
||
|
h2fmi_ppn_set_debug_data(fmi, ce, PPN_SET_DEBUG_DATA__FORCE_ADDRESS, (UInt8 *)&addr, 4);
|
||
|
WMR_PRINT(ERROR, "Sent command to force GEB\n");
|
||
|
|
||
|
fmi->ppn->general_error = TRUE32;
|
||
|
fmi->ppn->general_error_ce = ce;
|
||
|
}
|
||
|
|
||
|
void h2fmi_ppn_set_feature_list(h2fmi_t *fmi, ppn_feature_entry_t *list, UInt32 size)
|
||
|
{
|
||
|
UInt32 i;
|
||
|
|
||
|
for (i = 0 ; i < size ; i++)
|
||
|
{
|
||
|
ppn_feature_entry_t entry;
|
||
|
h2fmi_ce_t ce;
|
||
|
|
||
|
WMR_MEMCPY(&entry, &list[i], sizeof(entry));
|
||
|
|
||
|
if (entry.version > fmi->ppn->spec_version)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
for (ce = 0 ; (ce < H2FMI_MAX_CE_TOTAL) && (ce <= fmi->valid_ces) ; ce++)
|
||
|
{
|
||
|
if (0 != (fmi->valid_ces & (1 << ce)))
|
||
|
{
|
||
|
Int32 result;
|
||
|
result = h2fmi_ppn_set_features(fmi, ce, entry.feature, (UInt8*)&entry.value, entry.bytes, TRUE32, NULL);
|
||
|
WMR_ASSERT((FIL_SUCCESS == result) || (FIL_UNSUPPORTED_ERROR == result));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if H2FMI_PPN_VERIFY_SET_FEATURES
|
||
|
|
||
|
static void _update_feature_shadow(h2fmi_t *fmi, UInt16 feature, UInt8 *value, UInt32 len)
|
||
|
{
|
||
|
UInt32 i;
|
||
|
|
||
|
for (i = 0 ; i < _ppn_feature_shadow[fmi->bus_id].count ; i++)
|
||
|
{
|
||
|
if (feature == _ppn_feature_shadow[fmi->bus_id].list[i].feature)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (i >= _ppn_feature_shadow[fmi->bus_id].count)
|
||
|
{
|
||
|
WMR_ASSERT(i < NUMELEMENTS(_ppn_feature_shadow[fmi->bus_id].list));
|
||
|
_ppn_feature_shadow[fmi->bus_id].list[i].feature = feature;
|
||
|
_ppn_feature_shadow[fmi->bus_id].count++;
|
||
|
}
|
||
|
|
||
|
_ppn_feature_shadow[fmi->bus_id].list[i].length = WMR_MIN(sizeof(_ppn_feature_shadow[fmi->bus_id].list[i].value), len);
|
||
|
WMR_MEMCPY(&_ppn_feature_shadow[fmi->bus_id].list[i].value, value, _ppn_feature_shadow[fmi->bus_id].list[i].length);
|
||
|
|
||
|
if (PPN_FEATURE__POWER_STATE == feature)
|
||
|
{
|
||
|
_ppn_feature_shadow[fmi->bus_id].state = 1UL << *value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void h2fmi_ppn_verify_feature_shadow(h2fmi_t *fmi)
|
||
|
{
|
||
|
UInt32 ce_mask = fmi->valid_ces;
|
||
|
BOOL32 failed = FALSE32;
|
||
|
|
||
|
while (0 != ce_mask)
|
||
|
{
|
||
|
const h2fmi_ce_t ce = WMR_LOG2(ce_mask);
|
||
|
UInt32 i;
|
||
|
|
||
|
for (i = 0 ; i < _ppn_feature_shadow[fmi->bus_id].count ; i++)
|
||
|
{
|
||
|
UInt8 status;
|
||
|
UInt8 value[sizeof(_ppn_feature_shadow[fmi->bus_id].list[i].value)];
|
||
|
UInt32 statesIdx;
|
||
|
|
||
|
for (statesIdx = 0 ; statesIdx < NUMELEMENTS(_ppn_feature_states) ; statesIdx++)
|
||
|
{
|
||
|
if (_ppn_feature_states[statesIdx].feature == _ppn_feature_shadow[fmi->bus_id].list[i].feature)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if ((statesIdx < NUMELEMENTS(_ppn_feature_states)) &&
|
||
|
(0 == (_ppn_feature_states[statesIdx].states & _ppn_feature_shadow[fmi->bus_id].state)))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!h2fmi_ppn_get_feature(fmi, ce, _ppn_feature_shadow[fmi->bus_id].list[i].feature, value, _ppn_feature_shadow[fmi->bus_id].list[i].length, &status))
|
||
|
{
|
||
|
if ((PPN_FEAT_OPERATION_STATUS__UNSUPPORTED != status) && (PPN_FEAT_OPERATION_STATUS__UNSUPPORTED_BY_CH != status))
|
||
|
{
|
||
|
WMR_PRINT(ERROR, "Verify set feature of 0x%04x failed for FMI %d CE %d, Status: 0x%02x\n", _ppn_feature_shadow[fmi->bus_id].list[i].feature, fmi->bus_id, ce, status);
|
||
|
failed = TRUE32;
|
||
|
}
|
||
|
}
|
||
|
else if (0 != WMR_MEMCMP(&_ppn_feature_shadow[fmi->bus_id].list[i].value, value, _ppn_feature_shadow[fmi->bus_id].list[i].length))
|
||
|
{
|
||
|
UInt32 j;
|
||
|
WMR_PRINT(ERROR, "Verify set feature of 0x%04x failed for FMI %d CE %d,\n", _ppn_feature_shadow[fmi->bus_id].list[i].feature, fmi->bus_id, ce);
|
||
|
WMR_PRINT(ERROR, "Expected: Actual:\n");
|
||
|
for (j = 0 ; j < _ppn_feature_shadow[fmi->bus_id].list[i].length ; j++)
|
||
|
{
|
||
|
UInt8 expected = ((UInt8 *)&_ppn_feature_shadow[fmi->bus_id].list[i].value)[j];
|
||
|
WMR_PRINT(ERROR, "0x%02x 0x%02x\n", expected, value[j]);
|
||
|
}
|
||
|
failed = TRUE32;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ce_mask &= ~(1UL << ce);
|
||
|
}
|
||
|
|
||
|
WMR_ASSERT(!failed);
|
||
|
}
|
||
|
|
||
|
void h2fmi_ppn_reset_feature_shadow(h2fmi_t *fmi)
|
||
|
{
|
||
|
_ppn_feature_shadow[fmi->bus_id].count = 0;
|
||
|
}
|
||
|
|
||
|
#endif // H2FMI_PPN_VERIFY_SET_FEATURES
|
||
|
|
||
|
#endif //FMI_VERSION > 0
|