iBoot/drivers/apple/h2fmi/H2fmi_boot.c

484 lines
14 KiB
C

// *****************************************************************************
//
// File: H2fmi_boot.c
//
// *****************************************************************************
//
// Notes:
//
// - This file contains initialization functions for iBoot and IOP
//
//
// *****************************************************************************
//
// Copyright (C) 2008-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 Computer, Inc.
//
// *****************************************************************************
#include "H2fmi_private.h"
#include "H2fmi_ppn.h"
#if WMR_BUILDING_EFI
#include "SoC.h"
#else
#include "soc.h"
#endif // WMR_BUILDING_EFI
#include "WMROAM.h"
// =============================================================================
// implementation function declarations shared between SecureROM and iBoot
// =============================================================================
static BOOL32 h2fmi_wait_ready(h2fmi_t* fmi, UInt8 io_mask, UInt8 io_val);
BOOL32 h2fmi_is_chipid_invalid(h2fmi_chipid_t* const id)
{
const h2fmi_chipid_t Zeroes = { 0x00, 0x00, 0x00, 0x00, 0x00 };
const h2fmi_chipid_t Ones = { 0xff, 0xff, 0xff, 0xff, 0xff };
if (WMR_MEMCMP(id, Zeroes, sizeof(h2fmi_chipid_t)) &&
WMR_MEMCMP(id, Ones, sizeof(h2fmi_chipid_t)))
{
return FALSE32;
}
else
{
return TRUE32;
}
}
void h2fmi_reset(h2fmi_t* fmi)
{
// reset the specified FMI subsystem
WMR_CLOCK_RESET_DEVICE(h2fmi_get_gate(fmi));
#if FMI_VERSION > 0
h2fmi_wr(fmi, FMI_CONTROL, FMI_CONTROL__MODE__SOFTWARE_RESET);
#endif
turn_on_fmc(fmi);
restoreTimingRegs(fmi);
}
BOOL32 h2fmi_wait_done(h2fmi_t* fmi,
UInt32 reg,
UInt32 mask,
UInt32 bits)
{
BOOL32 result = TRUE32;
UInt64 check_time = H2FMI_DEFAULT_TIMEOUT_MICROS * WMR_GET_TICKS_PER_US();
UInt64 start_time = WMR_CLOCK_TICKS();
// wait for specified bits to be set
while ((h2fmi_rd(fmi, reg) & mask) != bits)
{
if (WMR_HAS_TIME_ELAPSED_TICKS(start_time, check_time))
{
h2fmi_fail(result);
break;
}
WMR_YIELD();
}
// clear specified bits
h2fmi_wr(fmi, reg, bits);
return result;
}
BOOL32 h2fmi_wait_dma_task_pending(h2fmi_t* fmi)
{
BOOL32 result = TRUE32;
UInt64 check_time = H2FMI_DEFAULT_TIMEOUT_MICROS * WMR_GET_TICKS_PER_US();
UInt64 start_time = WMR_CLOCK_TICKS();
// wait for FMI to complete transfer of sector to fifo
const uint32_t msk = FMI_DEBUG0__DMA_TASKS_PENDING(0x3);
// wait for FMI to to be ready for at least one dma task
while ((h2fmi_rd(fmi, FMI_DEBUG0) & msk) == 0)
{
if (WMR_HAS_TIME_ELAPSED_TICKS(start_time, check_time))
{
h2fmi_fail(result);
break;
}
WMR_YIELD();
}
return result;
}
void h2fmi_config_page_addr(h2fmi_t* fmi,
UInt32 page)
{
const UInt32 page_addr_size = 5;
const UInt32 page_addr_2 = (page >> 16) & 0xFF;
const UInt32 page_addr_1 = (page >> 8) & 0xFF;
const UInt32 page_addr_0 = (page >> 0) & 0xFF;
h2fmi_wr(fmi, FMC_ADDR1, (FMC_ADDR1__SEQ7(0x00) |
FMC_ADDR1__SEQ6(0x00) |
FMC_ADDR1__SEQ5(0x00) |
FMC_ADDR1__SEQ4(page_addr_2)));
h2fmi_wr(fmi, FMC_ADDR0, (FMC_ADDR0__SEQ3(page_addr_1) |
FMC_ADDR0__SEQ2(page_addr_0) |
FMC_ADDR0__SEQ1(0x00) |
FMC_ADDR0__SEQ0(0x00)));
h2fmi_wr(fmi, FMC_ADDRNUM, FMC_ADDRNUM__NUM(page_addr_size - 1));
}
void h2fmi_clean_ecc(h2fmi_t* fmi)
{
// clean status bits by setting them
#if FMI_VERSION == 0
// S5L9820X
h2fmi_wr(fmi, ECC_PND, (ECC_PND__BCH_ALL_FF |
ECC_PND__SECTOR_ERROR_ALERT |
ECC_PND__UNCORRECTABLE));
#else
// S5L8922X and later
h2fmi_wr(fmi, ECC_PND, (ECC_PND__SOME_ALL_ZERO |
ECC_PND__SOME_ALL_FF |
ECC_PND__SOME_ERROR_ALERT |
ECC_PND__ANY_UNCORRECTABLE));
#endif
}
void h2fmi_fmc_read_data(h2fmi_t* fmi,
UInt32 size,
UInt8* data)
{
register UInt32 idx;
UInt32 delay_ticks;
UInt32 if_ctrl;
// Force-clear FMC timings. We're manually hitting /RE and the I/O lines
// so we can take care of timings ourselves.
if_ctrl = h2fmi_rd(fmi, FMC_IF_CTRL);
h2fmi_set_if_ctrl(fmi, 0);
delay_ticks = WMR_GET_TICKS_PER_US() / 8; //(125ns)
// employ REBHOLD to hold nRD low so that each byte on the bus can
// be read, one at a time, from NAND_STATUS
h2fmi_wr(fmi, FMC_DATANUM, FMC_DATANUM__NUM(0));
for (idx = 0; idx < size; idx++)
{
UInt64 start;
h2fmi_wr(fmi, FMC_RW_CTRL, (FMC_RW_CTRL__REBHOLD |
FMC_RW_CTRL__RD_MODE));
// FMC requires 8 + REBSETUP cycles between asserting REBHOLD and reading FMC_NAND_STATUS.
start = WMR_CLOCK_TICKS();
while(!WMR_HAS_TIME_ELAPSED_TICKS(start, delay_ticks));
data[idx] = (UInt8)h2fmi_rd(fmi, FMC_NAND_STATUS);
// clear FMC_RW_CTRL, thereby releasing REBHOLD and completing
// current read cycle
h2fmi_wr(fmi, FMC_RW_CTRL, 0);
}
h2fmi_set_if_ctrl(fmi, if_ctrl);
}
BOOL32 h2fmi_nand_reset(h2fmi_t* fmi,
h2fmi_ce_t ce)
{
BOOL32 result = TRUE32;
// enable specified nand device
h2fmi_fmc_enable_ce(fmi, ce);
// Only for H5X - // <rdar://problem/11677742> Software workaround for Samsung reset and bad chip ID isssue
#if FMI_VERSION >= 5
WMR_SLEEP_US(H2FMI_EXTRA_CE_SETUP_TIME_US);
#endif // FMI_VERSION >= 5
// perform reset command
h2fmi_wr(fmi, FMC_CMD, FMC_CMD__CMD1(NAND_CMD__RESET));
h2fmi_wr(fmi, FMC_RW_CTRL, FMC_RW_CTRL__CMD1_MODE);
// wait until cmd sent on bus
if (!h2fmi_wait_done(fmi, FMC_STATUS,
FMC_STATUS__CMD1DONE,
FMC_STATUS__CMD1DONE))
{
h2fmi_fail(result);
}
// disable all nand devices
h2fmi_fmc_disable_ce(fmi, ce);
#if SUPPORT_PPN && H2FMI_PPN_VERIFY_SET_FEATURES
if (fmi->is_ppn)
{
h2fmi_ppn_reset_feature_shadow(fmi);
}
#endif // SUPPORT_PPN && H2FMI_PPN_VERIFY_SET_FEATURES
return result;
}
BOOL32 h2fmi_nand_read_id(h2fmi_t* fmi,
h2fmi_ce_t ce,
h2fmi_chipid_t* id,
UInt8 addr)
{
const UInt32 cmd1_addr_done = (FMC_STATUS__ADDRESSDONE |
FMC_STATUS__CMD1DONE);
BOOL32 result = TRUE32;
#if SUPPORT_TOGGLE_NAND
WMR_ASSERT(!fmi->is_toggle);
#endif
// enable specified nand device
h2fmi_fmc_enable_ce(fmi, ce);
// set up Read Id command and address cycles
h2fmi_wr(fmi, FMC_CMD, FMC_CMD__CMD1(NAND_CMD__READ_ID));
h2fmi_wr(fmi, FMC_ADDR0, FMC_ADDR0__SEQ0(addr));
h2fmi_wr(fmi, FMC_ADDRNUM, FMC_ADDRNUM__NUM(0));
h2fmi_wr(fmi, FMC_RW_CTRL, (FMC_RW_CTRL__ADDR_MODE |
FMC_RW_CTRL__CMD1_MODE));
// wait until cmd & addr completion
if (!h2fmi_wait_done(fmi, FMC_STATUS,
cmd1_addr_done,
cmd1_addr_done))
{
h2fmi_fail(result);
}
else
{
#if FMI_VERSION == 0
// read correct number of id bytes into local temporary
h2fmi_fmc_read_data(fmi, H2FMI_NAND_ID_SIZE, *id);
#else
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_wr(fmi, FMI_CONFIG, 0);
h2fmi_wr(fmi, FMI_DATA_SIZE, (FMI_DATA_SIZE__BYTES_PER_SECTOR( ROUNDUPTO( sizeof(h2fmi_chipid_t), sizeof(UInt32) ) ) |
FMI_DATA_SIZE__SECTORS_PER_PAGE(1)));
h2fmi_wr(fmi, FMI_CONTROL, fmi_control);
if (!h2fmi_wait_done(fmi, FMI_INT_PEND, FMI_INT_PEND__LAST_FMC_DONE, FMI_INT_PEND__LAST_FMC_DONE))
{
h2fmi_fail(result);
}
else
{
h2fmi_pio_read_sector(fmi, id, sizeof(h2fmi_chipid_t));
}
h2fmi_reset(fmi);
#endif
}
// disable all nand devices
h2fmi_fmc_disable_ce(fmi, ce);
return result;
}
BOOL32 h2fmi_pio_write_sector(h2fmi_t* fmi,
const void* buf,
UInt32 len)
{
BOOL32 result = TRUE32;
UInt32* ptr = (UInt32*)buf;
UInt32 i;
if (!h2fmi_wait_dma_task_pending(fmi))
{
// timed out
WMR_PANIC();
h2fmi_fail(result);
}
else
{
for( i = 0; i < len / sizeof(UInt32); i++ )
{
h2fmi_wr(fmi, FMI_DATA_BUF, *ptr++);
}
}
return result;
}
BOOL32 h2fmi_pio_read_sector(h2fmi_t* fmi,
void* buf,
UInt32 len)
{
BOOL32 result = TRUE32;
UInt32 *word_ptr = (UInt32*)buf;
UInt8 *byte_ptr;
int i;
// wait for FMI to be ready with data in fifo for DMA to transfer out
if (!h2fmi_wait_dma_task_pending(fmi))
{
// timed out
h2fmi_fail(result);
}
else
{
if (((UInt32)word_ptr & 0xF) == 0)
{
// Our buffers (particularly boot pages) are generally aligned.
// Do as many 32 bit operations as we can.
while (len >= sizeof(UInt32))
{
*word_ptr++ = h2fmi_rd(fmi, FMI_DATA_BUF);
len -= sizeof(UInt32);
}
}
byte_ptr = (UInt8*)word_ptr;
while (len)
{
UInt8 bytes = WMR_MIN(sizeof(UInt32), len);
UInt32 tmp = h2fmi_rd(fmi, FMI_DATA_BUF);
for (i = 0; i < bytes; i++)
{
*byte_ptr++ = (tmp >> (i*8)) & 0xFF;
}
len -= bytes;
}
}
return result;
}
UInt32 h2fmi_get_gate(h2fmi_t* fmi)
{
return h2fmi_select_by_bus(fmi, 0, 1 );
}
// =============================================================================
// static implementation function definitions used by both SecureROM and iBoot
// =============================================================================
BOOL32 h2fmi_start_nand_page_read(h2fmi_t* fmi,
UInt32 page)
{
const UInt32 cmd1_addr_cmd2_done = (FMC_STATUS__ADDRESSDONE |
FMC_STATUS__CMD2DONE |
FMC_STATUS__CMD1DONE);
BOOL32 result = TRUE32;
// configure FMC for cmd1/addr/cmd2 sequence
h2fmi_config_page_addr(fmi, page);
h2fmi_wr(fmi, FMC_CMD, (FMC_CMD__CMD2(NAND_CMD__READ_CONFIRM) |
FMC_CMD__CMD1(NAND_CMD__READ)));
// start cmd1/addr/cmd2 sequence
h2fmi_wr(fmi, FMC_RW_CTRL, (FMC_RW_CTRL__ADDR_MODE |
FMC_RW_CTRL__CMD2_MODE |
FMC_RW_CTRL__CMD1_MODE));
// wait until cmd1/addr/cmd2 sequence completed
if (!h2fmi_wait_done(fmi, FMC_STATUS,
cmd1_addr_cmd2_done,
cmd1_addr_cmd2_done))
{
h2fmi_fail(result);
}
// wait for ready using Read Status command
else if (!h2fmi_wait_ready(fmi,
NAND_STATUS__DATA_CACHE_RB,
NAND_STATUS__DATA_CACHE_RB_READY))
{
h2fmi_fail(result);
}
else
{
// reissue cmd1 and wait until completed
h2fmi_wr(fmi, FMC_CMD, FMC_CMD__CMD1(NAND_CMD__READ));
h2fmi_wr(fmi, FMC_RW_CTRL, FMC_RW_CTRL__CMD1_MODE);
if (!h2fmi_wait_done(fmi, FMC_STATUS,
FMC_STATUS__CMD1DONE,
FMC_STATUS__CMD1DONE))
{
h2fmi_fail(result);
}
}
return result;
}
static BOOL32 h2fmi_wait_ready(h2fmi_t* fmi,
UInt8 io_mask,
UInt8 io_val)
{
BOOL32 result = TRUE32;
// turn off RBBEN and configure it to use look for I/O bit 6 (0x40)
h2fmi_set_if_ctrl(fmi, (~FMC_IF_CTRL__RBBEN & h2fmi_rd(fmi, FMC_IF_CTRL)));
h2fmi_wr(fmi, FMC_RBB_CONFIG, (FMC_RBB_CONFIG__POL(io_val) |
FMC_RBB_CONFIG__POS(io_mask)));
// exec Read Status command cycle
h2fmi_wr(fmi, FMC_CMD, FMC_CMD__CMD1(NAND_CMD__READ_STATUS));
h2fmi_wr(fmi, FMC_RW_CTRL, FMC_RW_CTRL__CMD1_MODE);
// wait until command cycle completed
if (!h2fmi_wait_done(fmi, FMC_STATUS,
FMC_STATUS__CMD1DONE,
FMC_STATUS__CMD1DONE))
{
h2fmi_fail(result);
}
else
{
// clear the FMC_STATUS__NSRBBDONE bit in FMC_STATUS
h2fmi_wr(fmi, FMC_STATUS, FMC_STATUS__NSRBBDONE);
// set up read cycle with REBHOLD to wait for ready bit to be
// set on I/O lines
h2fmi_wr(fmi, FMC_DATANUM, FMC_DATANUM__NUM(0));
h2fmi_wr(fmi, FMC_RW_CTRL, (FMC_RW_CTRL__REBHOLD | FMC_RW_CTRL__RD_MODE));
// wait until NSRBB done signaled
if (!h2fmi_wait_done(fmi, FMC_STATUS,
FMC_STATUS__NSRBBDONE,
FMC_STATUS__NSRBBDONE))
{
h2fmi_fail(result);
}
// clear FMC_RW_CTRL, thereby releasing REBHOLD and completing
// current read cycle
h2fmi_wr(fmi, FMC_RW_CTRL, 0);
}
return result;
}
// ********************************** EOF **************************************