/** @file | |
The EHCI register operation routines. | |
Copyright (c) 2007 - 2017, Intel Corporation. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "Ehci.h" | |
/** | |
Read EHCI capability register. | |
@param Ehc The EHCI device. | |
@param Offset Capability register address. | |
@return The register content read. | |
@retval If err, return 0xffff. | |
**/ | |
UINT32 | |
EhcReadCapRegister ( | |
IN USB2_HC_DEV *Ehc, | |
IN UINT32 Offset | |
) | |
{ | |
UINT32 Data; | |
EFI_STATUS Status; | |
Status = Ehc->PciIo->Mem.Read ( | |
Ehc->PciIo, | |
EfiPciIoWidthUint32, | |
EHC_BAR_INDEX, | |
(UINT64)Offset, | |
1, | |
&Data | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "EhcReadCapRegister: Pci Io read error - %r at %d\n", Status, Offset)); | |
Data = 0xFFFF; | |
} | |
return Data; | |
} | |
/** | |
Read EHCI debug port register. | |
@param Ehc The EHCI device. | |
@param Offset Debug port register offset. | |
@return The register content read. | |
@retval If err, return 0xffff. | |
**/ | |
UINT32 | |
EhcReadDbgRegister ( | |
IN CONST USB2_HC_DEV *Ehc, | |
IN UINT32 Offset | |
) | |
{ | |
UINT32 Data; | |
EFI_STATUS Status; | |
Status = Ehc->PciIo->Mem.Read ( | |
Ehc->PciIo, | |
EfiPciIoWidthUint32, | |
Ehc->DebugPortBarNum, | |
Ehc->DebugPortOffset + Offset, | |
1, | |
&Data | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "EhcReadDbgRegister: Pci Io read error - %r at %d\n", Status, Offset)); | |
Data = 0xFFFF; | |
} | |
return Data; | |
} | |
/** | |
Check whether the host controller has an in-use debug port. | |
@param[in] Ehc The Enhanced Host Controller to query. | |
@param[in] PortNumber If PortNumber is not NULL, then query whether | |
PortNumber is an in-use debug port on Ehc. (PortNumber | |
is taken in UEFI notation, i.e., zero-based.) | |
Otherwise, query whether Ehc has any in-use debug | |
port. | |
@retval TRUE PortNumber is an in-use debug port on Ehc (if PortNumber is | |
not NULL), or some port on Ehc is an in-use debug port | |
(otherwise). | |
@retval FALSE PortNumber is not an in-use debug port on Ehc (if PortNumber | |
is not NULL), or no port on Ehc is an in-use debug port | |
(otherwise). | |
**/ | |
BOOLEAN | |
EhcIsDebugPortInUse ( | |
IN CONST USB2_HC_DEV *Ehc, | |
IN CONST UINT8 *PortNumber OPTIONAL | |
) | |
{ | |
UINT32 State; | |
if (Ehc->DebugPortNum == 0) { | |
// | |
// The host controller has no debug port. | |
// | |
return FALSE; | |
} | |
// | |
// The Debug Port Number field in HCSPARAMS is one-based. | |
// | |
if ((PortNumber != NULL) && (*PortNumber != Ehc->DebugPortNum - 1)) { | |
// | |
// The caller specified a port, but it's not the debug port of the host | |
// controller. | |
// | |
return FALSE; | |
} | |
// | |
// Deduce usage from the Control Register. | |
// | |
State = EhcReadDbgRegister (Ehc, 0); | |
return (State & USB_DEBUG_PORT_IN_USE_MASK) == USB_DEBUG_PORT_IN_USE_MASK; | |
} | |
/** | |
Read EHCI Operation register. | |
@param Ehc The EHCI device. | |
@param Offset The operation register offset. | |
@return The register content read. | |
@retval If err, return 0xffff. | |
**/ | |
UINT32 | |
EhcReadOpReg ( | |
IN USB2_HC_DEV *Ehc, | |
IN UINT32 Offset | |
) | |
{ | |
UINT32 Data; | |
EFI_STATUS Status; | |
ASSERT (Ehc->CapLen != 0); | |
Status = Ehc->PciIo->Mem.Read ( | |
Ehc->PciIo, | |
EfiPciIoWidthUint32, | |
EHC_BAR_INDEX, | |
Ehc->CapLen + Offset, | |
1, | |
&Data | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "EhcReadOpReg: Pci Io Read error - %r at %d\n", Status, Offset)); | |
Data = 0xFFFF; | |
} | |
return Data; | |
} | |
/** | |
Write the data to the EHCI operation register. | |
@param Ehc The EHCI device. | |
@param Offset EHCI operation register offset. | |
@param Data The data to write. | |
**/ | |
VOID | |
EhcWriteOpReg ( | |
IN USB2_HC_DEV *Ehc, | |
IN UINT32 Offset, | |
IN UINT32 Data | |
) | |
{ | |
EFI_STATUS Status; | |
ASSERT (Ehc->CapLen != 0); | |
Status = Ehc->PciIo->Mem.Write ( | |
Ehc->PciIo, | |
EfiPciIoWidthUint32, | |
EHC_BAR_INDEX, | |
Ehc->CapLen + Offset, | |
1, | |
&Data | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "EhcWriteOpReg: Pci Io Write error: %r at %d\n", Status, Offset)); | |
} | |
} | |
/** | |
Set one bit of the operational register while keeping other bits. | |
@param Ehc The EHCI device. | |
@param Offset The offset of the operational register. | |
@param Bit The bit mask of the register to set. | |
**/ | |
VOID | |
EhcSetOpRegBit ( | |
IN USB2_HC_DEV *Ehc, | |
IN UINT32 Offset, | |
IN UINT32 Bit | |
) | |
{ | |
UINT32 Data; | |
Data = EhcReadOpReg (Ehc, Offset); | |
Data |= Bit; | |
EhcWriteOpReg (Ehc, Offset, Data); | |
} | |
/** | |
Clear one bit of the operational register while keeping other bits. | |
@param Ehc The EHCI device. | |
@param Offset The offset of the operational register. | |
@param Bit The bit mask of the register to clear. | |
**/ | |
VOID | |
EhcClearOpRegBit ( | |
IN USB2_HC_DEV *Ehc, | |
IN UINT32 Offset, | |
IN UINT32 Bit | |
) | |
{ | |
UINT32 Data; | |
Data = EhcReadOpReg (Ehc, Offset); | |
Data &= ~Bit; | |
EhcWriteOpReg (Ehc, Offset, Data); | |
} | |
/** | |
Wait the operation register's bit as specified by Bit | |
to become set (or clear). | |
@param Ehc The EHCI device. | |
@param Offset The offset of the operation register. | |
@param Bit The bit of the register to wait for. | |
@param WaitToSet Wait the bit to set or clear. | |
@param Timeout The time to wait before abort (in millisecond). | |
@retval EFI_SUCCESS The bit successfully changed by host controller. | |
@retval EFI_TIMEOUT The time out occurred. | |
**/ | |
EFI_STATUS | |
EhcWaitOpRegBit ( | |
IN USB2_HC_DEV *Ehc, | |
IN UINT32 Offset, | |
IN UINT32 Bit, | |
IN BOOLEAN WaitToSet, | |
IN UINT32 Timeout | |
) | |
{ | |
UINT32 Index; | |
for (Index = 0; Index < Timeout / EHC_SYNC_POLL_INTERVAL + 1; Index++) { | |
if (EHC_REG_BIT_IS_SET (Ehc, Offset, Bit) == WaitToSet) { | |
return EFI_SUCCESS; | |
} | |
gBS->Stall (EHC_SYNC_POLL_INTERVAL); | |
} | |
return EFI_TIMEOUT; | |
} | |
/** | |
Add support for UEFI Over Legacy (UoL) feature, stop | |
the legacy USB SMI support. | |
@param Ehc The EHCI device. | |
**/ | |
VOID | |
EhcClearLegacySupport ( | |
IN USB2_HC_DEV *Ehc | |
) | |
{ | |
UINT32 ExtendCap; | |
EFI_PCI_IO_PROTOCOL *PciIo; | |
UINT32 Value; | |
UINT32 TimeOut; | |
DEBUG ((DEBUG_INFO, "EhcClearLegacySupport: called to clear legacy support\n")); | |
PciIo = Ehc->PciIo; | |
ExtendCap = (Ehc->HcCapParams >> 8) & 0xFF; | |
PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, ExtendCap, 1, &Value); | |
PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, ExtendCap + 0x4, 1, &Value); | |
PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, ExtendCap, 1, &Value); | |
Value |= (0x1 << 24); | |
PciIo->Pci.Write (PciIo, EfiPciIoWidthUint32, ExtendCap, 1, &Value); | |
TimeOut = 40; | |
while (TimeOut-- != 0) { | |
gBS->Stall (500); | |
PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, ExtendCap, 1, &Value); | |
if ((Value & 0x01010000) == 0x01000000) { | |
break; | |
} | |
} | |
PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, ExtendCap, 1, &Value); | |
PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, ExtendCap + 0x4, 1, &Value); | |
} | |
/** | |
Set door bell and wait it to be ACKed by host controller. | |
This function is used to synchronize with the hardware. | |
@param Ehc The EHCI device. | |
@param Timeout The time to wait before abort (in millisecond, ms). | |
@retval EFI_SUCCESS Synchronized with the hardware. | |
@retval EFI_TIMEOUT Time out happened while waiting door bell to set. | |
**/ | |
EFI_STATUS | |
EhcSetAndWaitDoorBell ( | |
IN USB2_HC_DEV *Ehc, | |
IN UINT32 Timeout | |
) | |
{ | |
EFI_STATUS Status; | |
UINT32 Data; | |
EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_IAAD); | |
Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_IAA, TRUE, Timeout); | |
// | |
// ACK the IAA bit in USBSTS register. Make sure other | |
// interrupt bits are not ACKed. These bits are WC (Write Clean). | |
// | |
Data = EhcReadOpReg (Ehc, EHC_USBSTS_OFFSET); | |
Data &= ~USBSTS_INTACK_MASK; | |
Data |= USBSTS_IAA; | |
EhcWriteOpReg (Ehc, EHC_USBSTS_OFFSET, Data); | |
return Status; | |
} | |
/** | |
Clear all the interrutp status bits, these bits | |
are Write-Clean. | |
@param Ehc The EHCI device. | |
**/ | |
VOID | |
EhcAckAllInterrupt ( | |
IN USB2_HC_DEV *Ehc | |
) | |
{ | |
EhcWriteOpReg (Ehc, EHC_USBSTS_OFFSET, USBSTS_INTACK_MASK); | |
} | |
/** | |
Enable the periodic schedule then wait EHC to | |
actually enable it. | |
@param Ehc The EHCI device. | |
@param Timeout The time to wait before abort (in millisecond, ms). | |
@retval EFI_SUCCESS The periodical schedule is enabled. | |
@retval EFI_TIMEOUT Time out happened while enabling periodic schedule. | |
**/ | |
EFI_STATUS | |
EhcEnablePeriodSchd ( | |
IN USB2_HC_DEV *Ehc, | |
IN UINT32 Timeout | |
) | |
{ | |
EFI_STATUS Status; | |
EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_ENABLE_PERIOD); | |
Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_PERIOD_ENABLED, TRUE, Timeout); | |
return Status; | |
} | |
/** | |
Enable asynchrounous schedule. | |
@param Ehc The EHCI device. | |
@param Timeout Time to wait before abort. | |
@retval EFI_SUCCESS The EHCI asynchronous schedule is enabled. | |
@return Others Failed to enable the asynchronous scheudle. | |
**/ | |
EFI_STATUS | |
EhcEnableAsyncSchd ( | |
IN USB2_HC_DEV *Ehc, | |
IN UINT32 Timeout | |
) | |
{ | |
EFI_STATUS Status; | |
EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_ENABLE_ASYNC); | |
Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_ASYNC_ENABLED, TRUE, Timeout); | |
return Status; | |
} | |
/** | |
Whether Ehc is halted. | |
@param Ehc The EHCI device. | |
@retval TRUE The controller is halted. | |
@retval FALSE It isn't halted. | |
**/ | |
BOOLEAN | |
EhcIsHalt ( | |
IN USB2_HC_DEV *Ehc | |
) | |
{ | |
return EHC_REG_BIT_IS_SET (Ehc, EHC_USBSTS_OFFSET, USBSTS_HALT); | |
} | |
/** | |
Whether system error occurred. | |
@param Ehc The EHCI device. | |
@return TRUE System error happened. | |
@return FALSE No system error. | |
**/ | |
BOOLEAN | |
EhcIsSysError ( | |
IN USB2_HC_DEV *Ehc | |
) | |
{ | |
return EHC_REG_BIT_IS_SET (Ehc, EHC_USBSTS_OFFSET, USBSTS_SYS_ERROR); | |
} | |
/** | |
Reset the host controller. | |
@param Ehc The EHCI device. | |
@param Timeout Time to wait before abort (in millisecond, ms). | |
@retval EFI_SUCCESS The host controller is reset. | |
@return Others Failed to reset the host. | |
**/ | |
EFI_STATUS | |
EhcResetHC ( | |
IN USB2_HC_DEV *Ehc, | |
IN UINT32 Timeout | |
) | |
{ | |
EFI_STATUS Status; | |
// | |
// Host can only be reset when it is halt. If not so, halt it | |
// | |
if (!EHC_REG_BIT_IS_SET (Ehc, EHC_USBSTS_OFFSET, USBSTS_HALT)) { | |
Status = EhcHaltHC (Ehc, Timeout); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
} | |
EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_RESET); | |
Status = EhcWaitOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_RESET, FALSE, Timeout); | |
return Status; | |
} | |
/** | |
Halt the host controller. | |
@param Ehc The EHCI device. | |
@param Timeout Time to wait before abort. | |
@retval EFI_SUCCESS The EHCI is halt. | |
@retval EFI_TIMEOUT Failed to halt the controller before Timeout. | |
**/ | |
EFI_STATUS | |
EhcHaltHC ( | |
IN USB2_HC_DEV *Ehc, | |
IN UINT32 Timeout | |
) | |
{ | |
EFI_STATUS Status; | |
EhcClearOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_RUN); | |
Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_HALT, TRUE, Timeout); | |
return Status; | |
} | |
/** | |
Set the EHCI to run. | |
@param Ehc The EHCI device. | |
@param Timeout Time to wait before abort. | |
@retval EFI_SUCCESS The EHCI is running. | |
@return Others Failed to set the EHCI to run. | |
**/ | |
EFI_STATUS | |
EhcRunHC ( | |
IN USB2_HC_DEV *Ehc, | |
IN UINT32 Timeout | |
) | |
{ | |
EFI_STATUS Status; | |
EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_RUN); | |
Status = EhcWaitOpRegBit (Ehc, EHC_USBSTS_OFFSET, USBSTS_HALT, FALSE, Timeout); | |
return Status; | |
} | |
/** | |
Initialize the HC hardware. | |
EHCI spec lists the five things to do to initialize the hardware: | |
1. Program CTRLDSSEGMENT | |
2. Set USBINTR to enable interrupts | |
3. Set periodic list base | |
4. Set USBCMD, interrupt threshold, frame list size etc | |
5. Write 1 to CONFIGFLAG to route all ports to EHCI | |
@param Ehc The EHCI device. | |
@return EFI_SUCCESS The EHCI has come out of halt state. | |
@return EFI_TIMEOUT Time out happened. | |
**/ | |
EFI_STATUS | |
EhcInitHC ( | |
IN USB2_HC_DEV *Ehc | |
) | |
{ | |
EFI_STATUS Status; | |
UINT32 Index; | |
UINT32 RegVal; | |
// This ASSERT crashes the BeagleBoard. There is some issue in the USB stack. | |
// This ASSERT needs to be removed so the BeagleBoard will boot. When we fix | |
// the USB stack we can put this ASSERT back in | |
// ASSERT (EhcIsHalt (Ehc)); | |
// | |
// Allocate the periodic frame and associated memeory | |
// management facilities if not already done. | |
// | |
if (Ehc->PeriodFrame != NULL) { | |
EhcFreeSched (Ehc); | |
} | |
Status = EhcInitSched (Ehc); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// 1. Clear USBINTR to disable all the interrupt. UEFI works by polling | |
// | |
EhcWriteOpReg (Ehc, EHC_USBINTR_OFFSET, 0); | |
// | |
// 2. Start the Host Controller | |
// | |
EhcSetOpRegBit (Ehc, EHC_USBCMD_OFFSET, USBCMD_RUN); | |
// | |
// 3. Power up all ports if EHCI has Port Power Control (PPC) support | |
// | |
if (Ehc->HcStructParams & HCSP_PPC) { | |
for (Index = 0; Index < (UINT8)(Ehc->HcStructParams & HCSP_NPORTS); Index++) { | |
// | |
// Do not clear port status bits on initialization. Otherwise devices will | |
// not enumerate properly at startup. | |
// | |
RegVal = EhcReadOpReg (Ehc, (UINT32)(EHC_PORT_STAT_OFFSET + (4 * Index))); | |
RegVal &= ~PORTSC_CHANGE_MASK; | |
RegVal |= PORTSC_POWER; | |
EhcWriteOpReg (Ehc, (UINT32)(EHC_PORT_STAT_OFFSET + (4 * Index)), RegVal); | |
} | |
} | |
// | |
// Wait roothub port power stable | |
// | |
gBS->Stall (EHC_ROOT_PORT_RECOVERY_STALL); | |
// | |
// 4. Set all ports routing to EHC | |
// | |
EhcSetOpRegBit (Ehc, EHC_CONFIG_FLAG_OFFSET, CONFIGFLAG_ROUTE_EHC); | |
Status = EhcEnablePeriodSchd (Ehc, EHC_GENERIC_TIMEOUT); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "EhcInitHC: failed to enable period schedule\n")); | |
return Status; | |
} | |
Status = EhcEnableAsyncSchd (Ehc, EHC_GENERIC_TIMEOUT); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "EhcInitHC: failed to enable async schedule\n")); | |
return Status; | |
} | |
return EFI_SUCCESS; | |
} |