/** @file
The SD host controller driver model and HC protocol routines.
Copyright (c) 2013-2016 Intel Corporation.
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 "SDController.h"
EFI_DRIVER_BINDING_PROTOCOL gSDControllerDriverBinding = {
SDControllerSupported,
SDControllerStart,
SDControllerStop,
0x20,
NULL,
NULL
};
EFI_SD_HOST_IO_PROTOCOL mSDHostIo = {
EFI_SD_HOST_IO_PROTOCOL_REVISION_01,
{
0, // HighSpeedSupport
0, // V18Support
0, // V30Support
0, // V33Support
0, // Reserved0
0, // BusWidth4
0, // BusWidth8
0, // Reserved1
0, // Reserved1
(512 * 1024) //BoundarySize
},
SendCommand,
SetClockFrequency,
SetBusWidth,
SetHostVoltage,
ResetSDHost,
EnableAutoStopCmd,
DetectCardAndInitHost,
SetBlockLength,
SetHighSpeedMode,
SetDDRMode
};
/**
Find sdclk_freq_sel and upr_sdclk_freq_sel bits
for Clock Control Register (CLK_CTL)Offset 2Ch when using 8bit or 10bit
divided clock mode.
@param BaseClockFreg Base Clock Frequency in Hz For SD Clock in the
Capabilities register.
@param TargetFreq Target Frequency in Hz to reach.
@param Is8BitMode True if 8-bit Divided Clock Mode else 10bit mode.
@param Bits sdclk_freq_sel and upr_sdclk_freq_sel bits for
TargetFreq.
@return EFI_SUCCESS // Bits setup.
@return EFI_UNSUPPORTED // Cannot divide base clock to reach target clock.
**/
EFI_STATUS
DividedClockModeBits (
IN CONST UINTN BaseClockFreg,
IN CONST UINTN TargetFreq,
IN CONST BOOLEAN Is8BitMode,
OUT UINT16 *Bits
)
{
UINTN N;
UINTN CurrFreq;
*Bits = 0;
CurrFreq = BaseClockFreg;
N = 0;
//
// N == 0 same for 8bit & 10bit mode i.e. BaseClockFreg of controller.
//
if (TargetFreq < CurrFreq) {
if (Is8BitMode) {
N = 1;
do {
//
// N values for 8bit mode when N > 0.
// Bit[15:8] SDCLK Frequency Select at offset 2Ch
// 80h - base clock divided by 256
// 40h - base clock divided by 128
// 20h - base clock divided by 64
// 10h - base clock divided by 32
// 08h - base clock divided by 16
// 04h - base clock divided by 8
// 02h - base clock divided by 4
// 01h - base clock divided by 2
//
CurrFreq = BaseClockFreg / (2 * N);
if (TargetFreq >= CurrFreq) {
break;
}
N *= 2;
if (N > V_MMIO_CLKCTL_MAX_8BIT_FREQ_SEL) {
return EFI_UNSUPPORTED;
}
} while (TRUE);
} else {
N = 1;
CurrFreq = BaseClockFreg / (2 * N);
//
// (try N = 0 or 1 first since don't want divide by 0).
//
if (TargetFreq < CurrFreq) {
//
// If still no match then calculate it for 10bit.
// N values for 10bit mode.
// N 1/2N Divided Clock (Duty 50%).
// from Spec "The length of divider is extended to 10 bits and all
// divider values shall be supported.
//
N = (BaseClockFreg / TargetFreq) / 2;
//
// Can only be N or N+1;
//
CurrFreq = BaseClockFreg / (2 * N);
if (TargetFreq < CurrFreq) {
N++;
CurrFreq = BaseClockFreg / (2 * N);
}
if (N > V_MMIO_CLKCTL_MAX_10BIT_FREQ_SEL) {
return EFI_UNSUPPORTED;
}
//
// Set upper bits of SDCLK Frequency Select (bits 7:6 of reg 0x2c).
//
*Bits |= ((UINT16) ((N >> 2) & B_MMIO_CLKCTL_UPR_SDCLK_FREQ_SEL_MASK));
}
}
}
//
// Set lower bits of SDCLK Frequency Select (bits 15:8 of reg 0x2c).
//
*Bits |= ((UINT16) ((UINT8) N) << 8);
DEBUG (
(EFI_D_INFO,
"SDIO:DividedClockModeBits: %dbit mode Want %dHz Got %dHz bits = %04x\r\n",
(Is8BitMode) ? 8 : 10,
TargetFreq,
CurrFreq,
(UINTN) *Bits
));
return EFI_SUCCESS;
}
/**
Print type of error and command index
@param CommandIndex Command index to set the command index field of command register.
@param ErrorCode Error interrupt status read from host controller
@return EFI_DEVICE_ERROR
@return EFI_TIMEOUT
@return EFI_CRC_ERROR
**/
EFI_STATUS
GetErrorReason (
IN UINT16 CommandIndex,
IN UINT16 ErrorCode
)
{
EFI_STATUS Status;
Status = EFI_DEVICE_ERROR;
DEBUG((EFI_D_ERROR, "[%2d] -- ", CommandIndex));
if (ErrorCode & BIT0) {
Status = EFI_TIMEOUT;
DEBUG((EFI_D_ERROR, "Command Timeout Erro"));
}
if (ErrorCode & BIT1) {
Status = EFI_CRC_ERROR;
DEBUG((EFI_D_ERROR, "Command CRC Error"));
}
if (ErrorCode & BIT2) {
DEBUG((EFI_D_ERROR, "Command End Bit Error"));
}
if (ErrorCode & BIT3) {
DEBUG((EFI_D_ERROR, "Command Index Error"));
}
if (ErrorCode & BIT4) {
Status = EFI_TIMEOUT;
DEBUG((EFI_D_ERROR, "Data Timeout Error"));
}
if (ErrorCode & BIT5) {
Status = EFI_CRC_ERROR;
DEBUG((EFI_D_ERROR, "Data CRC Error"));
}
if (ErrorCode & BIT6) {
DEBUG((EFI_D_ERROR, "Data End Bit Error"));
}
if (ErrorCode & BIT7) {
DEBUG((EFI_D_ERROR, "Current Limit Error"));
}
if (ErrorCode & BIT8) {
DEBUG((EFI_D_ERROR, "Auto CMD12 Error"));
}
if (ErrorCode & BIT9) {
DEBUG((EFI_D_ERROR, "ADMA Error"));
}
DEBUG((EFI_D_ERROR, "\n"));
return Status;
}
/**
Enable/Disable High Speed transfer mode
@param This A pointer to the EFI_SD_HOST_IO_PROTOCOL instance.
@param Enable TRUE to Enable, FALSE to Disable
@return EFI_SUCCESS
**/
EFI_STATUS
EFIAPI
SetHighSpeedMode (
IN EFI_SD_HOST_IO_PROTOCOL *This,
IN BOOLEAN Enable
)
{
UINT32 Data;
SDHOST_DATA *SDHostData;
EFI_PCI_IO_PROTOCOL *PciIo;
SDHostData = SDHOST_DATA_FROM_THIS (This);
PciIo = SDHostData->PciIo;
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint8,
0,
(UINT64)MMIO_HOSTCTL,
1,
&Data
);
if (Enable) {
if (PcdGetBool(PcdSdHciQuirkNoHiSpd)) {
DEBUG ((EFI_D_INFO, "SDIO: Quirk never set High Speed Enable bit\r\n"));
return EFI_SUCCESS;
}
DEBUG ((EFI_D_INFO, "Enable High Speed transfer mode ... \r\n"));
Data |= BIT2;
} else {
Data &= ~BIT2;
}
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint8,
0,
(UINT64)MMIO_HOSTCTL,
1,
&Data
);
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
SetDDRMode (
IN EFI_SD_HOST_IO_PROTOCOL *This,
IN BOOLEAN Enable
)
{
UINT16 Data;
SDHOST_DATA *SDHostData;
EFI_PCI_IO_PROTOCOL *PciIo;
SDHostData = SDHOST_DATA_FROM_THIS (This);
PciIo = SDHostData->PciIo;
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_HOSTCTL2,
1,
&Data
);
Data &= 0xFFF0;
if (Enable) {
Data |= 0x0004; // Enable DDR50 by default, later should enable other mode like HS200/400
Data |= BIT3; // Enable 1.8V Signaling
}
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_HOSTCTL2,
1,
&Data
);
return EFI_SUCCESS;
}
/**
Power on/off the LED associated with the slot
@param This A pointer to the EFI_SD_HOST_IO_PROTOCOL instance.
@param Enable TRUE to set LED on, FALSE to set LED off
@return EFI_SUCCESS
**/
EFI_STATUS
HostLEDEnable (
IN EFI_SD_HOST_IO_PROTOCOL *This,
IN BOOLEAN Enable
)
{
SDHOST_DATA *SDHostData;
EFI_PCI_IO_PROTOCOL *PciIo;
UINT32 Data;
SDHostData = SDHOST_DATA_FROM_THIS (This);
PciIo = SDHostData->PciIo;
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint8,
0,
(UINT64)MMIO_HOSTCTL,
1,
&Data
);
if (Enable) {
//
//LED On
//
Data |= BIT0;
} else {
//
//LED Off
//
Data &= ~BIT0;
}
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint8,
0,
(UINT64)MMIO_HOSTCTL,
1,
&Data
);
return EFI_SUCCESS;
}
/**
The main function used to send the command to the card inserted into the SD host slot.
It will assemble the arguments to set the command register and wait for the command
and transfer completed until timeout. Then it will read the response register to fill
the ResponseData.
@param This A pointer to the EFI_SD_HOST_IO_PROTOCOL instance.
@param CommandIndex The command index to set the command index field of command register.
@param Argument Command argument to set the argument field of command register.
@param DataType TRANSFER_TYPE, indicates no data, data in or data out.
@param Buffer Contains the data read from / write to the device.
@param BufferSize The size of the buffer.
@param ResponseType RESPONSE_TYPE.
@param TimeOut Time out value in 1 ms unit.
@param ResponseData Depending on the ResponseType, such as CSD or card status.
@retval EFI_SUCCESS
@retval EFI_INVALID_PARAMETER
@retval EFI_OUT_OF_RESOURCES
@retval EFI_TIMEOUT
@retval EFI_DEVICE_ERROR
**/
EFI_STATUS
EFIAPI
SendCommand (
IN EFI_SD_HOST_IO_PROTOCOL *This,
IN UINT16 CommandIndex,
IN UINT32 Argument,
IN TRANSFER_TYPE DataType,
IN UINT8 *Buffer, OPTIONAL
IN UINT32 BufferSize,
IN RESPONSE_TYPE ResponseType,
IN UINT32 TimeOut,
OUT UINT32 *ResponseData OPTIONAL
)
/*++
Routine Description:
The main function used to send the command to the card inserted into the SD host
slot.
It will assemble the arguments to set the command register and wait for the command
and transfer completed until timeout. Then it will read the response register to fill
the ResponseData
Arguments:
This - Pointer to EFI_SD_HOST_IO_PROTOCOL
CommandIndex - The command index to set the command index field of command register
Argument - Command argument to set the argument field of command register
DataType - TRANSFER_TYPE, indicates no data, data in or data out
Buffer - Contains the data read from / write to the device
BufferSize - The size of the buffer
ResponseType - RESPONSE_TYPE
TimeOut - Time out value in 1 ms unit
ResponseData - Depending on the ResponseType, such as CSD or card status
Returns:
EFI_SUCCESS
EFI_INVALID_PARAMETER
EFI_OUT_OF_RESOURCES
EFI_TIMEOUT
EFI_DEVICE_ERROR
--*/
{
EFI_STATUS Status;
SDHOST_DATA *SDHostData;
EFI_PCI_IO_PROTOCOL *PciIo;
UINT32 ResponseDataCount;
UINT32 Data;
UINT64 Data64;
UINT8 Index;
INTN TimeOut2;
BOOLEAN AutoCMD12Enable = FALSE;
Status = EFI_SUCCESS;
ResponseDataCount = 1;
SDHostData = SDHOST_DATA_FROM_THIS (This);
PciIo = SDHostData->PciIo;
AutoCMD12Enable = (CommandIndex & AUTO_CMD12_ENABLE) ? TRUE : FALSE;
CommandIndex = CommandIndex & CMD_INDEX_MASK;
if (Buffer != NULL && DataType == NoData) {
Status = EFI_INVALID_PARAMETER;
DEBUG ((EFI_D_ERROR, "SendCommand: invalid parameter \r\n"));
goto Exit;
}
if (((UINTN)Buffer & (This->HostCapability.BoundarySize - 1)) != (UINTN)NULL) {
Status = EFI_INVALID_PARAMETER;
DEBUG ((EFI_D_ERROR, "SendCommand: invalid parameter \r\n"));
goto Exit;
}
DEBUG ((EFI_D_INFO, "SendCommand: Command Index = %d \r\n", CommandIndex));
//
TimeOut2 = 1000; // 10 ms
do {
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint32,
0,
(UINT64)MMIO_PSTATE,
1,
&Data
);
gBS->Stall (10);
}while ((TimeOut2-- > 0) && (Data & BIT0));
TimeOut2 = 1000; // 10 ms
do {
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint32,
0,
(UINT64)MMIO_PSTATE,
1,
&Data
);
gBS->Stall (10);
}while ((TimeOut2-- > 0) && (Data & BIT1));
//Clear status bits
//
Data = 0xFFFF;
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_NINTSTS,
1,
&Data
);
Data = 0xFFFF;
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_ERINTSTS,
1,
&Data
);
if (Buffer != NULL) {
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint32,
0,
(UINT64)MMIO_DMAADR,
1,
&Buffer
);
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_BLKSZ,
1,
&Data
);
Data &= ~(0xFFF);
if (BufferSize <= SDHostData->BlockLength) {
Data |= (BufferSize | 0x7000);
} else {
Data |= (SDHostData->BlockLength | 0x7000);
}
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_BLKSZ,
1,
&Data
);
if (BufferSize <= SDHostData->BlockLength) {
Data = 1;
} else {
Data = BufferSize / SDHostData->BlockLength;
}
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_BLKCNT,
1,
&Data
);
} else {
Data = 0;
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_BLKSZ,
1,
&Data
);
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_BLKCNT,
1,
&Data
);
}
//
//Argument
//
Data = Argument;
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint32,
0,
(UINT64)MMIO_CMDARG,
1,
&Data
);
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_XFRMODE,
1,
&Data
);
DEBUG ((EFI_D_INFO, "Transfer mode read = 0x%x \r\n", (Data & 0xFFFF)));
//
//BIT0 - DMA Enable
//BIT2 - Auto Cmd12
//
if (DataType == InData) {
Data |= BIT4 | BIT0;
} else if (DataType == OutData){
Data &= ~BIT4;
Data |= BIT0;
} else {
Data &= ~(BIT4 | BIT0);
}
if (BufferSize <= SDHostData->BlockLength) {
Data &= ~ (BIT5 | BIT1 | BIT2);
Data |= BIT1; // Enable block count always
} else {
if (SDHostData->IsAutoStopCmd && AutoCMD12Enable) {
Data |= (BIT5 | BIT1 | BIT2);
} else {
Data |= (BIT5 | BIT1);
}
}
DEBUG ((EFI_D_INFO, "Transfer mode write = 0x%x \r\n", (Data & 0xffff)));
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_XFRMODE,
1,
&Data
);
//
//Command
//
//ResponseTypeSelect IndexCheck CRCCheck ResponseType
// 00 0 0 NoResponse
// 01 0 1 R2
// 10 0 0 R3, R4
// 10 1 1 R1, R5, R6, R7
// 11 1 1 R1b, R5b
//
switch (ResponseType) {
case ResponseNo:
Data = (CommandIndex << 8);
ResponseDataCount = 0;
break;
case ResponseR1:
case ResponseR5:
case ResponseR6:
case ResponseR7:
Data = (CommandIndex << 8) | BIT1 | BIT4| BIT3;
ResponseDataCount = 1;
break;
case ResponseR1b:
case ResponseR5b:
Data = (CommandIndex << 8) | BIT0 | BIT1 | BIT4| BIT3;
ResponseDataCount = 1;
break;
case ResponseR2:
Data = (CommandIndex << 8) | BIT0 | BIT3;
ResponseDataCount = 4;
break;
case ResponseR3:
case ResponseR4:
Data = (CommandIndex << 8) | BIT1;
ResponseDataCount = 1;
break;
default:
ASSERT (0);
Status = EFI_INVALID_PARAMETER;
DEBUG ((EFI_D_ERROR, "SendCommand: invalid parameter \r\n"));
goto Exit;
}
if (DataType != NoData) {
Data |= BIT5;
}
HostLEDEnable (This, TRUE);
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_SDCMD,
1,
&Data
);
Data = 0;
do {
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_ERINTSTS,
1,
&Data
);
if ((Data & 0x07FF) != 0) {
Status = GetErrorReason (CommandIndex, (UINT16)Data);
DEBUG ((EFI_D_ERROR, "SendCommand: Error happens \r\n"));
goto Exit;
}
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_NINTSTS,
1,
&Data
);
if ((Data & BIT0) == BIT0) {
//
//Command completed, can read response
//
if (DataType == NoData) {
break;
} else {
//
//Transfer completed
//
if ((Data & BIT1) == BIT1) {
break;
}
}
}
gBS->Stall (1 * 1000);
TimeOut --;
} while (TimeOut > 0);
if (TimeOut == 0) {
Status = EFI_TIMEOUT;
DEBUG ((EFI_D_ERROR, "SendCommand: Time out \r\n"));
goto Exit;
}
if (ResponseData != NULL) {
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint32,
0,
(UINT64)MMIO_RESP,
ResponseDataCount,
ResponseData
);
if (ResponseType == ResponseR2) {
//
// Adjustment for R2 response
//
Data = 1;
for (Index = 0; Index < ResponseDataCount; Index++) {
Data64 = LShiftU64(*ResponseData, 8);
*ResponseData = (UINT32)((Data64 & 0xFFFFFFFF) | Data);
Data = (UINT32)RShiftU64 (Data64, 32);
ResponseData++;
}
}
}
Exit:
HostLEDEnable (This, FALSE);
return Status;
}
/**
Set max clock frequency of the host, the actual frequency may not be the same as MaxFrequency.
It depends on the max frequency the host can support, divider, and host speed mode.
@param This A pointer to the EFI_SD_HOST_IO_PROTOCOL instance.
@param MaxFrequency Max frequency in HZ.
@retval EFI_SUCCESS
@retval EFI_TIMEOUT
**/
EFI_STATUS
EFIAPI
SetClockFrequency (
IN EFI_SD_HOST_IO_PROTOCOL *This,
IN UINT32 MaxFrequency
)
{
UINT32 Data;
UINT16 FreqSelBits;
EFI_STATUS Status;
SDHOST_DATA *SDHostData;
EFI_PCI_IO_PROTOCOL *PciIo;
UINT32 TimeOutCount;
UINT32 Revision;
SDHostData = SDHOST_DATA_FROM_THIS (This);
PciIo = SDHostData->PciIo;
Data = 0;
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_CLKCTL,
1,
&Data
);
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint8,
0,
(UINT64)MMIO_CTRLRVER,
1,
&Revision
);
Revision &= 0x000000FF;
Status = DividedClockModeBits (
SDHostData->BaseClockInMHz * 1000 * 1000,
MaxFrequency,
(Revision < SDHCI_SPEC_300),
&FreqSelBits
);
if (EFI_ERROR (Status)) {
//
// Cannot reach MaxFrequency with SDHostData->BaseClockInMHz.
//
ASSERT_EFI_ERROR (Status);
return Status;
}
Data = 0;
//
//Enable internal clock and Stop Clock Enable
//
Data = BIT0;
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_CLKCTL,
1,
&Data
);
TimeOutCount = TIME_OUT_1S;
do {
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_CLKCTL,
1,
&Data
);
gBS->Stall (1 * 1000);
TimeOutCount --;
if (TimeOutCount == 0) {
DEBUG ((EFI_D_ERROR, "SetClockFrequency: Time out \r\n"));
return EFI_TIMEOUT;
}
} while ((Data & BIT1) != BIT1);
DEBUG ((EFI_D_INFO, "Base Clock In MHz: %d\r\n", SDHostData->BaseClockInMHz));
Data = (BIT0 | ((UINT32) FreqSelBits));
DEBUG ((EFI_D_INFO, "Data write to MMIO_CLKCTL: 0x%04x \r\n", Data));
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_CLKCTL,
1,
&Data
);
TimeOutCount = TIME_OUT_1S;
do {
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_CLKCTL,
1,
&Data
);
gBS->Stall (1 * 1000);
TimeOutCount --;
if (TimeOutCount == 0) {
DEBUG ((EFI_D_ERROR, "SetClockFrequency: Time out \r\n"));
return EFI_TIMEOUT;
}
} while ((Data & BIT1) != BIT1);
gBS->Stall (20 * 1000);
Data |= BIT2;
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_CLKCTL,
1,
&Data
);
return EFI_SUCCESS;
}
/**
Set bus width of the host controller
@param This A pointer to the EFI_SD_HOST_IO_PROTOCOL instance.
@param BusWidth Bus width in 1, 4, 8 bits.
@retval EFI_SUCCESS
@retval EFI_INVALID_PARAMETER
**/
EFI_STATUS
EFIAPI
SetBusWidth (
IN EFI_SD_HOST_IO_PROTOCOL *This,
IN UINT32 BusWidth
)
{
SDHOST_DATA *SDHostData;
EFI_PCI_IO_PROTOCOL *PciIo;
UINT8 Data;
SDHostData = SDHOST_DATA_FROM_THIS (This);
if ((BusWidth != 1) && (BusWidth != 4) && (BusWidth != 8)) {
DEBUG ((EFI_D_ERROR, "SetBusWidth: Invalid parameter \r\n"));
return EFI_INVALID_PARAMETER;
}
if ((SDHostData->SDHostIo.HostCapability.BusWidth8 == FALSE) && (BusWidth == 8)) {
DEBUG ((EFI_D_ERROR, "SetBusWidth: Invalid parameter \r\n"));
return EFI_INVALID_PARAMETER;
}
PciIo = SDHostData->PciIo;
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint8,
0,
(UINT64)MMIO_HOSTCTL,
1,
&Data
);
//
// BIT5 8-bit MMC Support (MMC8):
// If set, IOH supports 8-bit MMC. When cleared, IOH does not support this feature
//
if (BusWidth == 8) {
DEBUG ((EFI_D_INFO, "Bus Width is 8-bit ... \r\n"));
Data |= BIT5;
} else if (BusWidth == 4) {
DEBUG ((EFI_D_INFO, "Bus Width is 4-bit ... \r\n"));
Data &= ~BIT5;
Data |= BIT1;
} else {
DEBUG ((EFI_D_INFO, "Bus Width is 1-bit ... \r\n"));
Data &= ~BIT5;
Data &= ~BIT1;
}
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint8,
0,
(UINT64)MMIO_HOSTCTL,
1,
&Data
);
return EFI_SUCCESS;
}
/**
Set voltage which could supported by the host controller.
Support 0(Power off the host), 1.8V, 3.0V, 3.3V
@param This A pointer to the EFI_SD_HOST_IO_PROTOCOL instance.
@param Voltage Units in 0.1 V.
@retval EFI_SUCCESS
@retval EFI_INVALID_PARAMETER
**/
EFI_STATUS
EFIAPI
SetHostVoltage (
IN EFI_SD_HOST_IO_PROTOCOL *This,
IN UINT32 Voltage
)
{
SDHOST_DATA *SDHostData;
EFI_PCI_IO_PROTOCOL *PciIo;
UINT8 Data;
EFI_STATUS Status;
SDHostData = SDHOST_DATA_FROM_THIS (This);
PciIo = SDHostData->PciIo;
Status = EFI_SUCCESS;
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint8,
0,
(UINT64)MMIO_PWRCTL,
1,
&Data
);
if (Voltage == 0) {
//
//Power Off the host
//
Data &= ~BIT0;
} else if (Voltage <= 18 && This->HostCapability.V18Support) {
//
//1.8V
//
Data |= (BIT1 | BIT3 | BIT0);
} else if (Voltage > 18 && Voltage <= 30 && This->HostCapability.V30Support) {
//
//3.0V
//
Data |= (BIT2 | BIT3 | BIT0);
} else if (Voltage > 30 && Voltage <= 33 && This->HostCapability.V33Support) {
//
//3.3V
//
Data |= (BIT1 | BIT2 | BIT3 | BIT0);
} else {
Status = EFI_UNSUPPORTED;
goto Exit;
}
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint8,
0,
(UINT64)MMIO_PWRCTL,
1,
&Data
);
gBS->Stall (10 * 1000);
Exit:
return Status;
}
/**
Reset the host controller.
@param This A pointer to the EFI_SD_HOST_IO_PROTOCOL instance.
@param ResetAll TRUE to reset all.
@retval EFI_SUCCESS
@retval EFI_TIMEOUT
**/
EFI_STATUS
EFIAPI
ResetSDHost (
IN EFI_SD_HOST_IO_PROTOCOL *This,
IN RESET_TYPE ResetType
)
{
SDHOST_DATA *SDHostData;
EFI_PCI_IO_PROTOCOL *PciIo;
UINT32 Data;
UINT16 ErrStatus;
UINT32 Mask;
UINT32 TimeOutCount;
UINT16 SaveClkCtl;
UINT16 ZeroClkCtl;
SDHostData = SDHOST_DATA_FROM_THIS (This);
PciIo = SDHostData->PciIo;
Mask = 0;
ErrStatus = 0;
if (ResetType == Reset_Auto) {
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_ERINTSTS,
1,
&ErrStatus
);
if ((ErrStatus & 0xF) != 0) {
//
//Command Line
//
Mask |= BIT1;
}
if ((ErrStatus & 0x70) != 0) {
//
//Data Line
//
Mask |= BIT2;
}
}
if (ResetType == Reset_DAT || ResetType == Reset_DAT_CMD) {
Mask |= BIT2;
}
if (ResetType == Reset_CMD || ResetType == Reset_DAT_CMD) {
Mask |= BIT1;
}
if (ResetType == Reset_All) {
Mask = BIT0;
}
if (Mask == 0) {
return EFI_SUCCESS;
}
//
// To improve SD stability, we zero the MMIO_CLKCTL register and
// stall for 50 microseconds before resetting the controller. We
// restore the register setting following the reset operation.
//
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_CLKCTL,
1,
&SaveClkCtl
);
ZeroClkCtl = (UINT16) 0;
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_CLKCTL,
1,
&ZeroClkCtl
);
gBS->Stall (50);
//
// Reset the SD host controller
//
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint8,
0,
(UINT64)MMIO_SWRST,
1,
&Mask
);
Data = 0;
TimeOutCount = TIME_OUT_1S;
do {
gBS->Stall (1 * 1000);
TimeOutCount --;
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint8,
0,
(UINT64)MMIO_SWRST,
1,
&Data
);
if ((Data & Mask) == 0) {
break;
}
} while (TimeOutCount > 0);
//
// We now restore the MMIO_CLKCTL register which we set to 0 above.
//
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_CLKCTL,
1,
&SaveClkCtl
);
if (TimeOutCount == 0) {
DEBUG ((EFI_D_ERROR, "ResetSDHost: Time out \r\n"));
return EFI_TIMEOUT;
}
return EFI_SUCCESS;
}
/**
Enable auto stop on the host controller.
@param This A pointer to the EFI_SD_HOST_IO_PROTOCOL instance.
@param Enable TRUE to enable, FALSE to disable.
@retval EFI_SUCCESS
@retval EFI_TIMEOUT
**/
EFI_STATUS
EFIAPI
EnableAutoStopCmd (
IN EFI_SD_HOST_IO_PROTOCOL *This,
IN BOOLEAN Enable
)
{
SDHOST_DATA *SDHostData;
SDHostData = SDHOST_DATA_FROM_THIS (This);
SDHostData->IsAutoStopCmd = Enable;
return EFI_SUCCESS;
}
/**
Set the Block length on the host controller.
@param This A pointer to the EFI_SD_HOST_IO_PROTOCOL instance.
@param BlockLength card supportes block length.
@retval EFI_SUCCESS
@retval EFI_TIMEOUT
**/
EFI_STATUS
EFIAPI
SetBlockLength (
IN EFI_SD_HOST_IO_PROTOCOL *This,
IN UINT32 BlockLength
)
{
SDHOST_DATA *SDHostData;
SDHostData = SDHOST_DATA_FROM_THIS (This);
DEBUG ((EFI_D_INFO, "Block length on the host controller: %d \r\n", BlockLength));
SDHostData->BlockLength = BlockLength;
return EFI_SUCCESS;
}
/**
Find whether these is a card inserted into the slot. If so init the host.
If not, return EFI_NOT_FOUND.
@param This A pointer to the EFI_SD_HOST_IO_PROTOCOL instance.
@retval EFI_SUCCESS
@retval EFI_NOT_FOUND
**/
EFI_STATUS
EFIAPI
DetectCardAndInitHost (
IN EFI_SD_HOST_IO_PROTOCOL *This
)
{
SDHOST_DATA *SDHostData;
EFI_PCI_IO_PROTOCOL *PciIo;
UINT32 Data;
EFI_STATUS Status;
UINT8 Voltages[] = { 33, 30, 18 };
UINTN Loop;
SDHostData = SDHOST_DATA_FROM_THIS (This);
PciIo = SDHostData->PciIo;
Status = EFI_NOT_FOUND;
Data = 0;
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint32,
0,
(UINT64)MMIO_PSTATE,
1,
&Data
);
if ((Data & (BIT16 | BIT17 | BIT18)) != (BIT16 | BIT17 | BIT18)) {
//
// Has no card inserted
//
DEBUG ((EFI_D_INFO, "DetectCardAndInitHost: No Cards \r\n"));
Status = EFI_NOT_FOUND;
goto Exit;
}
DEBUG ((EFI_D_INFO, "DetectCardAndInitHost: Find Cards \r\n"));
Status = EFI_NOT_FOUND;
for (Loop = 0; Loop < sizeof (Voltages); Loop++) {
DEBUG ((
EFI_D_INFO,
"DetectCardAndInitHost: SetHostVoltage %d.%dV \r\n",
Voltages[Loop] / 10,
Voltages[Loop] % 10
));
Status = SetHostVoltage (This, Voltages[Loop]);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_INFO, "DetectCardAndInitHost set voltages: [failed]\n"));
} else {
DEBUG ((EFI_D_INFO, "DetectCardAndInitHost set voltages: [success]\n"));
break;
}
}
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "DetectCardAndInitHost: Fail to set voltage \r\n"));
goto Exit;
}
Status = SetClockFrequency (This, FREQUENCY_OD);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "DetectCardAndInitHost: Fail to set frequency \r\n"));
goto Exit;
}
SetBusWidth (This, 1);
//
//Enable normal status change
//
Data = (BIT0 | BIT1);
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_NINTEN,
1,
&Data
);
//
//Enable error status change
//
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_ERINTEN,
1,
&Data
);
Data |= (BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7 | BIT8);
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_ERINTEN,
1,
&Data
);
//
//Data transfer Timeout control
//
Data = 0x0E;
PciIo->Mem.Write (
PciIo,
EfiPciIoWidthUint8,
0,
(UINT64)MMIO_TOCTL,
1,
&Data
);
//
//Set Default Bus width as 1 bit
//
Exit:
return Status;
}
/**
Entry point for EFI drivers.
@param ImageHandle EFI_HANDLE.
@param SystemTable EFI_SYSTEM_TABLE.
@retval EFI_SUCCESS Driver is successfully loaded.
@return Others Failed.
**/
EFI_STATUS
EFIAPI
InitializeSDController (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
return EfiLibInstallDriverBindingComponentName2 (
ImageHandle,
SystemTable,
&gSDControllerDriverBinding,
ImageHandle,
&gSDControllerName,
&gSDControllerName2
);
}
/**
Test to see if this driver supports ControllerHandle. Any
ControllerHandle that has SDHostIoProtocol installed will be supported.
@param This Protocol instance pointer.
@param Controller Handle of device to test.
@param RemainingDevicePath Not used.
@return EFI_SUCCESS This driver supports this device.
@return EFI_UNSUPPORTED This driver does not support this device.
**/
EFI_STATUS
EFIAPI
SDControllerSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
)
{
EFI_STATUS OpenStatus;
EFI_STATUS Status;
EFI_PCI_IO_PROTOCOL *PciIo;
PCI_CLASSC PciClass;
EFI_SD_HOST_IO_PROTOCOL *SdHostIo;
Status = gBS->OpenProtocol (
Controller,
&gEfiSDHostIoProtocolGuid,
(VOID **)&SdHostIo,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (!EFI_ERROR (Status)) {
DEBUG (( DEBUG_INFO, "SdHost controller is already started\n"));
return EFI_ALREADY_STARTED;
}
//
// Test whether there is PCI IO Protocol attached on the controller handle.
//
OpenStatus = gBS->OpenProtocol (
Controller,
&gEfiPciIoProtocolGuid,
(VOID **) &PciIo,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (OpenStatus)) {
return OpenStatus;
}
Status = PciIo->Pci.Read (
PciIo,
EfiPciIoWidthUint8,
PCI_CLASSCODE_OFFSET,
sizeof (PCI_CLASSC) / sizeof (UINT8),
&PciClass
);
if (EFI_ERROR (Status)) {
Status = EFI_UNSUPPORTED;
goto ON_EXIT;
}
//
// Test whether the controller belongs to SD type
//
if ((PciClass.BaseCode != PCI_CLASS_SYSTEM_PERIPHERAL) ||
(PciClass.SubClassCode != PCI_SUBCLASS_SD_HOST_CONTROLLER) ||
((PciClass.PI != PCI_IF_STANDARD_HOST_NO_DMA) && (PciClass.PI != PCI_IF_STANDARD_HOST_SUPPORT_DMA))
) {
Status = EFI_UNSUPPORTED;
}
ON_EXIT:
gBS->CloseProtocol (
Controller,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
Controller
);
return Status;
}
/**
Starting the SD Host Controller Driver.
@param This Protocol instance pointer.
@param Controller Handle of device to test.
@param RemainingDevicePath Not used.
@retval EFI_SUCCESS This driver supports this device.
@retval EFI_UNSUPPORTED This driver does not support this device.
@retval EFI_DEVICE_ERROR This driver cannot be started due to device Error.
EFI_OUT_OF_RESOURCES- Failed due to resource shortage.
**/
EFI_STATUS
EFIAPI
SDControllerStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
)
{
EFI_STATUS Status;
EFI_PCI_IO_PROTOCOL *PciIo;
SDHOST_DATA *SDHostData;
UINT32 Data;
SDHostData = NULL;
Data = 0;
//
// Open PCI I/O Protocol and save pointer to open protocol
// in private data area.
//
Status = gBS->OpenProtocol (
Controller,
&gEfiPciIoProtocolGuid,
(VOID **) &PciIo,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
goto Exit;
}
//
// Enable the SD Host Controller MMIO space
//
Status = PciIo->Attributes (
PciIo,
EfiPciIoAttributeOperationEnable,
EFI_PCI_DEVICE_ENABLE,
NULL
);
if (EFI_ERROR (Status)) {
Status = EFI_OUT_OF_RESOURCES;
goto Exit;
}
SDHostData = (SDHOST_DATA*)AllocateZeroPool(sizeof (SDHOST_DATA));
if (SDHostData == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Exit;
}
SDHostData->Signature = SDHOST_DATA_SIGNATURE;
SDHostData->PciIo = PciIo;
CopyMem (&SDHostData->SDHostIo, &mSDHostIo, sizeof (EFI_SD_HOST_IO_PROTOCOL));
ResetSDHost (&SDHostData->SDHostIo, Reset_All);
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint16,
0,
(UINT64)MMIO_CTRLRVER,
1,
&Data
);
SDHostData->SDHostIo.HostCapability.HostVersion = Data & 0xFF;
DEBUG ((EFI_D_INFO, "SdHostDriverBindingStart: HostVersion 0x%x \r\n", SDHostData->SDHostIo.HostCapability.HostVersion));
PciIo->Mem.Read (
PciIo,
EfiPciIoWidthUint32,
0,
(UINT64)MMIO_CAP,
1,
&Data
);
DEBUG ((EFI_D_INFO, "SdHostDriverBindingStart: MMIO_CAP 0x%x \r\n", Data));
if ((Data & BIT18) != 0) {
SDHostData->SDHostIo.HostCapability.BusWidth8 = TRUE;
}
if ((Data & BIT21) != 0) {
SDHostData->SDHostIo.HostCapability.HighSpeedSupport = TRUE;
}
if ((Data & BIT24) != 0) {
SDHostData->SDHostIo.HostCapability.V33Support = TRUE;
}
if ((Data & BIT25) != 0) {
SDHostData->SDHostIo.HostCapability.V30Support = TRUE;
}
if ((Data & BIT26) != 0) {
SDHostData->SDHostIo.HostCapability.V18Support = TRUE;
}
SDHostData->SDHostIo.HostCapability.BusWidth4 = TRUE;
if(SDHostData->SDHostIo.HostCapability.HostVersion < SDHCI_SPEC_300) {
SDHostData->BaseClockInMHz = (Data >> 8) & 0x3F;
}
else {
SDHostData->BaseClockInMHz = (Data >> 8) & 0xFF;
}
SDHostData->BlockLength = 512 << ((Data >> 16) & 0x03);
DEBUG ((EFI_D_INFO, "SdHostDriverBindingStart: BlockLength 0x%x \r\n", SDHostData->BlockLength));
SDHostData->IsAutoStopCmd = TRUE;
Status = gBS->InstallProtocolInterface (
&Controller,
&gEfiSDHostIoProtocolGuid,
EFI_NATIVE_INTERFACE,
&SDHostData->SDHostIo
);
if (EFI_ERROR (Status)) {
goto Exit;
}
//
// Install the component name protocol
//
SDHostData->ControllerNameTable = NULL;
AddUnicodeString2 (
"eng",
gSDControllerName.SupportedLanguages,
&SDHostData->ControllerNameTable,
L"SD Host Controller",
TRUE
);
AddUnicodeString2 (
"en",
gSDControllerName2.SupportedLanguages,
&SDHostData->ControllerNameTable,
L"SD Host Controller",
FALSE
);
Exit:
if (EFI_ERROR (Status)) {
if (SDHostData != NULL) {
FreePool (SDHostData);
}
}
return Status;
}
/**
Stop this driver on ControllerHandle. Support stopping any child handles
created by this driver.
@param This Protocol instance pointer.
@param Controller Handle of device to stop driver on.
@param NumberOfChildren Number of Children in the ChildHandleBuffer.
@param ChildHandleBuffer List of handles for the children we need to stop.
@return EFI_SUCCESS
@return others
**/
EFI_STATUS
EFIAPI
SDControllerStop (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN UINTN NumberOfChildren,
IN EFI_HANDLE *ChildHandleBuffer
)
{
EFI_STATUS Status;
EFI_SD_HOST_IO_PROTOCOL *SDHostIo;
SDHOST_DATA *SDHostData;
Status = gBS->OpenProtocol (
Controller,
&gEfiSDHostIoProtocolGuid,
(VOID **) &SDHostIo,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
//
// Test whether the Controller handler passed in is a valid
// Usb controller handle that should be supported, if not,
// return the error status directly
//
if (EFI_ERROR (Status)) {
return Status;
}
SetHostVoltage (SDHostIo, 0);
SDHostData = SDHOST_DATA_FROM_THIS(SDHostIo);
//
// Uninstall Block I/O protocol from the device handle
//
Status = gBS->UninstallProtocolInterface (
Controller,
&gEfiSDHostIoProtocolGuid,
SDHostIo
);
if (EFI_ERROR (Status)) {
return Status;
}
FreeUnicodeStringTable (SDHostData->ControllerNameTable);
FreePool (SDHostData);
gBS->CloseProtocol (
Controller,
&gEfiPciIoProtocolGuid,
This->DriverBindingHandle,
Controller
);
return EFI_SUCCESS;
}