/** @file NorFlashDxe.c
Copyright (c) 2011 - 2014, ARM Ltd. All rights reserved.
Copyright (c) 2017, Socionext Inc. All rights reserved.
Copyright (c) 2017, Linaro, Ltd. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include
#include
#include
#include
#include
#include
#include "NorFlash.h"
#define FW_CODE_REGION_BASE SYNQUACER_SPI_NOR_BASE
#define FW_CODE_REGION_SIZE (FW_ENV_REGION_BASE - FW_CODE_REGION_BASE)
#define FW_ENV_REGION_BASE FixedPcdGet32 (PcdFlashNvStorageVariableBase)
#define FW_ENV_REGION_SIZE (FixedPcdGet32 (PcdFlashNvStorageVariableSize) + \
FixedPcdGet32 (PcdFlashNvStorageFtwWorkingSize) + \
FixedPcdGet32 (PcdFlashNvStorageFtwSpareSize))
STATIC NOR_FLASH_DESCRIPTION mNorFlashDevices[] = {
{
// UEFI code region
SYNQUACER_SPI_NOR_BASE, // device base
FW_CODE_REGION_BASE, // region base
FW_CODE_REGION_SIZE, // region size
SIZE_64KB, // block size
},
{
// Environment variable region
SYNQUACER_SPI_NOR_BASE, // device base
FW_ENV_REGION_BASE, // region base
FW_ENV_REGION_SIZE, // region size
SIZE_64KB, // block size
},
};
STATIC CONST UINT16 mFip006NullCmdSeq[] = {
CSDC (CSDC_END, CSDC_CONT_NON_CONTINUOUS, CSDC_TRP_MBM, CSDC_DEC_DECODE),
CSDC (CSDC_END, CSDC_CONT_NON_CONTINUOUS, CSDC_TRP_MBM, CSDC_DEC_DECODE),
CSDC (CSDC_END, CSDC_CONT_NON_CONTINUOUS, CSDC_TRP_MBM, CSDC_DEC_DECODE),
CSDC (CSDC_END, CSDC_CONT_NON_CONTINUOUS, CSDC_TRP_MBM, CSDC_DEC_DECODE),
CSDC (CSDC_END, CSDC_CONT_NON_CONTINUOUS, CSDC_TRP_MBM, CSDC_DEC_DECODE),
CSDC (CSDC_END, CSDC_CONT_NON_CONTINUOUS, CSDC_TRP_MBM, CSDC_DEC_DECODE),
CSDC (CSDC_END, CSDC_CONT_NON_CONTINUOUS, CSDC_TRP_MBM, CSDC_DEC_DECODE),
CSDC (CSDC_END, CSDC_CONT_NON_CONTINUOUS, CSDC_TRP_MBM, CSDC_DEC_DECODE),
};
STATIC CONST CSDC_DEFINITION mN25qCSDCDefTable[] = {
// Identification Operations
{ SPINOR_OP_RDID, FALSE, FALSE, FALSE, FALSE, CS_CFG_MBM_SINGLE,
CSDC_TRP_SINGLE },
// Register Operations
{ SPINOR_OP_RDSR, FALSE, FALSE, FALSE, FALSE, CS_CFG_MBM_SINGLE,
CSDC_TRP_SINGLE },
{ SPINOR_OP_WRSR, FALSE, FALSE, FALSE, TRUE, CS_CFG_MBM_SINGLE,
CSDC_TRP_SINGLE },
{ SPINOR_OP_RD_ARRAY, TRUE, FALSE, FALSE, FALSE, CS_CFG_MBM_SINGLE,
CSDC_TRP_SINGLE },
{ SPINOR_OP_RDFSR, FALSE, FALSE, FALSE, FALSE, CS_CFG_MBM_SINGLE,
CSDC_TRP_SINGLE },
{ SPINOR_OP_RD_NVCFG, FALSE, FALSE, FALSE, FALSE, CS_CFG_MBM_SINGLE,
CSDC_TRP_SINGLE },
{ SPINOR_OP_RD_VCR, FALSE, FALSE, FALSE, FALSE, CS_CFG_MBM_SINGLE,
CSDC_TRP_SINGLE },
{ SPINOR_OP_RD_EVCR, FALSE, FALSE, FALSE, FALSE, CS_CFG_MBM_SINGLE,
CSDC_TRP_SINGLE },
// Read Operations
{ SPINOR_OP_READ_4B, TRUE, TRUE, FALSE, FALSE, CS_CFG_MBM_SINGLE,
CSDC_TRP_SINGLE },
// Write Operations
{ SPINOR_OP_PP_4B, TRUE, TRUE, FALSE, TRUE, CS_CFG_MBM_SINGLE,
CSDC_TRP_SINGLE },
// Erase Operations
{ SPINOR_OP_BE_4B, FALSE, FALSE, FALSE, TRUE, CS_CFG_MBM_SINGLE,
CSDC_TRP_SINGLE },
};
STATIC CONST NOR_FLASH_INSTANCE mNorFlashInstanceTemplate = {
NOR_FLASH_SIGNATURE, // Signature
NULL, // Handle ... NEED TO BE FILLED
FALSE, // Initialized
NULL, // Initialize
0, // HostRegisterBaseAddress ... NEED TO BE FILLED
0, // DeviceBaseAddress ... NEED TO BE FILLED
0, // RegionBaseAddress ... NEED TO BE FILLED
0, // Size ... NEED TO BE FILLED
0, // BlockSize
0, // LastBlock
0, // StartLba
0, // OffsetLba
{
FvbGetAttributes, // GetAttributes
FvbSetAttributes, // SetAttributes
FvbGetPhysicalAddress, // GetPhysicalAddress
FvbGetBlockSize, // GetBlockSize
FvbRead, // Read
FvbWrite, // Write
FvbEraseBlocks, // EraseBlocks
NULL, //ParentHandle
}, // FvbProtoccol;
NULL, // ShadowBuffer
{
{
{
HARDWARE_DEVICE_PATH,
HW_VENDOR_DP,
{
(UINT8)(OFFSET_OF (NOR_FLASH_DEVICE_PATH, End)),
(UINT8)(OFFSET_OF (NOR_FLASH_DEVICE_PATH, End) >> 8)
}
},
{ 0x0, 0x0, 0x0, { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 } },
},
0, // Index
{
END_DEVICE_PATH_TYPE,
END_ENTIRE_DEVICE_PATH_SUBTYPE,
{ sizeof (EFI_DEVICE_PATH_PROTOCOL), 0 }
}
}, // DevicePath
0 // Flags
};
EFI_STATUS
NorFlashCreateInstance (
IN UINTN HostRegisterBase,
IN UINTN NorFlashDeviceBase,
IN UINTN NorFlashRegionBase,
IN UINTN NorFlashSize,
IN UINT32 Index,
IN UINT32 BlockSize,
IN BOOLEAN HasVarStore,
OUT NOR_FLASH_INSTANCE** NorFlashInstance
)
{
EFI_STATUS Status;
NOR_FLASH_INSTANCE* Instance;
NOR_FLASH_INFO *FlashInfo;
UINT8 JedecId[3];
ASSERT(NorFlashInstance != NULL);
Instance = AllocateRuntimeCopyPool (sizeof mNorFlashInstanceTemplate,
&mNorFlashInstanceTemplate);
if (Instance == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Instance->HostRegisterBaseAddress = HostRegisterBase;
Instance->DeviceBaseAddress = NorFlashDeviceBase;
Instance->RegionBaseAddress = NorFlashRegionBase;
Instance->Size = NorFlashSize;
Instance->BlockSize = BlockSize;
Instance->LastBlock = (NorFlashSize / BlockSize) - 1;
Instance->OffsetLba = (NorFlashRegionBase - NorFlashDeviceBase) / BlockSize;
CopyGuid (&Instance->DevicePath.Vendor.Guid, &gEfiCallerIdGuid);
Instance->DevicePath.Index = (UINT8)Index;
NorFlashReset (Instance);
NorFlashReadID (Instance, JedecId);
Status = NorFlashGetInfo (JedecId, &FlashInfo, FALSE);
if (EFI_ERROR (Status)) {
goto FreeInstance;
}
NorFlashPrintInfo (FlashInfo);
Instance->Flags = 0;
if (FlashInfo->Flags & NOR_FLASH_WRITE_FSR) {
Instance->Flags = NOR_FLASH_POLL_FSR;
}
Instance->ShadowBuffer = AllocateRuntimePool (BlockSize);
if (Instance->ShadowBuffer == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto FreeInstance;
}
if (HasVarStore) {
Instance->Initialize = NorFlashFvbInitialize;
}
*NorFlashInstance = Instance;
FreePool (FlashInfo);
return EFI_SUCCESS;
FreeInstance:
FreePool (Instance);
return Status;
}
STATIC
EFI_STATUS
NorFlashSetHostCSDC (
IN NOR_FLASH_INSTANCE *Instance,
IN BOOLEAN ReadWrite,
IN CONST UINT16 CSDC[ARRAY_SIZE (mFip006NullCmdSeq)]
)
{
EFI_PHYSICAL_ADDRESS Dst;
UINTN Index;
Dst = Instance->HostRegisterBaseAddress
+ (ReadWrite ? FIP006_REG_CS_WR : FIP006_REG_CS_RD);
for (Index = 0; Index < ARRAY_SIZE (mFip006NullCmdSeq); Index++) {
MmioWrite16 (Dst + (Index << 1), CSDC[Index]);
}
return EFI_SUCCESS;
}
STATIC
CONST CSDC_DEFINITION *
NorFlashGetCmdDef (
IN NOR_FLASH_INSTANCE *Instance,
IN UINT8 Code
)
{
CONST CSDC_DEFINITION *Cmd;
UINTN Index;
Cmd = NULL;
for (Index = 0; Index < ARRAY_SIZE (mN25qCSDCDefTable); Index++) {
if (Code == mN25qCSDCDefTable[Index].Code) {
Cmd = &mN25qCSDCDefTable[Index];
break;
}
}
return Cmd;
}
STATIC
EFI_STATUS
GenCSDC (
IN UINT8 Cmd,
IN BOOLEAN AddrAccess,
IN BOOLEAN AddrMode4Byte,
IN BOOLEAN HighZ,
IN UINT8 TransferMode,
OUT UINT16 *CmdSeq
)
{
UINTN Index;
if (!CmdSeq) {
return EFI_INVALID_PARAMETER;
}
Index = 0;
CopyMem (CmdSeq, mFip006NullCmdSeq, sizeof (mFip006NullCmdSeq));
CmdSeq[Index++] = CSDC (Cmd, CSDC_CONT_NON_CONTINUOUS, TransferMode,
CSDC_DEC_LEAVE_ASIS);
if (AddrAccess) {
if (AddrMode4Byte) {
CmdSeq[Index++] = CSDC (CSDC_ADDRESS_31_24, CSDC_CONT_NON_CONTINUOUS,
TransferMode, CSDC_DEC_DECODE);
}
CmdSeq[Index++] = CSDC (CSDC_ADDRESS_23_16, CSDC_CONT_NON_CONTINUOUS,
TransferMode, CSDC_DEC_DECODE);
CmdSeq[Index++] = CSDC (CSDC_ADDRESS_15_8, CSDC_CONT_NON_CONTINUOUS,
TransferMode, CSDC_DEC_DECODE);
CmdSeq[Index++] = CSDC (CSDC_ADDRESS_7_0, CSDC_CONT_NON_CONTINUOUS,
TransferMode, CSDC_DEC_DECODE);
}
if (HighZ) {
CmdSeq[Index++] = CSDC (CSDC_HIGH_Z, CSDC_CONT_NON_CONTINUOUS,
TransferMode, CSDC_DEC_DECODE);
}
return EFI_SUCCESS;
}
STATIC
EFI_STATUS
NorFlashSetHostCommand (
IN NOR_FLASH_INSTANCE *Instance,
IN UINT8 Code
)
{
CONST CSDC_DEFINITION *Cmd;
UINT16 CSDC[ARRAY_SIZE (mFip006NullCmdSeq)];
Cmd = NorFlashGetCmdDef (Instance, Code);
if (Cmd == NULL) {
return EFI_INVALID_PARAMETER;
}
GenCSDC (
Cmd->Code,
Cmd->AddrAccess,
Cmd->AddrMode4Byte,
Cmd->HighZ,
Cmd->CsdcTrp,
CSDC
);
NorFlashSetHostCSDC (Instance, Cmd->ReadWrite, CSDC);
return EFI_SUCCESS;
}
STATIC
UINT8
NorFlashReadStatusRegister (
IN NOR_FLASH_INSTANCE *Instance
)
{
UINT8 StatusRegister;
NorFlashSetHostCommand (Instance, SPINOR_OP_RDSR);
StatusRegister = MmioRead8 (Instance->RegionBaseAddress);
NorFlashSetHostCommand (Instance, SPINOR_OP_READ_4B);
return StatusRegister;
}
STATIC
EFI_STATUS
NorFlashWaitProgramErase (
IN NOR_FLASH_INSTANCE *Instance
)
{
BOOLEAN SRegDone;
BOOLEAN FSRegDone;
DEBUG ((DEBUG_BLKIO, "NorFlashWaitProgramErase()\n"));
do {
SRegDone = (NorFlashReadStatusRegister (Instance) & SPINOR_SR_WIP) == 0;
FSRegDone = TRUE;
if (Instance->Flags & NOR_FLASH_POLL_FSR) {
NorFlashSetHostCommand (Instance, SPINOR_OP_RDFSR);
FSRegDone = (MmioRead8 (Instance->RegionBaseAddress) &
SPINOR_FSR_READY) != 0;
}
} while (!SRegDone || !FSRegDone);
NorFlashSetHostCommand (Instance, SPINOR_OP_READ_4B);
return EFI_SUCCESS;
}
// TODO: implement lock checking
STATIC
BOOLEAN
NorFlashBlockIsLocked (
IN NOR_FLASH_INSTANCE *Instance,
IN UINTN BlockAddress
)
{
return FALSE;
}
// TODO: implement sector unlocking
STATIC
EFI_STATUS
NorFlashUnlockSingleBlock (
IN NOR_FLASH_INSTANCE *Instance,
IN UINTN BlockAddress
)
{
return EFI_SUCCESS;
}
STATIC
EFI_STATUS
NorFlashUnlockSingleBlockIfNecessary (
IN NOR_FLASH_INSTANCE *Instance,
IN UINTN BlockAddress
)
{
EFI_STATUS Status;
Status = EFI_SUCCESS;
if (NorFlashBlockIsLocked (Instance, BlockAddress) == TRUE) {
Status = NorFlashUnlockSingleBlock (Instance, BlockAddress);
}
return Status;
}
STATIC
EFI_STATUS
NorFlashEnableWrite (
IN NOR_FLASH_INSTANCE *Instance
)
{
EFI_STATUS Status;
UINT8 StatusRegister;
UINTN Retry;
DEBUG ((DEBUG_BLKIO, "NorFlashEnableWrite()\n"));
Status = EFI_DEVICE_ERROR;
Retry = NOR_FLASH_ERASE_RETRY;
NorFlashSetHostCSDC (Instance, TRUE, mFip006NullCmdSeq);
while (Retry > 0 && EFI_ERROR (Status)) {
MmioWrite8 (Instance->RegionBaseAddress, SPINOR_OP_WREN);
MemoryFence ();
StatusRegister = NorFlashReadStatusRegister (Instance);
Status = (StatusRegister & BIT1) ? EFI_SUCCESS : EFI_DEVICE_ERROR;
Retry--;
}
return Status;
}
STATIC
EFI_STATUS
NorFlashDisableWrite (
IN NOR_FLASH_INSTANCE *Instance
)
{
EFI_STATUS Status;
UINT8 StatusRegister;
UINTN Retry;
DEBUG ((DEBUG_BLKIO, "NorFlashDisableWrite()\n"));
Status = EFI_DEVICE_ERROR;
Retry = NOR_FLASH_ERASE_RETRY;
NorFlashSetHostCSDC (Instance, TRUE, mFip006NullCmdSeq);
while (Retry > 0 && EFI_ERROR (Status)) {
MmioWrite8 (Instance->RegionBaseAddress, SPINOR_OP_WRDIS);
MemoryFence ();
StatusRegister = NorFlashReadStatusRegister (Instance);
Status = (StatusRegister & BIT1) ? EFI_DEVICE_ERROR : EFI_SUCCESS;
Retry--;
}
return Status;
}
/**
* The following function presumes that the block has already been unlocked.
**/
STATIC
EFI_STATUS
NorFlashEraseSingleBlock (
IN NOR_FLASH_INSTANCE *Instance,
IN UINTN BlockAddress
)
{
DEBUG ((DEBUG_BLKIO, "NorFlashEraseSingleBlock(BlockAddress=0x%08x)\n",
BlockAddress));
if (EFI_ERROR (NorFlashEnableWrite (Instance))) {
return EFI_DEVICE_ERROR;
}
//
// The virtual address chosen by the OS may have a different offset modulo
// 16 MB than the physical address, so we need to subtract the region base
// address before we can mask off a block index. Note that the relative
// offset between device base address and region base address may have changed
// as well, so we cannot use the device base address directly.
//
BlockAddress -= Instance->RegionBaseAddress;
BlockAddress += Instance->OffsetLba * Instance->BlockSize;
NorFlashSetHostCommand (Instance, SPINOR_OP_BE_4B);
MmioWrite32 (Instance->DeviceBaseAddress, SwapBytes32 (BlockAddress));
NorFlashWaitProgramErase (Instance);
NorFlashSetHostCSDC (Instance, TRUE, mFip006NullCmdSeq);
if (EFI_ERROR (NorFlashDisableWrite (Instance))) {
return EFI_DEVICE_ERROR;
}
return EFI_SUCCESS;
}
/**
* This function unlock and erase an entire NOR Flash block.
**/
EFI_STATUS
NorFlashUnlockAndEraseSingleBlock (
IN NOR_FLASH_INSTANCE *Instance,
IN UINTN BlockAddress
)
{
EFI_STATUS Status;
UINTN Index;
NOR_FLASH_LOCK_CONTEXT Lock;
NorFlashLock (&Lock);
Index = 0;
// The block erase might fail a first time (SW bug ?). Retry it ...
do {
// Unlock the block if we have to
Status = NorFlashUnlockSingleBlockIfNecessary (Instance, BlockAddress);
if (EFI_ERROR (Status)) {
break;
}
Status = NorFlashEraseSingleBlock (Instance, BlockAddress);
Index++;
} while ((Index < NOR_FLASH_ERASE_RETRY) && (Status == EFI_WRITE_PROTECTED));
if (Index == NOR_FLASH_ERASE_RETRY) {
DEBUG ((DEBUG_ERROR,
"EraseSingleBlock(BlockAddress=0x%08x: Block Locked Error (try to erase %d times)\n",
BlockAddress,Index));
}
NorFlashUnlock (&Lock);
return Status;
}
STATIC
EFI_STATUS
NorFlashWriteSingleWord (
IN NOR_FLASH_INSTANCE *Instance,
IN UINTN WordAddress,
IN UINT32 WriteData
)
{
EFI_STATUS Status;
DEBUG ((DEBUG_BLKIO,
"NorFlashWriteSingleWord(WordAddress=0x%08x, WriteData=0x%08x)\n",
WordAddress, WriteData));
Status = EFI_SUCCESS;
if (EFI_ERROR (NorFlashEnableWrite (Instance))) {
return EFI_DEVICE_ERROR;
}
NorFlashSetHostCommand (Instance, SPINOR_OP_PP_4B);
MmioWrite32 (WordAddress, WriteData);
NorFlashWaitProgramErase (Instance);
NorFlashDisableWrite (Instance);
NorFlashSetHostCSDC (Instance, TRUE, mFip006NullCmdSeq);
return Status;
}
STATIC
EFI_STATUS
NorFlashWriteFullBlock (
IN NOR_FLASH_INSTANCE *Instance,
IN EFI_LBA Lba,
IN UINT32 *DataBuffer,
IN UINT32 BlockSizeInWords
)
{
EFI_STATUS Status;
UINTN WordAddress;
UINT32 WordIndex;
UINTN BlockAddress;
NOR_FLASH_LOCK_CONTEXT Lock;
Status = EFI_SUCCESS;
// Get the physical address of the block
BlockAddress = GET_NOR_BLOCK_ADDRESS (Instance->RegionBaseAddress, Lba,
BlockSizeInWords * 4);
// Start writing from the first address at the start of the block
WordAddress = BlockAddress;
NorFlashLock (&Lock);
Status = NorFlashUnlockAndEraseSingleBlock (Instance, BlockAddress);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR,
"WriteSingleBlock: ERROR - Failed to Unlock and Erase the single block at 0x%X\n",
BlockAddress));
goto EXIT;
}
for (WordIndex=0;
WordIndex < BlockSizeInWords;
WordIndex++, DataBuffer++, WordAddress += 4) {
Status = NorFlashWriteSingleWord (Instance, WordAddress, *DataBuffer);
if (EFI_ERROR (Status)) {
goto EXIT;
}
}
EXIT:
NorFlashUnlock (&Lock);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR,
"NOR FLASH Programming [WriteSingleBlock] failed at address 0x%08x. Exit Status = \"%r\".\n",
WordAddress, Status));
}
return Status;
}
EFI_STATUS
NorFlashWriteBlocks (
IN NOR_FLASH_INSTANCE *Instance,
IN EFI_LBA Lba,
IN UINTN BufferSizeInBytes,
IN VOID *Buffer
)
{
UINT32 *pWriteBuffer;
EFI_STATUS Status = EFI_SUCCESS;
EFI_LBA CurrentBlock;
UINT32 BlockSizeInWords;
UINT32 NumBlocks;
UINT32 BlockCount;
// The buffer must be valid
if (Buffer == NULL) {
return EFI_INVALID_PARAMETER;
}
// We must have some bytes to read
DEBUG ((DEBUG_BLKIO, "NorFlashWriteBlocks: BufferSizeInBytes=0x%x\n",
BufferSizeInBytes));
if (BufferSizeInBytes == 0) {
return EFI_BAD_BUFFER_SIZE;
}
// The size of the buffer must be a multiple of the block size
DEBUG ((DEBUG_BLKIO, "NorFlashWriteBlocks: BlockSize in bytes =0x%x\n",
Instance->BlockSize));
if ((BufferSizeInBytes % Instance->BlockSize) != 0) {
return EFI_BAD_BUFFER_SIZE;
}
// All blocks must be within the device
NumBlocks = ((UINT32)BufferSizeInBytes) / Instance->BlockSize ;
DEBUG ((DEBUG_BLKIO,
"NorFlashWriteBlocks: NumBlocks=%d, LastBlock=%ld, Lba=%ld.\n", NumBlocks,
Instance->LastBlock, Lba));
if ((Lba + NumBlocks) > (Instance->LastBlock + 1)) {
DEBUG ((DEBUG_ERROR,
"NorFlashWriteBlocks: ERROR - Write will exceed last block.\n"));
return EFI_INVALID_PARAMETER;
}
ASSERT (((UINTN)Buffer % sizeof (UINT32)) == 0);
BlockSizeInWords = Instance->BlockSize / 4;
// Because the target *Buffer is a pointer to VOID, we must put
// all the data into a pointer to a proper data type, so use *ReadBuffer
pWriteBuffer = (UINT32 *)Buffer;
CurrentBlock = Lba;
for (BlockCount = 0;
BlockCount < NumBlocks;
BlockCount++, CurrentBlock++, pWriteBuffer += BlockSizeInWords) {
DEBUG ((DEBUG_BLKIO, "NorFlashWriteBlocks: Writing block #%d\n",
(UINTN)CurrentBlock));
Status = NorFlashWriteFullBlock (Instance, CurrentBlock, pWriteBuffer,
BlockSizeInWords);
if (EFI_ERROR (Status)) {
break;
}
}
DEBUG ((DEBUG_BLKIO, "NorFlashWriteBlocks: Exit Status = \"%r\".\n", Status));
return Status;
}
EFI_STATUS
NorFlashReadBlocks (
IN NOR_FLASH_INSTANCE *Instance,
IN EFI_LBA Lba,
IN UINTN BufferSizeInBytes,
OUT VOID *Buffer
)
{
UINT32 NumBlocks;
UINTN StartAddress;
DEBUG ((DEBUG_BLKIO,
"NorFlashReadBlocks: BufferSize=0x%xB BlockSize=0x%xB LastBlock=%ld, Lba=%ld.\n",
BufferSizeInBytes, Instance->BlockSize, Instance->LastBlock,
Lba));
// The buffer must be valid
if (Buffer == NULL) {
return EFI_INVALID_PARAMETER;
}
// Return if we have not any byte to read
if (BufferSizeInBytes == 0) {
return EFI_SUCCESS;
}
// The size of the buffer must be a multiple of the block size
if ((BufferSizeInBytes % Instance->BlockSize) != 0) {
return EFI_BAD_BUFFER_SIZE;
}
// All blocks must be within the device
NumBlocks = ((UINT32)BufferSizeInBytes) / Instance->BlockSize ;
if ((Lba + NumBlocks) > (Instance->LastBlock + 1)) {
DEBUG ((DEBUG_ERROR,
"NorFlashReadBlocks: ERROR - Read will exceed last block\n"));
return EFI_INVALID_PARAMETER;
}
// Get the address to start reading from
StartAddress = GET_NOR_BLOCK_ADDRESS (Instance->RegionBaseAddress, Lba,
Instance->BlockSize);
// Put the device into Read Array mode
NorFlashSetHostCommand (Instance, SPINOR_OP_READ_4B);
NorFlashSetHostCSDC (Instance, TRUE, mFip006NullCmdSeq);
// Readout the data
CopyMem(Buffer, (UINTN *)StartAddress, BufferSizeInBytes);
return EFI_SUCCESS;
}
EFI_STATUS
NorFlashRead (
IN NOR_FLASH_INSTANCE *Instance,
IN EFI_LBA Lba,
IN UINTN Offset,
IN UINTN BufferSizeInBytes,
OUT VOID *Buffer
)
{
UINTN StartAddress;
// The buffer must be valid
if (Buffer == NULL) {
return EFI_INVALID_PARAMETER;
}
// Return if we have not any byte to read
if (BufferSizeInBytes == 0) {
return EFI_SUCCESS;
}
if (((Lba * Instance->BlockSize) + Offset + BufferSizeInBytes) >
Instance->Size) {
DEBUG ((DEBUG_ERROR,
"NorFlashRead: ERROR - Read will exceed device size.\n"));
return EFI_INVALID_PARAMETER;
}
// Get the address to start reading from
StartAddress = GET_NOR_BLOCK_ADDRESS (Instance->RegionBaseAddress, Lba,
Instance->BlockSize);
// Put the device into Read Array mode
NorFlashSetHostCommand (Instance, SPINOR_OP_READ_4B);
NorFlashSetHostCSDC (Instance, TRUE, mFip006NullCmdSeq);
// Readout the data
CopyMem (Buffer, (UINTN *)(StartAddress + Offset), BufferSizeInBytes);
return EFI_SUCCESS;
}
/*
Write a full or portion of a block. It must not span block boundaries;
that is, Offset + *NumBytes <= Instance->BlockSize.
*/
EFI_STATUS
NorFlashWriteSingleBlock (
IN NOR_FLASH_INSTANCE *Instance,
IN EFI_LBA Lba,
IN UINTN Offset,
IN OUT UINTN *NumBytes,
IN UINT8 *Buffer
)
{
EFI_STATUS TempStatus;
UINT32 Tmp;
UINT32 TmpBuf;
UINT32 WordToWrite;
UINT32 Mask;
BOOLEAN DoErase;
UINTN BytesToWrite;
UINTN CurOffset;
UINTN WordAddr;
UINTN BlockSize;
UINTN BlockAddress;
UINTN PrevBlockAddress;
PrevBlockAddress = 0;
if (!Instance->Initialized && Instance->Initialize) {
Instance->Initialize(Instance);
}
DEBUG ((DEBUG_BLKIO,
"NorFlashWriteSingleBlock(Parameters: Lba=%ld, Offset=0x%x, *NumBytes=0x%x, Buffer @ 0x%08x)\n",
Lba, Offset, *NumBytes, Buffer));
// Cache the block size to avoid de-referencing pointers all the time
BlockSize = Instance->BlockSize;
// The write must not span block boundaries.
// We need to check each variable individually because adding two large
// values together overflows.
if (Offset >= BlockSize ||
*NumBytes > BlockSize ||
(Offset + *NumBytes) > BlockSize) {
DEBUG ((DEBUG_ERROR,
"NorFlashWriteSingleBlock: ERROR - EFI_BAD_BUFFER_SIZE: (Offset=0x%x + NumBytes=0x%x) > BlockSize=0x%x\n",
Offset, *NumBytes, BlockSize ));
return EFI_BAD_BUFFER_SIZE;
}
// We must have some bytes to write
if (*NumBytes == 0) {
DEBUG ((DEBUG_ERROR,
"NorFlashWriteSingleBlock: ERROR - EFI_BAD_BUFFER_SIZE: (Offset=0x%x + NumBytes=0x%x) > BlockSize=0x%x\n",
Offset, *NumBytes, BlockSize ));
return EFI_BAD_BUFFER_SIZE;
}
// Pick 128bytes as a good start for word operations as opposed to erasing the
// block and writing the data regardless if an erase is really needed.
// It looks like most individual NV variable writes are smaller than 128bytes.
if (*NumBytes <= 128) {
// Check to see if we need to erase before programming the data into NOR.
// If the destination bits are only changing from 1s to 0s we can just write.
// After a block is erased all bits in the block is set to 1.
// If any byte requires us to erase we just give up and rewrite all of it.
DoErase = FALSE;
BytesToWrite = *NumBytes;
CurOffset = Offset;
while (BytesToWrite > 0) {
// Read full word from NOR, splice as required. A word is the smallest
// unit we can write.
TempStatus = NorFlashRead (Instance, Lba, CurOffset & ~(0x3), sizeof(Tmp),
&Tmp);
if (EFI_ERROR (TempStatus)) {
return EFI_DEVICE_ERROR;
}
// Physical address of word in NOR to write.
WordAddr = (CurOffset & ~(0x3)) +
GET_NOR_BLOCK_ADDRESS (Instance->RegionBaseAddress, Lba,
BlockSize);
// The word of data that is to be written.
TmpBuf = ReadUnaligned32 ((UINT32 *)(Buffer + (*NumBytes - BytesToWrite)));
// First do word aligned chunks.
if ((CurOffset & 0x3) == 0) {
if (BytesToWrite >= 4) {
// Is the destination still in 'erased' state?
if (~Tmp != 0) {
// Check to see if we are only changing bits to zero.
if ((Tmp ^ TmpBuf) & TmpBuf) {
DoErase = TRUE;
break;
}
}
// Write this word to NOR
WordToWrite = TmpBuf;
CurOffset += sizeof(TmpBuf);
BytesToWrite -= sizeof(TmpBuf);
} else {
// BytesToWrite < 4. Do small writes and left-overs
Mask = ~((~0) << (BytesToWrite * 8));
// Mask out the bytes we want.
TmpBuf &= Mask;
// Is the destination still in 'erased' state?
if ((Tmp & Mask) != Mask) {
// Check to see if we are only changing bits to zero.
if ((Tmp ^ TmpBuf) & TmpBuf) {
DoErase = TRUE;
break;
}
}
// Merge old and new data. Write merged word to NOR
WordToWrite = (Tmp & ~Mask) | TmpBuf;
CurOffset += BytesToWrite;
BytesToWrite = 0;
}
} else {
// Do multiple words, but starting unaligned.
if (BytesToWrite > (4 - (CurOffset & 0x3))) {
Mask = ((~0) << ((CurOffset & 0x3) * 8));
// Mask out the bytes we want.
TmpBuf &= Mask;
// Is the destination still in 'erased' state?
if ((Tmp & Mask) != Mask) {
// Check to see if we are only changing bits to zero.
if ((Tmp ^ TmpBuf) & TmpBuf) {
DoErase = TRUE;
break;
}
}
// Merge old and new data. Write merged word to NOR
WordToWrite = (Tmp & ~Mask) | TmpBuf;
BytesToWrite -= (4 - (CurOffset & 0x3));
CurOffset += (4 - (CurOffset & 0x3));
} else {
// Unaligned and fits in one word.
Mask = (~((~0) << (BytesToWrite * 8))) << ((CurOffset & 0x3) * 8);
// Mask out the bytes we want.
TmpBuf = (TmpBuf << ((CurOffset & 0x3) * 8)) & Mask;
// Is the destination still in 'erased' state?
if ((Tmp & Mask) != Mask) {
// Check to see if we are only changing bits to zero.
if ((Tmp ^ TmpBuf) & TmpBuf) {
DoErase = TRUE;
break;
}
}
// Merge old and new data. Write merged word to NOR
WordToWrite = (Tmp & ~Mask) | TmpBuf;
CurOffset += BytesToWrite;
BytesToWrite = 0;
}
}
//
// Write the word to NOR.
//
BlockAddress = GET_NOR_BLOCK_ADDRESS (Instance->RegionBaseAddress, Lba,
BlockSize);
if (BlockAddress != PrevBlockAddress) {
TempStatus = NorFlashUnlockSingleBlockIfNecessary (Instance,
BlockAddress);
if (EFI_ERROR (TempStatus)) {
return EFI_DEVICE_ERROR;
}
PrevBlockAddress = BlockAddress;
}
TempStatus = NorFlashWriteSingleWord (Instance, WordAddr, WordToWrite);
if (EFI_ERROR (TempStatus)) {
return EFI_DEVICE_ERROR;
}
}
// Exit if we got here and could write all the data. Otherwise do the
// Erase-Write cycle.
if (!DoErase) {
return EFI_SUCCESS;
}
}
// Check we did get some memory. Buffer is BlockSize.
if (Instance->ShadowBuffer == NULL) {
DEBUG ((DEBUG_ERROR, "FvbWrite: ERROR - Buffer not ready\n"));
return EFI_DEVICE_ERROR;
}
// Read NOR Flash data into shadow buffer
TempStatus = NorFlashReadBlocks (Instance, Lba, BlockSize,
Instance->ShadowBuffer);
if (EFI_ERROR (TempStatus)) {
// Return one of the pre-approved error statuses
return EFI_DEVICE_ERROR;
}
// Put the data at the appropriate location inside the buffer area
CopyMem ((VOID*)((UINTN)Instance->ShadowBuffer + Offset), Buffer, *NumBytes);
// Write the modified buffer back to the NorFlash
TempStatus = NorFlashWriteBlocks (Instance, Lba, BlockSize,
Instance->ShadowBuffer);
if (EFI_ERROR (TempStatus)) {
// Return one of the pre-approved error statuses
return EFI_DEVICE_ERROR;
}
return EFI_SUCCESS;
}
EFI_STATUS
NorFlashReset (
IN NOR_FLASH_INSTANCE *Instance
)
{
FIP006_CS_CFG CsCfg;
DEBUG ((DEBUG_BLKIO, "NorFlashReset()\n"));
CsCfg.Raw = MmioRead32 (Instance->HostRegisterBaseAddress +
FIP006_REG_CS_CFG);
CsCfg.Reg.MBM = CS_CFG_MBM_SINGLE;
CsCfg.Reg.SRAM = CS_CFG_SRAM_RW;
MmioWrite32 (Instance->HostRegisterBaseAddress + FIP006_REG_CS_CFG,
CsCfg.Raw);
NorFlashSetHostCommand (Instance, SPINOR_OP_READ_4B);
NorFlashSetHostCSDC (Instance, TRUE, mFip006NullCmdSeq);
return EFI_SUCCESS;
}
EFI_STATUS
NorFlashReadID (
IN NOR_FLASH_INSTANCE *Instance,
OUT UINT8 JedecId[3]
)
{
if (Instance == NULL || JedecId == NULL) {
return EFI_INVALID_PARAMETER;
}
NorFlashSetHostCommand (Instance, SPINOR_OP_RDID);
JedecId[0] = MmioRead8 (Instance->DeviceBaseAddress);
JedecId[1] = MmioRead8 (Instance->DeviceBaseAddress + 1);
JedecId[2] = MmioRead8 (Instance->DeviceBaseAddress + 2);
NorFlashSetHostCommand (Instance, SPINOR_OP_READ_4B);
return EFI_SUCCESS;
}
EFI_STATUS
NorFlashPlatformGetDevices (
OUT NOR_FLASH_DESCRIPTION **NorFlashDevices,
OUT UINT32 *Count
)
{
if (NorFlashDevices == NULL ||
Count == NULL) {
return EFI_INVALID_PARAMETER;
}
*Count = ARRAY_SIZE (mNorFlashDevices);
*NorFlashDevices = mNorFlashDevices;
return EFI_SUCCESS;
}