/** @file The EHCI register operation routines. Copyright (c) 2007 - 2017, Intel Corporation. All rights reserved.
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; }