hackedteam/vector-edk

View on GitHub
ArmPlatformPkg/Drivers/PL180MciDxe/PL180Mci.c

Summary

Maintainability
Test Coverage
/** @file
  This file implement the MMC Host Protocol for the ARM PrimeCell PL180.

  Copyright (c) 2011-2012, ARM Limited. All rights reserved.
  
  This program and the accompanying materials                          
  are licensed and made available under the terms and conditions of the BSD License         
  which accompanies this distribution.  The full text of the license may be found at        
  http://opensource.org/licenses/bsd-license.php                                            

  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,                     
  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.             

**/

#include "PL180Mci.h"

#include <Library/DevicePathLib.h>
#include <Library/BaseMemoryLib.h>

EFI_MMC_HOST_PROTOCOL     *gpMmcHost;

// Untested ...
//#define USE_STREAM

#define MMCI0_BLOCKLEN 512
#define MMCI0_POW2_BLOCKLEN     9
#define MMCI0_TIMEOUT           1000

#define SYS_MCI_CARDIN          BIT0
#define SYS_MCI_WPROT           BIT1

BOOLEAN
MciIsPowerOn (
  VOID
  )
{
  return ((MmioRead32 (MCI_POWER_CONTROL_REG) & MCI_POWER_ON) == MCI_POWER_ON);
}

EFI_STATUS
MciInitialize (
  VOID
  )
{
  MCI_TRACE ("MciInitialize()");
  return EFI_SUCCESS;
}

BOOLEAN
MciIsCardPresent (
  IN EFI_MMC_HOST_PROTOCOL     *This
  )
{
  return (MmioRead32 (FixedPcdGet32 (PcdPL180SysMciRegAddress)) & SYS_MCI_CARDIN);
}

BOOLEAN
MciIsReadOnly (
  IN EFI_MMC_HOST_PROTOCOL     *This
  )
{
  return (MmioRead32 (FixedPcdGet32 (PcdPL180SysMciRegAddress)) & SYS_MCI_WPROT);
}

#if 0
//Note: This function has been commented out because it is not used yet.
//      This function could be used to remove the hardcoded BlockLen used
//      in MciPrepareDataPath

// Convert block size to 2^n
STATIC
UINT32
GetPow2BlockLen (
  IN UINT32 BlockLen
  )
{
  UINTN Loop;
  UINTN Pow2BlockLen;

  Loop    = 0x8000;
  Pow2BlockLen = 15;
  do {
    Loop = (Loop >> 1) & 0xFFFF;
    Pow2BlockLen--;
  } while (Pow2BlockLen && (!(Loop & BlockLen)));

  return Pow2BlockLen;
}
#endif

VOID
MciPrepareDataPath (
  IN UINTN TransferDirection
  )
{
  // Set Data Length & Data Timer
  MmioWrite32 (MCI_DATA_TIMER_REG, 0xFFFFFFF);
  MmioWrite32 (MCI_DATA_LENGTH_REG, MMCI0_BLOCKLEN);

#ifndef USE_STREAM
  //Note: we are using a hardcoded BlockLen (==512). If we decide to use a variable size, we could
  // compute the pow2 of BlockLen with the above function GetPow2BlockLen ()
  MmioWrite32 (MCI_DATA_CTL_REG, MCI_DATACTL_ENABLE | MCI_DATACTL_DMA_ENABLE | TransferDirection | (MMCI0_POW2_BLOCKLEN << 4));
#else
  MmioWrite32 (MCI_DATA_CTL_REG, MCI_DATACTL_ENABLE | MCI_DATACTL_DMA_ENABLE | TransferDirection | MCI_DATACTL_STREAM_TRANS);
#endif
}

EFI_STATUS
MciSendCommand (
  IN EFI_MMC_HOST_PROTOCOL     *This,
  IN MMC_CMD                    MmcCmd,
  IN UINT32                     Argument
  )
{
  UINT32  Status;
  UINT32  Cmd;
  UINTN   RetVal;
  UINTN   CmdCtrlReg;
  UINT32  DoneMask;

  RetVal = EFI_SUCCESS;

  if ((MmcCmd == MMC_CMD17) || (MmcCmd == MMC_CMD11)) {
    MciPrepareDataPath (MCI_DATACTL_CARD_TO_CONT);
  } else if ((MmcCmd == MMC_CMD24) || (MmcCmd == MMC_CMD20)) {
    MciPrepareDataPath (MCI_DATACTL_CONT_TO_CARD);
  }

  // Create Command for PL180
  Cmd = (MMC_GET_INDX (MmcCmd) & INDX_MASK)  | MCI_CPSM_ENABLE;
  if (MmcCmd & MMC_CMD_WAIT_RESPONSE) {
    Cmd |= MCI_CPSM_WAIT_RESPONSE;
  }

  if (MmcCmd & MMC_CMD_LONG_RESPONSE) {
    Cmd |= MCI_CPSM_LONG_RESPONSE;
  }

  // Clear Status register static flags
  MmioWrite32 (MCI_CLEAR_STATUS_REG, MCI_CLR_ALL_STATUS);

  // Write to command argument register
  MmioWrite32 (MCI_ARGUMENT_REG, Argument);

  // Write to command register
  MmioWrite32 (MCI_COMMAND_REG, Cmd);

  DoneMask  = (Cmd & MCI_CPSM_WAIT_RESPONSE)
                ? (MCI_STATUS_CMD_RESPEND | MCI_STATUS_CMD_ERROR)
                : (MCI_STATUS_CMD_SENT    | MCI_STATUS_CMD_ERROR);
  do {
    Status = MmioRead32 (MCI_STATUS_REG);
  } while (! (Status & DoneMask));

  if ((Status & MCI_STATUS_CMD_ERROR)) {
    // Clear Status register error flags
    MmioWrite32 (MCI_CLEAR_STATUS_REG, MCI_STATUS_CMD_ERROR);
      
    if ((Status & MCI_STATUS_CMD_START_BIT_ERROR)) {
      DEBUG ((EFI_D_ERROR, "MciSendCommand(CmdIndex:%d) Start bit Error! Response:0x%X Status:0x%x\n", (Cmd & 0x3F), MmioRead32 (MCI_RESPONSE0_REG), Status));
      RetVal = EFI_NO_RESPONSE;
    } else if ((Status & MCI_STATUS_CMD_CMDTIMEOUT)) {
      //DEBUG ((EFI_D_ERROR, "MciSendCommand(CmdIndex:%d) TIMEOUT! Response:0x%X Status:0x%x\n", (Cmd & 0x3F), MmioRead32 (MCI_RESPONSE0_REG), Status));
      RetVal = EFI_TIMEOUT;
    } else if ((!(MmcCmd & MMC_CMD_NO_CRC_RESPONSE)) && (Status & MCI_STATUS_CMD_CMDCRCFAIL)) {
      // The CMD1 and response type R3 do not contain CRC. We should ignore the CRC failed Status.
      RetVal = EFI_CRC_ERROR;
    }
  }

  // Disable Command Path
  CmdCtrlReg = MmioRead32 (MCI_COMMAND_REG);
  MmioWrite32 (MCI_COMMAND_REG, (CmdCtrlReg & ~MCI_CPSM_ENABLE));
  return RetVal;
}

EFI_STATUS
MciReceiveResponse (
  IN EFI_MMC_HOST_PROTOCOL     *This,
  IN MMC_RESPONSE_TYPE          Type,
  IN UINT32*                    Buffer
  )
{
  if (Buffer == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (   (Type == MMC_RESPONSE_TYPE_R1)
      || (Type == MMC_RESPONSE_TYPE_R1b)
      || (Type == MMC_RESPONSE_TYPE_R3)
      || (Type == MMC_RESPONSE_TYPE_R6)
      || (Type == MMC_RESPONSE_TYPE_R7))
  {
    Buffer[0] = MmioRead32 (MCI_RESPONSE3_REG);
  } else if (Type == MMC_RESPONSE_TYPE_R2) {
    Buffer[0] = MmioRead32 (MCI_RESPONSE0_REG);
    Buffer[1] = MmioRead32 (MCI_RESPONSE1_REG);
    Buffer[2] = MmioRead32 (MCI_RESPONSE2_REG);
    Buffer[3] = MmioRead32 (MCI_RESPONSE3_REG);
  }

  return EFI_SUCCESS;
}

EFI_STATUS
MciReadBlockData (
  IN EFI_MMC_HOST_PROTOCOL     *This,
  IN EFI_LBA                    Lba,
  IN UINTN                      Length,
  IN UINT32*                    Buffer
  )
{
  UINTN Loop;
  UINTN Finish;
  UINTN Status;
  EFI_STATUS RetVal;
  UINTN  DataCtrlReg;
  EFI_TPL Tpl;

  RetVal = EFI_SUCCESS;

  // Read data from the RX FIFO
  Loop   = 0;
  Finish = MMCI0_BLOCKLEN / 4;
  
  // Raise the TPL at the highest level to disable Interrupts.
  Tpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);

  do {
    // Read the Status flags
    Status = MmioRead32 (MCI_STATUS_REG);

    // Do eight reads if possible else a single read
    if (Status & MCI_STATUS_CMD_RXFIFOHALFFULL) {
      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
      Loop++;
      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
      Loop++;
      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
      Loop++;
      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
      Loop++;
      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
      Loop++;
      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
      Loop++;
      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
      Loop++;
      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
      Loop++;
    } else if (Status & MCI_STATUS_CMD_RXDATAAVAILBL) {
      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
      Loop++;
    } else {
      //Check for error conditions and timeouts
      if (Status & MCI_STATUS_CMD_DATATIMEOUT) {
        DEBUG ((EFI_D_ERROR, "MciReadBlockData(): TIMEOUT! Response:0x%X Status:0x%x\n", MmioRead32 (MCI_RESPONSE0_REG), Status));
        RetVal = EFI_TIMEOUT;
        break;
      } else if (Status & MCI_STATUS_CMD_DATACRCFAIL) {
        DEBUG ((EFI_D_ERROR, "MciReadBlockData(): CRC Error! Response:0x%X Status:0x%x\n", MmioRead32 (MCI_RESPONSE0_REG), Status));
        RetVal = EFI_CRC_ERROR;
        break;
      } else if (Status & MCI_STATUS_CMD_START_BIT_ERROR) {
        DEBUG ((EFI_D_ERROR, "MciReadBlockData(): Start-bit Error! Response:0x%X Status:0x%x\n", MmioRead32 (MCI_RESPONSE0_REG), Status));
        RetVal = EFI_NO_RESPONSE;
        break;
      }
    }
    //clear RX over run flag
    if(Status & MCI_STATUS_CMD_RXOVERRUN) {
      MmioWrite32(MCI_CLEAR_STATUS_REG, MCI_STATUS_CMD_RXOVERRUN);
    }
  } while ((Loop < Finish));

  // Restore Tpl
  gBS->RestoreTPL (Tpl);

  // Clear Status flags
  MmioWrite32 (MCI_CLEAR_STATUS_REG, MCI_CLR_ALL_STATUS);

  //Disable Data path
  DataCtrlReg = MmioRead32 (MCI_DATA_CTL_REG);
  MmioWrite32 (MCI_DATA_CTL_REG, (DataCtrlReg & MCI_DATACTL_DISABLE_MASK));

  return RetVal;
}

EFI_STATUS
MciWriteBlockData (
  IN EFI_MMC_HOST_PROTOCOL     *This,
  IN EFI_LBA                   Lba,
  IN UINTN                     Length,
  IN UINT32*                   Buffer
  )
{
  UINTN Loop;
  UINTN Finish;
  UINTN Timer;
  UINTN Status;
  EFI_STATUS RetVal;
  UINTN  DataCtrlReg;
  EFI_TPL Tpl;

  RetVal = EFI_SUCCESS;

  // Write the data to the TX FIFO
  Loop   = 0;
  Finish = MMCI0_BLOCKLEN / 4;
  Timer  = MMCI0_TIMEOUT * 100;

  // Raise the TPL at the highest level to disable Interrupts.
  Tpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);

  do {
    // Read the Status flags
    Status = MmioRead32 (MCI_STATUS_REG);

    // Do eight writes if possible else a single write
    if (Status & MCI_STATUS_CMD_TXFIFOHALFEMPTY) {
      MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
      Loop++;
      MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
      Loop++;
      MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
      Loop++;
      MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
      Loop++;
      MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
      Loop++;
      MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
      Loop++;
      MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
      Loop++;
      MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
      Loop++;
    } else if (!(Status & MCI_STATUS_CMD_TXFIFOFULL)) {
        MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
        Loop++;
    } else {
      // Check for error conditions and timeouts
      if (Status & MCI_STATUS_CMD_DATATIMEOUT) {
        DEBUG ((EFI_D_ERROR, "MciWriteBlockData(): TIMEOUT! Response:0x%X Status:0x%x\n", MmioRead32 (MCI_RESPONSE0_REG), Status));
        RetVal = EFI_TIMEOUT;
        goto Exit;
      } else if (Status & MCI_STATUS_CMD_DATACRCFAIL) {
        DEBUG ((EFI_D_ERROR, "MciWriteBlockData(): CRC Error! Response:0x%X Status:0x%x\n", MmioRead32 (MCI_RESPONSE0_REG), Status));
        RetVal = EFI_CRC_ERROR;
        goto Exit;
      } else if (Status & MCI_STATUS_CMD_TX_UNDERRUN) {
        DEBUG ((EFI_D_ERROR, "MciWriteBlockData(): TX buffer Underrun! Response:0x%X Status:0x%x, Number of bytes written 0x%x\n",MmioRead32(MCI_RESPONSE0_REG),Status, Loop));
        RetVal = EFI_BUFFER_TOO_SMALL;
        ASSERT(0);
        goto Exit;
      }
    }
  } while (Loop < Finish);

  // Restore Tpl
  gBS->RestoreTPL (Tpl);

  // Wait for FIFO to drain
  Timer  = MMCI0_TIMEOUT * 60;
  Status = MmioRead32 (MCI_STATUS_REG);
#ifndef USE_STREAM
  // Single block
  while (((Status & MCI_STATUS_TXDONE) != MCI_STATUS_TXDONE) && Timer) {
#else
  // Stream
  while (((Status & MCI_STATUS_CMD_DATAEND) != MCI_STATUS_CMD_DATAEND) && Timer) {
#endif
    NanoSecondDelay(10);
    Status = MmioRead32 (MCI_STATUS_REG);
    Timer--;
  }

  // Clear Status flags
  MmioWrite32 (MCI_CLEAR_STATUS_REG, MCI_CLR_ALL_STATUS);

  if (Timer == 0) {
    DEBUG ((EFI_D_ERROR, "MciWriteBlockData(): Data End timeout Number of words written 0x%x\n", Loop));
    RetVal = EFI_TIMEOUT;
  }

Exit:
  // Disable Data path
  DataCtrlReg = MmioRead32 (MCI_DATA_CTL_REG);
  MmioWrite32 (MCI_DATA_CTL_REG, (DataCtrlReg & MCI_DATACTL_DISABLE_MASK));
  return RetVal;
}

EFI_STATUS
MciNotifyState (
  IN  EFI_MMC_HOST_PROTOCOL     *This,
  IN MMC_STATE                  State
  )
{
  UINT32      Data32;

  switch (State) {
  case MmcInvalidState:
    ASSERT (0);
    break;
  case MmcHwInitializationState:
    // If device already turn on then restart it
    Data32 = MmioRead32 (MCI_POWER_CONTROL_REG);
    if ((Data32 & 0x2) == MCI_POWER_UP) {
      MCI_TRACE ("MciNotifyState(MmcHwInitializationState): TurnOff MCI");

      // Turn off
      MmioWrite32 (MCI_CLOCK_CONTROL_REG, 0);
      MmioWrite32 (MCI_POWER_CONTROL_REG, 0);
      MicroSecondDelay (100);
    }

    MCI_TRACE ("MciNotifyState(MmcHwInitializationState): TurnOn MCI");
    // Setup clock
    //  - 0x1D = 29 => should be the clock divider to be less than 400kHz at MCLK = 24Mhz
    MmioWrite32 (MCI_CLOCK_CONTROL_REG, 0x1D | MCI_CLOCK_ENABLE | MCI_CLOCK_POWERSAVE);

    // Set the voltage
    MmioWrite32 (MCI_POWER_CONTROL_REG, MCI_POWER_OPENDRAIN | (15<<2));
    MmioWrite32 (MCI_POWER_CONTROL_REG, MCI_POWER_ROD | MCI_POWER_OPENDRAIN | (15<<2) | MCI_POWER_UP);
    MicroSecondDelay (10);
    MmioWrite32 (MCI_POWER_CONTROL_REG, MCI_POWER_ROD | MCI_POWER_OPENDRAIN | (15<<2) | MCI_POWER_ON);
    MicroSecondDelay (100);

    // Set Data Length & Data Timer
    MmioWrite32 (MCI_DATA_TIMER_REG, 0xFFFFF);
    MmioWrite32 (MCI_DATA_LENGTH_REG, 8);

    ASSERT ((MmioRead32 (MCI_POWER_CONTROL_REG) & 0x3) == MCI_POWER_ON);
    break;
  case MmcIdleState:
    MCI_TRACE ("MciNotifyState(MmcIdleState)");
    break;
  case MmcReadyState:
    MCI_TRACE ("MciNotifyState(MmcReadyState)");
    break;
  case MmcIdentificationState:
    MCI_TRACE ("MciNotifyState (MmcIdentificationState)");
    break;
  case MmcStandByState:{
    volatile UINT32 PwrCtrlReg;
    MCI_TRACE ("MciNotifyState (MmcStandByState)");

    // Enable MCICMD push-pull drive
    PwrCtrlReg = MmioRead32 (MCI_POWER_CONTROL_REG);
    //Disable Open Drain output
    PwrCtrlReg &= ~ (MCI_POWER_OPENDRAIN);
    MmioWrite32 (MCI_POWER_CONTROL_REG, PwrCtrlReg);

    // Set MMCI0 clock to 4MHz (24MHz may be possible with cache enabled)
    //
    // Note: Increasing clock speed causes TX FIFO under-run errors.
    //       So careful when optimising this driver for higher performance.
    //
    MmioWrite32(MCI_CLOCK_CONTROL_REG,0x02 | MCI_CLOCK_ENABLE | MCI_CLOCK_POWERSAVE);
    // Set MMCI0 clock to 24MHz (by bypassing the divider)
    //MmioWrite32(MCI_CLOCK_CONTROL_REG,MCI_CLOCK_BYPASS | MCI_CLOCK_ENABLE);
    break;
  }
  case MmcTransferState:
    //MCI_TRACE ("MciNotifyState(MmcTransferState)");
    break;
  case MmcSendingDataState:
    MCI_TRACE ("MciNotifyState(MmcSendingDataState)");
    break;
  case MmcReceiveDataState:
    MCI_TRACE ("MciNotifyState(MmcReceiveDataState)");
    break;
  case MmcProgrammingState:
    MCI_TRACE ("MciNotifyState(MmcProgrammingState)");
    break;
  case MmcDisconnectState:
    MCI_TRACE ("MciNotifyState(MmcDisconnectState)");
    break;
  default:
    ASSERT (0);
  }
  return EFI_SUCCESS;
}

EFI_GUID mPL180MciDevicePathGuid = EFI_CALLER_ID_GUID;

EFI_STATUS
MciBuildDevicePath (
  IN EFI_MMC_HOST_PROTOCOL      *This,
  IN EFI_DEVICE_PATH_PROTOCOL   **DevicePath
  )
{
  EFI_DEVICE_PATH_PROTOCOL    *NewDevicePathNode;

  NewDevicePathNode = CreateDeviceNode (HARDWARE_DEVICE_PATH, HW_VENDOR_DP, sizeof (VENDOR_DEVICE_PATH));
  CopyGuid (& ((VENDOR_DEVICE_PATH*)NewDevicePathNode)->Guid, &mPL180MciDevicePathGuid);

  *DevicePath = NewDevicePathNode;
  return EFI_SUCCESS;
}

EFI_MMC_HOST_PROTOCOL gMciHost = {
  MMC_HOST_PROTOCOL_REVISION,
  MciIsCardPresent,
  MciIsReadOnly,
  MciBuildDevicePath,
  MciNotifyState,
  MciSendCommand,
  MciReceiveResponse,
  MciReadBlockData,
  MciWriteBlockData
};

EFI_STATUS
PL180MciDxeInitialize (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
{
  EFI_STATUS    Status;
  EFI_HANDLE    Handle;

  Handle = NULL;

  MCI_TRACE ("PL180MciDxeInitialize()");

  //Publish Component Name, BlockIO protocol interfaces
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &Handle, 
                  &gEfiMmcHostProtocolGuid,         &gMciHost,
                  NULL
                  );
  ASSERT_EFI_ERROR (Status);

  return EFI_SUCCESS;
}