#include "IOPMSlots99.h"
#include <IOKit/IOLib.h>
#include <IOKit/IOPlatformExpert.h>
#include <IOKit/pwr_mgt/RootDomain.h>
#include <IOKit/IODeviceTreeSupport.h>
#include "IOKit/pwr_mgt/IOPowerConnection.h"
#include "IOKit/pwr_mgt/IOPM.h"
#include "IOKit/pci/IOPCIDevice.h"
bool auxDriverHasRoot( OSObject * us, void *, IOService * yourDevice );
#define number_of_power_states 3
static IOPMPowerState ourPowerStates[number_of_power_states] = {
{1,0,0,0,0,0,0,0,0,0,0,0},
{1,IOPMPowerOn,IOPMPowerOn,IOPMPowerOn,0,0,0,0,0,0,0,0},
{1,IOPMPowerOn,IOPMPowerOn,IOPMPowerOn,0,0,0,0,0,0,0,0}
};
static const UInt32 kPCIPowerCapabilitiesAuxCurrentTable[] = { 0, 55, 100, 160, 220, 270, 320, 375 };
static const UInt32 kMaxAuxPowerScalingFactorTable[] = { 1000, 100, 10, 1 };
#define super IOService
OSDefineMetaClassAndStructors(IOPMSlots99,IOService)
#ifndef kIOPMIsPowerManagedKey
#define kIOPMIsPowerManagedKey "IOPMIsPowerManaged"
#endif
bool IOPMSlots99::start ( IOService * nub )
{
OSData * prop;
IORegistryEntry * node;
UInt32 x;
auxCapacity = 0;
rootDomain = NULL;
super::start(nub);
PMinit();
registerPowerDriver(this,ourPowerStates,number_of_power_states);
clampPowerOn (300*NSEC_PER_SEC);
setProperty ("IOClass", "IOPMSlots99");
addNotification( gIOPublishNotification,serviceMatching("IOPMrootDomain"), (IOServiceNotificationHandler)auxDriverHasRoot, this, 0 );
node = IORegistryEntry::fromPath("mac-io/via-pmu/power-mgt",gIODTPlane);
if ( node != NULL ) {
prop = OSDynamicCast(OSData, node->getProperty("power-supply-millivolts"));
if ( prop != NULL ) {
powerSupplyMillivolts = *((UInt32 *)(prop->getBytesNoCopy()));
}
else {
powerSupplyMillivolts = kSawtoothPowerSupplyMillivolts;
}
prop = OSDynamicCast(OSData, node->getProperty("max-aux-power"));
if ( prop != NULL ) {
x = *((UInt32 *)(prop->getBytesNoCopy()));
auxCapacity = ( x & ~kMaxAuxPowerScalingFactorBitMask ) *
kMaxAuxPowerScalingFactorTable[ x & kMaxAuxPowerScalingFactorBitMask ];
}
}
checkAuxCapacity = ((true == getPlatform()->hasPrivPMFeature( kPMHasLegacyDesktopSleepMask ))
|| (true == getPlatform()->hasPMFeature( kPMCanPowerOffPCIBusMask )));
return true;
}
bool auxDriverHasRoot( OSObject * us, void *, IOService * yourDevice )
{
if ( yourDevice != NULL ) {
((IOPMSlots99 *)us)->rootDomain = (IOPMrootDomain *)yourDevice;
}
return true;
}
IOReturn IOPMSlots99::setPowerState ( unsigned long powerStateOrdinal, IOService* whatDevice)
{
OSIterator * iter;
OSObject * next;
IOPowerConnection * connection;
IOService * nub;
bool canSleep = true;
IOPCIDevice * PCInub;
unsigned long totalPower = 0;
unsigned long childPower;
OSIterator * pciChildIter;
OSObject * obj;
if ( powerStateOrdinal == 0 ) {
iter = getChildIterator(gIOPowerPlane);
if ( iter ) {
while ( (next = iter->getNextObject()) ) {
if ( (connection = OSDynamicCast(IOPowerConnection,next)) ) {
nub = ((IOService *)(connection->getChildEntry(gIOPowerPlane)));
if ( (PCInub = OSDynamicCast(IOPCIDevice,nub)) ) {
childPower = PCInub->currentPowerConsumption();
if ( childPower != kIOPMUnknown ) {
if ( checkAuxCapacity )
totalPower += childPower;
}
else {
if ( checkAuxCapacity ) probePCIhardware(PCInub,&canSleep,&totalPower);
if ((obj = nub->getProperty( kIOPMIsPowerManagedKey )))
{
if (obj != kOSBooleanTrue)
{
IOLog("PCI sleep prevented by non-power-managed %s (3)\n",nub->getName());
canSleep = false;
}
}
else if (( pciChildIter = nub->getChildIterator( gIOServicePlane ) ))
{
IORegistryEntry * child;
UInt32 childCount = 0;
while (( child = (IORegistryEntry *) pciChildIter->getNextObject() ))
{
childCount++;
if ( child->inPlane( gIOPowerPlane ) ) break;
}
if ( childCount && (child == 0) )
{
IOLog("PCI sleep prevented by non-power-managed %s (1)\n",nub->getName());
canSleep = false;
}
pciChildIter->release();
}
if ( ! connection->childHasRequestedPower() ) {
IOLog("PCI sleep prevented by non-power-managed %s (2)\n",nub->getName());
canSleep = false;
}
}
}
else {
canSleep = false; }
}
}
iter->release();
}
if ( totalPower > auxCapacity ) {
IOLog("PCI sleep prevented by high-power expansion cards %d %d (4)\n",totalPower,auxCapacity);
canSleep = false;
}
if ( ! canSleep ) {
if ( rootDomain != NULL ) {
rootDomain->setSleepSupported(kPCICantSleep);
}
}
}
return IOPMAckImplied;
}
void IOPMSlots99::probePCIhardware ( IOPCIDevice * PCInub, bool * canSleep, unsigned long * totalPower )
{
UInt32 data;
UInt32 tempData;
UInt32 milliwatts;
UInt8 offset;
UInt8 nextOffset;
int loopCount;
data = PCInub->configRead16(kPCIStatusConfigOffset);
if( !( data & kPCIStatusPowerCapabilitiesSupportBitMask ) ) {
#if 0
IOLog("PCI sleep prevented by non-power-managed %s (3)\n",PCInub->getName());
*canSleep = false;
#endif
return;
}
data = PCInub->configRead8(kPCIHeaderTypeConfigOffset);
data &= kPCIHeaderTypeBitMask;
switch ( data ) {
case kPCIStandardHeaderType:
offset = kPCIPowerCapabilitiesPtrStandardConfigOffset;
break;
case kPCItoPCIBridgeHeaderType:
offset = kPCIPowerCapabilitiesPtrPCItoPCIBridgeConfigOffset;
break;
case kPCICardBusBridgeHeaderType:
offset = kPCIPowerCapabilitiesPtrCardBusBridgeConfigOffset;
break;
default:
*canSleep = false;
return;
}
data = PCInub->configRead8(offset);
if( ( data == 0 ) ||
( data < kPCIPowerCapabilitiesMinOffset ) ||
( data > kPCIPowerCapabilitiesMaxOffset ) ) {
*canSleep = false;
return;
}
offset = data;
loopCount = 0;
while ( true ) {
data = PCInub->configRead8(offset);
nextOffset = PCInub->configRead8(offset + 1);
if( data == kPCIPowerCapabilityID ) {
break;
}
if( nextOffset == 0 ) {
*canSleep = false;
return;
}
if( ( nextOffset < kPCIPowerCapabilitiesMinOffset ) ||
( nextOffset > kPCIPowerCapabilitiesMaxOffset ) ) {
*canSleep = false;
return;
}
++loopCount;
if( (offset == nextOffset) || (loopCount > kMaxPowerCapabilitiesListLoopCount) ) {
*canSleep = false;
return;
}
offset = nextOffset;
}
if ( ( PCInub->configRead16( offset + kPCIPowerCapabilitiesPMCRegisterOffset ) &
kPCIPowerCapabilitiesPMESupportD3ColdBitMask ) == 0 )
{
milliwatts = 0;
}
else if ( dataRegisterPresent(PCInub,offset) ) {
milliwatts = getD3power(PCInub,offset);
}
else {
data = PCInub->configRead16(offset + kPCIPowerCapabilitiesPMCRegisterOffset);
if( ( data & kPCIPowerCapabilitiesPMCVersionBitMask ) >= kMinPCIPowerCapabilitiesVersion ) {
data = PCInub->configRead16(offset + kPCIPowerCapabilitiesPMCRegisterOffset);
tempData = ( data >> kPCIPowerCapabilitiesPMCAuxCurrentOffset ) & 7;
milliwatts = kPCIPowerCapabilitiesAuxCurrentTable[tempData] * powerSupplyMillivolts / kMillivoltsPerVolt;
}
else { milliwatts = kPCIStandardSleepCurrentNeeded * powerSupplyMillivolts / kMillivoltsPerVolt;
}
}
if ( ( PCInub->configRead16( offset + kPCIPowerCapabilitiesPMCSROffset ) &
kPCIPowerCapabilitiesPMEEnableBitMask ) == 0 )
{
milliwatts = min( milliwatts, 66 );
}
*totalPower += milliwatts;
}
bool IOPMSlots99::dataRegisterPresent ( IOPCIDevice * PCInub, UInt8 offset )
{
UInt16 data;
UInt16 tempData;
UInt32 index;
data = PCInub->configRead16(offset + kPCIPowerCapabilitiesPMCSROffset);
for( index = 0; index < kPCIPowerCapabilitiesDataSelectMaxCombinations; ++index ) {
data &= ~kPCIPowerCapabilitiesDataSelectBitMask;
data |= (index << kPCIPowerCapabilitiesDataSelectBitShift);
PCInub->configWrite16(offset + kPCIPowerCapabilitiesPMCSROffset,data);
tempData = PCInub->configRead16(offset + kPCIPowerCapabilitiesPMCSROffset);
if( tempData & kPCIPowerCapabilitiesDataScaleBitMask ) {
return true; }
}
return false;
}
UInt32 IOPMSlots99::getD3power ( IOPCIDevice * PCInub, UInt8 offset )
{
UInt16 pmcsr;
UInt16 dataScale;
UInt32 power;
pmcsr = PCInub->configRead16(offset + kPCIPowerCapabilitiesPMCSROffset);
pmcsr &= ~kPCIPowerCapabilitiesDataSelectBitMask;
pmcsr |= (kPCIPowerCapabilitiesDataSelectD3PowerConsumed << kPCIPowerCapabilitiesDataSelectBitShift);
PCInub->configWrite16(offset + kPCIPowerCapabilitiesPMCSROffset, pmcsr );
dataScale = PCInub->configRead16(offset + kPCIPowerCapabilitiesPMCSROffset);
dataScale &= kPCIPowerCapabilitiesDataScaleBitMask;
dataScale >>= kPCIPowerCapabilitiesDataScaleBitShift;
power = PCInub->configRead8(offset + kPCIPowerCapabilitiesDataRegisterOffset);
if( dataScale == kPCIPowerCapabilitiesDataScale10Divisor ) { power *= 100;
}
if( dataScale == kPCIPowerCapabilitiesDataScale100Divisor ) {
power *= 10;
}
return power;
}