blob: 9a04493895fa1e602a405fd05503e1465a8bc4a0 [file] [log] [blame]
/*******************************************************************************
Copyright (C) 2016 Marvell International Ltd.
Marvell BSD License Option
If you received this File from Marvell, you may opt to use, redistribute and/or
modify this File under the following licensing terms.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Marvell nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/
#include "MvSpiFlash.h"
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 poll_bit = STATUS_REG_POLL_WIP;
UINT8 check_status = 0x0;
CmdStatus = (UINT8)PcdGet32 (PcdSpiFlashPollCmd);
if (CmdStatus == CMD_FLAG_STATUS) {
poll_bit = STATUS_REG_POLL_PEC;
check_status = poll_bit;
}
// 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 & poll_bit) == check_status)
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;
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 AddrSize, EraseAddr;
UINTN EraseSize;
UINT8 Cmd[5];
AddrSize = PcdGet32 (PcdSpiFlashAddressCycles);
EraseSize = PcdGet64 (PcdSpiFlashEraseSize);
// 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;
}
Cmd[0] = CMD_ERASE_64K;
while (Length) {
EraseAddr = Offset;
SpiFlashBank (Slave, EraseAddr);
SpiFlashFormatAddress (EraseAddr, AddrSize, Cmd);
// Programm proper erase address
Status = MvSpiFlashWriteCommon (Slave, Cmd, 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 AddrSize, ReadAddr, ReadLength, RemainLength;
UINTN BankSel = 0;
AddrSize = PcdGet32 (PcdSpiFlashAddressCycles);
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, AddrSize, Cmd);
// Program proper read address and read data
Status = MvSpiFlashReadCmd (Slave, Cmd, AddrSize + 2, Buf, Length);
Offset += ReadLength;
Length -= ReadLength;
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], AddrSize;
AddrSize = PcdGet32 (PcdSpiFlashAddressCycles);
PageSize = PcdGet32 (PcdSpiFlashPageSize);
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, AddrSize, Cmd);
// Program proper write address and write data
Status = MvSpiFlashWriteCommon (Slave, Cmd, AddrSize + 1, 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 EraseSize, ToUpdate, Scale = 1;
UINT8 *TmpBuf, *End;
EraseSize = PcdGet64 (PcdSpiFlashEraseSize);
End = Buf + ByteCount;
TmpBuf = (UINT8 *)AllocateZeroPool (EraseSize);
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), EraseSize);
Print (L" \rUpdating, %d%%", 100 - (End - Buf) / Scale);
Status = MvSpiFlashUpdateBlock (Slave, Offset, ToUpdate, Buf, TmpBuf, EraseSize);
if (EFI_ERROR (Status)) {
DEBUG((DEBUG_ERROR, "SpiFlash: Error while updating\n"));
return Status;
}
}
Print(L"\n");
FreePool (TmpBuf);
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
MvSpiFlashReadId (
IN SPI_DEVICE *SpiDev,
IN UINT32 DataByteCount,
IN OUT UINT8 *Buffer
)
{
EFI_STATUS Status;
UINT8 *DataOut;
DataOut = (UINT8 *) AllocateZeroPool (DataByteCount);
if (DataOut == NULL) {
DEBUG((DEBUG_ERROR, "SpiFlash: Cannot allocate memory\n"));
return EFI_OUT_OF_RESOURCES;
}
Status = SpiMasterProtocol->Transfer (SpiMasterProtocol, SpiDev,
DataByteCount, Buffer, DataOut, SPI_TRANSFER_BEGIN | SPI_TRANSFER_END);
if (EFI_ERROR(Status)) {
FreePool (DataOut);
DEBUG((DEBUG_ERROR, "SpiFlash: Spi transfer error\n"));
return Status;
}
// Bytes 1,2 and 3 contain SPI flash ID
Buffer[0] = DataOut[1];
Buffer[1] = DataOut[2];
Buffer[2] = DataOut[3];
FreePool (DataOut);
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
MvSpiFlashInit (
IN MARVELL_SPI_FLASH_PROTOCOL *This,
IN SPI_DEVICE *Slave
)
{
EFI_STATUS Status;
UINT8 Cmd, StatusRegister;
UINT32 AddrSize;
AddrSize = PcdGet32 (PcdSpiFlashAddressCycles);
if (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;
return EFI_SUCCESS;
}
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 = AllocateZeroPool (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)) {
FreePool (mSpiFlashInstance);
DEBUG((DEBUG_ERROR, "SpiFlash: Cannot install SPI flash protocol\n"));
return EFI_DEVICE_ERROR;
}
return EFI_SUCCESS;
}