/******************************************************************************* Copyright (C) 2016 Marvell International Ltd. Copyright (c) 2020, Arm Limited. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent *******************************************************************************/ #include "MvSpiFlashDxe.h" STATIC EFI_EVENT mMvSpiFlashVirtualAddrChangeEvent; MARVELL_SPI_MASTER_PROTOCOL *SpiMasterProtocol; SPI_FLASH_INSTANCE *mSpiFlashInstance; STATIC VOID SpiFlashFormatAddress ( IN UINT32 Address, IN UINT8 AddrSize, IN OUT UINT8 *Cmd ) { if (AddrSize == 4) { Cmd[1] = Address >> 24; Cmd[2] = Address >> 16; Cmd[3] = Address >> 8; Cmd[4] = Address; } else { Cmd[1] = Address >> 16; Cmd[2] = Address >> 8; Cmd[3] = Address; } } STATIC EFI_STATUS MvSpiFlashReadCmd ( IN SPI_DEVICE *Slave, IN UINT8 *Cmd, IN UINTN CmdSize, OUT UINT8 *DataIn, IN UINTN DataSize ) { EFI_STATUS Status; // Send command and gather response Status = SpiMasterProtocol->ReadWrite (SpiMasterProtocol, Slave, Cmd, CmdSize, NULL, DataIn, DataSize); return Status; } STATIC EFI_STATUS MvSpiFlashWriteEnableCmd ( IN SPI_DEVICE *Slave ) { EFI_STATUS Status; UINT8 CmdEn = CMD_WRITE_ENABLE; // Send write_enable command Status = SpiMasterProtocol->Transfer (SpiMasterProtocol, Slave, 1, &CmdEn, NULL, SPI_TRANSFER_BEGIN | SPI_TRANSFER_END); return Status; } STATIC EFI_STATUS MvSpiFlashWriteCommon ( IN SPI_DEVICE *Slave, IN UINT8 *Cmd, IN UINT32 Length, IN UINT8* Buffer, IN UINT32 BufferLength ) { UINT8 CmdStatus = CMD_READ_STATUS; UINT8 State; UINT32 Counter = 0xFFFFF; UINT8 PollBit = STATUS_REG_POLL_WIP; UINT8 CheckStatus = 0x0; if (Slave->Info->Flags & NOR_FLASH_WRITE_FSR) { CmdStatus = CMD_FLAG_STATUS; PollBit = STATUS_REG_POLL_PEC; CheckStatus = STATUS_REG_POLL_PEC; } // Send command MvSpiFlashWriteEnableCmd (Slave); // Write data SpiMasterProtocol->ReadWrite (SpiMasterProtocol, Slave, Cmd, Length, Buffer, NULL, BufferLength); // Poll status register SpiMasterProtocol->Transfer (SpiMasterProtocol, Slave, 1, &CmdStatus, NULL, SPI_TRANSFER_BEGIN); do { SpiMasterProtocol->Transfer (SpiMasterProtocol, Slave, 1, NULL, &State, 0); Counter--; if ((State & PollBit) == CheckStatus) break; } while (Counter > 0); if (Counter == 0) { DEBUG((DEBUG_ERROR, "SpiFlash: Timeout while writing to spi flash\n")); return EFI_DEVICE_ERROR; } // Deactivate CS SpiMasterProtocol->Transfer (SpiMasterProtocol, Slave, 0, NULL, NULL, SPI_TRANSFER_END); return EFI_SUCCESS; } STATIC VOID SpiFlashCmdBankaddrWrite ( IN SPI_DEVICE *Slave, IN UINT8 BankSel ) { UINT8 Cmd = CMD_BANK_WRITE; /* Update bank selection command for Spansion */ if (Slave->Info->Id[0] == NOR_FLASH_ID_SPANSION) { Cmd = CMD_BANKADDR_BRWR; } MvSpiFlashWriteCommon (Slave, &Cmd, 1, &BankSel, 1); } STATIC UINT8 SpiFlashBank ( IN SPI_DEVICE *Slave, IN UINT32 Offset ) { UINT8 BankSel; BankSel = Offset / SPI_FLASH_16MB_BOUN; SpiFlashCmdBankaddrWrite (Slave, BankSel); return BankSel; } EFI_STATUS MvSpiFlashErase ( IN SPI_DEVICE *Slave, IN UINTN Offset, IN UINTN Length ) { EFI_STATUS Status; UINT32 EraseAddr; UINTN EraseSize; UINT8 Cmd[5]; if (Slave->Info->Flags & NOR_FLASH_ERASE_4K) { Cmd[0] = CMD_ERASE_4K; EraseSize = SIZE_4KB; } else if (Slave->Info->Flags & NOR_FLASH_ERASE_32K) { Cmd[0] = CMD_ERASE_32K; EraseSize = SIZE_32KB; } else { Cmd[0] = CMD_ERASE_64K; EraseSize = Slave->Info->SectorSize; } // Check input parameters if (Offset % EraseSize || Length % EraseSize) { DEBUG((DEBUG_ERROR, "SpiFlash: Either erase offset or length " "is not multiple of erase size\n")); return EFI_DEVICE_ERROR; } while (Length) { EraseAddr = Offset; SpiFlashBank (Slave, EraseAddr); SpiFlashFormatAddress (EraseAddr, Slave->AddrSize, Cmd); // Programm proper erase address Status = MvSpiFlashWriteCommon (Slave, Cmd, Slave->AddrSize + 1, NULL, 0); if (EFI_ERROR (Status)) { DEBUG((DEBUG_ERROR, "SpiFlash: Error while programming target address\n")); return Status; } Offset += EraseSize; Length -= EraseSize; } return EFI_SUCCESS; } EFI_STATUS MvSpiFlashRead ( IN SPI_DEVICE *Slave, IN UINT32 Offset, IN UINTN Length, IN VOID *Buf ) { EFI_STATUS Status = EFI_SUCCESS; UINT8 Cmd[6]; UINT32 ReadAddr, ReadLength, RemainLength; UINTN BankSel = 0; Cmd[0] = CMD_READ_ARRAY_FAST; // Sign end of address with 0 byte Cmd[5] = 0; while (Length) { ReadAddr = Offset; BankSel = SpiFlashBank (Slave, ReadAddr); RemainLength = (SPI_FLASH_16MB_BOUN * (BankSel + 1)) - Offset; if (Length < RemainLength) { ReadLength = Length; } else { ReadLength = RemainLength; } SpiFlashFormatAddress (ReadAddr, Slave->AddrSize, Cmd); // Program proper read address and read data Status = MvSpiFlashReadCmd (Slave, Cmd, Slave->AddrSize + 2, Buf, Length); Offset += ReadLength; Length -= ReadLength; Buf = (VOID*)((UINTN)Buf + ReadLength); } return Status; } EFI_STATUS MvSpiFlashWrite ( IN SPI_DEVICE *Slave, IN UINT32 Offset, IN UINTN Length, IN VOID *Buf ) { EFI_STATUS Status; UINTN ByteAddr, ChunkLength, ActualIndex, PageSize; UINT32 WriteAddr; UINT8 Cmd[5]; PageSize = Slave->Info->PageSize; Cmd[0] = CMD_PAGE_PROGRAM; for (ActualIndex = 0; ActualIndex < Length; ActualIndex += ChunkLength) { WriteAddr = Offset; SpiFlashBank (Slave, WriteAddr); ByteAddr = Offset % PageSize; ChunkLength = MIN(Length - ActualIndex, (UINT64) (PageSize - ByteAddr)); SpiFlashFormatAddress (WriteAddr, Slave->AddrSize, Cmd); // Program proper write address and write data Status = MvSpiFlashWriteCommon ( Slave, Cmd, Slave->AddrSize + 1, (VOID*)((UINTN)Buf + ActualIndex), ChunkLength ); if (EFI_ERROR (Status)) { DEBUG((DEBUG_ERROR, "SpiFlash: Error while programming write address\n")); return Status; } Offset += ChunkLength; } return EFI_SUCCESS; } STATIC EFI_STATUS MvSpiFlashUpdateBlock ( IN SPI_DEVICE *Slave, IN UINT32 Offset, IN UINTN ToUpdate, IN UINT8 *Buf, IN UINT8 *TmpBuf, IN UINTN EraseSize ) { EFI_STATUS Status; // Read backup Status = MvSpiFlashRead (Slave, Offset, EraseSize, TmpBuf); if (EFI_ERROR (Status)) { DEBUG((DEBUG_ERROR, "SpiFlash: Update: Error while reading old data\n")); return Status; } // Erase entire sector Status = MvSpiFlashErase (Slave, Offset, EraseSize); if (EFI_ERROR (Status)) { DEBUG((DEBUG_ERROR, "SpiFlash: Update: Error while erasing block\n")); return Status; } // Write new data MvSpiFlashWrite (Slave, Offset, ToUpdate, Buf); if (EFI_ERROR (Status)) { DEBUG((DEBUG_ERROR, "SpiFlash: Update: Error while writing new data\n")); return Status; } // Write backup if (ToUpdate != EraseSize) { Status = MvSpiFlashWrite (Slave, Offset + ToUpdate, EraseSize - ToUpdate, &TmpBuf[ToUpdate]); if (EFI_ERROR (Status)) { DEBUG((DEBUG_ERROR, "SpiFlash: Update: Error while writing backup\n")); return Status; } } return EFI_SUCCESS; } EFI_STATUS MvSpiFlashUpdate ( IN SPI_DEVICE *Slave, IN UINT32 Offset, IN UINTN ByteCount, IN UINT8 *Buf ) { EFI_STATUS Status; UINT64 SectorSize, ToUpdate, Scale = 1; UINT8 *TmpBuf, *End; SectorSize = Slave->Info->SectorSize; End = Buf + ByteCount; TmpBuf = (UINT8 *)AllocateZeroPool (SectorSize); if (TmpBuf == NULL) { DEBUG((DEBUG_ERROR, "SpiFlash: Cannot allocate memory\n")); return EFI_OUT_OF_RESOURCES; } if (End - Buf >= 200) Scale = (End - Buf) / 100; for (; Buf < End; Buf += ToUpdate, Offset += ToUpdate) { ToUpdate = MIN((UINT64)(End - Buf), SectorSize); Print (L" \rUpdating, %d%%", 100 - (End - Buf) / Scale); Status = MvSpiFlashUpdateBlock (Slave, Offset, ToUpdate, Buf, TmpBuf, SectorSize); if (EFI_ERROR (Status)) { DEBUG((DEBUG_ERROR, "SpiFlash: Error while updating\n")); return Status; } } Print(L"\n"); FreePool (TmpBuf); return EFI_SUCCESS; } EFI_STATUS MvSpiFlashUpdateWithProgress ( IN SPI_DEVICE *Slave, IN UINT32 Offset, IN UINTN ByteCount, IN UINT8 *Buffer, IN EFI_FIRMWARE_MANAGEMENT_UPDATE_IMAGE_PROGRESS Progress, OPTIONAL IN UINTN StartPercentage, IN UINTN EndPercentage ) { EFI_STATUS Status; UINTN SectorSize; UINTN SectorNum; UINTN ToUpdate; UINTN Index; UINT8 *TmpBuf; SectorSize = Slave->Info->SectorSize; SectorNum = (ByteCount / SectorSize) + 1; ToUpdate = SectorSize; TmpBuf = (UINT8 *)AllocateZeroPool (SectorSize); if (TmpBuf == NULL) { DEBUG ((DEBUG_ERROR, "%a: Cannot allocate memory\n", __FUNCTION__)); return EFI_OUT_OF_RESOURCES; } for (Index = 0; Index < SectorNum; Index++) { if (Progress != NULL) { Progress (StartPercentage + ((Index * (EndPercentage - StartPercentage)) / SectorNum)); } // In the last chunk update only an actual number of remaining bytes. if (Index + 1 == SectorNum) { ToUpdate = ByteCount % SectorSize; } Status = MvSpiFlashUpdateBlock (Slave, Offset + Index * SectorSize, ToUpdate, Buffer + Index * SectorSize, TmpBuf, SectorSize); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "%a: Error while updating\n", __FUNCTION__)); return Status; } } FreePool (TmpBuf); if (Progress != NULL) { Progress (EndPercentage); } return EFI_SUCCESS; } EFI_STATUS EFIAPI MvSpiFlashReadId ( IN SPI_DEVICE *SpiDev, IN BOOLEAN UseInRuntime ) { EFI_STATUS Status; UINT8 Id[NOR_FLASH_MAX_ID_LEN]; UINT8 Cmd; Cmd = CMD_READ_ID; Status = SpiMasterProtocol->ReadWrite (SpiMasterProtocol, SpiDev, &Cmd, SPI_CMD_LEN, NULL, Id, NOR_FLASH_MAX_ID_LEN); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "ReadId: Spi transfer error\n")); return Status; } Status = NorFlashGetInfo (Id, &SpiDev->Info, UseInRuntime); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "%a: Unrecognized JEDEC Id bytes: 0x%02x%02x%02x\n", __FUNCTION__, Id[0], Id[1], Id[2])); return Status; } NorFlashPrintInfo (SpiDev->Info); return EFI_SUCCESS; } EFI_STATUS EFIAPI MvSpiFlashInit ( IN MARVELL_SPI_FLASH_PROTOCOL *This, IN SPI_DEVICE *Slave ) { EFI_STATUS Status; UINT8 Cmd, StatusRegister; if (Slave->Info->Flags & NOR_FLASH_4B_ADDR) { Slave->AddrSize = 4; } else { Slave->AddrSize = 3; } if (Slave->AddrSize == 4) { // Set 4 byte address mode Status = MvSpiFlashWriteEnableCmd (Slave); if (EFI_ERROR (Status)) { DEBUG((DEBUG_ERROR, "SpiFlash: Error while setting write_enable\n")); return Status; } Cmd = CMD_4B_ADDR_ENABLE; Status = SpiMasterProtocol->Transfer (SpiMasterProtocol, Slave, 1, &Cmd, NULL, SPI_TRANSFER_BEGIN | SPI_TRANSFER_END); if (EFI_ERROR (Status)) { DEBUG((DEBUG_ERROR, "SpiFlash: Error while setting 4B address\n")); return Status; } } // Write flash status register Status = MvSpiFlashWriteEnableCmd (Slave); if (EFI_ERROR (Status)) { DEBUG((DEBUG_ERROR, "SpiFlash: Error while setting write_enable\n")); return Status; } Cmd = CMD_WRITE_STATUS_REG; StatusRegister = 0x0; Status = SpiMasterProtocol->ReadWrite (SpiMasterProtocol, Slave, &Cmd, 1, &StatusRegister, NULL, 1); if (EFI_ERROR (Status)) { DEBUG((DEBUG_ERROR, "SpiFlash: Error with spi transfer\n")); return Status; } return EFI_SUCCESS; } EFI_STATUS MvSpiFlashInitProtocol ( IN MARVELL_SPI_FLASH_PROTOCOL *SpiFlashProtocol ) { SpiFlashProtocol->Init = MvSpiFlashInit; SpiFlashProtocol->ReadId = MvSpiFlashReadId; SpiFlashProtocol->Read = MvSpiFlashRead; SpiFlashProtocol->Write = MvSpiFlashWrite; SpiFlashProtocol->Erase = MvSpiFlashErase; SpiFlashProtocol->Update = MvSpiFlashUpdate; SpiFlashProtocol->UpdateWithProgress = MvSpiFlashUpdateWithProgress; return EFI_SUCCESS; } /** Fixup internal data so that EFI can be call in virtual mode. Call the passed in Child Notify event and convert any pointers in lib to virtual mode. @param[in] Event The Event that is being processed @param[in] Context Event Context **/ STATIC VOID EFIAPI MvSpiFlashVirtualNotifyEvent ( IN EFI_EVENT Event, IN VOID *Context ) { // // Convert SpiMasterProtocol callbacks in MvSpiFlashErase and // MvSpiFlashWrite required by runtime variable support. // EfiConvertPointer (0x0, (VOID**)&SpiMasterProtocol->ReadWrite); EfiConvertPointer (0x0, (VOID**)&SpiMasterProtocol->Transfer); EfiConvertPointer (0x0, (VOID**)&SpiMasterProtocol); return; } EFI_STATUS EFIAPI MvSpiFlashEntryPoint ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; Status = gBS->LocateProtocol ( &gMarvellSpiMasterProtocolGuid, NULL, (VOID **)&SpiMasterProtocol ); if (EFI_ERROR (Status)) { DEBUG((DEBUG_ERROR, "SpiFlash: Cannot locate SPI Master protocol\n")); return EFI_DEVICE_ERROR; } mSpiFlashInstance = AllocateRuntimeZeroPool (sizeof (SPI_FLASH_INSTANCE)); if (mSpiFlashInstance == NULL) { DEBUG((DEBUG_ERROR, "SpiFlash: Cannot allocate memory\n")); return EFI_OUT_OF_RESOURCES; } MvSpiFlashInitProtocol (&mSpiFlashInstance->SpiFlashProtocol); mSpiFlashInstance->Signature = SPI_FLASH_SIGNATURE; Status = gBS->InstallMultipleProtocolInterfaces ( &(mSpiFlashInstance->Handle), &gMarvellSpiFlashProtocolGuid, &(mSpiFlashInstance->SpiFlashProtocol), NULL ); if (EFI_ERROR (Status)) { DEBUG((DEBUG_ERROR, "SpiFlash: Cannot install SPI flash protocol\n")); goto ErrorInstallProto; } // // Register for the virtual address change event // Status = gBS->CreateEventEx (EVT_NOTIFY_SIGNAL, TPL_NOTIFY, MvSpiFlashVirtualNotifyEvent, NULL, &gEfiEventVirtualAddressChangeGuid, &mMvSpiFlashVirtualAddrChangeEvent); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "%a: Failed to register VA change event\n", __FUNCTION__)); goto ErrorCreateEvent; } return EFI_SUCCESS; ErrorCreateEvent: gBS->UninstallMultipleProtocolInterfaces (&mSpiFlashInstance->Handle, &gMarvellSpiFlashProtocolGuid, NULL); ErrorInstallProto: FreePool (mSpiFlashInstance); return EFI_SUCCESS; }