/** @file This file implement the MMC Host Protocol for the ARM PrimeCell PL180. Copyright (c) 2011-2012, ARM Limited. All rights reserved. SPDX-License-Identifier: BSD-2-Clause-Patent **/ #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); } // 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; } 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); } else if (MmcCmd == MMC_CMD6) { MmioWrite32 (MCI_DATA_TIMER_REG, 0xFFFFFFF); MmioWrite32 (MCI_DATA_LENGTH_REG, 64); #ifndef USE_STREAM MmioWrite32 (MCI_DATA_CTL_REG, MCI_DATACTL_ENABLE | MCI_DATACTL_CARD_TO_CONT | GetPow2BlockLen (64)); #else MmioWrite32 (MCI_DATA_CTL_REG, MCI_DATACTL_ENABLE | MCI_DATACTL_CARD_TO_CONT | MCI_DATACTL_STREAM_TRANS); #endif } else if (MmcCmd == MMC_ACMD51) { MmioWrite32 (MCI_DATA_TIMER_REG, 0xFFFFFFF); /* SCR register is 8 bytes long. */ MmioWrite32 (MCI_DATA_LENGTH_REG, 8); #ifndef USE_STREAM MmioWrite32 (MCI_DATA_CTL_REG, MCI_DATACTL_ENABLE | MCI_DATACTL_CARD_TO_CONT | GetPow2BlockLen (8)); #else MmioWrite32 (MCI_DATA_CTL_REG, MCI_DATACTL_ENABLE | MCI_DATACTL_CARD_TO_CONT | MCI_DATACTL_STREAM_TRANS); #endif } // 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; if (Length < MMCI0_BLOCKLEN) { Finish = Length / 4; } else { 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; }