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