/** @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 #include 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; DEBUG ((EFI_D_WARN, "Probing ID registers at 0x%lx for a PL180\n", MCI_PERIPH_ID_REG0)); // Check if this is a PL180 if (MmioRead8 (MCI_PERIPH_ID_REG0) != MCI_PERIPH_ID0 || MmioRead8 (MCI_PERIPH_ID_REG1) != MCI_PERIPH_ID1 || MmioRead8 (MCI_PERIPH_ID_REG2) != MCI_PERIPH_ID2 || MmioRead8 (MCI_PCELL_ID_REG0) != MCI_PCELL_ID0 || MmioRead8 (MCI_PCELL_ID_REG1) != MCI_PCELL_ID1 || MmioRead8 (MCI_PCELL_ID_REG2) != MCI_PCELL_ID2 || MmioRead8 (MCI_PCELL_ID_REG3) != MCI_PCELL_ID3) { DEBUG ((EFI_D_WARN, "Probing ID registers at 0x%lx for a PL180" " failed\n", MCI_PERIPH_ID_REG0)); return EFI_NOT_FOUND; } 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; }