diff options
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/SdMmcPciHcDxe/SdMmcPciHci.c')
-rw-r--r-- | src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/SdMmcPciHcDxe/SdMmcPciHci.c | 2838 |
1 files changed, 2838 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/SdMmcPciHcDxe/SdMmcPciHci.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/SdMmcPciHcDxe/SdMmcPciHci.c new file mode 100644 index 00000000..91465b7f --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/SdMmcPciHcDxe/SdMmcPciHci.c @@ -0,0 +1,2838 @@ +/** @file + This driver is used to manage SD/MMC PCI host controllers which are compliance + with SD Host Controller Simplified Specification version 3.00 plus the 64-bit + System Addressing support in SD Host Controller Simplified Specification version + 4.20. + + It would expose EFI_SD_MMC_PASS_THRU_PROTOCOL for upper layer use. + + Copyright (c) 2018-2019, NVIDIA CORPORATION. All rights reserved. + Copyright (c) 2015 - 2020, Intel Corporation. All rights reserved.<BR> + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "SdMmcPciHcDxe.h" + +/** + Dump the content of SD/MMC host controller's Capability Register. + + @param[in] Slot The slot number of the SD card to send the command to. + @param[in] Capability The buffer to store the capability data. + +**/ +VOID +DumpCapabilityReg ( + IN UINT8 Slot, + IN SD_MMC_HC_SLOT_CAP *Capability + ) +{ + // + // Dump Capability Data + // + DEBUG ((DEBUG_INFO, " == Slot [%d] Capability is 0x%x ==\n", Slot, Capability)); + DEBUG ((DEBUG_INFO, " Timeout Clk Freq %d%a\n", Capability->TimeoutFreq, (Capability->TimeoutUnit) ? "MHz" : "KHz")); + DEBUG ((DEBUG_INFO, " Base Clk Freq %dMHz\n", Capability->BaseClkFreq)); + DEBUG ((DEBUG_INFO, " Max Blk Len %dbytes\n", 512 * (1 << Capability->MaxBlkLen))); + DEBUG ((DEBUG_INFO, " 8-bit Support %a\n", Capability->BusWidth8 ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " ADMA2 Support %a\n", Capability->Adma2 ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " HighSpeed Support %a\n", Capability->HighSpeed ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " SDMA Support %a\n", Capability->Sdma ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " Suspend/Resume %a\n", Capability->SuspRes ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " Voltage 3.3 %a\n", Capability->Voltage33 ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " Voltage 3.0 %a\n", Capability->Voltage30 ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " Voltage 1.8 %a\n", Capability->Voltage18 ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " V4 64-bit Sys Bus %a\n", Capability->SysBus64V4 ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " V3 64-bit Sys Bus %a\n", Capability->SysBus64V3 ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " Async Interrupt %a\n", Capability->AsyncInt ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " SlotType ")); + if (Capability->SlotType == 0x00) { + DEBUG ((DEBUG_INFO, "%a\n", "Removable Slot")); + } else if (Capability->SlotType == 0x01) { + DEBUG ((DEBUG_INFO, "%a\n", "Embedded Slot")); + } else if (Capability->SlotType == 0x02) { + DEBUG ((DEBUG_INFO, "%a\n", "Shared Bus Slot")); + } else { + DEBUG ((DEBUG_INFO, "%a\n", "Reserved")); + } + DEBUG ((DEBUG_INFO, " SDR50 Support %a\n", Capability->Sdr50 ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " SDR104 Support %a\n", Capability->Sdr104 ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " DDR50 Support %a\n", Capability->Ddr50 ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " Driver Type A %a\n", Capability->DriverTypeA ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " Driver Type C %a\n", Capability->DriverTypeC ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " Driver Type D %a\n", Capability->DriverTypeD ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " Driver Type 4 %a\n", Capability->DriverType4 ? "TRUE" : "FALSE")); + if (Capability->TimerCount == 0) { + DEBUG ((DEBUG_INFO, " Retuning TimerCnt Disabled\n", 2 * (Capability->TimerCount - 1))); + } else { + DEBUG ((DEBUG_INFO, " Retuning TimerCnt %dseconds\n", 2 * (Capability->TimerCount - 1))); + } + DEBUG ((DEBUG_INFO, " SDR50 Tuning %a\n", Capability->TuningSDR50 ? "TRUE" : "FALSE")); + DEBUG ((DEBUG_INFO, " Retuning Mode Mode %d\n", Capability->RetuningMod + 1)); + DEBUG ((DEBUG_INFO, " Clock Multiplier M = %d\n", Capability->ClkMultiplier + 1)); + DEBUG ((DEBUG_INFO, " HS 400 %a\n", Capability->Hs400 ? "TRUE" : "FALSE")); + return; +} + +/** + Read SlotInfo register from SD/MMC host controller pci config space. + + @param[in] PciIo The PCI IO protocol instance. + @param[out] FirstBar The buffer to store the first BAR value. + @param[out] SlotNum The buffer to store the supported slot number. + + @retval EFI_SUCCESS The operation succeeds. + @retval Others The operation fails. + +**/ +EFI_STATUS +EFIAPI +SdMmcHcGetSlotInfo ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + OUT UINT8 *FirstBar, + OUT UINT8 *SlotNum + ) +{ + EFI_STATUS Status; + SD_MMC_HC_SLOT_INFO SlotInfo; + + Status = PciIo->Pci.Read ( + PciIo, + EfiPciIoWidthUint8, + SD_MMC_HC_SLOT_OFFSET, + sizeof (SlotInfo), + &SlotInfo + ); + if (EFI_ERROR (Status)) { + return Status; + } + + *FirstBar = SlotInfo.FirstBar; + *SlotNum = SlotInfo.SlotNum + 1; + ASSERT ((*FirstBar + *SlotNum) < SD_MMC_HC_MAX_SLOT); + return EFI_SUCCESS; +} + +/** + Read/Write specified SD/MMC host controller mmio register. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] BarIndex The BAR index of the standard PCI Configuration + header to use as the base address for the memory + operation to perform. + @param[in] Offset The offset within the selected BAR to start the + memory operation. + @param[in] Read A boolean to indicate it's read or write operation. + @param[in] Count The width of the mmio register in bytes. + Must be 1, 2 , 4 or 8 bytes. + @param[in, out] Data For read operations, the destination buffer to store + the results. For write operations, the source buffer + to write data from. The caller is responsible for + having ownership of the data buffer and ensuring its + size not less than Count bytes. + + @retval EFI_INVALID_PARAMETER The PciIo or Data is NULL or the Count is not valid. + @retval EFI_SUCCESS The read/write operation succeeds. + @retval Others The read/write operation fails. + +**/ +EFI_STATUS +EFIAPI +SdMmcHcRwMmio ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 BarIndex, + IN UINT32 Offset, + IN BOOLEAN Read, + IN UINT8 Count, + IN OUT VOID *Data + ) +{ + EFI_STATUS Status; + EFI_PCI_IO_PROTOCOL_WIDTH Width; + + if ((PciIo == NULL) || (Data == NULL)) { + return EFI_INVALID_PARAMETER; + } + + switch (Count) { + case 1: + Width = EfiPciIoWidthUint8; + break; + case 2: + Width = EfiPciIoWidthUint16; + Count = 1; + break; + case 4: + Width = EfiPciIoWidthUint32; + Count = 1; + break; + case 8: + Width = EfiPciIoWidthUint32; + Count = 2; + break; + default: + return EFI_INVALID_PARAMETER; + } + + if (Read) { + Status = PciIo->Mem.Read ( + PciIo, + Width, + BarIndex, + (UINT64) Offset, + Count, + Data + ); + } else { + Status = PciIo->Mem.Write ( + PciIo, + Width, + BarIndex, + (UINT64) Offset, + Count, + Data + ); + } + + return Status; +} + +/** + Do OR operation with the value of the specified SD/MMC host controller mmio register. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] BarIndex The BAR index of the standard PCI Configuration + header to use as the base address for the memory + operation to perform. + @param[in] Offset The offset within the selected BAR to start the + memory operation. + @param[in] Count The width of the mmio register in bytes. + Must be 1, 2 , 4 or 8 bytes. + @param[in] OrData The pointer to the data used to do OR operation. + The caller is responsible for having ownership of + the data buffer and ensuring its size not less than + Count bytes. + + @retval EFI_INVALID_PARAMETER The PciIo or OrData is NULL or the Count is not valid. + @retval EFI_SUCCESS The OR operation succeeds. + @retval Others The OR operation fails. + +**/ +EFI_STATUS +EFIAPI +SdMmcHcOrMmio ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 BarIndex, + IN UINT32 Offset, + IN UINT8 Count, + IN VOID *OrData + ) +{ + EFI_STATUS Status; + UINT64 Data; + UINT64 Or; + + Status = SdMmcHcRwMmio (PciIo, BarIndex, Offset, TRUE, Count, &Data); + if (EFI_ERROR (Status)) { + return Status; + } + + if (Count == 1) { + Or = *(UINT8*) OrData; + } else if (Count == 2) { + Or = *(UINT16*) OrData; + } else if (Count == 4) { + Or = *(UINT32*) OrData; + } else if (Count == 8) { + Or = *(UINT64*) OrData; + } else { + return EFI_INVALID_PARAMETER; + } + + Data |= Or; + Status = SdMmcHcRwMmio (PciIo, BarIndex, Offset, FALSE, Count, &Data); + + return Status; +} + +/** + Do AND operation with the value of the specified SD/MMC host controller mmio register. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] BarIndex The BAR index of the standard PCI Configuration + header to use as the base address for the memory + operation to perform. + @param[in] Offset The offset within the selected BAR to start the + memory operation. + @param[in] Count The width of the mmio register in bytes. + Must be 1, 2 , 4 or 8 bytes. + @param[in] AndData The pointer to the data used to do AND operation. + The caller is responsible for having ownership of + the data buffer and ensuring its size not less than + Count bytes. + + @retval EFI_INVALID_PARAMETER The PciIo or AndData is NULL or the Count is not valid. + @retval EFI_SUCCESS The AND operation succeeds. + @retval Others The AND operation fails. + +**/ +EFI_STATUS +EFIAPI +SdMmcHcAndMmio ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 BarIndex, + IN UINT32 Offset, + IN UINT8 Count, + IN VOID *AndData + ) +{ + EFI_STATUS Status; + UINT64 Data; + UINT64 And; + + Status = SdMmcHcRwMmio (PciIo, BarIndex, Offset, TRUE, Count, &Data); + if (EFI_ERROR (Status)) { + return Status; + } + + if (Count == 1) { + And = *(UINT8*) AndData; + } else if (Count == 2) { + And = *(UINT16*) AndData; + } else if (Count == 4) { + And = *(UINT32*) AndData; + } else if (Count == 8) { + And = *(UINT64*) AndData; + } else { + return EFI_INVALID_PARAMETER; + } + + Data &= And; + Status = SdMmcHcRwMmio (PciIo, BarIndex, Offset, FALSE, Count, &Data); + + return Status; +} + +/** + Wait for the value of the specified MMIO register set to the test value. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] BarIndex The BAR index of the standard PCI Configuration + header to use as the base address for the memory + operation to perform. + @param[in] Offset The offset within the selected BAR to start the + memory operation. + @param[in] Count The width of the mmio register in bytes. + Must be 1, 2, 4 or 8 bytes. + @param[in] MaskValue The mask value of memory. + @param[in] TestValue The test value of memory. + + @retval EFI_NOT_READY The MMIO register hasn't set to the expected value. + @retval EFI_SUCCESS The MMIO register has expected value. + @retval Others The MMIO operation fails. + +**/ +EFI_STATUS +EFIAPI +SdMmcHcCheckMmioSet ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 BarIndex, + IN UINT32 Offset, + IN UINT8 Count, + IN UINT64 MaskValue, + IN UINT64 TestValue + ) +{ + EFI_STATUS Status; + UINT64 Value; + + // + // Access PCI MMIO space to see if the value is the tested one. + // + Value = 0; + Status = SdMmcHcRwMmio (PciIo, BarIndex, Offset, TRUE, Count, &Value); + if (EFI_ERROR (Status)) { + return Status; + } + + Value &= MaskValue; + + if (Value == TestValue) { + return EFI_SUCCESS; + } + + return EFI_NOT_READY; +} + +/** + Wait for the value of the specified MMIO register set to the test value. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] BarIndex The BAR index of the standard PCI Configuration + header to use as the base address for the memory + operation to perform. + @param[in] Offset The offset within the selected BAR to start the + memory operation. + @param[in] Count The width of the mmio register in bytes. + Must be 1, 2, 4 or 8 bytes. + @param[in] MaskValue The mask value of memory. + @param[in] TestValue The test value of memory. + @param[in] Timeout The time out value for wait memory set, uses 1 + microsecond as a unit. + + @retval EFI_TIMEOUT The MMIO register hasn't expected value in timeout + range. + @retval EFI_SUCCESS The MMIO register has expected value. + @retval Others The MMIO operation fails. + +**/ +EFI_STATUS +EFIAPI +SdMmcHcWaitMmioSet ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 BarIndex, + IN UINT32 Offset, + IN UINT8 Count, + IN UINT64 MaskValue, + IN UINT64 TestValue, + IN UINT64 Timeout + ) +{ + EFI_STATUS Status; + BOOLEAN InfiniteWait; + + if (Timeout == 0) { + InfiniteWait = TRUE; + } else { + InfiniteWait = FALSE; + } + + while (InfiniteWait || (Timeout > 0)) { + Status = SdMmcHcCheckMmioSet ( + PciIo, + BarIndex, + Offset, + Count, + MaskValue, + TestValue + ); + if (Status != EFI_NOT_READY) { + return Status; + } + + // + // Stall for 1 microsecond. + // + gBS->Stall (1); + + Timeout--; + } + + return EFI_TIMEOUT; +} + +/** + Get the controller version information from the specified slot. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] Slot The slot number of the SD card to send the command to. + @param[out] Version The buffer to store the version information. + + @retval EFI_SUCCESS The operation executes successfully. + @retval Others The operation fails. + +**/ +EFI_STATUS +SdMmcHcGetControllerVersion ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 Slot, + OUT UINT16 *Version + ) +{ + EFI_STATUS Status; + + Status = SdMmcHcRwMmio (PciIo, Slot, SD_MMC_HC_CTRL_VER, TRUE, sizeof (UINT16), Version); + if (EFI_ERROR (Status)) { + return Status; + } + + *Version &= 0xFF; + + return EFI_SUCCESS; +} + +/** + Software reset the specified SD/MMC host controller and enable all interrupts. + + @param[in] Private A pointer to the SD_MMC_HC_PRIVATE_DATA instance. + @param[in] Slot The slot number of the SD card to send the command to. + + @retval EFI_SUCCESS The software reset executes successfully. + @retval Others The software reset fails. + +**/ +EFI_STATUS +SdMmcHcReset ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN UINT8 Slot + ) +{ + EFI_STATUS Status; + UINT8 SwReset; + EFI_PCI_IO_PROTOCOL *PciIo; + + // + // Notify the SD/MMC override protocol that we are about to reset + // the SD/MMC host controller. + // + if (mOverride != NULL && mOverride->NotifyPhase != NULL) { + Status = mOverride->NotifyPhase ( + Private->ControllerHandle, + Slot, + EdkiiSdMmcResetPre, + NULL); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, + "%a: SD/MMC pre reset notifier callback failed - %r\n", + __FUNCTION__, Status)); + return Status; + } + } + + PciIo = Private->PciIo; + SwReset = BIT0; + Status = SdMmcHcOrMmio (PciIo, Slot, SD_MMC_HC_SW_RST, sizeof (SwReset), &SwReset); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "SdMmcHcReset: write SW Reset for All fails: %r\n", Status)); + return Status; + } + + Status = SdMmcHcWaitMmioSet ( + PciIo, + Slot, + SD_MMC_HC_SW_RST, + sizeof (SwReset), + BIT0, + 0x00, + SD_MMC_HC_GENERIC_TIMEOUT + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_INFO, "SdMmcHcReset: reset done with %r\n", Status)); + return Status; + } + + // + // Enable all interrupt after reset all. + // + Status = SdMmcHcEnableInterrupt (PciIo, Slot); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_INFO, "SdMmcHcReset: SdMmcHcEnableInterrupt done with %r\n", + Status)); + return Status; + } + + // + // Notify the SD/MMC override protocol that we have just reset + // the SD/MMC host controller. + // + if (mOverride != NULL && mOverride->NotifyPhase != NULL) { + Status = mOverride->NotifyPhase ( + Private->ControllerHandle, + Slot, + EdkiiSdMmcResetPost, + NULL); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, + "%a: SD/MMC post reset notifier callback failed - %r\n", + __FUNCTION__, Status)); + } + } + + return Status; +} + +/** + Set all interrupt status bits in Normal and Error Interrupt Status Enable + register. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] Slot The slot number of the SD card to send the command to. + + @retval EFI_SUCCESS The operation executes successfully. + @retval Others The operation fails. + +**/ +EFI_STATUS +SdMmcHcEnableInterrupt ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 Slot + ) +{ + EFI_STATUS Status; + UINT16 IntStatus; + + // + // Enable all bits in Error Interrupt Status Enable Register + // + IntStatus = 0xFFFF; + Status = SdMmcHcRwMmio (PciIo, Slot, SD_MMC_HC_ERR_INT_STS_EN, FALSE, sizeof (IntStatus), &IntStatus); + if (EFI_ERROR (Status)) { + return Status; + } + // + // Enable all bits in Normal Interrupt Status Enable Register + // + IntStatus = 0xFFFF; + Status = SdMmcHcRwMmio (PciIo, Slot, SD_MMC_HC_NOR_INT_STS_EN, FALSE, sizeof (IntStatus), &IntStatus); + + return Status; +} + +/** + Get the capability data from the specified slot. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] Slot The slot number of the SD card to send the command to. + @param[out] Capability The buffer to store the capability data. + + @retval EFI_SUCCESS The operation executes successfully. + @retval Others The operation fails. + +**/ +EFI_STATUS +SdMmcHcGetCapability ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 Slot, + OUT SD_MMC_HC_SLOT_CAP *Capability + ) +{ + EFI_STATUS Status; + UINT64 Cap; + + Status = SdMmcHcRwMmio (PciIo, Slot, SD_MMC_HC_CAP, TRUE, sizeof (Cap), &Cap); + if (EFI_ERROR (Status)) { + return Status; + } + + CopyMem (Capability, &Cap, sizeof (Cap)); + + return EFI_SUCCESS; +} + +/** + Get the maximum current capability data from the specified slot. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] Slot The slot number of the SD card to send the command to. + @param[out] MaxCurrent The buffer to store the maximum current capability data. + + @retval EFI_SUCCESS The operation executes successfully. + @retval Others The operation fails. + +**/ +EFI_STATUS +SdMmcHcGetMaxCurrent ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 Slot, + OUT UINT64 *MaxCurrent + ) +{ + EFI_STATUS Status; + + Status = SdMmcHcRwMmio (PciIo, Slot, SD_MMC_HC_MAX_CURRENT_CAP, TRUE, sizeof (UINT64), MaxCurrent); + + return Status; +} + +/** + Detect whether there is a SD/MMC card attached at the specified SD/MMC host controller + slot. + + Refer to SD Host Controller Simplified spec 3.0 Section 3.1 for details. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] Slot The slot number of the SD card to send the command to. + @param[out] MediaPresent The pointer to the media present boolean value. + + @retval EFI_SUCCESS There is no media change happened. + @retval EFI_MEDIA_CHANGED There is media change happened. + @retval Others The detection fails. + +**/ +EFI_STATUS +SdMmcHcCardDetect ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 Slot, + OUT BOOLEAN *MediaPresent + ) +{ + EFI_STATUS Status; + UINT16 Data; + UINT32 PresentState; + + // + // Check Present State Register to see if there is a card presented. + // + Status = SdMmcHcRwMmio (PciIo, Slot, SD_MMC_HC_PRESENT_STATE, TRUE, sizeof (PresentState), &PresentState); + if (EFI_ERROR (Status)) { + return Status; + } + + if ((PresentState & BIT16) != 0) { + *MediaPresent = TRUE; + } else { + *MediaPresent = FALSE; + } + + // + // Check Normal Interrupt Status Register + // + Status = SdMmcHcRwMmio (PciIo, Slot, SD_MMC_HC_NOR_INT_STS, TRUE, sizeof (Data), &Data); + if (EFI_ERROR (Status)) { + return Status; + } + + if ((Data & (BIT6 | BIT7)) != 0) { + // + // Clear BIT6 and BIT7 by writing 1 to these two bits if set. + // + Data &= BIT6 | BIT7; + Status = SdMmcHcRwMmio (PciIo, Slot, SD_MMC_HC_NOR_INT_STS, FALSE, sizeof (Data), &Data); + if (EFI_ERROR (Status)) { + return Status; + } + + return EFI_MEDIA_CHANGED; + } + + return EFI_SUCCESS; +} + +/** + Stop SD/MMC card clock. + + Refer to SD Host Controller Simplified spec 3.0 Section 3.2.2 for details. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] Slot The slot number of the SD card to send the command to. + + @retval EFI_SUCCESS Succeed to stop SD/MMC clock. + @retval Others Fail to stop SD/MMC clock. + +**/ +EFI_STATUS +SdMmcHcStopClock ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 Slot + ) +{ + EFI_STATUS Status; + UINT32 PresentState; + UINT16 ClockCtrl; + + // + // Ensure no SD transactions are occurring on the SD Bus by + // waiting for Command Inhibit (DAT) and Command Inhibit (CMD) + // in the Present State register to be 0. + // + Status = SdMmcHcWaitMmioSet ( + PciIo, + Slot, + SD_MMC_HC_PRESENT_STATE, + sizeof (PresentState), + BIT0 | BIT1, + 0, + SD_MMC_HC_GENERIC_TIMEOUT + ); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Set SD Clock Enable in the Clock Control register to 0 + // + ClockCtrl = (UINT16)~BIT2; + Status = SdMmcHcAndMmio (PciIo, Slot, SD_MMC_HC_CLOCK_CTRL, sizeof (ClockCtrl), &ClockCtrl); + + return Status; +} + +/** + Start the SD clock. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] Slot The slot number. + + @retval EFI_SUCCESS Succeeded to start the SD clock. + @retval Others Failed to start the SD clock. +**/ +EFI_STATUS +SdMmcHcStartSdClock ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 Slot + ) +{ + UINT16 ClockCtrl; + + // + // Set SD Clock Enable in the Clock Control register to 1 + // + ClockCtrl = BIT2; + return SdMmcHcOrMmio (PciIo, Slot, SD_MMC_HC_CLOCK_CTRL, sizeof (ClockCtrl), &ClockCtrl); +} + +/** + SD/MMC card clock supply. + + Refer to SD Host Controller Simplified spec 3.0 Section 3.2.1 for details. + + @param[in] Private A pointer to the SD_MMC_HC_PRIVATE_DATA instance. + @param[in] Slot The slot number of the SD card to send the command to. + @param[in] BusTiming BusTiming at which the frequency change is done. + @param[in] FirstTimeSetup Flag to indicate whether the clock is being setup for the first time. + @param[in] ClockFreq The max clock frequency to be set. The unit is KHz. + + @retval EFI_SUCCESS The clock is supplied successfully. + @retval Others The clock isn't supplied successfully. + +**/ +EFI_STATUS +SdMmcHcClockSupply ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN UINT8 Slot, + IN SD_MMC_BUS_MODE BusTiming, + IN BOOLEAN FirstTimeSetup, + IN UINT64 ClockFreq + ) +{ + EFI_STATUS Status; + UINT32 SettingFreq; + UINT32 Divisor; + UINT32 Remainder; + UINT16 ClockCtrl; + UINT32 BaseClkFreq; + UINT16 ControllerVer; + EFI_PCI_IO_PROTOCOL *PciIo; + + PciIo = Private->PciIo; + BaseClkFreq = Private->BaseClkFreq[Slot]; + ControllerVer = Private->ControllerVersion[Slot]; + + if (BaseClkFreq == 0 || ClockFreq == 0) { + return EFI_INVALID_PARAMETER; + } + + if (ClockFreq > (BaseClkFreq * 1000)) { + ClockFreq = BaseClkFreq * 1000; + } + + // + // Calculate the divisor of base frequency. + // + Divisor = 0; + SettingFreq = BaseClkFreq * 1000; + while (ClockFreq < SettingFreq) { + Divisor++; + + SettingFreq = (BaseClkFreq * 1000) / (2 * Divisor); + Remainder = (BaseClkFreq * 1000) % (2 * Divisor); + if ((ClockFreq == SettingFreq) && (Remainder == 0)) { + break; + } + if ((ClockFreq == SettingFreq) && (Remainder != 0)) { + SettingFreq ++; + } + } + + DEBUG ((DEBUG_INFO, "BaseClkFreq %dMHz Divisor %d ClockFreq %dKhz\n", BaseClkFreq, Divisor, ClockFreq)); + + // + // Set SDCLK Frequency Select and Internal Clock Enable fields in Clock Control register. + // + if ((ControllerVer >= SD_MMC_HC_CTRL_VER_300) && + (ControllerVer <= SD_MMC_HC_CTRL_VER_420)) { + ASSERT (Divisor <= 0x3FF); + ClockCtrl = ((Divisor & 0xFF) << 8) | ((Divisor & 0x300) >> 2); + } else if ((ControllerVer == SD_MMC_HC_CTRL_VER_100) || + (ControllerVer == SD_MMC_HC_CTRL_VER_200)) { + // + // Only the most significant bit can be used as divisor. + // + if (((Divisor - 1) & Divisor) != 0) { + Divisor = 1 << (HighBitSet32 (Divisor) + 1); + } + ASSERT (Divisor <= 0x80); + ClockCtrl = (Divisor & 0xFF) << 8; + } else { + DEBUG ((DEBUG_ERROR, "Unknown SD Host Controller Spec version [0x%x]!!!\n", ControllerVer)); + return EFI_UNSUPPORTED; + } + + // + // Stop bus clock at first + // + Status = SdMmcHcStopClock (PciIo, Slot); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Supply clock frequency with specified divisor + // + ClockCtrl |= BIT0; + Status = SdMmcHcRwMmio (PciIo, Slot, SD_MMC_HC_CLOCK_CTRL, FALSE, sizeof (ClockCtrl), &ClockCtrl); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Set SDCLK Frequency Select and Internal Clock Enable fields fails\n")); + return Status; + } + + // + // Wait Internal Clock Stable in the Clock Control register to be 1 + // + Status = SdMmcHcWaitMmioSet ( + PciIo, + Slot, + SD_MMC_HC_CLOCK_CTRL, + sizeof (ClockCtrl), + BIT1, + BIT1, + SD_MMC_HC_GENERIC_TIMEOUT + ); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = SdMmcHcStartSdClock (PciIo, Slot); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // We don't notify the platform on first time setup to avoid changing + // legacy behavior. During first time setup we also don't know what type + // of the card slot it is and which enum value of BusTiming applies. + // + if (!FirstTimeSetup && mOverride != NULL && mOverride->NotifyPhase != NULL) { + Status = mOverride->NotifyPhase ( + Private->ControllerHandle, + Slot, + EdkiiSdMmcSwitchClockFreqPost, + &BusTiming + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "%a: SD/MMC switch clock freq post notifier callback failed - %r\n", + __FUNCTION__, + Status + )); + return Status; + } + } + + Private->Slot[Slot].CurrentFreq = ClockFreq; + + return Status; +} + +/** + SD/MMC bus power control. + + Refer to SD Host Controller Simplified spec 3.0 Section 3.3 for details. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] Slot The slot number of the SD card to send the command to. + @param[in] PowerCtrl The value setting to the power control register. + + @retval TRUE There is a SD/MMC card attached. + @retval FALSE There is no a SD/MMC card attached. + +**/ +EFI_STATUS +SdMmcHcPowerControl ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 Slot, + IN UINT8 PowerCtrl + ) +{ + EFI_STATUS Status; + + // + // Clr SD Bus Power + // + PowerCtrl &= (UINT8)~BIT0; + Status = SdMmcHcRwMmio (PciIo, Slot, SD_MMC_HC_POWER_CTRL, FALSE, sizeof (PowerCtrl), &PowerCtrl); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Set SD Bus Voltage Select and SD Bus Power fields in Power Control Register + // + PowerCtrl |= BIT0; + Status = SdMmcHcRwMmio (PciIo, Slot, SD_MMC_HC_POWER_CTRL, FALSE, sizeof (PowerCtrl), &PowerCtrl); + + return Status; +} + +/** + Set the SD/MMC bus width. + + Refer to SD Host Controller Simplified spec 3.0 Section 3.4 for details. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] Slot The slot number of the SD card to send the command to. + @param[in] BusWidth The bus width used by the SD/MMC device, it must be 1, 4 or 8. + + @retval EFI_SUCCESS The bus width is set successfully. + @retval Others The bus width isn't set successfully. + +**/ +EFI_STATUS +SdMmcHcSetBusWidth ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 Slot, + IN UINT16 BusWidth + ) +{ + EFI_STATUS Status; + UINT8 HostCtrl1; + + if (BusWidth == 1) { + HostCtrl1 = (UINT8)~(BIT5 | BIT1); + Status = SdMmcHcAndMmio (PciIo, Slot, SD_MMC_HC_HOST_CTRL1, sizeof (HostCtrl1), &HostCtrl1); + } else if (BusWidth == 4) { + Status = SdMmcHcRwMmio (PciIo, Slot, SD_MMC_HC_HOST_CTRL1, TRUE, sizeof (HostCtrl1), &HostCtrl1); + if (EFI_ERROR (Status)) { + return Status; + } + HostCtrl1 |= BIT1; + HostCtrl1 &= (UINT8)~BIT5; + Status = SdMmcHcRwMmio (PciIo, Slot, SD_MMC_HC_HOST_CTRL1, FALSE, sizeof (HostCtrl1), &HostCtrl1); + } else if (BusWidth == 8) { + Status = SdMmcHcRwMmio (PciIo, Slot, SD_MMC_HC_HOST_CTRL1, TRUE, sizeof (HostCtrl1), &HostCtrl1); + if (EFI_ERROR (Status)) { + return Status; + } + HostCtrl1 &= (UINT8)~BIT1; + HostCtrl1 |= BIT5; + Status = SdMmcHcRwMmio (PciIo, Slot, SD_MMC_HC_HOST_CTRL1, FALSE, sizeof (HostCtrl1), &HostCtrl1); + } else { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + return Status; +} + +/** + Configure V4 controller enhancements at initialization. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] Slot The slot number of the SD card to send the command to. + @param[in] Capability The capability of the slot. + @param[in] ControllerVer The version of host controller. + + @retval EFI_SUCCESS The clock is supplied successfully. + +**/ +EFI_STATUS +SdMmcHcInitV4Enhancements ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 Slot, + IN SD_MMC_HC_SLOT_CAP Capability, + IN UINT16 ControllerVer + ) +{ + EFI_STATUS Status; + UINT16 HostCtrl2; + + // + // Check if controller version V4 or higher + // + if (ControllerVer >= SD_MMC_HC_CTRL_VER_400) { + HostCtrl2 = SD_MMC_HC_V4_EN; + // + // Check if controller version V4.0 + // + if (ControllerVer == SD_MMC_HC_CTRL_VER_400) { + // + // Check if 64bit support is available + // + if (Capability.SysBus64V3 != 0) { + HostCtrl2 |= SD_MMC_HC_64_ADDR_EN; + DEBUG ((DEBUG_INFO, "Enabled V4 64 bit system bus support\n")); + } + } + // + // Check if controller version V4.10 or higher + // + else if (ControllerVer >= SD_MMC_HC_CTRL_VER_410) { + // + // Check if 64bit support is available + // + if (Capability.SysBus64V4 != 0) { + HostCtrl2 |= SD_MMC_HC_64_ADDR_EN; + DEBUG ((DEBUG_INFO, "Enabled V4 64 bit system bus support\n")); + } + HostCtrl2 |= SD_MMC_HC_26_DATA_LEN_ADMA_EN; + DEBUG ((DEBUG_INFO, "Enabled V4 26 bit data length ADMA support\n")); + } + Status = SdMmcHcOrMmio (PciIo, Slot, SD_MMC_HC_HOST_CTRL2, sizeof (HostCtrl2), &HostCtrl2); + if (EFI_ERROR (Status)) { + return Status; + } + } + + return EFI_SUCCESS; +} + +/** + Supply SD/MMC card with maximum voltage at initialization. + + Refer to SD Host Controller Simplified spec 3.0 Section 3.3 for details. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] Slot The slot number of the SD card to send the command to. + @param[in] Capability The capability of the slot. + + @retval EFI_SUCCESS The voltage is supplied successfully. + @retval Others The voltage isn't supplied successfully. + +**/ +EFI_STATUS +SdMmcHcInitPowerVoltage ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 Slot, + IN SD_MMC_HC_SLOT_CAP Capability + ) +{ + EFI_STATUS Status; + UINT8 MaxVoltage; + UINT8 HostCtrl2; + + // + // Calculate supported maximum voltage according to SD Bus Voltage Select + // + if (Capability.Voltage33 != 0) { + // + // Support 3.3V + // + MaxVoltage = 0x0E; + } else if (Capability.Voltage30 != 0) { + // + // Support 3.0V + // + MaxVoltage = 0x0C; + } else if (Capability.Voltage18 != 0) { + // + // Support 1.8V + // + MaxVoltage = 0x0A; + HostCtrl2 = BIT3; + Status = SdMmcHcOrMmio (PciIo, Slot, SD_MMC_HC_HOST_CTRL2, sizeof (HostCtrl2), &HostCtrl2); + gBS->Stall (5000); + if (EFI_ERROR (Status)) { + return Status; + } + } else { + ASSERT (FALSE); + return EFI_DEVICE_ERROR; + } + + // + // Set SD Bus Voltage Select and SD Bus Power fields in Power Control Register + // + Status = SdMmcHcPowerControl (PciIo, Slot, MaxVoltage); + + return Status; +} + +/** + Initialize the Timeout Control register with most conservative value at initialization. + + Refer to SD Host Controller Simplified spec 3.0 Section 2.2.15 for details. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] Slot The slot number of the SD card to send the command to. + + @retval EFI_SUCCESS The timeout control register is configured successfully. + @retval Others The timeout control register isn't configured successfully. + +**/ +EFI_STATUS +SdMmcHcInitTimeoutCtrl ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 Slot + ) +{ + EFI_STATUS Status; + UINT8 Timeout; + + Timeout = 0x0E; + Status = SdMmcHcRwMmio (PciIo, Slot, SD_MMC_HC_TIMEOUT_CTRL, FALSE, sizeof (Timeout), &Timeout); + + return Status; +} + +/** + Initial SD/MMC host controller with lowest clock frequency, max power and max timeout value + at initialization. + + @param[in] Private A pointer to the SD_MMC_HC_PRIVATE_DATA instance. + @param[in] Slot The slot number of the SD card to send the command to. + + @retval EFI_SUCCESS The host controller is initialized successfully. + @retval Others The host controller isn't initialized successfully. + +**/ +EFI_STATUS +SdMmcHcInitHost ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN UINT8 Slot + ) +{ + EFI_STATUS Status; + EFI_PCI_IO_PROTOCOL *PciIo; + SD_MMC_HC_SLOT_CAP Capability; + + // + // Notify the SD/MMC override protocol that we are about to initialize + // the SD/MMC host controller. + // + if (mOverride != NULL && mOverride->NotifyPhase != NULL) { + Status = mOverride->NotifyPhase ( + Private->ControllerHandle, + Slot, + EdkiiSdMmcInitHostPre, + NULL); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, + "%a: SD/MMC pre init notifier callback failed - %r\n", + __FUNCTION__, Status)); + return Status; + } + } + + PciIo = Private->PciIo; + Capability = Private->Capability[Slot]; + + Status = SdMmcHcInitV4Enhancements (PciIo, Slot, Capability, Private->ControllerVersion[Slot]); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Perform first time clock setup with 400 KHz frequency. + // We send the 0 as the BusTiming value because at this time + // we still do not know the slot type and which enum value will apply. + // Since it is a first time setup SdMmcHcClockSupply won't notify + // the platofrm driver anyway so it doesn't matter. + // + Status = SdMmcHcClockSupply (Private, Slot, 0, TRUE, 400); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = SdMmcHcInitPowerVoltage (PciIo, Slot, Capability); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = SdMmcHcInitTimeoutCtrl (PciIo, Slot); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Notify the SD/MMC override protocol that we are have just initialized + // the SD/MMC host controller. + // + if (mOverride != NULL && mOverride->NotifyPhase != NULL) { + Status = mOverride->NotifyPhase ( + Private->ControllerHandle, + Slot, + EdkiiSdMmcInitHostPost, + NULL); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, + "%a: SD/MMC post init notifier callback failed - %r\n", + __FUNCTION__, Status)); + } + } + return Status; +} + +/** + Set SD Host Controler control 2 registry according to selected speed. + + @param[in] ControllerHandle The handle of the controller. + @param[in] PciIo The PCI IO protocol instance. + @param[in] Slot The slot number of the SD card to send the command to. + @param[in] Timing The timing to select. + + @retval EFI_SUCCESS The timing is set successfully. + @retval Others The timing isn't set successfully. +**/ +EFI_STATUS +SdMmcHcUhsSignaling ( + IN EFI_HANDLE ControllerHandle, + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 Slot, + IN SD_MMC_BUS_MODE Timing + ) +{ + EFI_STATUS Status; + UINT8 HostCtrl2; + + HostCtrl2 = (UINT8)~SD_MMC_HC_CTRL_UHS_MASK; + Status = SdMmcHcAndMmio (PciIo, Slot, SD_MMC_HC_HOST_CTRL2, sizeof (HostCtrl2), &HostCtrl2); + if (EFI_ERROR (Status)) { + return Status; + } + + switch (Timing) { + case SdMmcUhsSdr12: + HostCtrl2 = SD_MMC_HC_CTRL_UHS_SDR12; + break; + case SdMmcUhsSdr25: + HostCtrl2 = SD_MMC_HC_CTRL_UHS_SDR25; + break; + case SdMmcUhsSdr50: + HostCtrl2 = SD_MMC_HC_CTRL_UHS_SDR50; + break; + case SdMmcUhsSdr104: + HostCtrl2 = SD_MMC_HC_CTRL_UHS_SDR104; + break; + case SdMmcUhsDdr50: + HostCtrl2 = SD_MMC_HC_CTRL_UHS_DDR50; + break; + case SdMmcMmcLegacy: + HostCtrl2 = SD_MMC_HC_CTRL_MMC_LEGACY; + break; + case SdMmcMmcHsSdr: + HostCtrl2 = SD_MMC_HC_CTRL_MMC_HS_SDR; + break; + case SdMmcMmcHsDdr: + HostCtrl2 = SD_MMC_HC_CTRL_MMC_HS_DDR; + break; + case SdMmcMmcHs200: + HostCtrl2 = SD_MMC_HC_CTRL_MMC_HS200; + break; + case SdMmcMmcHs400: + HostCtrl2 = SD_MMC_HC_CTRL_MMC_HS400; + break; + default: + HostCtrl2 = 0; + break; + } + Status = SdMmcHcOrMmio (PciIo, Slot, SD_MMC_HC_HOST_CTRL2, sizeof (HostCtrl2), &HostCtrl2); + if (EFI_ERROR (Status)) { + return Status; + } + + if (mOverride != NULL && mOverride->NotifyPhase != NULL) { + Status = mOverride->NotifyPhase ( + ControllerHandle, + Slot, + EdkiiSdMmcUhsSignaling, + &Timing + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "%a: SD/MMC uhs signaling notifier callback failed - %r\n", + __FUNCTION__, + Status + )); + return Status; + } + } + + return EFI_SUCCESS; +} + +/** + Set driver strength in host controller. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] SlotIndex The slot index of the card. + @param[in] DriverStrength DriverStrength to set in the controller. + + @retval EFI_SUCCESS Driver strength programmed successfully. + @retval Others Failed to set driver strength. +**/ +EFI_STATUS +SdMmcSetDriverStrength ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 SlotIndex, + IN SD_DRIVER_STRENGTH_TYPE DriverStrength + ) +{ + EFI_STATUS Status; + UINT16 HostCtrl2; + + if (DriverStrength == SdDriverStrengthIgnore) { + return EFI_SUCCESS; + } + + HostCtrl2 = (UINT16)~SD_MMC_HC_CTRL_DRIVER_STRENGTH_MASK; + Status = SdMmcHcAndMmio (PciIo, SlotIndex, SD_MMC_HC_HOST_CTRL2, sizeof (HostCtrl2), &HostCtrl2); + if (EFI_ERROR (Status)) { + return Status; + } + + HostCtrl2 = (DriverStrength << 4) & SD_MMC_HC_CTRL_DRIVER_STRENGTH_MASK; + return SdMmcHcOrMmio (PciIo, SlotIndex, SD_MMC_HC_HOST_CTRL2, sizeof (HostCtrl2), &HostCtrl2); +} + +/** + Turn on/off LED. + + @param[in] PciIo The PCI IO protocol instance. + @param[in] Slot The slot number of the SD card to send the command to. + @param[in] On The boolean to turn on/off LED. + + @retval EFI_SUCCESS The LED is turned on/off successfully. + @retval Others The LED isn't turned on/off successfully. + +**/ +EFI_STATUS +SdMmcHcLedOnOff ( + IN EFI_PCI_IO_PROTOCOL *PciIo, + IN UINT8 Slot, + IN BOOLEAN On + ) +{ + EFI_STATUS Status; + UINT8 HostCtrl1; + + if (On) { + HostCtrl1 = BIT0; + Status = SdMmcHcOrMmio (PciIo, Slot, SD_MMC_HC_HOST_CTRL1, sizeof (HostCtrl1), &HostCtrl1); + } else { + HostCtrl1 = (UINT8)~BIT0; + Status = SdMmcHcAndMmio (PciIo, Slot, SD_MMC_HC_HOST_CTRL1, sizeof (HostCtrl1), &HostCtrl1); + } + + return Status; +} + +/** + Build ADMA descriptor table for transfer. + + Refer to SD Host Controller Simplified spec 4.2 Section 1.13 for details. + + @param[in] Trb The pointer to the SD_MMC_HC_TRB instance. + @param[in] ControllerVer The version of host controller. + + @retval EFI_SUCCESS The ADMA descriptor table is created successfully. + @retval Others The ADMA descriptor table isn't created successfully. + +**/ +EFI_STATUS +BuildAdmaDescTable ( + IN SD_MMC_HC_TRB *Trb, + IN UINT16 ControllerVer + ) +{ + EFI_PHYSICAL_ADDRESS Data; + UINT64 DataLen; + UINT64 Entries; + UINT32 Index; + UINT64 Remaining; + UINT64 Address; + UINTN TableSize; + EFI_PCI_IO_PROTOCOL *PciIo; + EFI_STATUS Status; + UINTN Bytes; + UINT32 AdmaMaxDataPerLine; + UINT32 DescSize; + VOID *AdmaDesc; + + AdmaMaxDataPerLine = ADMA_MAX_DATA_PER_LINE_16B; + DescSize = sizeof (SD_MMC_HC_ADMA_32_DESC_LINE); + AdmaDesc = NULL; + + Data = Trb->DataPhy; + DataLen = Trb->DataLen; + PciIo = Trb->Private->PciIo; + + // + // Check for valid ranges in 32bit ADMA Descriptor Table + // + if ((Trb->Mode == SdMmcAdma32bMode) && + ((Data >= 0x100000000ul) || ((Data + DataLen) > 0x100000000ul))) { + return EFI_INVALID_PARAMETER; + } + // + // Check address field alignment + // + if (Trb->Mode != SdMmcAdma32bMode) { + // + // Address field shall be set on 64-bit boundary (Lower 3-bit is always set to 0) + // + if ((Data & (BIT0 | BIT1 | BIT2)) != 0) { + DEBUG ((DEBUG_INFO, "The buffer [0x%x] to construct ADMA desc is not aligned to 8 bytes boundary!\n", Data)); + } + } else { + // + // Address field shall be set on 32-bit boundary (Lower 2-bit is always set to 0) + // + if ((Data & (BIT0 | BIT1)) != 0) { + DEBUG ((DEBUG_INFO, "The buffer [0x%x] to construct ADMA desc is not aligned to 4 bytes boundary!\n", Data)); + } + } + + // + // Configure 64b ADMA. + // + if (Trb->Mode == SdMmcAdma64bV3Mode) { + DescSize = sizeof (SD_MMC_HC_ADMA_64_V3_DESC_LINE); + }else if (Trb->Mode == SdMmcAdma64bV4Mode) { + DescSize = sizeof (SD_MMC_HC_ADMA_64_V4_DESC_LINE); + } + // + // Configure 26b data length. + // + if (Trb->AdmaLengthMode == SdMmcAdmaLen26b) { + AdmaMaxDataPerLine = ADMA_MAX_DATA_PER_LINE_26B; + } + + Entries = DivU64x32 ((DataLen + AdmaMaxDataPerLine - 1), AdmaMaxDataPerLine); + TableSize = (UINTN)MultU64x32 (Entries, DescSize); + Trb->AdmaPages = (UINT32)EFI_SIZE_TO_PAGES (TableSize); + Status = PciIo->AllocateBuffer ( + PciIo, + AllocateAnyPages, + EfiBootServicesData, + EFI_SIZE_TO_PAGES (TableSize), + (VOID **)&AdmaDesc, + 0 + ); + if (EFI_ERROR (Status)) { + return EFI_OUT_OF_RESOURCES; + } + ZeroMem (AdmaDesc, TableSize); + Bytes = TableSize; + Status = PciIo->Map ( + PciIo, + EfiPciIoOperationBusMasterCommonBuffer, + AdmaDesc, + &Bytes, + &Trb->AdmaDescPhy, + &Trb->AdmaMap + ); + + if (EFI_ERROR (Status) || (Bytes != TableSize)) { + // + // Map error or unable to map the whole RFis buffer into a contiguous region. + // + PciIo->FreeBuffer ( + PciIo, + EFI_SIZE_TO_PAGES (TableSize), + AdmaDesc + ); + return EFI_OUT_OF_RESOURCES; + } + + if ((Trb->Mode == SdMmcAdma32bMode) && + (UINT64)(UINTN)Trb->AdmaDescPhy > 0x100000000ul) { + // + // The ADMA doesn't support 64bit addressing. + // + PciIo->Unmap ( + PciIo, + Trb->AdmaMap + ); + Trb->AdmaMap = NULL; + + PciIo->FreeBuffer ( + PciIo, + EFI_SIZE_TO_PAGES (TableSize), + AdmaDesc + ); + return EFI_DEVICE_ERROR; + } + + Remaining = DataLen; + Address = Data; + if (Trb->Mode == SdMmcAdma32bMode) { + Trb->Adma32Desc = AdmaDesc; + } else if (Trb->Mode == SdMmcAdma64bV3Mode) { + Trb->Adma64V3Desc = AdmaDesc; + } else { + Trb->Adma64V4Desc = AdmaDesc; + } + + for (Index = 0; Index < Entries; Index++) { + if (Trb->Mode == SdMmcAdma32bMode) { + if (Remaining <= AdmaMaxDataPerLine) { + Trb->Adma32Desc[Index].Valid = 1; + Trb->Adma32Desc[Index].Act = 2; + if (Trb->AdmaLengthMode == SdMmcAdmaLen26b) { + Trb->Adma32Desc[Index].UpperLength = (UINT16)RShiftU64 (Remaining, 16); + } + Trb->Adma32Desc[Index].LowerLength = (UINT16)(Remaining & MAX_UINT16); + Trb->Adma32Desc[Index].Address = (UINT32)Address; + break; + } else { + Trb->Adma32Desc[Index].Valid = 1; + Trb->Adma32Desc[Index].Act = 2; + if (Trb->AdmaLengthMode == SdMmcAdmaLen26b) { + Trb->Adma32Desc[Index].UpperLength = 0; + } + Trb->Adma32Desc[Index].LowerLength = 0; + Trb->Adma32Desc[Index].Address = (UINT32)Address; + } + } else if (Trb->Mode == SdMmcAdma64bV3Mode) { + if (Remaining <= AdmaMaxDataPerLine) { + Trb->Adma64V3Desc[Index].Valid = 1; + Trb->Adma64V3Desc[Index].Act = 2; + if (Trb->AdmaLengthMode == SdMmcAdmaLen26b) { + Trb->Adma64V3Desc[Index].UpperLength = (UINT16)RShiftU64 (Remaining, 16); + } + Trb->Adma64V3Desc[Index].LowerLength = (UINT16)(Remaining & MAX_UINT16); + Trb->Adma64V3Desc[Index].LowerAddress = (UINT32)Address; + Trb->Adma64V3Desc[Index].UpperAddress = (UINT32)RShiftU64 (Address, 32); + break; + } else { + Trb->Adma64V3Desc[Index].Valid = 1; + Trb->Adma64V3Desc[Index].Act = 2; + if (Trb->AdmaLengthMode == SdMmcAdmaLen26b) { + Trb->Adma64V3Desc[Index].UpperLength = 0; + } + Trb->Adma64V3Desc[Index].LowerLength = 0; + Trb->Adma64V3Desc[Index].LowerAddress = (UINT32)Address; + Trb->Adma64V3Desc[Index].UpperAddress = (UINT32)RShiftU64 (Address, 32); + } + } else { + if (Remaining <= AdmaMaxDataPerLine) { + Trb->Adma64V4Desc[Index].Valid = 1; + Trb->Adma64V4Desc[Index].Act = 2; + if (Trb->AdmaLengthMode == SdMmcAdmaLen26b) { + Trb->Adma64V4Desc[Index].UpperLength = (UINT16)RShiftU64 (Remaining, 16); + } + Trb->Adma64V4Desc[Index].LowerLength = (UINT16)(Remaining & MAX_UINT16); + Trb->Adma64V4Desc[Index].LowerAddress = (UINT32)Address; + Trb->Adma64V4Desc[Index].UpperAddress = (UINT32)RShiftU64 (Address, 32); + break; + } else { + Trb->Adma64V4Desc[Index].Valid = 1; + Trb->Adma64V4Desc[Index].Act = 2; + if (Trb->AdmaLengthMode == SdMmcAdmaLen26b) { + Trb->Adma64V4Desc[Index].UpperLength = 0; + } + Trb->Adma64V4Desc[Index].LowerLength = 0; + Trb->Adma64V4Desc[Index].LowerAddress = (UINT32)Address; + Trb->Adma64V4Desc[Index].UpperAddress = (UINT32)RShiftU64 (Address, 32); + } + } + + Remaining -= AdmaMaxDataPerLine; + Address += AdmaMaxDataPerLine; + } + + // + // Set the last descriptor line as end of descriptor table + // + if (Trb->Mode == SdMmcAdma32bMode) { + Trb->Adma32Desc[Index].End = 1; + } else if (Trb->Mode == SdMmcAdma64bV3Mode) { + Trb->Adma64V3Desc[Index].End = 1; + } else { + Trb->Adma64V4Desc[Index].End = 1; + } + return EFI_SUCCESS; +} + +/** + Prints the contents of the command packet to the debug port. + + @param[in] DebugLevel Debug level at which the packet should be printed. + @param[in] Packet Pointer to packet to print. +**/ +VOID +SdMmcPrintPacket ( + IN UINT32 DebugLevel, + IN EFI_SD_MMC_PASS_THRU_COMMAND_PACKET *Packet + ) +{ + if (Packet == NULL) { + return; + } + + DEBUG ((DebugLevel, "Printing EFI_SD_MMC_PASS_THRU_COMMAND_PACKET\n")); + if (Packet->SdMmcCmdBlk != NULL) { + DEBUG ((DebugLevel, "Command index: %d, argument: %X\n", Packet->SdMmcCmdBlk->CommandIndex, Packet->SdMmcCmdBlk->CommandArgument)); + DEBUG ((DebugLevel, "Command type: %d, response type: %d\n", Packet->SdMmcCmdBlk->CommandType, Packet->SdMmcCmdBlk->ResponseType)); + } + if (Packet->SdMmcStatusBlk != NULL) { + DEBUG ((DebugLevel, "Response 0: %X, 1: %X, 2: %X, 3: %X\n", + Packet->SdMmcStatusBlk->Resp0, + Packet->SdMmcStatusBlk->Resp1, + Packet->SdMmcStatusBlk->Resp2, + Packet->SdMmcStatusBlk->Resp3 + )); + } + DEBUG ((DebugLevel, "Timeout: %ld\n", Packet->Timeout)); + DEBUG ((DebugLevel, "InDataBuffer: %p\n", Packet->InDataBuffer)); + DEBUG ((DebugLevel, "OutDataBuffer: %p\n", Packet->OutDataBuffer)); + DEBUG ((DebugLevel, "InTransferLength: %d\n", Packet->InTransferLength)); + DEBUG ((DebugLevel, "OutTransferLength: %d\n", Packet->OutTransferLength)); + DEBUG ((DebugLevel, "TransactionStatus: %r\n", Packet->TransactionStatus)); +} + +/** + Prints the contents of the TRB to the debug port. + + @param[in] DebugLevel Debug level at which the TRB should be printed. + @param[in] Trb Pointer to the TRB structure. +**/ +VOID +SdMmcPrintTrb ( + IN UINT32 DebugLevel, + IN SD_MMC_HC_TRB *Trb + ) +{ + if (Trb == NULL) { + return; + } + + DEBUG ((DebugLevel, "Printing SD_MMC_HC_TRB\n")); + DEBUG ((DebugLevel, "Slot: %d\n", Trb->Slot)); + DEBUG ((DebugLevel, "BlockSize: %d\n", Trb->BlockSize)); + DEBUG ((DebugLevel, "Data: %p\n", Trb->Data)); + DEBUG ((DebugLevel, "DataLen: %d\n", Trb->DataLen)); + DEBUG ((DebugLevel, "Read: %d\n", Trb->Read)); + DEBUG ((DebugLevel, "DataPhy: %lX\n", Trb->DataPhy)); + DEBUG ((DebugLevel, "DataMap: %p\n", Trb->DataMap)); + DEBUG ((DebugLevel, "Mode: %d\n", Trb->Mode)); + DEBUG ((DebugLevel, "AdmaLengthMode: %d\n", Trb->AdmaLengthMode)); + DEBUG ((DebugLevel, "Event: %p\n", Trb->Event)); + DEBUG ((DebugLevel, "Started: %d\n", Trb->Started)); + DEBUG ((DebugLevel, "CommandComplete: %d\n", Trb->CommandComplete)); + DEBUG ((DebugLevel, "Timeout: %ld\n", Trb->Timeout)); + DEBUG ((DebugLevel, "Retries: %d\n", Trb->Retries)); + DEBUG ((DebugLevel, "PioModeTransferCompleted: %d\n", Trb->PioModeTransferCompleted)); + DEBUG ((DebugLevel, "PioBlockIndex: %d\n", Trb->PioBlockIndex)); + DEBUG ((DebugLevel, "Adma32Desc: %p\n", Trb->Adma32Desc)); + DEBUG ((DebugLevel, "Adma64V3Desc: %p\n", Trb->Adma64V3Desc)); + DEBUG ((DebugLevel, "Adma64V4Desc: %p\n", Trb->Adma64V4Desc)); + DEBUG ((DebugLevel, "AdmaMap: %p\n", Trb->AdmaMap)); + DEBUG ((DebugLevel, "AdmaPages: %X\n", Trb->AdmaPages)); + + SdMmcPrintPacket (DebugLevel, Trb->Packet); +} + +/** + Sets up host memory to allow DMA transfer. + + @param[in] Private A pointer to the SD_MMC_HC_PRIVATE_DATA instance. + @param[in] Slot The slot number of the SD card to send the command to. + @param[in] Packet A pointer to the SD command data structure. + + @retval EFI_SUCCESS Memory has been mapped for DMA transfer. + @retval Others Memory has not been mapped. +**/ +EFI_STATUS +SdMmcSetupMemoryForDmaTransfer ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN UINT8 Slot, + IN SD_MMC_HC_TRB *Trb + ) +{ + EFI_PCI_IO_PROTOCOL_OPERATION Flag; + EFI_PCI_IO_PROTOCOL *PciIo; + UINTN MapLength; + EFI_STATUS Status; + + if (Trb->Read) { + Flag = EfiPciIoOperationBusMasterWrite; + } else { + Flag = EfiPciIoOperationBusMasterRead; + } + + PciIo = Private->PciIo; + if (Trb->Data != NULL && Trb->DataLen != 0) { + MapLength = Trb->DataLen; + Status = PciIo->Map ( + PciIo, + Flag, + Trb->Data, + &MapLength, + &Trb->DataPhy, + &Trb->DataMap + ); + if (EFI_ERROR (Status) || (Trb->DataLen != MapLength)) { + return EFI_BAD_BUFFER_SIZE; + } + } + + if (Trb->Mode == SdMmcAdma32bMode || + Trb->Mode == SdMmcAdma64bV3Mode || + Trb->Mode == SdMmcAdma64bV4Mode) { + Status = BuildAdmaDescTable (Trb, Private->ControllerVersion[Slot]); + if (EFI_ERROR (Status)) { + return Status; + } + } + + return EFI_SUCCESS; +} + +/** + Create a new TRB for the SD/MMC cmd request. + + @param[in] Private A pointer to the SD_MMC_HC_PRIVATE_DATA instance. + @param[in] Slot The slot number of the SD card to send the command to. + @param[in] Packet A pointer to the SD command data structure. + @param[in] Event If Event is NULL, blocking I/O is performed. If Event is + not NULL, then nonblocking I/O is performed, and Event + will be signaled when the Packet completes. + + @return Created Trb or NULL. + +**/ +SD_MMC_HC_TRB * +SdMmcCreateTrb ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN UINT8 Slot, + IN EFI_SD_MMC_PASS_THRU_COMMAND_PACKET *Packet, + IN EFI_EVENT Event + ) +{ + SD_MMC_HC_TRB *Trb; + EFI_STATUS Status; + EFI_TPL OldTpl; + + Trb = AllocateZeroPool (sizeof (SD_MMC_HC_TRB)); + if (Trb == NULL) { + return NULL; + } + + Trb->Signature = SD_MMC_HC_TRB_SIG; + Trb->Slot = Slot; + Trb->BlockSize = 0x200; + Trb->Packet = Packet; + Trb->Event = Event; + Trb->Started = FALSE; + Trb->CommandComplete = FALSE; + Trb->Timeout = Packet->Timeout; + Trb->Retries = SD_MMC_TRB_RETRIES; + Trb->PioModeTransferCompleted = FALSE; + Trb->PioBlockIndex = 0; + Trb->Private = Private; + + if ((Packet->InTransferLength != 0) && (Packet->InDataBuffer != NULL)) { + Trb->Data = Packet->InDataBuffer; + Trb->DataLen = Packet->InTransferLength; + Trb->Read = TRUE; + } else if ((Packet->OutTransferLength != 0) && (Packet->OutDataBuffer != NULL)) { + Trb->Data = Packet->OutDataBuffer; + Trb->DataLen = Packet->OutTransferLength; + Trb->Read = FALSE; + } else if ((Packet->InTransferLength == 0) && (Packet->OutTransferLength == 0)) { + Trb->Data = NULL; + Trb->DataLen = 0; + } else { + goto Error; + } + + if ((Trb->DataLen != 0) && (Trb->DataLen < Trb->BlockSize)) { + Trb->BlockSize = (UINT16)Trb->DataLen; + } + + if (((Private->Slot[Trb->Slot].CardType == EmmcCardType) && + (Packet->SdMmcCmdBlk->CommandIndex == EMMC_SEND_TUNING_BLOCK)) || + ((Private->Slot[Trb->Slot].CardType == SdCardType) && + (Packet->SdMmcCmdBlk->CommandIndex == SD_SEND_TUNING_BLOCK))) { + Trb->Mode = SdMmcPioMode; + } else { + if (Trb->DataLen == 0) { + Trb->Mode = SdMmcNoData; + } else if (Private->Capability[Slot].Adma2 != 0) { + Trb->Mode = SdMmcAdma32bMode; + Trb->AdmaLengthMode = SdMmcAdmaLen16b; + if ((Private->ControllerVersion[Slot] == SD_MMC_HC_CTRL_VER_300) && + (Private->Capability[Slot].SysBus64V3 == 1)) { + Trb->Mode = SdMmcAdma64bV3Mode; + } else if (((Private->ControllerVersion[Slot] == SD_MMC_HC_CTRL_VER_400) && + (Private->Capability[Slot].SysBus64V3 == 1)) || + ((Private->ControllerVersion[Slot] >= SD_MMC_HC_CTRL_VER_410) && + (Private->Capability[Slot].SysBus64V4 == 1))) { + Trb->Mode = SdMmcAdma64bV4Mode; + } + if (Private->ControllerVersion[Slot] >= SD_MMC_HC_CTRL_VER_410) { + Trb->AdmaLengthMode = SdMmcAdmaLen26b; + } + Status = SdMmcSetupMemoryForDmaTransfer (Private, Slot, Trb); + if (EFI_ERROR (Status)) { + goto Error; + } + } else if (Private->Capability[Slot].Sdma != 0) { + Trb->Mode = SdMmcSdmaMode; + Status = SdMmcSetupMemoryForDmaTransfer (Private, Slot, Trb); + if (EFI_ERROR (Status)) { + goto Error; + } + } else { + Trb->Mode = SdMmcPioMode; + } + } + + if (Event != NULL) { + OldTpl = gBS->RaiseTPL (TPL_NOTIFY); + InsertTailList (&Private->Queue, &Trb->TrbList); + gBS->RestoreTPL (OldTpl); + } + + return Trb; + +Error: + SdMmcFreeTrb (Trb); + return NULL; +} + +/** + Free the resource used by the TRB. + + @param[in] Trb The pointer to the SD_MMC_HC_TRB instance. + +**/ +VOID +SdMmcFreeTrb ( + IN SD_MMC_HC_TRB *Trb + ) +{ + EFI_PCI_IO_PROTOCOL *PciIo; + + PciIo = Trb->Private->PciIo; + + if (Trb->AdmaMap != NULL) { + PciIo->Unmap ( + PciIo, + Trb->AdmaMap + ); + } + if (Trb->Adma32Desc != NULL) { + PciIo->FreeBuffer ( + PciIo, + Trb->AdmaPages, + Trb->Adma32Desc + ); + } + if (Trb->Adma64V3Desc != NULL) { + PciIo->FreeBuffer ( + PciIo, + Trb->AdmaPages, + Trb->Adma64V3Desc + ); + } + if (Trb->Adma64V4Desc != NULL) { + PciIo->FreeBuffer ( + PciIo, + Trb->AdmaPages, + Trb->Adma64V4Desc + ); + } + if (Trb->DataMap != NULL) { + PciIo->Unmap ( + PciIo, + Trb->DataMap + ); + } + FreePool (Trb); + return; +} + +/** + Check if the env is ready for execute specified TRB. + + @param[in] Private A pointer to the SD_MMC_HC_PRIVATE_DATA instance. + @param[in] Trb The pointer to the SD_MMC_HC_TRB instance. + + @retval EFI_SUCCESS The env is ready for TRB execution. + @retval EFI_NOT_READY The env is not ready for TRB execution. + @retval Others Some erros happen. + +**/ +EFI_STATUS +SdMmcCheckTrbEnv ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN SD_MMC_HC_TRB *Trb + ) +{ + EFI_STATUS Status; + EFI_SD_MMC_PASS_THRU_COMMAND_PACKET *Packet; + EFI_PCI_IO_PROTOCOL *PciIo; + UINT32 PresentState; + + Packet = Trb->Packet; + + if ((Packet->SdMmcCmdBlk->CommandType == SdMmcCommandTypeAdtc) || + (Packet->SdMmcCmdBlk->ResponseType == SdMmcResponseTypeR1b) || + (Packet->SdMmcCmdBlk->ResponseType == SdMmcResponseTypeR5b)) { + // + // Wait Command Inhibit (CMD) and Command Inhibit (DAT) in + // the Present State register to be 0 + // + PresentState = BIT0 | BIT1; + } else { + // + // Wait Command Inhibit (CMD) in the Present State register + // to be 0 + // + PresentState = BIT0; + } + + PciIo = Private->PciIo; + Status = SdMmcHcCheckMmioSet ( + PciIo, + Trb->Slot, + SD_MMC_HC_PRESENT_STATE, + sizeof (PresentState), + PresentState, + 0 + ); + + return Status; +} + +/** + Wait for the env to be ready for execute specified TRB. + + @param[in] Private A pointer to the SD_MMC_HC_PRIVATE_DATA instance. + @param[in] Trb The pointer to the SD_MMC_HC_TRB instance. + + @retval EFI_SUCCESS The env is ready for TRB execution. + @retval EFI_TIMEOUT The env is not ready for TRB execution in time. + @retval Others Some erros happen. + +**/ +EFI_STATUS +SdMmcWaitTrbEnv ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN SD_MMC_HC_TRB *Trb + ) +{ + EFI_STATUS Status; + EFI_SD_MMC_PASS_THRU_COMMAND_PACKET *Packet; + UINT64 Timeout; + BOOLEAN InfiniteWait; + + // + // Wait Command Complete Interrupt Status bit in Normal Interrupt Status Register + // + Packet = Trb->Packet; + Timeout = Packet->Timeout; + if (Timeout == 0) { + InfiniteWait = TRUE; + } else { + InfiniteWait = FALSE; + } + + while (InfiniteWait || (Timeout > 0)) { + // + // Check Trb execution result by reading Normal Interrupt Status register. + // + Status = SdMmcCheckTrbEnv (Private, Trb); + if (Status != EFI_NOT_READY) { + return Status; + } + // + // Stall for 1 microsecond. + // + gBS->Stall (1); + + Timeout--; + } + + return EFI_TIMEOUT; +} + +/** + Execute the specified TRB. + + @param[in] Private A pointer to the SD_MMC_HC_PRIVATE_DATA instance. + @param[in] Trb The pointer to the SD_MMC_HC_TRB instance. + + @retval EFI_SUCCESS The TRB is sent to host controller successfully. + @retval Others Some erros happen when sending this request to the host controller. + +**/ +EFI_STATUS +SdMmcExecTrb ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN SD_MMC_HC_TRB *Trb + ) +{ + EFI_STATUS Status; + EFI_SD_MMC_PASS_THRU_COMMAND_PACKET *Packet; + EFI_PCI_IO_PROTOCOL *PciIo; + UINT16 Cmd; + UINT16 IntStatus; + UINT32 Argument; + UINT32 BlkCount; + UINT16 BlkSize; + UINT16 TransMode; + UINT8 HostCtrl1; + UINT64 SdmaAddr; + UINT64 AdmaAddr; + BOOLEAN AddressingMode64; + + AddressingMode64 = FALSE; + + Packet = Trb->Packet; + PciIo = Trb->Private->PciIo; + // + // Clear all bits in Error Interrupt Status Register + // + IntStatus = 0xFFFF; + Status = SdMmcHcRwMmio (PciIo, Trb->Slot, SD_MMC_HC_ERR_INT_STS, FALSE, sizeof (IntStatus), &IntStatus); + if (EFI_ERROR (Status)) { + return Status; + } + // + // Clear all bits in Normal Interrupt Status Register excepts for Card Removal & Card Insertion bits. + // + IntStatus = 0xFF3F; + Status = SdMmcHcRwMmio (PciIo, Trb->Slot, SD_MMC_HC_NOR_INT_STS, FALSE, sizeof (IntStatus), &IntStatus); + if (EFI_ERROR (Status)) { + return Status; + } + + if (Private->ControllerVersion[Trb->Slot] >= SD_MMC_HC_CTRL_VER_400) { + Status = SdMmcHcCheckMmioSet(PciIo, Trb->Slot, SD_MMC_HC_HOST_CTRL2, sizeof(UINT16), + SD_MMC_HC_64_ADDR_EN, SD_MMC_HC_64_ADDR_EN); + if (!EFI_ERROR (Status)) { + AddressingMode64 = TRUE; + } + } + + // + // Set Host Control 1 register DMA Select field + // + if ((Trb->Mode == SdMmcAdma32bMode) || + (Trb->Mode == SdMmcAdma64bV4Mode)) { + HostCtrl1 = BIT4; + Status = SdMmcHcOrMmio (PciIo, Trb->Slot, SD_MMC_HC_HOST_CTRL1, sizeof (HostCtrl1), &HostCtrl1); + if (EFI_ERROR (Status)) { + return Status; + } + } else if (Trb->Mode == SdMmcAdma64bV3Mode) { + HostCtrl1 = BIT4|BIT3; + Status = SdMmcHcOrMmio (PciIo, Trb->Slot, SD_MMC_HC_HOST_CTRL1, sizeof (HostCtrl1), &HostCtrl1); + if (EFI_ERROR (Status)) { + return Status; + } + } + + SdMmcHcLedOnOff (PciIo, Trb->Slot, TRUE); + + if (Trb->Mode == SdMmcSdmaMode) { + if ((!AddressingMode64) && + ((UINT64)(UINTN)Trb->DataPhy >= 0x100000000ul)) { + return EFI_INVALID_PARAMETER; + } + + SdmaAddr = (UINT64)(UINTN)Trb->DataPhy; + + if (Private->ControllerVersion[Trb->Slot] >= SD_MMC_HC_CTRL_VER_400) { + Status = SdMmcHcRwMmio (PciIo, Trb->Slot, SD_MMC_HC_ADMA_SYS_ADDR, FALSE, sizeof (UINT64), &SdmaAddr); + } else { + Status = SdMmcHcRwMmio (PciIo, Trb->Slot, SD_MMC_HC_SDMA_ADDR, FALSE, sizeof (UINT32), &SdmaAddr); + } + + if (EFI_ERROR (Status)) { + return Status; + } + } else if ((Trb->Mode == SdMmcAdma32bMode) || + (Trb->Mode == SdMmcAdma64bV3Mode) || + (Trb->Mode == SdMmcAdma64bV4Mode)) { + AdmaAddr = (UINT64)(UINTN)Trb->AdmaDescPhy; + Status = SdMmcHcRwMmio (PciIo, Trb->Slot, SD_MMC_HC_ADMA_SYS_ADDR, FALSE, sizeof (AdmaAddr), &AdmaAddr); + if (EFI_ERROR (Status)) { + return Status; + } + } + + BlkSize = Trb->BlockSize; + if (Trb->Mode == SdMmcSdmaMode) { + // + // Set SDMA boundary to be 512K bytes. + // + BlkSize |= 0x7000; + } + + Status = SdMmcHcRwMmio (PciIo, Trb->Slot, SD_MMC_HC_BLK_SIZE, FALSE, sizeof (BlkSize), &BlkSize); + if (EFI_ERROR (Status)) { + return Status; + } + + BlkCount = 0; + if (Trb->Mode != SdMmcNoData) { + // + // Calcuate Block Count. + // + BlkCount = (Trb->DataLen / Trb->BlockSize); + } + if (Private->ControllerVersion[Trb->Slot] >= SD_MMC_HC_CTRL_VER_410) { + Status = SdMmcHcRwMmio (PciIo, Trb->Slot, SD_MMC_HC_SDMA_ADDR, FALSE, sizeof (UINT32), &BlkCount); + } else { + Status = SdMmcHcRwMmio (PciIo, Trb->Slot, SD_MMC_HC_BLK_COUNT, FALSE, sizeof (UINT16), &BlkCount); + } + if (EFI_ERROR (Status)) { + return Status; + } + + Argument = Packet->SdMmcCmdBlk->CommandArgument; + Status = SdMmcHcRwMmio (PciIo, Trb->Slot, SD_MMC_HC_ARG1, FALSE, sizeof (Argument), &Argument); + if (EFI_ERROR (Status)) { + return Status; + } + + TransMode = 0; + if (Trb->Mode != SdMmcNoData) { + if (Trb->Mode != SdMmcPioMode) { + TransMode |= BIT0; + } + if (Trb->Read) { + TransMode |= BIT4; + } + if (BlkCount > 1) { + TransMode |= BIT5 | BIT1; + } + // + // Only SD memory card needs to use AUTO CMD12 feature. + // + if (Private->Slot[Trb->Slot].CardType == SdCardType) { + if (BlkCount > 1) { + TransMode |= BIT2; + } + } + } + + Status = SdMmcHcRwMmio (PciIo, Trb->Slot, SD_MMC_HC_TRANS_MOD, FALSE, sizeof (TransMode), &TransMode); + if (EFI_ERROR (Status)) { + return Status; + } + + Cmd = (UINT16)LShiftU64(Packet->SdMmcCmdBlk->CommandIndex, 8); + if (Packet->SdMmcCmdBlk->CommandType == SdMmcCommandTypeAdtc) { + Cmd |= BIT5; + } + // + // Convert ResponseType to value + // + if (Packet->SdMmcCmdBlk->CommandType != SdMmcCommandTypeBc) { + switch (Packet->SdMmcCmdBlk->ResponseType) { + case SdMmcResponseTypeR1: + case SdMmcResponseTypeR5: + case SdMmcResponseTypeR6: + case SdMmcResponseTypeR7: + Cmd |= (BIT1 | BIT3 | BIT4); + break; + case SdMmcResponseTypeR2: + Cmd |= (BIT0 | BIT3); + break; + case SdMmcResponseTypeR3: + case SdMmcResponseTypeR4: + Cmd |= BIT1; + break; + case SdMmcResponseTypeR1b: + case SdMmcResponseTypeR5b: + Cmd |= (BIT0 | BIT1 | BIT3 | BIT4); + break; + default: + ASSERT (FALSE); + break; + } + } + // + // Execute cmd + // + Status = SdMmcHcRwMmio (PciIo, Trb->Slot, SD_MMC_HC_COMMAND, FALSE, sizeof (Cmd), &Cmd); + return Status; +} + +/** + Performs SW reset based on passed error status mask. + + @param[in] Private Pointer to driver private data. + @param[in] Slot Index of the slot to reset. + @param[in] ErrIntStatus Error interrupt status mask. + + @retval EFI_SUCCESS Software reset performed successfully. + @retval Other Software reset failed. +**/ +EFI_STATUS +SdMmcSoftwareReset ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN UINT8 Slot, + IN UINT16 ErrIntStatus + ) +{ + UINT8 SwReset; + EFI_STATUS Status; + + SwReset = 0; + if ((ErrIntStatus & 0x0F) != 0) { + SwReset |= BIT1; + } + if ((ErrIntStatus & 0x70) != 0) { + SwReset |= BIT2; + } + + Status = SdMmcHcRwMmio ( + Private->PciIo, + Slot, + SD_MMC_HC_SW_RST, + FALSE, + sizeof (SwReset), + &SwReset + ); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = SdMmcHcWaitMmioSet ( + Private->PciIo, + Slot, + SD_MMC_HC_SW_RST, + sizeof (SwReset), + 0xFF, + 0, + SD_MMC_HC_GENERIC_TIMEOUT + ); + if (EFI_ERROR (Status)) { + return Status; + } + + return EFI_SUCCESS; +} + +/** + Checks the error status in error status register + and issues appropriate software reset as described in + SD specification section 3.10. + + @param[in] Private Pointer to driver private data. + @param[in] Slot Index of the slot for device. + @param[in] IntStatus Normal interrupt status mask. + + @retval EFI_CRC_ERROR CRC error happened during CMD execution. + @retval EFI_SUCCESS No error reported. + @retval Others Some other error happened. + +**/ +EFI_STATUS +SdMmcCheckAndRecoverErrors ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN UINT8 Slot, + IN UINT16 IntStatus + ) +{ + UINT16 ErrIntStatus; + EFI_STATUS Status; + EFI_STATUS ErrorStatus; + + if ((IntStatus & BIT15) == 0) { + return EFI_SUCCESS; + } + + Status = SdMmcHcRwMmio ( + Private->PciIo, + Slot, + SD_MMC_HC_ERR_INT_STS, + TRUE, + sizeof (ErrIntStatus), + &ErrIntStatus + ); + if (EFI_ERROR (Status)) { + return Status; + } + + DEBUG ((DEBUG_ERROR, "Error reported by SDHCI\n")); + DEBUG ((DEBUG_ERROR, "Interrupt status = %X\n", IntStatus)); + DEBUG ((DEBUG_ERROR, "Error interrupt status = %X\n", ErrIntStatus)); + + // + // If the data timeout error is reported + // but data transfer is signaled as completed we + // have to ignore data timeout. We also assume that no + // other error is present on the link since data transfer + // completed successfully. Error interrupt status + // register is going to be reset when the next command + // is started. + // + if (((ErrIntStatus & BIT4) != 0) && ((IntStatus & BIT1) != 0)) { + return EFI_SUCCESS; + } + + // + // We treat both CMD and DAT CRC errors and + // end bits errors as EFI_CRC_ERROR. This will + // let higher layer know that the error possibly + // happened due to random bus condition and the + // command can be retried. + // + if ((ErrIntStatus & (BIT1 | BIT2 | BIT5 | BIT6)) != 0) { + ErrorStatus = EFI_CRC_ERROR; + } else { + ErrorStatus = EFI_DEVICE_ERROR; + } + + Status = SdMmcSoftwareReset (Private, Slot, ErrIntStatus); + if (EFI_ERROR (Status)) { + return Status; + } + + return ErrorStatus; +} + +/** + Reads the response data into the TRB buffer. + This function assumes that caller made sure that + command has completed. + + @param[in] Private A pointer to the SD_MMC_HC_PRIVATE_DATA instance. + @param[in] Trb The pointer to the SD_MMC_HC_TRB instance. + + @retval EFI_SUCCESS Response read successfully. + @retval Others Failed to get response. +**/ +EFI_STATUS +SdMmcGetResponse ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN SD_MMC_HC_TRB *Trb + ) +{ + EFI_SD_MMC_PASS_THRU_COMMAND_PACKET *Packet; + UINT8 Index; + UINT32 Response[4]; + EFI_STATUS Status; + + Packet = Trb->Packet; + + if (Packet->SdMmcCmdBlk->CommandType == SdMmcCommandTypeBc) { + return EFI_SUCCESS; + } + + for (Index = 0; Index < 4; Index++) { + Status = SdMmcHcRwMmio ( + Private->PciIo, + Trb->Slot, + SD_MMC_HC_RESPONSE + Index * 4, + TRUE, + sizeof (UINT32), + &Response[Index] + ); + if (EFI_ERROR (Status)) { + return Status; + } + } + CopyMem (Packet->SdMmcStatusBlk, Response, sizeof (Response)); + + return EFI_SUCCESS; +} + +/** + Checks if the command completed. If the command + completed it gets the response and records the + command completion in the TRB. + + @param[in] Private A pointer to the SD_MMC_HC_PRIVATE_DATA instance. + @param[in] Trb The pointer to the SD_MMC_HC_TRB instance. + @param[in] IntStatus Snapshot of the normal interrupt status register. + + @retval EFI_SUCCESS Command completed successfully. + @retval EFI_NOT_READY Command completion still pending. + @retval Others Command failed to complete. +**/ +EFI_STATUS +SdMmcCheckCommandComplete ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN SD_MMC_HC_TRB *Trb, + IN UINT16 IntStatus + ) +{ + UINT16 Data16; + EFI_STATUS Status; + + if ((IntStatus & BIT0) != 0) { + Data16 = BIT0; + Status = SdMmcHcRwMmio ( + Private->PciIo, + Trb->Slot, + SD_MMC_HC_NOR_INT_STS, + FALSE, + sizeof (Data16), + &Data16 + ); + if (EFI_ERROR (Status)) { + return Status; + } + Status = SdMmcGetResponse (Private, Trb); + if (EFI_ERROR (Status)) { + return Status; + } + Trb->CommandComplete = TRUE; + return EFI_SUCCESS; + } + + return EFI_NOT_READY; +} + +/** + Transfers data from card using PIO method. + + @param[in] Private A pointer to the SD_MMC_HC_PRIVATE_DATA instance. + @param[in] Trb The pointer to the SD_MMC_HC_TRB instance. + @param[in] IntStatus Snapshot of the normal interrupt status register. + + @retval EFI_SUCCESS PIO transfer completed successfully. + @retval EFI_NOT_READY PIO transfer completion still pending. + @retval Others PIO transfer failed to complete. +**/ +EFI_STATUS +SdMmcTransferDataWithPio ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN SD_MMC_HC_TRB *Trb, + IN UINT16 IntStatus + ) +{ + EFI_STATUS Status; + UINT16 Data16; + UINT32 BlockCount; + EFI_PCI_IO_PROTOCOL_WIDTH Width; + UINTN Count; + + BlockCount = (Trb->DataLen / Trb->BlockSize); + if (Trb->DataLen % Trb->BlockSize != 0) { + BlockCount += 1; + } + + if (Trb->PioBlockIndex >= BlockCount) { + return EFI_SUCCESS; + } + + switch (Trb->BlockSize % sizeof (UINT32)) { + case 0: + Width = EfiPciIoWidthFifoUint32; + Count = Trb->BlockSize / sizeof (UINT32); + break; + case 2: + Width = EfiPciIoWidthFifoUint16; + Count = Trb->BlockSize / sizeof (UINT16); + break; + case 1: + case 3: + default: + Width = EfiPciIoWidthFifoUint8; + Count = Trb->BlockSize; + break; + } + + if (Trb->Read) { + if ((IntStatus & BIT5) == 0) { + return EFI_NOT_READY; + } + Data16 = BIT5; + SdMmcHcRwMmio (Private->PciIo, Trb->Slot, SD_MMC_HC_NOR_INT_STS, FALSE, sizeof (Data16), &Data16); + + Status = Private->PciIo->Mem.Read ( + Private->PciIo, + Width, + Trb->Slot, + SD_MMC_HC_BUF_DAT_PORT, + Count, + (VOID*)((UINT8*)Trb->Data + (Trb->BlockSize * Trb->PioBlockIndex)) + ); + if (EFI_ERROR (Status)) { + return Status; + } + Trb->PioBlockIndex++; + } else { + if ((IntStatus & BIT4) == 0) { + return EFI_NOT_READY; + } + Data16 = BIT4; + SdMmcHcRwMmio (Private->PciIo, Trb->Slot, SD_MMC_HC_NOR_INT_STS, FALSE, sizeof (Data16), &Data16); + + Status = Private->PciIo->Mem.Write ( + Private->PciIo, + Width, + Trb->Slot, + SD_MMC_HC_BUF_DAT_PORT, + Count, + (VOID*)((UINT8*)Trb->Data + (Trb->BlockSize * Trb->PioBlockIndex)) + ); + if (EFI_ERROR (Status)) { + return Status; + } + Trb->PioBlockIndex++; + } + + if (Trb->PioBlockIndex >= BlockCount) { + Trb->PioModeTransferCompleted = TRUE; + return EFI_SUCCESS; + } else { + return EFI_NOT_READY; + } +} + +/** + Update the SDMA address on the SDMA buffer boundary interrupt. + + @param[in] Private A pointer to the SD_MMC_HC_PRIVATE_DATA instance. + @param[in] Trb The pointer to the SD_MMC_HC_TRB instance. + + @retval EFI_SUCCESS Updated SDMA buffer address. + @retval Others Failed to update SDMA buffer address. +**/ +EFI_STATUS +SdMmcUpdateSdmaAddress ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN SD_MMC_HC_TRB *Trb + ) +{ + UINT64 SdmaAddr; + EFI_STATUS Status; + + SdmaAddr = SD_MMC_SDMA_ROUND_UP ((UINTN)Trb->DataPhy, SD_MMC_SDMA_BOUNDARY); + + if (Private->ControllerVersion[Trb->Slot] >= SD_MMC_HC_CTRL_VER_400) { + Status = SdMmcHcRwMmio ( + Private->PciIo, + Trb->Slot, + SD_MMC_HC_ADMA_SYS_ADDR, + FALSE, + sizeof (UINT64), + &SdmaAddr + ); + } else { + Status = SdMmcHcRwMmio ( + Private->PciIo, + Trb->Slot, + SD_MMC_HC_SDMA_ADDR, + FALSE, + sizeof (UINT32), + &SdmaAddr + ); + } + + if (EFI_ERROR (Status)) { + return Status; + } + + Trb->DataPhy = (UINT64)(UINTN)SdmaAddr; + return EFI_SUCCESS; +} + +/** + Checks if the data transfer completed and performs any actions + neccessary to continue the data transfer such as SDMA system + address fixup or PIO data transfer. + + @param[in] Private A pointer to the SD_MMC_HC_PRIVATE_DATA instance. + @param[in] Trb The pointer to the SD_MMC_HC_TRB instance. + @param[in] IntStatus Snapshot of the normal interrupt status register. + + @retval EFI_SUCCESS Data transfer completed successfully. + @retval EFI_NOT_READY Data transfer completion still pending. + @retval Others Data transfer failed to complete. +**/ +EFI_STATUS +SdMmcCheckDataTransfer ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN SD_MMC_HC_TRB *Trb, + IN UINT16 IntStatus + ) +{ + UINT16 Data16; + EFI_STATUS Status; + + if ((IntStatus & BIT1) != 0) { + Data16 = BIT1; + Status = SdMmcHcRwMmio ( + Private->PciIo, + Trb->Slot, + SD_MMC_HC_NOR_INT_STS, + FALSE, + sizeof (Data16), + &Data16 + ); + return Status; + } + + if (Trb->Mode == SdMmcPioMode && !Trb->PioModeTransferCompleted) { + Status = SdMmcTransferDataWithPio (Private, Trb, IntStatus); + if (EFI_ERROR (Status)) { + return Status; + } + } + + if ((Trb->Mode == SdMmcSdmaMode) && ((IntStatus & BIT3) != 0)) { + Data16 = BIT3; + Status = SdMmcHcRwMmio ( + Private->PciIo, + Trb->Slot, + SD_MMC_HC_NOR_INT_STS, + FALSE, + sizeof (Data16), + &Data16 + ); + if (EFI_ERROR (Status)) { + return Status; + } + Status = SdMmcUpdateSdmaAddress (Private, Trb); + if (EFI_ERROR (Status)) { + return Status; + } + } + + return EFI_NOT_READY; +} + +/** + Check the TRB execution result. + + @param[in] Private A pointer to the SD_MMC_HC_PRIVATE_DATA instance. + @param[in] Trb The pointer to the SD_MMC_HC_TRB instance. + + @retval EFI_SUCCESS The TRB is executed successfully. + @retval EFI_NOT_READY The TRB is not completed for execution. + @retval Others Some erros happen when executing this request. + +**/ +EFI_STATUS +SdMmcCheckTrbResult ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN SD_MMC_HC_TRB *Trb + ) +{ + EFI_STATUS Status; + EFI_SD_MMC_PASS_THRU_COMMAND_PACKET *Packet; + UINT16 IntStatus; + + Packet = Trb->Packet; + // + // Check Trb execution result by reading Normal Interrupt Status register. + // + Status = SdMmcHcRwMmio ( + Private->PciIo, + Trb->Slot, + SD_MMC_HC_NOR_INT_STS, + TRUE, + sizeof (IntStatus), + &IntStatus + ); + if (EFI_ERROR (Status)) { + goto Done; + } + + // + // Check if there are any errors reported by host controller + // and if neccessary recover the controller before next command is executed. + // + Status = SdMmcCheckAndRecoverErrors (Private, Trb->Slot, IntStatus); + if (EFI_ERROR (Status)) { + goto Done; + } + + // + // Tuning commands are the only ones that do not generate command + // complete interrupt. Process them here before entering the code + // that waits for command completion. + // + if (((Private->Slot[Trb->Slot].CardType == EmmcCardType) && + (Packet->SdMmcCmdBlk->CommandIndex == EMMC_SEND_TUNING_BLOCK)) || + ((Private->Slot[Trb->Slot].CardType == SdCardType) && + (Packet->SdMmcCmdBlk->CommandIndex == SD_SEND_TUNING_BLOCK))) { + Status = SdMmcTransferDataWithPio (Private, Trb, IntStatus); + goto Done; + } + + if (!Trb->CommandComplete) { + Status = SdMmcCheckCommandComplete (Private, Trb, IntStatus); + if (EFI_ERROR (Status)) { + goto Done; + } + } + + if (Packet->SdMmcCmdBlk->CommandType == SdMmcCommandTypeAdtc || + Packet->SdMmcCmdBlk->ResponseType == SdMmcResponseTypeR1b || + Packet->SdMmcCmdBlk->ResponseType == SdMmcResponseTypeR5b) { + Status = SdMmcCheckDataTransfer (Private, Trb, IntStatus); + } else { + Status = EFI_SUCCESS; + } + +Done: + if (Status != EFI_NOT_READY) { + SdMmcHcLedOnOff (Private->PciIo, Trb->Slot, FALSE); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "TRB failed with %r\n", Status)); + SdMmcPrintTrb (DEBUG_ERROR, Trb); + } else { + DEBUG ((DEBUG_VERBOSE, "TRB success\n")); + SdMmcPrintTrb (DEBUG_VERBOSE, Trb); + } + } + + return Status; +} + +/** + Wait for the TRB execution result. + + @param[in] Private A pointer to the SD_MMC_HC_PRIVATE_DATA instance. + @param[in] Trb The pointer to the SD_MMC_HC_TRB instance. + + @retval EFI_SUCCESS The TRB is executed successfully. + @retval Others Some erros happen when executing this request. + +**/ +EFI_STATUS +SdMmcWaitTrbResult ( + IN SD_MMC_HC_PRIVATE_DATA *Private, + IN SD_MMC_HC_TRB *Trb + ) +{ + EFI_STATUS Status; + EFI_SD_MMC_PASS_THRU_COMMAND_PACKET *Packet; + UINT64 Timeout; + BOOLEAN InfiniteWait; + + Packet = Trb->Packet; + // + // Wait Command Complete Interrupt Status bit in Normal Interrupt Status Register + // + Timeout = Packet->Timeout; + if (Timeout == 0) { + InfiniteWait = TRUE; + } else { + InfiniteWait = FALSE; + } + + while (InfiniteWait || (Timeout > 0)) { + // + // Check Trb execution result by reading Normal Interrupt Status register. + // + Status = SdMmcCheckTrbResult (Private, Trb); + if (Status != EFI_NOT_READY) { + return Status; + } + // + // Stall for 1 microsecond. + // + gBS->Stall (1); + + Timeout--; + } + + return EFI_TIMEOUT; +} + |