/** @file This file implement the MMC Host Protocol for the DesignWare eMMC. Copyright (c) 2014-2017, Linaro Limited. All rights reserved. SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include #include #include #include #include #include "DwEmmc.h" #define DWEMMC_DESC_PAGE 1 #define DWEMMC_BLOCK_SIZE 512 #define DWEMMC_DMA_BUF_SIZE (512 * 8) #define DWEMMC_MAX_DESC_PAGES 512 typedef struct { UINT32 Des0; UINT32 Des1; UINT32 Des2; UINT32 Des3; } DWEMMC_IDMAC_DESCRIPTOR; EFI_MMC_HOST_PROTOCOL *gpMmcHost; DWEMMC_IDMAC_DESCRIPTOR *gpIdmacDesc; EFI_GUID mDwEmmcDevicePathGuid = EFI_CALLER_ID_GUID; STATIC UINT32 mDwEmmcCommand; STATIC UINT32 mDwEmmcArgument; EFI_STATUS DwEmmcReadBlockData ( IN EFI_MMC_HOST_PROTOCOL *This, IN EFI_LBA Lba, IN UINTN Length, IN UINT32* Buffer ); BOOLEAN DwEmmcIsPowerOn ( VOID ) { return TRUE; } EFI_STATUS DwEmmcInitialize ( VOID ) { DEBUG ((DEBUG_BLKIO, "DwEmmcInitialize()")); return EFI_SUCCESS; } BOOLEAN DwEmmcIsCardPresent ( IN EFI_MMC_HOST_PROTOCOL *This ) { return TRUE; } BOOLEAN DwEmmcIsReadOnly ( IN EFI_MMC_HOST_PROTOCOL *This ) { return FALSE; } BOOLEAN DwEmmcIsDmaSupported ( IN EFI_MMC_HOST_PROTOCOL *This ) { return TRUE; } EFI_STATUS DwEmmcBuildDevicePath ( 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, &mDwEmmcDevicePathGuid); *DevicePath = NewDevicePathNode; return EFI_SUCCESS; } EFI_STATUS DwEmmcUpdateClock ( VOID ) { UINT32 Data; /* CMD_UPDATE_CLK */ Data = BIT_CMD_WAIT_PRVDATA_COMPLETE | BIT_CMD_UPDATE_CLOCK_ONLY | BIT_CMD_START; MmioWrite32 (DWEMMC_CMD, Data); while (1) { Data = MmioRead32 (DWEMMC_CMD); if (!(Data & CMD_START_BIT)) { break; } Data = MmioRead32 (DWEMMC_RINTSTS); if (Data & DWEMMC_INT_HLE) { Print (L"failed to update mmc clock frequency\n"); return EFI_DEVICE_ERROR; } } return EFI_SUCCESS; } EFI_STATUS DwEmmcSetClock ( IN UINTN ClockFreq ) { UINT32 Divider, Rate, Data; EFI_STATUS Status; BOOLEAN Found = FALSE; for (Divider = 1; Divider < 256; Divider++) { Rate = PcdGet32 (PcdDwEmmcDxeClockFrequencyInHz); if ((Rate / (2 * Divider)) <= ClockFreq) { Found = TRUE; break; } } if (Found == FALSE) { return EFI_NOT_FOUND; } // Wait until MMC is idle do { Data = MmioRead32 (DWEMMC_STATUS); } while (Data & DWEMMC_STS_DATA_BUSY); // Disable MMC clock first MmioWrite32 (DWEMMC_CLKENA, 0); Status = DwEmmcUpdateClock (); ASSERT (!EFI_ERROR (Status)); MmioWrite32 (DWEMMC_CLKDIV, Divider); Status = DwEmmcUpdateClock (); ASSERT (!EFI_ERROR (Status)); // Enable MMC clock MmioWrite32 (DWEMMC_CLKENA, 1); MmioWrite32 (DWEMMC_CLKSRC, 0); Status = DwEmmcUpdateClock (); ASSERT (!EFI_ERROR (Status)); return EFI_SUCCESS; } EFI_STATUS DwEmmcNotifyState ( IN EFI_MMC_HOST_PROTOCOL *This, IN MMC_STATE State ) { UINT32 Data; EFI_STATUS Status; switch (State) { case MmcInvalidState: return EFI_INVALID_PARAMETER; case MmcHwInitializationState: MmioWrite32 (DWEMMC_PWREN, 1); // If device already turn on then restart it Data = DWEMMC_CTRL_RESET_ALL; MmioWrite32 (DWEMMC_CTRL, Data); do { // Wait until reset operation finished Data = MmioRead32 (DWEMMC_CTRL); } while (Data & DWEMMC_CTRL_RESET_ALL); // Setup clock that could not be higher than 400KHz. Status = DwEmmcSetClock (400000); ASSERT (!EFI_ERROR (Status)); // Wait clock stable MicroSecondDelay (100); MmioWrite32 (DWEMMC_RINTSTS, ~0); MmioWrite32 (DWEMMC_INTMASK, 0); MmioWrite32 (DWEMMC_TMOUT, ~0); MmioWrite32 (DWEMMC_IDINTEN, 0); MmioWrite32 (DWEMMC_BMOD, DWEMMC_IDMAC_SWRESET); MmioWrite32 (DWEMMC_BLKSIZ, DWEMMC_BLOCK_SIZE); do { Data = MmioRead32 (DWEMMC_BMOD); } while (Data & DWEMMC_IDMAC_SWRESET); break; case MmcIdleState: break; case MmcReadyState: break; case MmcIdentificationState: break; case MmcStandByState: break; case MmcTransferState: break; case MmcSendingDataState: break; case MmcReceiveDataState: break; case MmcProgrammingState: break; case MmcDisconnectState: break; default: return EFI_INVALID_PARAMETER; } return EFI_SUCCESS; } // Need to prepare DMA buffer first before sending commands to MMC card BOOLEAN IsPendingReadCommand ( IN MMC_CMD MmcCmd ) { UINTN Mask; Mask = BIT_CMD_DATA_EXPECTED | BIT_CMD_READ; if ((MmcCmd & Mask) == Mask) { return TRUE; } return FALSE; } BOOLEAN IsPendingWriteCommand ( IN MMC_CMD MmcCmd ) { UINTN Mask; Mask = BIT_CMD_DATA_EXPECTED | BIT_CMD_WRITE; if ((MmcCmd & Mask) == Mask) { return TRUE; } return FALSE; } EFI_STATUS SendCommand ( IN MMC_CMD MmcCmd, IN UINT32 Argument ) { UINT32 Data, ErrMask; // Wait until MMC is idle do { Data = MmioRead32 (DWEMMC_STATUS); } while (Data & DWEMMC_STS_DATA_BUSY); MmioWrite32 (DWEMMC_RINTSTS, ~0); MmioWrite32 (DWEMMC_CMDARG, Argument); MmioWrite32 (DWEMMC_CMD, MmcCmd); ErrMask = DWEMMC_INT_EBE | DWEMMC_INT_HLE | DWEMMC_INT_RTO | DWEMMC_INT_RCRC | DWEMMC_INT_RE; ErrMask |= DWEMMC_INT_DCRC | DWEMMC_INT_DRT | DWEMMC_INT_SBE; do { MicroSecondDelay(500); Data = MmioRead32 (DWEMMC_RINTSTS); if (Data & ErrMask) { return EFI_DEVICE_ERROR; } if (Data & DWEMMC_INT_DTO) { // Transfer Done break; } } while (!(Data & DWEMMC_INT_CMD_DONE)); return EFI_SUCCESS; } EFI_STATUS DwEmmcSendCommand ( IN EFI_MMC_HOST_PROTOCOL *This, IN MMC_CMD MmcCmd, IN UINT32 Argument ) { UINT32 Cmd = 0; EFI_STATUS Status = EFI_SUCCESS; switch (MMC_GET_INDX(MmcCmd)) { case MMC_INDX(0): Cmd = BIT_CMD_SEND_INIT; break; case MMC_INDX(1): Cmd = BIT_CMD_RESPONSE_EXPECT; break; case MMC_INDX(2): Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_LONG_RESPONSE | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_SEND_INIT; break; case MMC_INDX(3): Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_SEND_INIT; break; case MMC_INDX(7): if (Argument) Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC; else Cmd = 0; break; case MMC_INDX(8): Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_DATA_EXPECTED | BIT_CMD_READ | BIT_CMD_WAIT_PRVDATA_COMPLETE; break; case MMC_INDX(9): Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_LONG_RESPONSE; break; case MMC_INDX(12): Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_STOP_ABORT_CMD; break; case MMC_INDX(13): Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_WAIT_PRVDATA_COMPLETE; break; case MMC_INDX(16): Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_DATA_EXPECTED | BIT_CMD_READ | BIT_CMD_WAIT_PRVDATA_COMPLETE; break; case MMC_INDX(17): case MMC_INDX(18): Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_DATA_EXPECTED | BIT_CMD_READ | BIT_CMD_WAIT_PRVDATA_COMPLETE; break; case MMC_INDX(24): case MMC_INDX(25): Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_DATA_EXPECTED | BIT_CMD_WRITE | BIT_CMD_WAIT_PRVDATA_COMPLETE; break; case MMC_INDX(30): Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_DATA_EXPECTED; break; default: Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC; break; } Cmd |= MMC_GET_INDX(MmcCmd) | BIT_CMD_USE_HOLD_REG | BIT_CMD_START; if (IsPendingReadCommand (Cmd) || IsPendingWriteCommand (Cmd)) { mDwEmmcCommand = Cmd; mDwEmmcArgument = Argument; } else { Status = SendCommand (Cmd, Argument); } return Status; } EFI_STATUS DwEmmcReceiveResponse ( 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 (DWEMMC_RESP0); } else if (Type == MMC_RESPONSE_TYPE_R2) { Buffer[0] = MmioRead32 (DWEMMC_RESP0); Buffer[1] = MmioRead32 (DWEMMC_RESP1); Buffer[2] = MmioRead32 (DWEMMC_RESP2); Buffer[3] = MmioRead32 (DWEMMC_RESP3); } return EFI_SUCCESS; } VOID DwEmmcAdjustFifoThreshold ( VOID ) { /* DMA multiple transaction size map to reg value as array index */ CONST UINT32 BurstSize[] = {1, 4, 8, 16, 32, 64, 128, 256}; UINT32 BlkDepthInFifo, FifoThreshold, FifoWidth, FifoDepth; UINT32 BlkSize = DWEMMC_BLOCK_SIZE, Idx = 0, RxWatermark = 1, TxWatermark, TxWatermarkInvers; /* Skip FIFO adjustment if we do not have platform FIFO depth info */ FifoDepth = PcdGet32 (PcdDwEmmcDxeFifoDepth); if (!FifoDepth) { return; } TxWatermark = FifoDepth / 2; TxWatermarkInvers = FifoDepth - TxWatermark; FifoWidth = DWEMMC_GET_HDATA_WIDTH (MmioRead32 (DWEMMC_HCON)); if (!FifoWidth) { FifoWidth = 2; } else if (FifoWidth == 2) { FifoWidth = 8; } else { FifoWidth = 4; } BlkDepthInFifo = BlkSize / FifoWidth; Idx = ARRAY_SIZE (BurstSize) - 1; while (Idx && ((BlkDepthInFifo % BurstSize[Idx]) || (TxWatermarkInvers % BurstSize[Idx]))) { Idx--; } RxWatermark = BurstSize[Idx] - 1; FifoThreshold = DWEMMC_DMA_BURST_SIZE (Idx) | DWEMMC_FIFO_TWMARK (TxWatermark) | DWEMMC_FIFO_RWMARK (RxWatermark); MmioWrite32 (DWEMMC_FIFOTH, FifoThreshold); } EFI_STATUS PrepareDmaData ( IN DWEMMC_IDMAC_DESCRIPTOR* IdmacDesc, IN UINTN Length, IN UINT32* Buffer ) { UINTN Cnt, Blks, Idx, LastIdx; Cnt = (Length + DWEMMC_DMA_BUF_SIZE - 1) / DWEMMC_DMA_BUF_SIZE; Blks = (Length + DWEMMC_BLOCK_SIZE - 1) / DWEMMC_BLOCK_SIZE; Length = DWEMMC_BLOCK_SIZE * Blks; for (Idx = 0; Idx < Cnt; Idx++) { (IdmacDesc + Idx)->Des0 = DWEMMC_IDMAC_DES0_OWN | DWEMMC_IDMAC_DES0_CH | DWEMMC_IDMAC_DES0_DIC; (IdmacDesc + Idx)->Des1 = DWEMMC_IDMAC_DES1_BS1(DWEMMC_DMA_BUF_SIZE); /* Buffer Address */ (IdmacDesc + Idx)->Des2 = (UINT32)((UINTN)Buffer + DWEMMC_DMA_BUF_SIZE * Idx); /* Next Descriptor Address */ (IdmacDesc + Idx)->Des3 = (UINT32)((UINTN)IdmacDesc + (sizeof(DWEMMC_IDMAC_DESCRIPTOR) * (Idx + 1))); } /* First Descriptor */ IdmacDesc->Des0 |= DWEMMC_IDMAC_DES0_FS; /* Last Descriptor */ LastIdx = Cnt - 1; (IdmacDesc + LastIdx)->Des0 |= DWEMMC_IDMAC_DES0_LD; (IdmacDesc + LastIdx)->Des0 &= ~(DWEMMC_IDMAC_DES0_DIC | DWEMMC_IDMAC_DES0_CH); (IdmacDesc + LastIdx)->Des1 = DWEMMC_IDMAC_DES1_BS1(Length - (LastIdx * DWEMMC_DMA_BUF_SIZE)); /* Set the Next field of Last Descriptor */ (IdmacDesc + LastIdx)->Des3 = 0; MmioWrite32 (DWEMMC_DBADDR, (UINT32)((UINTN)IdmacDesc)); return EFI_SUCCESS; } VOID StartDma ( UINTN Length ) { UINT32 Data; Data = MmioRead32 (DWEMMC_CTRL); Data |= DWEMMC_CTRL_INT_EN | DWEMMC_CTRL_DMA_EN | DWEMMC_CTRL_IDMAC_EN; MmioWrite32 (DWEMMC_CTRL, Data); Data = MmioRead32 (DWEMMC_BMOD); Data |= DWEMMC_IDMAC_ENABLE | DWEMMC_IDMAC_FB; MmioWrite32 (DWEMMC_BMOD, Data); MmioWrite32 (DWEMMC_BLKSIZ, DWEMMC_BLOCK_SIZE); MmioWrite32 (DWEMMC_BYTCNT, Length); } EFI_STATUS DwEmmcReadBlockData ( IN EFI_MMC_HOST_PROTOCOL *This, IN EFI_LBA Lba, IN UINTN Length, IN UINT32* Buffer ) { EFI_STATUS Status; UINT32 DescPages, CountPerPage, Count; EFI_TPL Tpl; Tpl = gBS->RaiseTPL (TPL_NOTIFY); CountPerPage = EFI_PAGE_SIZE / 16; Count = (Length + DWEMMC_DMA_BUF_SIZE - 1) / DWEMMC_DMA_BUF_SIZE; DescPages = (Count + CountPerPage - 1) / CountPerPage; InvalidateDataCacheRange (Buffer, Length); Status = PrepareDmaData (gpIdmacDesc, Length, Buffer); if (EFI_ERROR (Status)) { goto out; } WriteBackDataCacheRange (gpIdmacDesc, DescPages * EFI_PAGE_SIZE); StartDma (Length); Status = SendCommand (mDwEmmcCommand, mDwEmmcArgument); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "Failed to read data, mDwEmmcCommand:%x, mDwEmmcArgument:%x, Status:%r\n", mDwEmmcCommand, mDwEmmcArgument, Status)); goto out; } out: // Restore Tpl gBS->RestoreTPL (Tpl); return Status; } EFI_STATUS DwEmmcWriteBlockData ( IN EFI_MMC_HOST_PROTOCOL *This, IN EFI_LBA Lba, IN UINTN Length, IN UINT32* Buffer ) { EFI_STATUS Status; UINT32 DescPages, CountPerPage, Count; EFI_TPL Tpl; Tpl = gBS->RaiseTPL (TPL_NOTIFY); CountPerPage = EFI_PAGE_SIZE / 16; Count = (Length + DWEMMC_DMA_BUF_SIZE - 1) / DWEMMC_DMA_BUF_SIZE; DescPages = (Count + CountPerPage - 1) / CountPerPage; WriteBackDataCacheRange (Buffer, Length); Status = PrepareDmaData (gpIdmacDesc, Length, Buffer); if (EFI_ERROR (Status)) { goto out; } WriteBackDataCacheRange (gpIdmacDesc, DescPages * EFI_PAGE_SIZE); StartDma (Length); Status = SendCommand (mDwEmmcCommand, mDwEmmcArgument); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "Failed to write data, mDwEmmcCommand:%x, mDwEmmcArgument:%x, Status:%r\n", mDwEmmcCommand, mDwEmmcArgument, Status)); goto out; } out: // Restore Tpl gBS->RestoreTPL (Tpl); return Status; } EFI_STATUS DwEmmcSetIos ( IN EFI_MMC_HOST_PROTOCOL *This, IN UINT32 BusClockFreq, IN UINT32 BusWidth, IN UINT32 TimingMode ) { EFI_STATUS Status = EFI_SUCCESS; UINT32 Data; if ((PcdGet32 (PcdDwEmmcDxeMaxClockFreqInHz) != 0) && (BusClockFreq > PcdGet32 (PcdDwEmmcDxeMaxClockFreqInHz))) { return EFI_UNSUPPORTED; } if (TimingMode != EMMCBACKWARD) { Data = MmioRead32 (DWEMMC_UHSREG); switch (TimingMode) { case EMMCHS52DDR1V2: case EMMCHS52DDR1V8: Data |= 1 << 16; break; case EMMCHS52: case EMMCHS26: Data &= ~(1 << 16); break; default: return EFI_UNSUPPORTED; } MmioWrite32 (DWEMMC_UHSREG, Data); } switch (BusWidth) { case 1: MmioWrite32 (DWEMMC_CTYPE, 0); break; case 4: MmioWrite32 (DWEMMC_CTYPE, 1); break; case 8: MmioWrite32 (DWEMMC_CTYPE, 1 << 16); break; default: return EFI_UNSUPPORTED; } if (BusClockFreq) { Status = DwEmmcSetClock (BusClockFreq); } return Status; } BOOLEAN DwEmmcIsMultiBlock ( IN EFI_MMC_HOST_PROTOCOL *This ) { return TRUE; } EFI_MMC_HOST_PROTOCOL gMciHost = { MMC_HOST_PROTOCOL_REVISION, DwEmmcIsCardPresent, DwEmmcIsReadOnly, DwEmmcBuildDevicePath, DwEmmcNotifyState, DwEmmcSendCommand, DwEmmcReceiveResponse, DwEmmcReadBlockData, DwEmmcWriteBlockData, DwEmmcSetIos, DwEmmcIsMultiBlock }; EFI_STATUS DwEmmcDxeInitialize ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; EFI_HANDLE Handle; Handle = NULL; DwEmmcAdjustFifoThreshold (); gpIdmacDesc = (DWEMMC_IDMAC_DESCRIPTOR *)AllocatePages (DWEMMC_MAX_DESC_PAGES); if (gpIdmacDesc == NULL) { return EFI_BUFFER_TOO_SMALL; } DEBUG ((DEBUG_BLKIO, "DwEmmcDxeInitialize()\n")); //Publish Component Name, BlockIO protocol interfaces Status = gBS->InstallMultipleProtocolInterfaces ( &Handle, &gEfiMmcHostProtocolGuid, &gMciHost, NULL ); ASSERT_EFI_ERROR (Status); return EFI_SUCCESS; }