/* * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include "AppleUSBUHCI.h" #define super IOUSBController // ======================================================================== #pragma mark Global variables // ======================================================================== class AppleUHCIPwrMgmtGlobals { public: AppleUHCIPwrMgmtGlobals(); ~AppleUHCIPwrMgmtGlobals(); inline bool isValid( void ) const; }; #define kUSBRemoteWakeupKey "usb_remote_wakeup" static const OSSymbol * gUSBRemoteWakeupKey; static AppleUHCIPwrMgmtGlobals gAppleUHCIPwrMgmtGlobals; AppleUHCIPwrMgmtGlobals::AppleUHCIPwrMgmtGlobals() { gUSBRemoteWakeupKey = OSSymbol::withCStringNoCopy( kUSBRemoteWakeupKey ); } AppleUHCIPwrMgmtGlobals::~AppleUHCIPwrMgmtGlobals() { if (gUSBRemoteWakeupKey) gUSBRemoteWakeupKey->release(); } bool AppleUHCIPwrMgmtGlobals::isValid( void ) const { return (gUSBRemoteWakeupKey); } // ======================================================================== #pragma mark Public power management interface // ======================================================================== #define kUHCI_NUM_POWER_STATES 2 static IOPMPowerState powerStates[kUHCI_NUM_POWER_STATES] = { { 1, // version 0, // capability flags 0, // output power character 0, // input power requirement 0, // static power 0, // unbudgeted power 0, // power to attain this state 0, // time to attain this state 0, // settle up time 0, // time to lower 0, // settle down time 0 // power domain budget }, { 1, // version IOPMDeviceUsable, // capability flags IOPMPowerOn, // output power character IOPMPowerOn, // input power requirement 0, // static power 0, // unbudgeted power 0, // power to attain this state 0, // time to attain this state 0, // settle up time 0, // time to lower 0, // settle down time 0 // power domain budget } }; void AppleUSBUHCI::initForPM (IOPCIDevice *provider) { USBLog(3, "%s[%p]::initForPM %p", getName(), this, provider); if (provider->getProperty("built-in") && (_errataBits & kErrataICH6PowerSequencing)) { // The ICH6 UHCI drivers on a Transition system just magically work on sleep/wake // so we will just hard code those. Other systems will have to be evaluated later setProperty("Card Type","Built-in"); _unloadUIMAcrossSleep = false; } else { // This appears to be necessary setProperty("Card Type","PCI"); _unloadUIMAcrossSleep = true; } if (_errataBits & kErrataICH6PowerSequencing) _powerDownNotifier = registerPrioritySleepWakeInterest(PowerDownHandler, this, 0); registerPowerDriver(this, powerStates, kUHCI_NUM_POWER_STATES); changePowerStateTo(kUHCIPowerLevelRunning); } unsigned long AppleUSBUHCI::maxCapabilityForDomainState ( IOPMPowerFlags domainState ) { if ( (domainState & IOPMPowerOn) || (domainState & kIOPMDoze) ) { return kUHCIPowerLevelRunning; } else { return kUHCIPowerLevelSuspend; } } unsigned long AppleUSBUHCI::initialPowerStateForDomainState ( IOPMPowerFlags domainState ) { return kUHCIPowerLevelRunning; } IOReturn AppleUSBUHCI::setPowerState( unsigned long powerStateOrdinal, IOService* whatDevice ) { IOReturn result; USBLog(3, "%s[%p]::setPowerState(%d, %p)", getName(), this, powerStateOrdinal, whatDevice); if (_powerLevel != kUHCIPowerLevelSuspend) { _workLoop->CloseGate(); } else { result = _workLoop->wake(&_powerLevel); if (result != kIOReturnSuccess) { USBError(1, "%s[%p] setPowerState - Can't wake workloop, error 0x%x", getName(), this, result); } } switch (powerStateOrdinal) { case kUHCIPowerLevelRunning: USBLog(3, "%s[%p]: changing to running state", getName(), this); if (_powerLevel == kUHCIPowerLevelSuspend) { if (!isInactive()) { //if (!_uimInitialized) { EnableUSBInterrupt(false); UIMInitializeForPowerUp(); EnableUSBInterrupt(true); //} if (_rootHubDevice == NULL) { result = CreateRootHubDevice(_device, &_rootHubDevice); if (result != kIOReturnSuccess) { USBError(1,"%s[%p] Could not create root hub device on wakeup (%x)!",getName(), this, result); } else { _rootHubDevice->registerService(kIOServiceRequired | kIOServiceSynchronous); } } } } ResumeController(); _remoteWakeupOccurred = true; _powerLevel = kUHCIPowerLevelRunning; break; case kUHCIPowerLevelSuspend: USBLog(3, "%s[%p]: changing to suspended state", getName(), this); if (_unloadUIMAcrossSleep) { USBLog(3, "%s[%p] Unloading UIM before going to sleep", getName(), this); if ( _rootHubDevice ) { _rootHubDevice->terminate(kIOServiceRequired | kIOServiceSynchronous); _rootHubDevice->detachAll(gIOUSBPlane); _rootHubDevice->release(); _rootHubDevice = NULL; } } SuspendController(); UIMFinalizeForPowerDown(); _remoteWakeupOccurred = false; _powerLevel = kUHCIPowerLevelSuspend; break; case kUHCIPowerLevelIdleSuspend: USBLog(3, "%s[%p]: changing to idle suspended state", getName(), this); SuspendController(); _powerLevel = kUHCIPowerLevelIdleSuspend; break; default: USBLog(3, "%s[%p]: unknown power state %d", getName(), this, powerStateOrdinal); break; } if (_powerLevel == kUHCIPowerLevelSuspend) { result = _workLoop->sleep(&_powerLevel); if (result!= kIOReturnSuccess) { USBError(1, "%s[%p] setPowerState - Can't sleep workloop, error 0x%x", getName(), this, result); } } else { _workLoop->OpenGate(); } return IOPMAckImplied; } IOReturn AppleUSBUHCI::callPlatformFunction(const OSSymbol *functionName, bool waitForFunction, void *param1, void *param2, void *param3, void *param4) { USBLog(3, "%s[%p]::callPlatformFunction(%s)", getName(), this, functionName->getCStringNoCopy()); if (functionName == gUSBRemoteWakeupKey) { bool *wake = (bool *)param1; if (_remoteWakeupOccurred) { *wake = true; } else { *wake = false; } return kIOReturnSuccess; } return super::callPlatformFunction(functionName, waitForFunction, param1, param2, param3, param4); } // ======================================================================== #pragma mark Internal methods // ======================================================================== void AppleUSBUHCI::ResumeController(void) { UInt16 cmd; int i; USBLog(3, "%s[%p]::ResumeController", getName(), this); USBLog(3, "%s[%p]: cmd state %x, status %x", getName(), this, ioRead16(kUHCI_CMD), ioRead16(kUHCI_STS)); for (i=0; i<10; i++) { cmd = ioRead16(kUHCI_CMD); if (cmd & kUHCI_CMD_EGSM) { /* Global Suspend mode. */ USBLog(5, "%s[%p]: taking controller out of global suspend mode", getName(), this); cmd &= ~kUHCI_CMD_EGSM; cmd |= kUHCI_CMD_FGR; ioWrite16(kUHCI_CMD, cmd); IODelay(20); continue; } if (cmd & kUHCI_CMD_FGR) { /* Resume is active; assert it for 20 ms. */ USBLog(3, "%s[%p]: taking controller out of force resume", getName(), this); IOSleep(20); cmd &= ~kUHCI_CMD_FGR; ioWrite16(kUHCI_CMD, cmd); IOSleep(3); continue; } if ((cmd & kUHCI_CMD_RS) == 0) { /* Controller is not running. */ USBLog(3, "%s[%p]: starting controller", getName(), this); Run(true); } /* Controller should be running at this point. */ break; } USBLog(3, "%s[%p]: resume done, cmd %x, status %x", getName(), this, ioRead16(kUHCI_CMD), ioRead16(kUHCI_STS)); } void AppleUSBUHCI::SuspendController(void) { UInt16 cmd; USBLog(3, "%s[%p]::SuspendController", getName(), this); USBLog(3, "%s[%p]: cmd state %x, status %x", getName(), this, ioRead16(kUHCI_CMD), ioRead16(kUHCI_STS)); /* Stop the controller. */ Run(false); /* Put the controller in Global Suspend. */ cmd = ioRead16(kUHCI_CMD) & ~kUHCI_CMD_FGR; cmd |= kUHCI_CMD_EGSM; ioWrite16(kUHCI_CMD, cmd); IOSleep(3); USBLog(3, "%s[%p]: suspend done, cmd %x, status %x", getName(), this, ioRead16(kUHCI_CMD), ioRead16(kUHCI_STS)); } void AppleUSBUHCI::StopController(void) { /* Stop the controller. */ Run(false); } void AppleUSBUHCI::RestartController(void) { /* Start the controller. */ Run(true); } IOReturn AppleUSBUHCI::PowerDownHandler(void *target, void *refCon, UInt32 messageType, IOService *service, void *messageArgument, vm_size_t argSize ) { AppleUSBUHCI * me = OSDynamicCast(AppleUSBUHCI, (OSObject *)target); if (!me) return kIOReturnUnsupported; USBLog(4, "UHCI: %p: PowerDownHandler %x %x", me, messageType, messageArgument); switch (messageType) { case kIOMessageSystemWillRestart: case kIOMessageSystemWillPowerOff: if (me->_powerLevel == kUHCIPowerLevelRunning) { if ( me->_rootHubDevice ) { me->_rootHubDevice->terminate(kIOServiceRequired | kIOServiceSynchronous); me->_rootHubDevice->detachAll(gIOUSBPlane); me->_rootHubDevice->release(); me->_rootHubDevice = NULL; } me->SuspendController(); me->_powerLevel = kUHCIPowerLevelSuspend; } if (me->_powerLevel != kUHCIPowerLevelSuspend) { me->UIMFinalizeForPowerDown(); } break; default: // We don't care about any other message that comes in here. break; } return kIOReturnSuccess; }