mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-12-27 16:58:09 +01:00
7c0aa811ec
Signed-off-by: Sergey Isakov <isakov-sl@bk.ru>
670 lines
16 KiB
C
670 lines
16 KiB
C
/** @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 ((EFI_D_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 ((EFI_D_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 ((EFI_D_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 ((EFI_D_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 ((EFI_D_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 ((EFI_D_ERROR, "EhcInitHC: failed to enable period schedule\n"));
|
|
return Status;
|
|
}
|
|
|
|
Status = EhcEnableAsyncSchd (Ehc, EHC_GENERIC_TIMEOUT);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((EFI_D_ERROR, "EhcInitHC: failed to enable async schedule\n"));
|
|
return Status;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|