/** @file

Block I/O protocol for CE-ATA device

Copyright (c) 2013-2015 Intel Corporation.

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

**/

#include "SDMediaDevice.h"

/**
  Implements EFI_BLOCK_IO_PROTOCOL.Reset() function.

  @param  This                   The EFI_BLOCK_IO_PROTOCOL instance.
  @param  ExtendedVerification   Indicates that the driver may perform a more exhaustive.
                                 verification operation of the device during reset.
                                 (This parameter is ingored in this driver.)

  @retval EFI_SUCCESS                Success
**/
EFI_STATUS
EFIAPI
CEATABlockReset (
  IN  EFI_BLOCK_IO_PROTOCOL   *This,
  IN  BOOLEAN                 ExtendedVerification
  )
{
  EFI_STATUS                 Status;
  CARD_DATA                  *CardData;
  EFI_SD_HOST_IO_PROTOCOL    *SDHostIo;

  CardData  = CARD_DATA_FROM_THIS(This);
  SDHostIo = CardData->SDHostIo;

  if (!ExtendedVerification) {
    Status = SoftwareReset (CardData);
  } else {
    Status = SDHostIo->ResetSDHost (SDHostIo, Reset_DAT_CMD);
    if (EFI_ERROR (Status)) {
    DEBUG((EFI_D_ERROR, "CEATABlockReset: Fail to ResetSDHost\n" ));
      return Status;
    }
    Status = MMCSDCardInit (CardData);
  }


  return Status;

 }

/**
  Implements EFI_BLOCK_IO_PROTOCOL.ReadBlocks() function.

  @param  This                   The EFI_BLOCK_IO_PROTOCOL instance.
  @param  MediaId                The media id that the write request is for.
  @param  LBA                    The starting logical block address to read from on the device.
                                 The caller is responsible for writing to only legitimate locations.
  @param  BufferSize             The size of the Buffer in bytes. This must be a multiple of the
                                 intrinsic block size of the device.
  @param  Buffer                 A pointer to the destination buffer for the data. The caller
                                 is responsible for either having implicit or explicit ownership
                                 of the buffer.

  @retval EFI_SUCCESS                Success
  @retval EFI_DEVICE_ERROR           Hardware Error
  @retval EFI_INVALID_PARAMETER      Parameter is error
  @retval EFI_NO_MEDIA               No media
  @retval EFI_MEDIA_CHANGED          Media Change
  @retval EFI_BAD_BUFFER_SIZE        Buffer size is bad
**/
EFI_STATUS
EFIAPI
CEATABlockReadBlocks (
  IN  EFI_BLOCK_IO_PROTOCOL   *This,
  IN  UINT32                  MediaId,
  IN  EFI_LBA                 LBA,
  IN  UINTN                   BufferSize,
  OUT VOID                    *Buffer
  )
{
  EFI_STATUS                  Status;
  CARD_DATA                   *CardData;
  UINT32                      TransferSize;
  UINT8                       *pBuf;
  UINT32                      Index;
  UINT64                      Address;
  UINT32                      Remainder;
  UINT64                      CEATALBA;
  UINT32                      BoundarySize;

  Status       = EFI_SUCCESS;
  CardData     = CARD_DATA_FROM_THIS(This);
  pBuf         = Buffer;
  Index        = 0;
  Address      = MultU64x32(LBA, CardData->BlockIoMedia.BlockSize);
  BoundarySize = CardData->SDHostIo->HostCapability.BoundarySize;

  if (!Buffer) {
    Status = EFI_INVALID_PARAMETER;
    DEBUG((EFI_D_ERROR, "CEATABlockReadBlocks:Invalid parameter\n" ));
    goto Exit;
  }

  if (MediaId != CardData->BlockIoMedia.MediaId) {
    Status = EFI_MEDIA_CHANGED;
  DEBUG((EFI_D_ERROR, "CEATABlockReadBlocks:Media changed\n" ));
    goto Exit;
  }

  if ((BufferSize % CardData->BlockIoMedia.BlockSize) != 0) {
    Status = EFI_BAD_BUFFER_SIZE;
  DEBUG((EFI_D_ERROR, "CEATABlockReadBlocks:Bad buffer size\n" ));
    goto Exit;
  }

  if (BufferSize == 0) {
    Status = EFI_SUCCESS;
    goto Exit;
  }

  if ((Address + BufferSize) > MultU64x32 (CardData->BlockIoMedia.LastBlock + 1, CardData->BlockIoMedia.BlockSize)) {
    Status = EFI_INVALID_PARAMETER;
    DEBUG((EFI_D_ERROR, "CEATABlockReadBlocks:Invalid parameter\n" ));
    goto Exit;
  }


  do {
    if (BufferSize < BoundarySize) {
      TransferSize = (UINT32)BufferSize;
    } else {
      TransferSize = BoundarySize;
    }

    Address += Index * TransferSize;
    CEATALBA = DivU64x32Remainder (Address, DATA_UNIT_SIZE, &Remainder);
    ASSERT(Remainder == 0);

    Status = ReadDMAExt (
               CardData,
               CEATALBA,
               pBuf,
               (UINT16)(TransferSize / DATA_UNIT_SIZE)
               );
    if (EFI_ERROR (Status)) {
     DEBUG((EFI_D_ERROR, "Read Failed at 0x%x, Index %d, Size 0x%x\n", Address, Index, TransferSize));
     This->Reset (This, TRUE);
     goto Exit;
    }
    BufferSize -= TransferSize;
    pBuf       += TransferSize;
    Index ++;
  } while (BufferSize != 0);


Exit:
  return Status;
}

/**
  Implements EFI_BLOCK_IO_PROTOCOL.WriteBlocks() function.

  @param  This                   The EFI_BLOCK_IO_PROTOCOL instance.
  @param  MediaId                The media id that the write request is for.
  @param  LBA                    The starting logical block address to read from on the device.
                                 The caller is responsible for writing to only legitimate locations.
  @param  BufferSize             The size of the Buffer in bytes. This must be a multiple of the
                                 intrinsic block size of the device.
  @param  Buffer                 A pointer to the destination buffer for the data. The caller
                                 is responsible for either having implicit or explicit ownership
                                 of the buffer.

  @retval EFI_SUCCESS                Success
  @retval EFI_DEVICE_ERROR           Hardware Error
  @retval EFI_INVALID_PARAMETER      Parameter is error
  @retval EFI_NO_MEDIA               No media
  @retval EFI_MEDIA_CHANGED          Media Change
  @retval EFI_BAD_BUFFER_SIZE        Buffer size is bad
**/
EFI_STATUS
EFIAPI
CEATABlockWriteBlocks (
  IN  EFI_BLOCK_IO_PROTOCOL   *This,
  IN  UINT32                  MediaId,
  IN  EFI_LBA                 LBA,
  IN  UINTN                   BufferSize,
  IN  VOID                    *Buffer
  )
{
  EFI_STATUS                  Status;
  CARD_DATA                   *CardData;
  UINT32                      TransferSize;
  UINT8                       *pBuf;
  UINT32                      Index;
  UINT64                      Address;
  UINT32                      Remainder;
  UINT64                      CEATALBA;
  UINT32                      BoundarySize;


  Status       = EFI_SUCCESS;
  CardData     = CARD_DATA_FROM_THIS(This);
  pBuf         = Buffer;
  Index        = 0;
  Address      = MultU64x32(LBA, CardData->BlockIoMedia.BlockSize);
  BoundarySize = CardData->SDHostIo->HostCapability.BoundarySize;


  if (!Buffer) {
    Status = EFI_INVALID_PARAMETER;
    goto Exit;
  }

  if (MediaId != CardData->BlockIoMedia.MediaId) {
    Status = EFI_MEDIA_CHANGED;
    goto Exit;
  }

  if ((BufferSize % CardData->BlockIoMedia.BlockSize) != 0) {
    Status = EFI_BAD_BUFFER_SIZE;
    goto Exit;
  }

  if (BufferSize == 0) {
    Status = EFI_SUCCESS;
    goto Exit;
  }

  if (CardData->BlockIoMedia.ReadOnly) {
    Status = EFI_WRITE_PROTECTED;
    goto Exit;
  }

  if ((Address + BufferSize) > MultU64x32 (CardData->BlockIoMedia.LastBlock + 1, CardData->BlockIoMedia.BlockSize)) {
    Status = EFI_INVALID_PARAMETER;
    goto Exit;
  }

  CardData->NeedFlush = TRUE;

  do {
    if (BufferSize < BoundarySize) {
      TransferSize = (UINT32)BufferSize;
    } else {
      TransferSize = BoundarySize;
    }

    Address += Index * TransferSize;
    CEATALBA = DivU64x32Remainder (Address, DATA_UNIT_SIZE, &Remainder);
    ASSERT(Remainder == 0);

    Status = WriteDMAExt (
               CardData,
               CEATALBA,
               pBuf,
               (UINT16)(TransferSize / DATA_UNIT_SIZE)
               );
    if (EFI_ERROR (Status)) {
     DEBUG((EFI_D_ERROR, "Write Failed at 0x%x, Index %d, Size 0x%x\n", Address, Index, TransferSize));
     This->Reset (This, TRUE);
     goto Exit;
    }
    BufferSize -= TransferSize;
    pBuf       += TransferSize;
    Index ++;
  } while (BufferSize != 0);


Exit:
  return Status;
}

/**
  Implements EFI_BLOCK_IO_PROTOCOL.FlushBlocks() function.
    (In this driver, this function just returns EFI_SUCCESS.)

  @param  This                   The EFI_BLOCK_IO_PROTOCOL instance.

  @retval EFI_SUCCESS
  @retval Others
**/
EFI_STATUS
EFIAPI
CEATABlockFlushBlocks (
  IN  EFI_BLOCK_IO_PROTOCOL   *This
  )
{

  CARD_DATA                   *CardData;

  CardData  = CARD_DATA_FROM_THIS(This);

  if (CardData->NeedFlush) {
    CardData->NeedFlush = FALSE;
    FlushCache (CardData);
  }

  return EFI_SUCCESS;
}


/**
  CEATA card BlockIo init function.

  @param  CardData               Pointer to CARD_DATA.

  @retval EFI_SUCCESS
  @retval Others
**/
EFI_STATUS
CEATABlockIoInit (
  IN  CARD_DATA    *CardData
  )
/*++

  Routine Description:
    CEATA card BlockIo init function

  Arguments:
    CardData  -   Pointer to CARD_DATA

  Returns:
    EFI_SUCCESS - Success
--*/
{
  EFI_STATUS   Status;
  UINT64       MaxSize;
  UINT32       Remainder;
  //
  //BlockIO protocol
  //
  CardData->BlockIo.Revision    = EFI_BLOCK_IO_PROTOCOL_REVISION;
  CardData->BlockIo.Media       = &(CardData->BlockIoMedia);
  CardData->BlockIo.Reset       = CEATABlockReset;
  CardData->BlockIo.ReadBlocks  = CEATABlockReadBlocks ;
  CardData->BlockIo.WriteBlocks = CEATABlockWriteBlocks;
  CardData->BlockIo.FlushBlocks = CEATABlockFlushBlocks;

  CardData->BlockIoMedia.MediaId          = 0;
  CardData->BlockIoMedia.RemovableMedia   = FALSE;
  CardData->BlockIoMedia.MediaPresent     = TRUE;
  CardData->BlockIoMedia.LogicalPartition = FALSE;

  if (CardData->CSDRegister.PERM_WRITE_PROTECT | CardData->CSDRegister.TMP_WRITE_PROTECT) {
    CardData->BlockIoMedia.ReadOnly       = TRUE;
  } else {
    CardData->BlockIoMedia.ReadOnly       = FALSE;
  }


  CardData->BlockIoMedia.WriteCaching     = FALSE;
  CardData->BlockIoMedia.IoAlign          = 1;

  Status = IndentifyDevice (CardData);
  if (EFI_ERROR (Status)) {
   goto Exit;
  }

  //
  //Some device does not support this feature
  //

  if (CardData->IndentifyDeviceData.MaxWritesPerAddress == 0) {
    CardData->BlockIoMedia.ReadOnly       = TRUE;
  }

  CardData->BlockIoMedia.BlockSize        = (1 << CardData->IndentifyDeviceData.Sectorsize);
  ASSERT(CardData->BlockIoMedia.BlockSize >= 12);


  MaxSize = *(UINT64*)(CardData->IndentifyDeviceData.MaximumLBA);
  MaxSize = MultU64x32 (MaxSize, 512);

  Remainder = 0;
  CardData->BlockNumber = DivU64x32Remainder (MaxSize, CardData->BlockIoMedia.BlockSize, &Remainder);
  ASSERT(Remainder == 0);

  CardData->BlockIoMedia.LastBlock        = (EFI_LBA)(CardData->BlockNumber - 1);


Exit:
  return Status;

}



