/** @file
  SPI NOR Flash operation functions.

  Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved.

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <Base.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/TimerLib.h>
#include <Protocol/SpiConfiguration.h>
#include <Protocol/SpiIo.h>
#include <IndustryStandard/SpiNorFlashJedecSfdp.h>
#include "SpiNorFlash.h"

/**
  Fill Write Buffer with Opcode, Address, Dummy Bytes, and Data.

  @param[in]    Instance               The instance of SPI_NOR_FLASH
  @param[in]    Opcode                 Opcode for transaction
  @param[in]    DummyBytes             The dummy bytes send to SPI flash device
  @param[in]    AddressBytesSupported  Bytes of address supported by SPI flash device
  @param[in]    UseAddress             Send the address for SPI flash command
  @param[in]    Address                SPI Offset Start Address
  @param[in]    WriteBytes             Number of bytes to write to SPI device
  @param[in]    WriteBuffer            Buffer containing bytes to write to SPI device

  @retval       Size of Data in Buffer
**/
UINT32
FillWriteBuffer (
  IN      SPI_NOR_FLASH_INSTANCE  *Instance,
  IN      UINT8                   Opcode,
  IN      UINT32                  DummyBytes,
  IN      UINT8                   AddressBytesSupported,
  IN      BOOLEAN                 UseAddress,
  IN      UINT32                  Address,
  IN      UINT32                  WriteBytes,
  IN      UINT8                   *WriteBuffer
  )
{
  UINT32  AddressSize;
  UINT32  BigEndianAddress;
  UINT32  Index;
  UINT8   SfdpAddressBytes;

  if ((Instance == NULL) || ((WriteBytes != 0) && (WriteBuffer == NULL))) {
    ASSERT (Instance != NULL);
    ASSERT (WriteBytes == 0 || WriteBuffer != NULL);
    return 0;
  }

  if (Instance->SfdpBasicFlash == NULL) {
    SfdpAddressBytes = 0;
  } else {
    SfdpAddressBytes = (UINT8)Instance->SfdpBasicFlash->AddressBytes;
  }

  // Copy Opcode into Write Buffer
  Instance->SpiTransactionWriteBuffer[0] = Opcode;
  Index                                  = 1;
  AddressSize                            = 0;
  if (UseAddress) {
    if (AddressBytesSupported == SPI_ADDR_3BYTE_ONLY) {
      if (SfdpAddressBytes != 0) {
        // Check if the supported address length is already initiated.
        if ((SfdpAddressBytes != SPI_ADDR_3BYTE_ONLY) && (SfdpAddressBytes != SPI_ADDR_3OR4BYTE)) {
          DEBUG ((DEBUG_ERROR, "%a: Unsupported Address Bytes: 0x%x, SFDP is: 0x%x\n", __func__, AddressBytesSupported, SfdpAddressBytes));
          ASSERT (FALSE);
        }
      }

      AddressSize = 3;
    } else if (AddressBytesSupported == SPI_ADDR_4BYTE_ONLY) {
      if (SfdpAddressBytes != 0) {
        // Check if the supported address length is already initiated.
        if ((SfdpAddressBytes != SPI_ADDR_4BYTE_ONLY) && (SfdpAddressBytes != SPI_ADDR_3OR4BYTE)) {
          DEBUG ((DEBUG_ERROR, "%a: Unsupported Address Bytes: 0x%x, SFDP is: 0x%x\n", __func__, AddressBytesSupported, SfdpAddressBytes));
          ASSERT (FALSE);
        }
      }

      AddressSize = 4;
    } else if (AddressBytesSupported == SPI_ADDR_3OR4BYTE) {
      if (SfdpAddressBytes != 0) {
        // Check if the supported address length is already initiated.
        if (SfdpAddressBytes != SPI_ADDR_3OR4BYTE) {
          DEBUG ((DEBUG_ERROR, "%a: Unsupported Address Bytes: 0x%x, SFDP is: 0x%x\n", __func__, AddressBytesSupported, SfdpAddressBytes));
          ASSERT (FALSE);
        }
      }

      if (Instance->Protocol.FlashSize <= SIZE_16MB) {
        AddressSize = 3;
      } else {
        // SPI part is > 16MB use 4-byte addressing.
        AddressSize = 4;
      }
    } else {
      DEBUG ((DEBUG_ERROR, "%a: Invalid Address Bytes\n", __func__));
      ASSERT (FALSE);
    }

    BigEndianAddress   = SwapBytes32 ((UINT32)Address);
    BigEndianAddress >>= ((sizeof (UINT32) - AddressSize) * 8);
    CopyMem (
      &Instance->SpiTransactionWriteBuffer[Index],
      &BigEndianAddress,
      AddressSize
      );
    Index += AddressSize;
  }

  if (SfdpAddressBytes == SPI_ADDR_3OR4BYTE) {
    //
    // TODO:
    // We may need to enter/exit 4-Byte mode if SPI flash
    // device is currently operated in 3-Bytes mode.
    //
  }

  // Fill DummyBytes
  if (DummyBytes != 0) {
    SetMem (
      &Instance->SpiTransactionWriteBuffer[Index],
      DummyBytes,
      0
      );
    Index += DummyBytes;
  }

  // Fill Data
  if (WriteBytes > 0) {
    CopyMem (
      &Instance->SpiTransactionWriteBuffer[Index],
      WriteBuffer,
      WriteBytes
      );
    Index += WriteBytes;
  }

  return Index;
}

/**
  Internal Read the flash status register.

  This routine reads the flash part status register.

  @param[in]  Instance       SPI_NOR_FLASH_INSTANCE
                             structure.
  @param[in]  LengthInBytes  Number of status bytes to read.
  @param[out] FlashStatus    Pointer to a buffer to receive the flash status.

  @retval EFI_SUCCESS  The status register was read successfully.

**/
EFI_STATUS
EFIAPI
InternalReadStatus (
  IN                SPI_NOR_FLASH_INSTANCE  *Instance,
  IN  UINT32                                LengthInBytes,
  OUT UINT8                                 *FlashStatus
  )
{
  EFI_STATUS  Status;
  UINT32      TransactionBufferLength;

  // Read Status register
  TransactionBufferLength = FillWriteBuffer (
                              Instance,
                              SPI_FLASH_RDSR,
                              SPI_FLASH_RDSR_DUMMY,
                              SPI_FLASH_RDSR_ADDR_BYTES,
                              FALSE,
                              0,
                              0,
                              NULL
                              );
  Status = Instance->SpiIo->Transaction (
                              Instance->SpiIo,
                              SPI_TRANSACTION_WRITE_THEN_READ,
                              FALSE,
                              0,
                              1,
                              8,
                              TransactionBufferLength,
                              Instance->SpiTransactionWriteBuffer,
                              1,
                              FlashStatus
                              );
  ASSERT_EFI_ERROR (Status);
  return Status;
}

/**
  Set Write Enable Latch.

  @param[in]  Instance          SPI NOR instance with all protocols, etc.

  @retval EFI_SUCCESS           SPI Write Enable succeeded
  @retval EFI_DEVICE_ERROR      SPI Flash part did not respond properly
**/
EFI_STATUS
SetWel (
  IN      SPI_NOR_FLASH_INSTANCE  *Instance
  )
{
  EFI_STATUS  Status;
  UINT32      TransactionBufferLength;

  TransactionBufferLength = FillWriteBuffer (
                              Instance,
                              Instance->WriteEnableLatchCommand,
                              SPI_FLASH_WREN_DUMMY,
                              SPI_FLASH_WREN_ADDR_BYTES,
                              FALSE,
                              0,
                              0,
                              NULL
                              );
  Status = Instance->SpiIo->Transaction (
                              Instance->SpiIo,
                              SPI_TRANSACTION_WRITE_ONLY,
                              FALSE,
                              0,
                              1,
                              8,
                              TransactionBufferLength,
                              Instance->SpiTransactionWriteBuffer,
                              0,
                              NULL
                              );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a: Set WEL fail.\n", __func__));
    ASSERT (FALSE);
  }

  return Status;
}

/**
  Check for not device write in progress.

  @param[in]  SpiNorFlashInstance  SPI NOR instance with all protocols, etc.
  @param[in]  Timeout              Timeout in microsecond
  @param[in]  RetryCount           The retry count

  @retval EFI_SUCCESS           Device does not have a write in progress
  @retval EFI_DEVICE_ERROR      SPI Flash part did not respond properly
**/
EFI_STATUS
WaitNotWip (
  IN      SPI_NOR_FLASH_INSTANCE  *SpiNorFlashInstance,
  IN      UINT32                  Timeout,
  IN      UINT32                  RetryCount
  )
{
  EFI_STATUS  Status;
  UINT8       DeviceStatus;
  UINT32      AlreadyDelayedInMicroseconds;

  if (Timeout == 0) {
    return EFI_SUCCESS;
  }

  if (RetryCount == 0) {
    RetryCount = 1;
  }

  do {
    AlreadyDelayedInMicroseconds = 0;
    while (AlreadyDelayedInMicroseconds < Timeout) {
      Status = InternalReadStatus (SpiNorFlashInstance, 1, &DeviceStatus);
      if (EFI_ERROR (Status)) {
        DEBUG ((DEBUG_ERROR, "%a: Read status error\n", __func__));
        ASSERT (FALSE);
        return Status;
      }

      if ((DeviceStatus & SPI_FLASH_SR_WIP) == SPI_FLASH_SR_NOT_WIP) {
        return Status;
      }

      MicroSecondDelay (FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds));
      AlreadyDelayedInMicroseconds += FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds);
    }

    RetryCount--;
  } while (RetryCount > 0);

  DEBUG ((DEBUG_ERROR, "%a: Timeout error\n", __func__));
  return EFI_DEVICE_ERROR;
}

/**
  Check for write enable latch set and not device write in progress.

  @param[in]  SpiNorFlashInstance  SPI NOR instance with all protocols, etc.
  @param[in]  Timeout              Timeout in microsecond
  @param[in]  RetryCount           The retry count

  @retval EFI_SUCCESS           Device does not have a write in progress and
                                write enable latch is set
  @retval EFI_DEVICE_ERROR      SPI Flash part did not respond properly
**/
EFI_STATUS
WaitWelNotWip (
  IN      SPI_NOR_FLASH_INSTANCE  *SpiNorFlashInstance,
  IN      UINT32                  Timeout,
  IN      UINT32                  RetryCount
  )
{
  EFI_STATUS  Status;
  UINT8       DeviceStatus;
  UINT32      AlreadyDelayedInMicroseconds;

  if (Timeout == 0) {
    return EFI_SUCCESS;
  }

  if (RetryCount == 0) {
    RetryCount = 1;
  }

  do {
    AlreadyDelayedInMicroseconds = 0;
    while (AlreadyDelayedInMicroseconds < Timeout) {
      Status = InternalReadStatus (SpiNorFlashInstance, 1, &DeviceStatus);
      if (EFI_ERROR (Status)) {
        DEBUG ((DEBUG_ERROR, "%a: Fail to read WEL.\n", __func__));
        ASSERT_EFI_ERROR (Status);
        return Status;
      }

      if ((DeviceStatus & (SPI_FLASH_SR_WIP | SPI_FLASH_SR_WEL)) == SPI_FLASH_SR_WEL) {
        return Status;
      }

      MicroSecondDelay (FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds));
      AlreadyDelayedInMicroseconds += FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds);
    }

    RetryCount--;
  } while (RetryCount > 0);

  DEBUG ((DEBUG_ERROR, "%a: Timeout error\n", __func__));
  return EFI_DEVICE_ERROR;
}

/**
  Check for not write enable latch set and not device write in progress.

  @param[in]  SpiNorFlashInstance  SPI NOR instance with all protocols, etc.
  @param[in]  Timeout              Timeout in microsecond
  @param[in]  RetryCount           The retry count

  @retval EFI_SUCCESS           Device does not have a write in progress and
                                write enable latch is not set
  @retval EFI_DEVICE_ERROR      SPI Flash part did not respond properly
**/
EFI_STATUS
WaitNotWelNotWip (
  IN      SPI_NOR_FLASH_INSTANCE  *SpiNorFlashInstance,
  IN      UINT32                  Timeout,
  IN      UINT32                  RetryCount
  )
{
  EFI_STATUS  Status;
  UINT8       DeviceStatus;
  UINT32      AlreadyDelayedInMicroseconds;

  if (Timeout == 0) {
    return EFI_SUCCESS;
  }

  if (RetryCount == 0) {
    RetryCount = 1;
  }

  do {
    AlreadyDelayedInMicroseconds = 0;
    while (AlreadyDelayedInMicroseconds < Timeout) {
      Status = InternalReadStatus (SpiNorFlashInstance, 1, &DeviceStatus);
      ASSERT_EFI_ERROR (Status);
      if (EFI_ERROR (Status) ||
          ((DeviceStatus & (SPI_FLASH_SR_WIP | SPI_FLASH_SR_WEL)) == SPI_FLASH_SR_NOT_WIP))
      {
        return Status;
      }

      MicroSecondDelay (FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds));
      AlreadyDelayedInMicroseconds += FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds);
    }

    RetryCount--;
  } while (RetryCount > 0);

  DEBUG ((DEBUG_ERROR, "SpiNorFlash:%a: Timeout error\n", __func__));
  return EFI_DEVICE_ERROR;
}

/**
  Read the 3 byte manufacture and device ID from the SPI flash.

  This routine must be called at or below TPL_NOTIFY.
  This routine reads the 3 byte manufacture and device ID from the flash part
  filling the buffer provided.

  @param[in]  This    Pointer to an EFI_SPI_NOR_FLASH_PROTOCOL data structure.
  @param[out] Buffer  Pointer to a 3 byte buffer to receive the manufacture and
                      device ID.

  @retval EFI_SUCCESS            The manufacture and device ID was read
                                 successfully.
  @retval EFI_INVALID_PARAMETER  Buffer is NULL
  @retval EFI_DEVICE_ERROR       Invalid data received from SPI flash part.

**/
EFI_STATUS
EFIAPI
GetFlashId (
  IN  CONST EFI_SPI_NOR_FLASH_PROTOCOL  *This,
  OUT UINT8                             *Buffer
  )
{
  EFI_STATUS              Status;
  SPI_NOR_FLASH_INSTANCE  *Instance;
  UINT32                  TransactionBufferLength;

  DEBUG ((DEBUG_INFO, "%a: Entry\n", __func__));

  if (Buffer == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Instance = SPI_NOR_FLASH_FROM_THIS (This);

  // Check not WIP
  Status = WaitNotWip (Instance, FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds), FixedPcdGet32 (PcdSpiNorFlashFixedTimeoutRetryCount));

  if (!EFI_ERROR (Status)) {
    TransactionBufferLength = FillWriteBuffer (
                                Instance,
                                SPI_FLASH_RDID,
                                SPI_FLASH_RDID_DUMMY,
                                SPI_FLASH_RDID_ADDR_BYTES,
                                FALSE,
                                0,
                                0,
                                NULL
                                );
    Status = Instance->SpiIo->Transaction (
                                Instance->SpiIo,
                                SPI_TRANSACTION_WRITE_THEN_READ,
                                FALSE,
                                0,
                                1,
                                8,
                                TransactionBufferLength,
                                Instance->SpiTransactionWriteBuffer,
                                3,
                                Buffer
                                );
    ASSERT_EFI_ERROR (Status);
  }

  return Status;
}

/**
  Read data from the SPI flash at not fast speed.

  This routine must be called at or below TPL_NOTIFY.
  This routine reads data from the SPI part in the buffer provided.

  @param[in]  This           Pointer to an EFI_SPI_NOR_FLASH_PROTOCOL data
                             structure.
  @param[in]  FlashAddress   Address in the flash to start reading
  @param[in]  LengthInBytes  Read length in bytes
  @param[out] Buffer         Address of a buffer to receive the data

  @retval EFI_SUCCESS            The data was read successfully.
  @retval EFI_INVALID_PARAMETER  Buffer is NULL, or
                                 FlashAddress >= This->FlashSize, or
                                 LengthInBytes > This->FlashSize - FlashAddress

**/
EFI_STATUS
EFIAPI
LfReadData (
  IN  CONST EFI_SPI_NOR_FLASH_PROTOCOL  *This,
  IN  UINT32                            FlashAddress,
  IN  UINT32                            LengthInBytes,
  OUT UINT8                             *Buffer
  )
{
  EFI_STATUS              Status;
  SPI_NOR_FLASH_INSTANCE  *Instance;
  UINT32                  ByteCounter;
  UINT32                  CurrentAddress;
  UINT8                   *CurrentBuffer;
  UINT32                  Length;
  UINT32                  TransactionBufferLength;
  UINT32                  MaximumTransferBytes;

  DEBUG ((DEBUG_INFO, "%a: Entry\n", __func__));

  Status = EFI_DEVICE_ERROR;
  if ((Buffer == NULL) ||
      (FlashAddress >= This->FlashSize) ||
      (LengthInBytes > This->FlashSize - FlashAddress))
  {
    return EFI_INVALID_PARAMETER;
  }

  Instance             = SPI_NOR_FLASH_FROM_THIS (This);
  MaximumTransferBytes = Instance->SpiIo->MaximumTransferBytes;

  CurrentBuffer = Buffer;
  Length        = 0;
  for (ByteCounter = 0; ByteCounter < LengthInBytes;) {
    CurrentAddress = FlashAddress + ByteCounter;
    CurrentBuffer  = Buffer + ByteCounter;
    Length         = LengthInBytes - ByteCounter;
    // Length must be MaximumTransferBytes or less
    if (Length > MaximumTransferBytes) {
      Length = MaximumTransferBytes;
    }

    // Check not WIP
    Status = WaitNotWip (Instance, FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds), FixedPcdGet32 (PcdSpiNorFlashFixedTimeoutRetryCount));
    if (EFI_ERROR (Status)) {
      break;
    }

    TransactionBufferLength = FillWriteBuffer (
                                Instance,
                                SPI_FLASH_READ,
                                SPI_FLASH_READ_DUMMY,
                                SPI_FLASH_READ_ADDR_BYTES,
                                TRUE,
                                CurrentAddress,
                                0,
                                NULL
                                );
    Status = Instance->SpiIo->Transaction (
                                Instance->SpiIo,
                                SPI_TRANSACTION_WRITE_THEN_READ,
                                FALSE,
                                0,
                                1,
                                8,
                                TransactionBufferLength,
                                Instance->SpiTransactionWriteBuffer,
                                Length,
                                CurrentBuffer
                                );
    ASSERT_EFI_ERROR (Status);
    ByteCounter += Length;
  }

  return Status;
}

/**
  Read data from the SPI flash.

  This routine must be called at or below TPL_NOTIFY.
  This routine reads data from the SPI part in the buffer provided.

  @param[in]  This           Pointer to an EFI_SPI_NOR_FLASH_PROTOCOL data
                             structure.
  @param[in]  FlashAddress   Address in the flash to start reading
  @param[in]  LengthInBytes  Read length in bytes
  @param[out] Buffer         Address of a buffer to receive the data

  @retval EFI_SUCCESS            The data was read successfully.
  @retval EFI_INVALID_PARAMETER  Buffer is NULL, or
                                 FlashAddress >= This->FlashSize, or
                                 LengthInBytes > This->FlashSize - FlashAddress

**/
EFI_STATUS
EFIAPI
ReadData (
  IN  CONST EFI_SPI_NOR_FLASH_PROTOCOL  *This,
  IN  UINT32                            FlashAddress,
  IN  UINT32                            LengthInBytes,
  OUT UINT8                             *Buffer
  )
{
  EFI_STATUS              Status;
  SPI_NOR_FLASH_INSTANCE  *Instance;
  UINT32                  ByteCounter;
  UINT32                  CurrentAddress;
  UINT8                   *CurrentBuffer;
  UINT32                  Length;
  UINT32                  TransactionBufferLength;
  UINT32                  MaximumTransferBytes;
  UINT8                   FastReadInstruction;
  UINT8                   FastReadWaitStateDummyClocks;
  UINT8                   FastReadModeClock;

  DEBUG ((DEBUG_INFO, "%a: Entry, Read address = 0x%08x, Length = 0x%08x\n", __func__, FlashAddress, LengthInBytes));

  Status = EFI_DEVICE_ERROR;
  if ((Buffer == NULL) ||
      (FlashAddress >= This->FlashSize) ||
      (LengthInBytes > This->FlashSize - FlashAddress))
  {
    return EFI_INVALID_PARAMETER;
  }

  Instance             = SPI_NOR_FLASH_FROM_THIS (This);
  MaximumTransferBytes = Instance->SpiIo->MaximumTransferBytes;

  //
  // Initial the default read operation parameters.
  //
  FastReadInstruction          = SPI_FLASH_FAST_READ;
  FastReadWaitStateDummyClocks = SPI_FLASH_FAST_READ_DUMMY * 8;
  FastReadModeClock            = 0;
  //
  // Override by the Fast Read capabiity table.
  //
  // Get the first supported fast read comamnd.
  // This will be the standard fast read command (0x0b),
  // which is the first fast read command added to the
  // supported list.
  // TODO: The mechanism to choose the advanced fast read
  //       is not determined yet in this version of
  //       SpiNorFlash driver.
  Status = GetFastReadParameter (
             Instance,
             &FastReadInstruction,
             &FastReadModeClock,
             &FastReadWaitStateDummyClocks
             );
  if (!EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VERBOSE, "  Use below Fast Read mode:\n"));
  } else {
    DEBUG ((DEBUG_VERBOSE, "  Use the default Fast Read mode:\n"));
  }

  DEBUG ((DEBUG_VERBOSE, "    Instruction                        : 0x%x\n", FastReadInstruction));
  DEBUG ((DEBUG_VERBOSE, "    Mode Clock                         : 0x%x\n", FastReadModeClock));
  DEBUG ((DEBUG_VERBOSE, "    Wait States (Dummy Clocks) in clock: 0x%x\n", FastReadWaitStateDummyClocks));
  DEBUG ((DEBUG_VERBOSE, "     Supported erase address bytes by device: 0x%02x.\n", Instance->SfdpBasicFlash->AddressBytes));
  DEBUG ((DEBUG_VERBOSE, "        (00: 3-Byte, 01: 3 or 4-Byte. 10: 4-Byte)\n"));

  CurrentBuffer = Buffer;
  Length        = 0;
  for (ByteCounter = 0; ByteCounter < LengthInBytes;) {
    CurrentAddress = FlashAddress + ByteCounter;
    CurrentBuffer  = Buffer + ByteCounter;
    Length         = LengthInBytes - ByteCounter;
    // Length must be MaximumTransferBytes or less
    if (Length > MaximumTransferBytes) {
      Length = MaximumTransferBytes;
    }

    // Check not WIP
    Status = WaitNotWip (Instance, FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds), FixedPcdGet32 (PcdSpiNorFlashFixedTimeoutRetryCount));
    if (EFI_ERROR (Status)) {
      break;
    }

    TransactionBufferLength = FillWriteBuffer (
                                Instance,
                                FastReadInstruction,
                                FastReadWaitStateDummyClocks / 8,
                                (UINT8)Instance->SfdpBasicFlash->AddressBytes,
                                TRUE,
                                CurrentAddress,
                                0,
                                NULL
                                );
    Status = Instance->SpiIo->Transaction (
                                Instance->SpiIo,
                                SPI_TRANSACTION_WRITE_THEN_READ,
                                FALSE,
                                0,
                                1,
                                8,
                                TransactionBufferLength,
                                Instance->SpiTransactionWriteBuffer,
                                Length,
                                CurrentBuffer
                                );
    ASSERT_EFI_ERROR (Status);
    ByteCounter += Length;
  }

  return Status;
}

/**
  Read the flash status register.

  This routine must be called at or below TPL_NOTIFY.
  This routine reads the flash part status register.

  @param[in]  This           Pointer to an EFI_SPI_NOR_FLASH_PROTOCOL data
                             structure.
  @param[in]  LengthInBytes  Number of status bytes to read.
  @param[out] FlashStatus    Pointer to a buffer to receive the flash status.

  @retval EFI_SUCCESS  The status register was read successfully.

**/
EFI_STATUS
EFIAPI
ReadStatus (
  IN  CONST EFI_SPI_NOR_FLASH_PROTOCOL  *This,
  IN  UINT32                            LengthInBytes,
  OUT UINT8                             *FlashStatus
  )
{
  EFI_STATUS              Status;
  SPI_NOR_FLASH_INSTANCE  *Instance;

  if (LengthInBytes != 1) {
    return EFI_INVALID_PARAMETER;
  }

  Instance = SPI_NOR_FLASH_FROM_THIS (This);

  Status = InternalReadStatus (Instance, LengthInBytes, FlashStatus);

  return Status;
}

/**
  Write the flash status register.

  This routine must be called at or below TPL_N OTIFY.
  This routine writes the flash part status register.

  @param[in] This           Pointer to an EFI_SPI_NOR_FLASH_PROTOCOL data
                            structure.
  @param[in] LengthInBytes  Number of status bytes to write.
  @param[in] FlashStatus    Pointer to a buffer containing the new status.

  @retval EFI_SUCCESS           The status write was successful.
  @retval EFI_OUT_OF_RESOURCES  Failed to allocate the write buffer.

**/
EFI_STATUS
EFIAPI
WriteStatus (
  IN CONST EFI_SPI_NOR_FLASH_PROTOCOL  *This,
  IN UINT32                            LengthInBytes,
  IN UINT8                             *FlashStatus
  )
{
  EFI_STATUS              Status;
  SPI_NOR_FLASH_INSTANCE  *Instance;
  UINT32                  TransactionBufferLength;

  if (LengthInBytes != 1) {
    return EFI_INVALID_PARAMETER;
  }

  Instance = SPI_NOR_FLASH_FROM_THIS (This);

  // Check not WIP
  Status = WaitNotWip (Instance, FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds), FixedPcdGet32 (PcdSpiNorFlashFixedTimeoutRetryCount));

  // Set Write Enable
  if (!EFI_ERROR (Status)) {
    if (Instance->WriteEnableLatchRequired) {
      Status = SetWel (Instance);
      DEBUG ((DEBUG_ERROR, "%a: set Write Enable Error.\n", __func__));
      ASSERT_EFI_ERROR (Status);
      // Check not WIP & WEL enabled
      Status = WaitWelNotWip (Instance, FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds), FixedPcdGet32 (PcdSpiNorFlashFixedTimeoutRetryCount));
    }

    // Write the Status Register
    if (!EFI_ERROR (Status)) {
      TransactionBufferLength = FillWriteBuffer (
                                  Instance,
                                  SPI_FLASH_WRSR,
                                  SPI_FLASH_WRSR_DUMMY,
                                  SPI_FLASH_WRSR_ADDR_BYTES,
                                  FALSE,
                                  0,
                                  0,
                                  NULL
                                  );
      Status = Instance->SpiIo->Transaction (
                                  Instance->SpiIo,
                                  SPI_TRANSACTION_WRITE_ONLY,
                                  FALSE,
                                  0,
                                  1,
                                  8,
                                  TransactionBufferLength,
                                  Instance->SpiTransactionWriteBuffer,
                                  0,
                                  NULL
                                  );
      ASSERT_EFI_ERROR (Status);
    }
  }

  return Status;
}

/**
  Write data to the SPI flash.

  This routine must be called at or below TPL_NOTIFY.
  This routine breaks up the write operation as necessary to write the data to
  the SPI part.

  @param[in] This           Pointer to an EFI_SPI_NOR_FLASH_PROTOCOL data
                            structure.
  @param[in] FlashAddress   Address in the flash to start writing
  @param[in] LengthInBytes  Write length in bytes
  @param[in] Buffer         Address of a buffer containing the data

  @retval EFI_SUCCESS            The data was written successfully.
  @retval EFI_INVALID_PARAMETER  Buffer is NULL, or
                                 FlashAddress >= This->FlashSize, or
                                 LengthInBytes > This->FlashSize - FlashAddress
  @retval EFI_OUT_OF_RESOURCES   Insufficient memory to copy buffer.

**/
EFI_STATUS
EFIAPI
WriteData (
  IN CONST EFI_SPI_NOR_FLASH_PROTOCOL  *This,
  IN UINT32                            FlashAddress,
  IN UINT32                            LengthInBytes,
  IN UINT8                             *Buffer
  )
{
  EFI_STATUS              Status;
  SPI_NOR_FLASH_INSTANCE  *Instance;
  UINT32                  ByteCounter;
  UINT32                  CurrentAddress;
  UINT32                  Length;
  UINT32                  BytesUntilBoundary;
  UINT8                   *CurrentBuffer;
  UINT32                  TransactionBufferLength;
  UINT32                  MaximumTransferBytes;
  UINT32                  SpiFlashPageSize;

  DEBUG ((DEBUG_INFO, "%a: Entry: Write address = 0x%08x, Length = 0x%08x\n", __func__, FlashAddress, LengthInBytes));

  Status = EFI_DEVICE_ERROR;
  if ((Buffer == NULL) ||
      (LengthInBytes == 0) ||
      (FlashAddress >= This->FlashSize) ||
      (LengthInBytes > This->FlashSize - FlashAddress))
  {
    return EFI_INVALID_PARAMETER;
  }

  Instance             = SPI_NOR_FLASH_FROM_THIS (This);
  MaximumTransferBytes = Instance->SpiIo->MaximumTransferBytes;
  if (Instance->SfdpBasicFlashByteCount >= 11 * 4) {
    // JESD216C spec DWORD 11
    SpiFlashPageSize = 1 << Instance->SfdpBasicFlash->PageSize;
  } else {
    SpiFlashPageSize = 256;
  }

  CurrentBuffer = Buffer;
  Length        = 0;
  for (ByteCounter = 0; ByteCounter < LengthInBytes;) {
    CurrentAddress = FlashAddress + ByteCounter;
    CurrentBuffer  = Buffer + ByteCounter;
    Length         = LengthInBytes - ByteCounter;
    // Length must be MaximumTransferBytes or less
    if (Length > MaximumTransferBytes) {
      Length = MaximumTransferBytes;
    }

    // Cannot cross SpiFlashPageSize boundary
    BytesUntilBoundary = SpiFlashPageSize
                         - (CurrentAddress % SpiFlashPageSize);
    if ((BytesUntilBoundary != 0) && (Length > BytesUntilBoundary)) {
      Length = BytesUntilBoundary;
    }

    // Check not WIP
    Status = WaitNotWip (Instance, FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds), FixedPcdGet32 (PcdSpiNorFlashFixedTimeoutRetryCount));
    if (EFI_ERROR (Status)) {
      break;
    }

    if (Instance->WriteEnableLatchRequired) {
      // Set Write Enable
      Status = SetWel (Instance);
      ASSERT_EFI_ERROR (Status);
      if (EFI_ERROR (Status)) {
        break;
      }

      // Check not WIP & WEL enabled
      Status = WaitWelNotWip (Instance, FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds), FixedPcdGet32 (PcdSpiNorFlashFixedTimeoutRetryCount));
      if (EFI_ERROR (Status)) {
        break;
      }
    }

    //  Write Data
    TransactionBufferLength = FillWriteBuffer (
                                Instance,
                                SPI_FLASH_PP,
                                SPI_FLASH_PP_DUMMY,
                                SPI_FLASH_PP_ADDR_BYTES,
                                TRUE,
                                CurrentAddress,
                                Length,
                                CurrentBuffer
                                );
    Status = Instance->SpiIo->Transaction (
                                Instance->SpiIo,
                                SPI_TRANSACTION_WRITE_ONLY,
                                FALSE,
                                0,
                                1,
                                8,
                                TransactionBufferLength,
                                Instance->SpiTransactionWriteBuffer,
                                0,
                                NULL
                                );
    ASSERT_EFI_ERROR (Status);
    if (EFI_ERROR (Status)) {
      break;
    }

    if (Instance->WriteEnableLatchRequired) {
      // Check not WIP & not WEL
      Status = WaitNotWelNotWip (Instance, FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds), FixedPcdGet32 (PcdSpiNorFlashFixedTimeoutRetryCount));
      if (EFI_ERROR (Status)) {
        break;
      }
    } else {
      Status = WaitNotWip (Instance, FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds), FixedPcdGet32 (PcdSpiNorFlashFixedTimeoutRetryCount));
      if (EFI_ERROR (Status)) {
        break;
      }
    }

    ByteCounter += Length;
  }

  return Status;
}

/**
  Efficiently erases blocks in the SPI flash.

  This routine must be called at or below TPL_NOTIFY.
  This routine may use the combination of variable earse sizes to erase the
  specified area accroding to the flash region.

  @param[in] This          Pointer to an EFI_SPI_NOR_FLASH_PROTOCOL data
                           structure.
  @param[in] FlashAddress  Address to start erasing
  @param[in] BlockCount    Number of blocks to erase. The block size is indicated
                           in EraseBlockBytes in EFI_SPI_NOR_FLASH_PROTOCOL.

  @retval EFI_SUCCESS            The erase was completed successfully.
  @retval EFI_DEVICE_ERROR       The flash devices has problems.
  @retval EFI_INVALID_PARAMETER  The given FlashAddress and/or BlockCount
                                 is invalid.

**/
EFI_STATUS
EFIAPI
Erase (
  IN CONST EFI_SPI_NOR_FLASH_PROTOCOL  *This,
  IN UINT32                            FlashAddress,
  IN UINT32                            BlockCount
  )
{
  EFI_STATUS                 Status;
  SPI_NOR_FLASH_INSTANCE     *Instance;
  UINT8                      Opcode;
  UINT32                     Dummy;
  UINT32                     ByteCounter;
  UINT32                     EraseLength;
  UINT32                     TotalEraseLength;
  UINT32                     CurrentAddress;
  UINT32                     TransactionBufferLength;
  UINT32                     BlockCountToErase;
  UINT32                     BlockSizeToErase;
  UINT8                      BlockEraseCommand;
  UINT32                     TypicalEraseTime;
  UINT64                     MaximumEraseTimeout;
  SFDP_SECTOR_REGION_RECORD  *FlashRegion;

  DEBUG ((DEBUG_INFO, "%a: Entry: Erase address = 0x%08x, Block count = 0x%x\n", __func__, FlashAddress, BlockCount));

  Status   = EFI_DEVICE_ERROR;
  Instance = SPI_NOR_FLASH_FROM_THIS (This);

  // Get the region of this flash address.
  Status = GetRegionByFlashAddress (Instance, FlashAddress, &FlashRegion);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "  Failed to get the flash region of this flash address.\n"));
    ASSERT (FALSE);
    return Status;
  }

  CurrentAddress    = FlashAddress;
  BlockCountToErase = BlockCount;
  BlockSizeToErase  = FlashRegion->SectorSize; // This is also the minimum block erase size.
  TotalEraseLength  = BlockCountToErase * FlashRegion->SectorSize;
  if ((FlashAddress + TotalEraseLength) > (FlashRegion->RegionAddress + FlashRegion->RegionTotalSize)) {
    DEBUG ((DEBUG_ERROR, "  The blocks to erase exceeds the region boundary.\n"));
    return EFI_INVALID_PARAMETER;
  }

  DEBUG ((DEBUG_VERBOSE, "  Region starting address: 0x%08x.\n", FlashRegion->RegionAddress));
  DEBUG ((DEBUG_VERBOSE, "  Region size            : 0x%08x.\n", FlashRegion->RegionTotalSize));
  DEBUG ((DEBUG_VERBOSE, "  Region sector size     : 0x%08x.\n", FlashRegion->SectorSize));
  DEBUG ((DEBUG_VERBOSE, "  Supported erase address bytes by device: 0x%02x.\n", Instance->SfdpBasicFlash->AddressBytes));
  DEBUG ((DEBUG_VERBOSE, "    (00: 3-Byte, 01: 3 or 4-Byte. 10: 4-Byte)\n"));

  // Loop until all blocks are erased.
  ByteCounter = 0;
  while (ByteCounter < TotalEraseLength) {
    CurrentAddress = FlashAddress + ByteCounter;

    // Is this the whole device erase.
    if (TotalEraseLength == This->FlashSize) {
      Opcode      = SPI_FLASH_CE;
      Dummy       = SPI_FLASH_CE_DUMMY;
      EraseLength = TotalEraseLength;
      DEBUG ((DEBUG_VERBOSE, "  This is the chip erase.\n"));
    } else {
      //
      // Get the erase block attributes.
      //
      Status = GetEraseBlockAttribute (
                 Instance,
                 FlashRegion,
                 CurrentAddress,
                 TotalEraseLength - ByteCounter,
                 &BlockSizeToErase,
                 &BlockCountToErase,
                 &BlockEraseCommand,
                 &TypicalEraseTime,
                 &MaximumEraseTimeout
                 );
      if (EFI_ERROR (Status)) {
        DEBUG ((DEBUG_ERROR, "  Failed to get erase block attribute.\n"));
        ASSERT (FALSE);
      }

      Opcode      = BlockEraseCommand;
      Dummy       = SPI_FLASH_BE_DUMMY;
      EraseLength = BlockCountToErase * BlockSizeToErase;
      DEBUG ((
        DEBUG_VERBOSE,
        "  Erase command 0x%02x at adddress 0x%08x for length 0x%08x.\n",
        BlockEraseCommand,
        CurrentAddress,
        EraseLength
        ));
    }

    //
    // Process the erase command.
    //

    // Check not WIP
    Status = WaitNotWip (Instance, FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds), FixedPcdGet32 (PcdSpiNorFlashFixedTimeoutRetryCount));
    if (EFI_ERROR (Status)) {
      break;
    }

    if (Instance->WriteEnableLatchRequired) {
      // Set Write Enable
      Status = SetWel (Instance);
      if (EFI_ERROR (Status)) {
        break;
      }

      // Check not WIP & WEL enabled
      Status = WaitWelNotWip (Instance, FixedPcdGet32 (PcdSpiNorFlashOperationDelayMicroseconds), FixedPcdGet32 (PcdSpiNorFlashFixedTimeoutRetryCount));
      if (EFI_ERROR (Status)) {
        break;
      }
    }

    // Erase Block
    TransactionBufferLength = FillWriteBuffer (
                                Instance,
                                Opcode,
                                Dummy,
                                (UINT8)Instance->SfdpBasicFlash->AddressBytes,
                                TRUE,
                                CurrentAddress,
                                0,
                                NULL
                                );
    Status = Instance->SpiIo->Transaction (
                                Instance->SpiIo,
                                SPI_TRANSACTION_WRITE_ONLY,
                                FALSE,
                                0,
                                1,
                                8,
                                TransactionBufferLength,
                                Instance->SpiTransactionWriteBuffer,
                                0,
                                NULL
                                );
    ASSERT_EFI_ERROR (Status);
    if (EFI_ERROR (Status)) {
      break;
    } else {
      DEBUG ((DEBUG_VERBOSE, "Erase command sucessfully.\n"));
    }

    if (Instance->WriteEnableLatchRequired) {
      //
      // Check not WIP & not WEL
      // Use the timeout value calculated by SPI NOR flash SFDP.
      //
      Status = WaitNotWelNotWip (Instance, (UINT32)MaximumEraseTimeout * 1000, FixedPcdGet32 (PcdSpiNorFlashOperationRetryCount));
      if (EFI_ERROR (Status)) {
        break;
      }
    } else {
      //
      // Use the timeout value calculated by SPI NOR flash SFDP.
      //
      Status = WaitNotWip (Instance, (UINT32)MaximumEraseTimeout * 1000, FixedPcdGet32 (PcdSpiNorFlashOperationRetryCount));
      if (EFI_ERROR (Status)) {
        break;
      }
    }

    ByteCounter += EraseLength;
  }

  return Status;
}
