| /** @file | |
| * | |
| * Copyright (c) 2008 - 2009, Apple Inc. All rights reserved. | |
| * Copyright (c) 2011 - 2014, ARM Limited. All rights reserved. | |
| * | |
| * This program and the accompanying materials | |
| * are licensed and made available under the terms and conditions of the BSD License | |
| * which accompanies this distribution. The full text of the license may be found at | |
| * http://opensource.org/licenses/bsd-license.php | |
| * | |
| * THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
| * | |
| **/ | |
| #include "MmcHostDxe.h" | |
| EMBEDDED_EXTERNAL_DEVICE *gTPS65950; | |
| UINT8 mMaxDataTransferRate = 0; | |
| UINT32 mRca = 0; | |
| BOOLEAN mBitModeSet = FALSE; | |
| typedef struct { | |
| VENDOR_DEVICE_PATH Mmc; | |
| EFI_DEVICE_PATH End; | |
| } MMCHS_DEVICE_PATH; | |
| MMCHS_DEVICE_PATH gMMCDevicePath = { | |
| { | |
| { | |
| HARDWARE_DEVICE_PATH, | |
| HW_VENDOR_DP, | |
| { (UINT8)(sizeof(VENDOR_DEVICE_PATH)), (UINT8)((sizeof(VENDOR_DEVICE_PATH)) >> 8) }, | |
| }, | |
| { 0xb615f1f5, 0x5088, 0x43cd, { 0x80, 0x9c, 0xa1, 0x6e, 0x52, 0x48, 0x7d, 0x00 } } | |
| }, | |
| { | |
| END_DEVICE_PATH_TYPE, | |
| END_ENTIRE_DEVICE_PATH_SUBTYPE, | |
| { sizeof (EFI_DEVICE_PATH_PROTOCOL), 0 } | |
| } | |
| }; | |
| BOOLEAN | |
| IgnoreCommand ( | |
| UINT32 Command | |
| ) | |
| { | |
| switch(Command) { | |
| case MMC_CMD12: | |
| return TRUE; | |
| case MMC_CMD13: | |
| return TRUE; | |
| default: | |
| return FALSE; | |
| } | |
| } | |
| UINT32 | |
| TranslateCommand ( | |
| UINT32 Command | |
| ) | |
| { | |
| UINT32 Translation; | |
| switch(Command) { | |
| case MMC_CMD2: | |
| Translation = CMD2; | |
| break; | |
| case MMC_CMD3: | |
| Translation = CMD3; | |
| break; | |
| /*case MMC_CMD6: | |
| Translation = CMD6; | |
| break;*/ | |
| case MMC_CMD7: | |
| Translation = CMD7; | |
| break; | |
| case MMC_CMD8: | |
| Translation = CMD8; | |
| break; | |
| case MMC_CMD9: | |
| Translation = CMD9; | |
| break; | |
| /*case MMC_CMD12: | |
| Translation = CMD12; | |
| break; | |
| case MMC_CMD13: | |
| Translation = CMD13; | |
| break;*/ | |
| case MMC_CMD16: | |
| Translation = CMD16; | |
| break; | |
| case MMC_CMD17: | |
| Translation = 0x113A0014;//CMD17; | |
| break; | |
| case MMC_CMD24: | |
| Translation = CMD24 | 4; | |
| break; | |
| case MMC_CMD55: | |
| Translation = CMD55; | |
| break; | |
| case MMC_ACMD41: | |
| Translation = ACMD41; | |
| break; | |
| default: | |
| Translation = Command; | |
| } | |
| return Translation; | |
| } | |
| VOID | |
| CalculateCardCLKD ( | |
| UINTN *ClockFrequencySelect | |
| ) | |
| { | |
| UINTN TransferRateValue = 0; | |
| UINTN TimeValue = 0 ; | |
| UINTN Frequency = 0; | |
| DEBUG ((DEBUG_BLKIO, "CalculateCardCLKD()\n")); | |
| // For SD Cards we would need to send CMD6 to set | |
| // speeds abouve 25MHz. High Speed mode 50 MHz and up | |
| // Calculate Transfer rate unit (Bits 2:0 of TRAN_SPEED) | |
| switch (mMaxDataTransferRate & 0x7) { // 2 | |
| case 0: | |
| TransferRateValue = 100 * 1000; | |
| break; | |
| case 1: | |
| TransferRateValue = 1 * 1000 * 1000; | |
| break; | |
| case 2: | |
| TransferRateValue = 10 * 1000 * 1000; | |
| break; | |
| case 3: | |
| TransferRateValue = 100 * 1000 * 1000; | |
| break; | |
| default: | |
| DEBUG ((DEBUG_BLKIO, "Invalid parameter.\n")); | |
| ASSERT(FALSE); | |
| return; | |
| } | |
| //Calculate Time value (Bits 6:3 of TRAN_SPEED) | |
| switch ((mMaxDataTransferRate >> 3) & 0xF) { // 6 | |
| case 1: | |
| TimeValue = 10; | |
| break; | |
| case 2: | |
| TimeValue = 12; | |
| break; | |
| case 3: | |
| TimeValue = 13; | |
| break; | |
| case 4: | |
| TimeValue = 15; | |
| break; | |
| case 5: | |
| TimeValue = 20; | |
| break; | |
| case 6: | |
| TimeValue = 25; | |
| break; | |
| case 7: | |
| TimeValue = 30; | |
| break; | |
| case 8: | |
| TimeValue = 35; | |
| break; | |
| case 9: | |
| TimeValue = 40; | |
| break; | |
| case 10: | |
| TimeValue = 45; | |
| break; | |
| case 11: | |
| TimeValue = 50; | |
| break; | |
| case 12: | |
| TimeValue = 55; | |
| break; | |
| case 13: | |
| TimeValue = 60; | |
| break; | |
| case 14: | |
| TimeValue = 70; | |
| break; | |
| case 15: | |
| TimeValue = 80; | |
| break; | |
| default: | |
| DEBUG ((DEBUG_BLKIO, "Invalid parameter.\n")); | |
| ASSERT(FALSE); | |
| return; | |
| } | |
| Frequency = TransferRateValue * TimeValue/10; | |
| // Calculate Clock divider value to program in MMCHS_SYSCTL[CLKD] field. | |
| *ClockFrequencySelect = ((MMC_REFERENCE_CLK/Frequency) + 1); | |
| DEBUG ((DEBUG_BLKIO, "mMaxDataTransferRate: 0x%x, Frequency: %d KHz, ClockFrequencySelect: %x\n", mMaxDataTransferRate, Frequency/1000, *ClockFrequencySelect)); | |
| } | |
| VOID | |
| UpdateMMCHSClkFrequency ( | |
| UINTN NewCLKD | |
| ) | |
| { | |
| DEBUG ((DEBUG_BLKIO, "UpdateMMCHSClkFrequency()\n")); | |
| // Set Clock enable to 0x0 to not provide the clock to the card | |
| MmioAnd32 (MMCHS_SYSCTL, ~CEN); | |
| // Set new clock frequency. | |
| MmioAndThenOr32 (MMCHS_SYSCTL, ~CLKD_MASK, NewCLKD << 6); | |
| // Poll till Internal Clock Stable | |
| while ((MmioRead32 (MMCHS_SYSCTL) & ICS_MASK) != ICS); | |
| // Set Clock enable to 0x1 to provide the clock to the card | |
| MmioOr32 (MMCHS_SYSCTL, CEN); | |
| } | |
| EFI_STATUS | |
| InitializeMMCHS ( | |
| VOID | |
| ) | |
| { | |
| UINT8 Data; | |
| EFI_STATUS Status; | |
| DEBUG ((DEBUG_BLKIO, "InitializeMMCHS()\n")); | |
| // Select Device group to belong to P1 device group in Power IC. | |
| Data = DEV_GRP_P1; | |
| Status = gTPS65950->Write (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID4, VMMC1_DEV_GRP), 1, &Data); | |
| ASSERT_EFI_ERROR(Status); | |
| // Configure voltage regulator for MMC1 in Power IC to output 3.0 voltage. | |
| Data = VSEL_3_00V; | |
| Status = gTPS65950->Write (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID4, VMMC1_DEDICATED_REG), 1, &Data); | |
| ASSERT_EFI_ERROR(Status); | |
| // After ramping up voltage, set VDDS stable bit to indicate that voltage level is stable. | |
| MmioOr32 (CONTROL_PBIAS_LITE, (PBIASLITEVMODE0 | PBIASLITEPWRDNZ0 | PBIASSPEEDCTRL0 | PBIASLITEVMODE1 | PBIASLITEWRDNZ1)); | |
| // Enable WP GPIO | |
| MmioAndThenOr32 (GPIO1_BASE + GPIO_OE, ~BIT23, BIT23); | |
| // Enable Card Detect | |
| Data = CARD_DETECT_ENABLE; | |
| gTPS65950->Write (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID2, TPS65950_GPIO_CTRL), 1, &Data); | |
| return Status; | |
| } | |
| BOOLEAN | |
| MMCIsCardPresent ( | |
| IN EFI_MMC_HOST_PROTOCOL *This | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINT8 Data; | |
| // | |
| // Card detect is a GPIO0 on the TPS65950 | |
| // | |
| Status = gTPS65950->Read (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID2, GPIODATAIN1), 1, &Data); | |
| if (EFI_ERROR (Status)) { | |
| return FALSE; | |
| } | |
| return !(Data & CARD_DETECT_BIT); | |
| } | |
| BOOLEAN | |
| MMCIsReadOnly ( | |
| IN EFI_MMC_HOST_PROTOCOL *This | |
| ) | |
| { | |
| /* Note: | |
| * On our BeagleBoard the SD card WP pin is always read as TRUE. | |
| * Probably something wrong with GPIO configuration. | |
| * BeagleBoard-xM uses microSD cards so there is no write protect at all. | |
| * Hence commenting out SD card WP pin read status. | |
| */ | |
| //return (MmioRead32 (GPIO1_BASE + GPIO_DATAIN) & BIT23) == BIT23; | |
| return 0; | |
| } | |
| // TODO | |
| EFI_GUID mPL180MciDevicePathGuid = EFI_CALLER_ID_GUID; | |
| EFI_STATUS | |
| MMCBuildDevicePath ( | |
| 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_STATUS | |
| MMCSendCommand ( | |
| IN EFI_MMC_HOST_PROTOCOL *This, | |
| IN MMC_CMD MmcCmd, | |
| IN UINT32 Argument | |
| ) | |
| { | |
| UINTN MmcStatus; | |
| UINTN RetryCount = 0; | |
| if (IgnoreCommand(MmcCmd)) | |
| return EFI_SUCCESS; | |
| MmcCmd = TranslateCommand(MmcCmd); | |
| //DEBUG ((EFI_D_ERROR, "MMCSendCommand(%d)\n", MmcCmd)); | |
| // Check if command line is in use or not. Poll till command line is available. | |
| while ((MmioRead32 (MMCHS_PSTATE) & DATI_MASK) == DATI_NOT_ALLOWED); | |
| // Provide the block size. | |
| MmioWrite32 (MMCHS_BLK, BLEN_512BYTES); | |
| // Setting Data timeout counter value to max value. | |
| MmioAndThenOr32 (MMCHS_SYSCTL, ~DTO_MASK, DTO_VAL); | |
| // Clear Status register. | |
| MmioWrite32 (MMCHS_STAT, 0xFFFFFFFF); | |
| // Set command argument register | |
| MmioWrite32 (MMCHS_ARG, Argument); | |
| //TODO: fix this | |
| //Enable interrupt enable events to occur | |
| //MmioWrite32 (MMCHS_IE, CmdInterruptEnableVal); | |
| // Send a command | |
| MmioWrite32 (MMCHS_CMD, MmcCmd); | |
| // Check for the command status. | |
| while (RetryCount < MAX_RETRY_COUNT) { | |
| do { | |
| MmcStatus = MmioRead32 (MMCHS_STAT); | |
| } while (MmcStatus == 0); | |
| // Read status of command response | |
| if ((MmcStatus & ERRI) != 0) { | |
| // Perform soft-reset for mmci_cmd line. | |
| MmioOr32 (MMCHS_SYSCTL, SRC); | |
| while ((MmioRead32 (MMCHS_SYSCTL) & SRC)); | |
| //DEBUG ((EFI_D_INFO, "MmcStatus: 0x%x\n", MmcStatus)); | |
| return EFI_DEVICE_ERROR; | |
| } | |
| // Check if command is completed. | |
| if ((MmcStatus & CC) == CC) { | |
| MmioWrite32 (MMCHS_STAT, CC); | |
| break; | |
| } | |
| RetryCount++; | |
| } | |
| if (RetryCount == MAX_RETRY_COUNT) { | |
| DEBUG ((DEBUG_BLKIO, "MMCSendCommand: Timeout\n")); | |
| return EFI_TIMEOUT; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| EFI_STATUS | |
| MMCNotifyState ( | |
| IN EFI_MMC_HOST_PROTOCOL *This, | |
| IN MMC_STATE State | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINTN FreqSel; | |
| switch(State) { | |
| case MmcInvalidState: | |
| ASSERT(0); | |
| break; | |
| case MmcHwInitializationState: | |
| mBitModeSet = FALSE; | |
| DEBUG ((DEBUG_BLKIO, "MMCHwInitializationState()\n")); | |
| Status = InitializeMMCHS (); | |
| if (EFI_ERROR(Status)) { | |
| DEBUG ((DEBUG_BLKIO, "Initialize MMC host controller fails. Status: %x\n", Status)); | |
| return Status; | |
| } | |
| // Software reset of the MMCHS host controller. | |
| MmioWrite32 (MMCHS_SYSCONFIG, SOFTRESET); | |
| gBS->Stall(1000); | |
| while ((MmioRead32 (MMCHS_SYSSTATUS) & RESETDONE_MASK) != RESETDONE); | |
| // Soft reset for all. | |
| MmioWrite32 (MMCHS_SYSCTL, SRA); | |
| gBS->Stall(1000); | |
| while ((MmioRead32 (MMCHS_SYSCTL) & SRA) != 0x0); | |
| //Voltage capabilities initialization. Activate VS18 and VS30. | |
| MmioOr32 (MMCHS_CAPA, (VS30 | VS18)); | |
| // Wakeup configuration | |
| MmioOr32 (MMCHS_SYSCONFIG, ENAWAKEUP); | |
| MmioOr32 (MMCHS_HCTL, IWE); | |
| // MMCHS Controller default initialization | |
| MmioOr32 (MMCHS_CON, (OD | DW8_1_4_BIT | CEATA_OFF)); | |
| MmioWrite32 (MMCHS_HCTL, (SDVS_3_0_V | DTW_1_BIT | SDBP_OFF)); | |
| // Enable internal clock | |
| MmioOr32 (MMCHS_SYSCTL, ICE); | |
| // Set the clock frequency to 80KHz. | |
| UpdateMMCHSClkFrequency (CLKD_80KHZ); | |
| // Enable SD bus power. | |
| MmioOr32 (MMCHS_HCTL, (SDBP_ON)); | |
| // Poll till SD bus power bit is set. | |
| while ((MmioRead32 (MMCHS_HCTL) & SDBP_MASK) != SDBP_ON); | |
| // Enable interrupts. | |
| MmioWrite32 (MMCHS_IE, (BADA_EN | CERR_EN | DEB_EN | DCRC_EN | DTO_EN | CIE_EN | | |
| CEB_EN | CCRC_EN | CTO_EN | BRR_EN | BWR_EN | TC_EN | CC_EN)); | |
| // Controller INIT procedure start. | |
| MmioOr32 (MMCHS_CON, INIT); | |
| MmioWrite32 (MMCHS_CMD, 0x00000000); | |
| while (!(MmioRead32 (MMCHS_STAT) & CC)); | |
| // Wait for 1 ms | |
| gBS->Stall (1000); | |
| // Set CC bit to 0x1 to clear the flag | |
| MmioOr32 (MMCHS_STAT, CC); | |
| // Retry INIT procedure. | |
| MmioWrite32 (MMCHS_CMD, 0x00000000); | |
| while (!(MmioRead32 (MMCHS_STAT) & CC)); | |
| // End initialization sequence | |
| MmioAnd32 (MMCHS_CON, ~INIT); | |
| MmioOr32 (MMCHS_HCTL, (SDVS_3_0_V | DTW_1_BIT | SDBP_ON)); | |
| // Change clock frequency to 400KHz to fit protocol | |
| UpdateMMCHSClkFrequency(CLKD_400KHZ); | |
| MmioOr32 (MMCHS_CON, OD); | |
| break; | |
| case MmcIdleState: | |
| break; | |
| case MmcReadyState: | |
| break; | |
| case MmcIdentificationState: | |
| break; | |
| case MmcStandByState: | |
| CalculateCardCLKD (&FreqSel); | |
| UpdateMMCHSClkFrequency (FreqSel); | |
| break; | |
| case MmcTransferState: | |
| if (!mBitModeSet) { | |
| Status = MMCSendCommand (This, CMD55, mRca << 16); | |
| if (!EFI_ERROR (Status)) { | |
| // Set device into 4-bit data bus mode | |
| Status = MMCSendCommand (This, ACMD6, 0x2); | |
| if (!EFI_ERROR (Status)) { | |
| // Set host controler into 4-bit mode | |
| MmioOr32 (MMCHS_HCTL, DTW_4_BIT); | |
| DEBUG ((DEBUG_BLKIO, "SD Memory Card set to 4-bit mode\n")); | |
| mBitModeSet = TRUE; | |
| } | |
| } | |
| } | |
| break; | |
| case MmcSendingDataState: | |
| break; | |
| case MmcReceiveDataState: | |
| break; | |
| case MmcProgrammingState: | |
| break; | |
| case MmcDisconnectState: | |
| default: | |
| ASSERT(0); | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| EFI_STATUS | |
| MMCReceiveResponse ( | |
| 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_R2) { | |
| Buffer[0] = MmioRead32 (MMCHS_RSP10); | |
| Buffer[1] = MmioRead32 (MMCHS_RSP32); | |
| Buffer[2] = MmioRead32 (MMCHS_RSP54); | |
| Buffer[3] = MmioRead32 (MMCHS_RSP76); | |
| } else { | |
| Buffer[0] = MmioRead32 (MMCHS_RSP10); | |
| } | |
| if (Type == MMC_RESPONSE_TYPE_CSD) { | |
| mMaxDataTransferRate = Buffer[3] & 0xFF; | |
| } else if (Type == MMC_RESPONSE_TYPE_RCA) { | |
| mRca = Buffer[0] >> 16; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| EFI_STATUS | |
| MMCReadBlockData ( | |
| IN EFI_MMC_HOST_PROTOCOL *This, | |
| IN EFI_LBA Lba, | |
| IN UINTN Length, | |
| IN UINT32* Buffer | |
| ) | |
| { | |
| UINTN MmcStatus; | |
| UINTN Count; | |
| UINTN RetryCount = 0; | |
| DEBUG ((DEBUG_BLKIO, "MMCReadBlockData(LBA: 0x%x, Length: 0x%x, Buffer: 0x%x)\n", Lba, Length, Buffer)); | |
| // Check controller status to make sure there is no error. | |
| while (RetryCount < MAX_RETRY_COUNT) { | |
| do { | |
| // Read Status. | |
| MmcStatus = MmioRead32 (MMCHS_STAT); | |
| } while(MmcStatus == 0); | |
| // Check if Buffer read ready (BRR) bit is set? | |
| if (MmcStatus & BRR) { | |
| // Clear BRR bit | |
| MmioOr32 (MMCHS_STAT, BRR); | |
| for (Count = 0; Count < Length / 4; Count++) { | |
| *Buffer++ = MmioRead32(MMCHS_DATA); | |
| } | |
| break; | |
| } | |
| RetryCount++; | |
| } | |
| if (RetryCount == MAX_RETRY_COUNT) { | |
| return EFI_TIMEOUT; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| EFI_STATUS | |
| MMCWriteBlockData ( | |
| IN EFI_MMC_HOST_PROTOCOL *This, | |
| IN EFI_LBA Lba, | |
| IN UINTN Length, | |
| IN UINT32* Buffer | |
| ) | |
| { | |
| UINTN MmcStatus; | |
| UINTN Count; | |
| UINTN RetryCount = 0; | |
| // Check controller status to make sure there is no error. | |
| while (RetryCount < MAX_RETRY_COUNT) { | |
| do { | |
| // Read Status. | |
| MmcStatus = MmioRead32 (MMCHS_STAT); | |
| } while(MmcStatus == 0); | |
| // Check if Buffer write ready (BWR) bit is set? | |
| if (MmcStatus & BWR) { | |
| // Clear BWR bit | |
| MmioOr32 (MMCHS_STAT, BWR); | |
| // Write block worth of data. | |
| for (Count = 0; Count < Length / 4; Count++) { | |
| MmioWrite32 (MMCHS_DATA, *Buffer++); | |
| } | |
| break; | |
| } | |
| RetryCount++; | |
| } | |
| if (RetryCount == MAX_RETRY_COUNT) { | |
| return EFI_TIMEOUT; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| EFI_MMC_HOST_PROTOCOL gMMCHost = { | |
| MMC_HOST_PROTOCOL_REVISION, | |
| MMCIsCardPresent, | |
| MMCIsReadOnly, | |
| MMCBuildDevicePath, | |
| MMCNotifyState, | |
| MMCSendCommand, | |
| MMCReceiveResponse, | |
| MMCReadBlockData, | |
| MMCWriteBlockData | |
| }; | |
| EFI_STATUS | |
| MMCInitialize ( | |
| IN EFI_HANDLE ImageHandle, | |
| IN EFI_SYSTEM_TABLE *SystemTable | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_HANDLE Handle = NULL; | |
| DEBUG ((DEBUG_BLKIO, "MMCInitialize()\n")); | |
| Status = gBS->LocateProtocol (&gEmbeddedExternalDeviceProtocolGuid, NULL, (VOID **)&gTPS65950); | |
| ASSERT_EFI_ERROR(Status); | |
| Status = gBS->InstallMultipleProtocolInterfaces ( | |
| &Handle, | |
| &gEfiMmcHostProtocolGuid, &gMMCHost, | |
| NULL | |
| ); | |
| ASSERT_EFI_ERROR (Status); | |
| return Status; | |
| } |