audk/ArmPkg/Drivers/PL180MciDxe/PL180Mci.c

388 lines
13 KiB
C

/** @file
This file implement the MMC Host Protocol for the ARM PrimeCell PL180.
Copyright (c) 2011, 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
BOOLEAN MciIsPowerOn() {
return ((MmioRead32(MCI_POWER_CONTROL_REG) & 0x3) == MCI_POWER_ON);
}
EFI_STATUS MciInitialize() {
MCI_TRACE("MciInitialize()");
return EFI_SUCCESS;
}
BOOLEAN MciIsCardPresent() {
return (MmioRead32(FixedPcdGet32(PcdPL180SysMciRegAddress)) & 1);
}
BOOLEAN MciIsReadOnly() {
return (MmioRead32(FixedPcdGet32(PcdPL180SysMciRegAddress)) & 2);
}
// Convert block size to 2^n
UINT32 GetPow2BlockLen(UINT32 BlockLen) {
UINTN Loop;
UINTN Pow2BlockLen;
Loop = 0x8000;
Pow2BlockLen = 15;
do {
Loop = (Loop >> 1) & 0xFFFF;
Pow2BlockLen--;
} while (Pow2BlockLen && (!(Loop & BlockLen)));
return Pow2BlockLen;
}
VOID MciPrepareDataPath(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 | TransferDirection | (MMCI0_POW2_BLOCKLEN << 4));
#else
MmioWrite32(MCI_DATA_CTL_REG, MCI_DATACTL_ENABLE | TransferDirection | MCI_DATACTL_STREAM_TRANS);
#endif
}
EFI_STATUS MciSendCommand(MMC_CMD MmcCmd, UINT32 Argument) {
UINT32 Status;
UINT32 Timer;
UINT32 Cmd;
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 = INDX(MmcCmd);
if (MmcCmd & MMC_CMD_WAIT_RESPONSE)
Cmd |= MCI_CPSM_WAIT_RESPONSE;
if (MmcCmd & MMC_CMD_LONG_RESPONSE)
Cmd |= MCI_CPSM_LONG_RESPONSE;
MmioWrite32(MCI_CLEAR_STATUS_REG,0x5FFF);
MmioWrite32(MCI_ARGUMENT_REG,Argument);
MmioWrite32(MCI_COMMAND_REG,Cmd);
Timer = 1000;
if (Cmd & MCI_CPSM_WAIT_RESPONSE) {
Status = MmioRead32(MCI_STATUS_REG);
while (!(Status & (MCI_STATUS_CMD_RESPEND | MCI_STATUS_CMD_CMDCRCFAIL | MCI_STATUS_CMD_CMDTIMEOUT)) && Timer) {
//NanoSecondDelay(10);
Status = MmioRead32(MCI_STATUS_REG);
Timer--;
}
if ((Timer == 0) || (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));
return EFI_TIMEOUT;
} else if (!((Cmd & 0x3F) == INDX(1)) && (Status & MCI_STATUS_CMD_CMDCRCFAIL)) {
// The CMD1 does not contain CRC. We should ignore the CRC failed Status.
return EFI_CRC_ERROR;
} else {
return EFI_SUCCESS;
}
} else {
Status = MmioRead32(MCI_STATUS_REG);
while (!(Status & MCI_STATUS_CMD_SENT) && Timer) {
//NanoSecondDelay(10);
Status = MmioRead32(MCI_STATUS_REG);
Timer--;
}
if (Timer == 0) {
//DEBUG ((EFI_D_ERROR, "MciSendCommand(CmdIndex:%d) TIMEOUT2! 0x%X\n",Cmd & 0x3F,MmioRead32(MCI_RESPONSE0_REG)));
return EFI_TIMEOUT;
} else {
return EFI_SUCCESS;
}
}
}
EFI_STATUS MciReceiveResponse(MMC_RESPONSE_TYPE Type, 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_RESPONSE0_REG);
Buffer[1] = MmioRead32(MCI_RESPONSE1_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(EFI_LBA Lba, UINTN Length, UINT32* Buffer) {
UINTN Loop;
UINTN Finish;
UINTN Timer;
UINTN Status;
// Read data from the RX FIFO
Loop = 0;
Finish = MMCI0_BLOCKLEN / 4;
Timer = MMCI0_TIMEOUT * 10;
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_RXFIFOEMPTY)) {
Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
Loop++;
} else
Timer--;
} while ((Loop < Finish) && Timer);
if (Timer == 0) {
DEBUG ((EFI_D_ERROR, "MciReadBlockData: Timeout Status:0x%X Loop:%d // Finish:%d\n",MmioRead32(MCI_STATUS_REG),Loop,Finish));
return EFI_TIMEOUT;
} else
return EFI_SUCCESS;
}
EFI_STATUS MciWriteBlockData(EFI_LBA Lba, UINTN Length, UINT32* Buffer) {
UINTN Loop;
UINTN Finish;
UINTN Timer;
UINTN Status;
// Write the data to the TX FIFO
Loop = 0;
Finish = MMCI0_BLOCKLEN / 4;
Timer = MMCI0_TIMEOUT * 100;
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
Timer--;
} while ((Loop < Finish) && Timer);
ASSERT(Timer > 0);
// Wait for FIFO to drain
Timer = MMCI0_TIMEOUT;
Status = MmioRead32(MCI_STATUS_REG);
/*#ifndef USE_STREAM
// Single block
while (((Status & MCI_STATUS_CMD_TXDONE) != MCI_STATUS_CMD_TXDONE) && Timer) {
#else*/
// Stream
while (((Status & MCI_STATUS_CMD_DATAEND) != MCI_STATUS_CMD_DATAEND) && Timer) {
//#endif
NanoSecondDelay(10);
Status = MmioRead32(MCI_STATUS_REG);
Timer--;
}
ASSERT(Timer > 0);
if (Timer == 0)
return EFI_TIMEOUT;
else
return EFI_SUCCESS;
}
EFI_STATUS MciNotifyState(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);
//MmioWrite32(MCI_CLOCK_CONTROL_REG,0x1D | MCI_CLOCK_ENABLE);
// 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:
MCI_TRACE("MciNotifyState(MmcStandByState)");
// Enable MCICMD push-pull drive
MmioWrite32(MCI_POWER_CONTROL_REG,MCI_POWER_ROD | (15<<2) | MCI_POWER_ON);
/*// Set MMCI0 clock to 4MHz (24MHz may be possible with cache enabled)
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 = { 0x621b6fa5, 0x4dc1, 0x476f, 0xb9, 0xd8, 0x52, 0xc5, 0x57, 0xd8, 0x10, 0x70 };
EFI_STATUS MciBuildDevicePath(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 = {
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 = NULL;
MCI_TRACE("PL180MciDxeInitialize()");
//Publish Component Name, BlockIO protocol interfaces
Status = gBS->InstallMultipleProtocolInterfaces (
&Handle,
&gEfiMmcHostProtocolGuid, &gMciHost,
NULL
);
ASSERT_EFI_ERROR (Status);
return EFI_SUCCESS;
}