/** @file

Block I/O protocol for MMC/SD 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
MMCSDBlockReset (
  IN  EFI_BLOCK_IO_PROTOCOL   *This,
  IN  BOOLEAN                 ExtendedVerification
  )
{
  CARD_DATA                  *CardData;
  EFI_SD_HOST_IO_PROTOCOL    *SDHostIo;

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

  return SDHostIo->ResetSDHost (SDHostIo, Reset_DAT_CMD);
 }

/**
  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
MMCSDBlockReadBlocks (
  IN  EFI_BLOCK_IO_PROTOCOL   *This,
  IN  UINT32                  MediaId,
  IN  EFI_LBA                 LBA,
  IN  UINTN                   BufferSize,
  OUT VOID                    *Buffer
  )
{
  EFI_STATUS                  Status;
  UINT32                      Address;
  CARD_DATA                   *CardData;
  EFI_SD_HOST_IO_PROTOCOL     *SDHostIo;
  UINT32                      RemainingLength;
  UINT32                      TransferLength;
  UINT8                       *BufferPointer;
  BOOLEAN                     SectorAddressing;
  UINTN                       TotalBlock;

  DEBUG((EFI_D_INFO, "Read(LBA=%08lx, Buffer=%08x, Size=%08x)\n", LBA, Buffer, BufferSize));
  Status   = EFI_SUCCESS;
  CardData  = CARD_DATA_FROM_THIS(This);
  SDHostIo = CardData->SDHostIo;
  if (MediaId != CardData->BlockIoMedia.MediaId) {
    return EFI_MEDIA_CHANGED;
  }

  if (ModU64x32 (BufferSize,CardData->BlockIoMedia.BlockSize) != 0) {
    return EFI_BAD_BUFFER_SIZE;
  }
  if ((CardData->CardType == SDMemoryCard2High) || (CardData->CardType == MMCCardHighCap)) {
    SectorAddressing = TRUE;
  } else {
    SectorAddressing = FALSE;
  }
  if (SectorAddressing) {
    //
    //Block Address
    //
    Address = (UINT32)DivU64x32 (MultU64x32 (LBA, CardData->BlockIoMedia.BlockSize), 512);
  } else {
    //
    //Byte Address
    //
    Address  = (UINT32)MultU64x32 (LBA, CardData->BlockIoMedia.BlockSize);
  }
  TotalBlock = (UINTN) DivU64x32 (BufferSize, CardData->BlockIoMedia.BlockSize);
  if (LBA + TotalBlock > CardData->BlockIoMedia.LastBlock + 1) {
    return EFI_INVALID_PARAMETER;
  }


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

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

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




    BufferPointer   = Buffer;
    RemainingLength = (UINT32)BufferSize;

    while (RemainingLength > 0) {
    if ((BufferSize > CardData->BlockIoMedia.BlockSize)) {
      if (RemainingLength > SDHostIo->HostCapability.BoundarySize) {
        TransferLength = SDHostIo->HostCapability.BoundarySize;
      } else {
        TransferLength = RemainingLength;
      }

      if (CardData->CardType == MMCCard || CardData->CardType == MMCCardHighCap) {
        if (!(CardData->ExtCSDRegister.CARD_TYPE & (BIT2 | BIT3))) {
      Status = SendCommand (
                     CardData,
                     SET_BLOCKLEN,
                     CardData->BlockIoMedia.BlockSize,
                     NoData,
                     NULL,
                     0,
                     ResponseR1,
                     TIMEOUT_COMMAND,
                     (UINT32*)&(CardData->CardStatus)
                     );
          if (EFI_ERROR (Status)) {
            break;
          }
        }
        Status = SendCommand (
                   CardData,
                   SET_BLOCK_COUNT,
                   TransferLength / CardData->BlockIoMedia.BlockSize,
                   NoData,
                   NULL,
                   0,
                   ResponseR1,
                   TIMEOUT_COMMAND,
                   (UINT32*)&(CardData->CardStatus)
                   );
        if (EFI_ERROR (Status)) {
          break;
        }
      }
      Status = SendCommand (
                 CardData,
                 READ_MULTIPLE_BLOCK,
                 Address,
                 InData,
                 CardData->AlignedBuffer,
                 TransferLength,
                 ResponseR1,
                 TIMEOUT_DATA,
                 (UINT32*)&(CardData->CardStatus)
                 );

      if (EFI_ERROR (Status)) {
        DEBUG ((EFI_D_ERROR, "MMCSDBlockReadBlocks: READ_MULTIPLE_BLOCK -> Fail\n"));
        break;
      }
    } else {
      if (RemainingLength > CardData->BlockIoMedia.BlockSize) {
        TransferLength = CardData->BlockIoMedia.BlockSize;
      } else {
        TransferLength = RemainingLength;
      }

      Status = SendCommand (
                 CardData,
                 READ_SINGLE_BLOCK,
                 Address,
                 InData,
                 CardData->AlignedBuffer,
                 (UINT32)TransferLength,
                 ResponseR1,
                 TIMEOUT_DATA,
                 (UINT32*)&(CardData->CardStatus)
                 );
      if (EFI_ERROR (Status)) {
        DEBUG ((EFI_D_ERROR, "MMCSDBlockReadBlocks: READ_SINGLE_BLOCK -> Fail\n"));
        break;
      }
    }
      CopyMem (BufferPointer, CardData->AlignedBuffer, TransferLength);

    if (SectorAddressing) {
        //
        //Block Address
        //
        Address += TransferLength / 512;
      } else {
        //
        //Byte Address
        //
        Address += TransferLength;
      }
      BufferPointer   += TransferLength;
      RemainingLength -= TransferLength;
   }


  if (EFI_ERROR (Status)) {
    if ((CardData->CardType == SDMemoryCard) ||
        (CardData->CardType == SDMemoryCard2)||
        (CardData->CardType == SDMemoryCard2High)) {
         SendCommand (
           CardData,
           STOP_TRANSMISSION,
           0,
           NoData,
           NULL,
           0,
           ResponseR1b,
           TIMEOUT_COMMAND,
           (UINT32*)&(CardData->CardStatus)
           );
    } else {
       SendCommand (
         CardData,
         STOP_TRANSMISSION,
         0,
         NoData,
         NULL,
         0,
         ResponseR1,
         TIMEOUT_COMMAND,
         (UINT32*)&(CardData->CardStatus)
         );
    }

  }


Done:
  DEBUG((EFI_D_INFO, "MMCSDBlockReadBlocks: Status = %r\n", Status));
  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
MMCSDBlockWriteBlocks (
  IN  EFI_BLOCK_IO_PROTOCOL   *This,
  IN  UINT32                  MediaId,
  IN  EFI_LBA                 LBA,
  IN  UINTN                   BufferSize,
  IN  VOID                    *Buffer
  )
{
  EFI_STATUS                  Status;
  UINT32                      Address;
  CARD_DATA                   *CardData;
  EFI_SD_HOST_IO_PROTOCOL     *SDHostIo;
  UINT32                      RemainingLength;
  UINT32                      TransferLength;
  UINT8                       *BufferPointer;
  BOOLEAN                     SectorAddressing;

  DEBUG((EFI_D_INFO, "Write(LBA=%08lx, Buffer=%08x, Size=%08x)\n", LBA, Buffer, BufferSize));
  Status   = EFI_SUCCESS;
  CardData  = CARD_DATA_FROM_THIS(This);
  SDHostIo = CardData->SDHostIo;
  if ((CardData->CardType == SDMemoryCard2High) || (CardData->CardType == MMCCardHighCap)) {
    SectorAddressing = TRUE;
  } else {
    SectorAddressing = FALSE;
  }
  if (SectorAddressing) {
    //
    //Block Address
    //
    Address = (UINT32)DivU64x32 (MultU64x32 (LBA, CardData->BlockIoMedia.BlockSize), 512);
  } else {
    //
    //Byte Address
    //
    Address = (UINT32)MultU64x32 (LBA, CardData->BlockIoMedia.BlockSize);
  }

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

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

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

  if (This->Media->ReadOnly == TRUE) {
    Status = EFI_WRITE_PROTECTED;
    DEBUG ((EFI_D_ERROR, "MMCSDBlockWriteBlocks: Write protected \r\n"));
    goto Done;
  }



    BufferPointer   = Buffer;
    RemainingLength = (UINT32)BufferSize;

    while (RemainingLength > 0) {
    if ((BufferSize > CardData->BlockIoMedia.BlockSize) ) {
      if (RemainingLength > SDHostIo->HostCapability.BoundarySize) {
        TransferLength = SDHostIo->HostCapability.BoundarySize;
      } else {
        TransferLength = RemainingLength;
      }

      if (CardData->CardType == MMCCard || CardData->CardType == MMCCardHighCap) {

        if (!(CardData->ExtCSDRegister.CARD_TYPE & (BIT2 | BIT3)))  {
            Status = SendCommand (
                       CardData,
                       SET_BLOCKLEN,
                       CardData->BlockIoMedia.BlockSize,
                       NoData,
                       NULL,
                       0,
                       ResponseR1,
                       TIMEOUT_COMMAND,
                       (UINT32*)&(CardData->CardStatus)
                       );
            if (EFI_ERROR (Status)) {
              break;
            }
        }
        Status = SendCommand (
                      CardData,
                   SET_BLOCK_COUNT,
                   TransferLength / CardData->BlockIoMedia.BlockSize,
                      NoData,
                      NULL,
                      0,
                      ResponseR1,
                      TIMEOUT_COMMAND,
                      (UINT32*)&(CardData->CardStatus)
                      );
        if (EFI_ERROR (Status)) {
          break;
        }
      }

      CopyMem (CardData->AlignedBuffer, BufferPointer, TransferLength);

      Status = SendCommand (
                 CardData,
                 WRITE_MULTIPLE_BLOCK,
                 Address,
                 OutData,
                 CardData->AlignedBuffer,
                 (UINT32)TransferLength,
                 ResponseR1,
                 TIMEOUT_DATA,
                 (UINT32*)&(CardData->CardStatus)
                 );
      if (EFI_ERROR (Status)) {
        DEBUG ((EFI_D_ERROR, "MMCSDBlockWriteBlocks: WRITE_MULTIPLE_BLOCK -> Fail\n"));
        break;
      }
    } else {
      if (RemainingLength > CardData->BlockIoMedia.BlockSize) {
        TransferLength = CardData->BlockIoMedia.BlockSize;
      } else {
        TransferLength = RemainingLength;
      }

      CopyMem (CardData->AlignedBuffer, BufferPointer, TransferLength);

      Status = SendCommand (
                 CardData,
                 WRITE_BLOCK,
                 Address,
                 OutData,
                 CardData->AlignedBuffer,
                 (UINT32)TransferLength,
                 ResponseR1,
                 TIMEOUT_DATA,
                 (UINT32*)&(CardData->CardStatus)
                 );
    }
    if (SectorAddressing) {
        //
        //Block Address
        //
        Address += TransferLength / 512;
      } else {
        //
        //Byte Address
        //
        Address += TransferLength;
      }
      BufferPointer   += TransferLength;
      RemainingLength -= TransferLength;

  }

  if (EFI_ERROR (Status)) {
    SendCommand (
      CardData,
      STOP_TRANSMISSION,
      0,
      NoData,
      NULL,
      0,
      ResponseR1b,
      TIMEOUT_COMMAND,
      (UINT32*)&(CardData->CardStatus)
      );

  }


Done:
  return EFI_SUCCESS;
}

/**
  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
MMCSDBlockFlushBlocks (
  IN  EFI_BLOCK_IO_PROTOCOL   *This
  )
{
  return EFI_SUCCESS;
}


/**
  MMC/SD card BlockIo init function.

  @param  CardData               Pointer to CARD_DATA.

  @retval EFI_SUCCESS
  @retval Others
**/
EFI_STATUS
MMCSDBlockIoInit (
  IN  CARD_DATA    *CardData
  )
{
  //
  //BlockIO protocol
  //
  CardData->BlockIo.Revision    = EFI_BLOCK_IO_PROTOCOL_REVISION;
  CardData->BlockIo.Media       = &(CardData->BlockIoMedia);
  CardData->BlockIo.Reset       = MMCSDBlockReset;
  CardData->BlockIo.ReadBlocks  = MMCSDBlockReadBlocks ;
  CardData->BlockIo.WriteBlocks = MMCSDBlockWriteBlocks;
  CardData->BlockIo.FlushBlocks = MMCSDBlockFlushBlocks;

  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.BlockSize        = CardData->BlockLen;
  CardData->BlockIoMedia.IoAlign          = 1;
  CardData->BlockIoMedia.LastBlock        = (EFI_LBA)(CardData->BlockNumber - 1);


  return EFI_SUCCESS;

}



