/* * Copyright (c) 2002 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 1.1 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at * http://www.apple.com/publicsource and read it before using this file. * * This 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 OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Copyright (c) 2002 Apple Computer, Inc. All rights reserved. * * DRI: Tom Sherman * */ #include "AppleLM8x.h" #define super IOService OSDefineMetaClassAndStructors(AppleLM8x, IOService) bool AppleLM8x::systemIsRestarting = FALSE; // instantiate static member systemIsRestarting and reflect restart state as false static const IOPMPowerState ourPowerStates[kLM8xNumStates] = { {kIOPMPowerStateVersion1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {kIOPMPowerStateVersion1, kIOPMSleepCapability, kIOPMSleep, kIOPMSleep, 0, 0, 0, 0, 0, 0, 0, 0 }, {kIOPMPowerStateVersion1, kIOPMDeviceUsable, IOPMPowerOn, IOPMPowerOn, 0, 0, 0, 0, 0, 0, 0, 0} }; bool AppleLM8x::start(IOService *provider) { mach_timespec_t WaitTimeOut; OSData *t; int *reg; IOReturn status; // Proper etiquette if ( !(super::start(provider)) ) return false; // Set flag to reflect non-restart state systemIsRestarting = FALSE; // Try and get the reg property if ( !(t = OSDynamicCast(OSData, provider->getProperty("reg"))) ) { IOLog( "AppleLM8x::start couldn't find 'reg' property in registry.\n"); return false; } // We have the reg property, lets make local copy if ( !(reg = (int*)t->getBytesNoCopy()) ) { IOLog( "AppleLM8x::start 'reg' property is present but empty.\n"); return false; } kLM8xBus = (UInt8)(*reg >> 8); // Determine I2C bus kLM8xAddr = (UInt8)(*reg >> 1); // Determine true address of device // Use a 30 second timeout when calling waitForService() WaitTimeOut.tv_sec = 30; WaitTimeOut.tv_nsec = 0; // Create some symbols for later use sOpenI2Cbus = OSSymbol::withCStringNoCopy(kOpenI2Cbus); sCloseI2Cbus = OSSymbol::withCStringNoCopy(kCloseI2Cbus); sSetPollingMode = OSSymbol::withCStringNoCopy(kSetPollingMode); sSetStandardSubMode = OSSymbol::withCStringNoCopy(kSetStandardSubMode); sSetCombinedMode = OSSymbol::withCStringNoCopy(kSetCombinedMode); sWriteI2Cbus = OSSymbol::withCStringNoCopy(kWriteI2Cbus); sReadI2Cbus = OSSymbol::withCStringNoCopy(kReadI2Cbus); sGetSensorValueSym = OSSymbol::withCString("getSensorValue"); interface = OSDynamicCast(IOService, provider->getParentEntry(gIOServicePlane)); if(interface == NULL) { IOLog("AppleLM8x::start(0x%x) failed to get i2c interface\n", kLM8xAddr<<1); return(false); } DLOG("AppleLM8x::start(0x%x) got i2c interface %s\n", kLM8xAddr<<1, interface->getName()); // Configure the LM87 if( initHW(provider) != kIOReturnSuccess ) { IOLog("AppleLM8x::start(0x%x) failed to initialize sensor.\n", kLM8xAddr<<1); return false; } // Save registers for PM if ( saveRegisters() != kIOReturnSuccess ) IOLog("AppleLM8x::powerStateWillChangeTo(0x%x) failed to save registers.\n", kLM8xAddr<<1); // Initialize Power Management superclass variables from IOService.h PMinit(); // Register as controlling driver from IOService.h status = registerPowerDriver( this, (IOPMPowerState *) ourPowerStates, kLM8xNumStates ); if (status != kIOReturnSuccess) { IOLog("%s: Failed to registerPowerDriver.\n", getName()); } // Join the Power Management tree from IOService.h provider->joinPMtree( this); // Install power change handler (for restart notification) registerPrioritySleepWakeInterest(&sysPowerDownHandler, this, 0); // Register so others can find us with a waitForService() registerService(); // Parse sensor properties and create nubs publishChildren(provider); return true; } void AppleLM8x::stop(IOService *provider) { super::stop(provider); } IOReturn AppleLM8x::initHW(IOService *provider) { IOReturn status; UInt8 cfgReg; UInt8 attempts = 0; DLOG("AppleLM8x::initHW(0x%x) entered.\n", kLM8xAddr<<1); status = openI2C(kLM8xBus); if(status != kIOReturnSuccess) { DLOG("AppleLM8x::initHW(0x%x) failed to open I2C bus.\n", kLM8xAddr<<1); return status; } while( attempts < kTriesToAttempt) { cfgReg = 0; // Read Configuration Register 1 status = readI2C((UInt8)kConfReg1, (UInt8 *) &cfgReg, 1); if(status != kIOReturnSuccess) { DLOG("AppleLM8x::initHW(0x%x) readI2C failed.\n", kLM8xAddr<<1); closeI2C(); return status; } // Check for RESET bit, this should not happen! if((cfgReg & 0x10) == 0x10) status = kIOReturnError; if(status == kIOReturnSuccess) { DLOG("AppleLM8x::readI2C(0x%x) retrieved data = 0x%x\n", kLM8xAddr<<1, *data); break; } attempts++; IOSleep(10); // sleep 10 milliseconds } if(status != kIOReturnSuccess) { IOLog("AppleLM8x::initHW(0x%x) readI2C failed due to persistant RESET bit.\n", kLM8xAddr<<1); closeI2C(); return status; } // Start monitoring operations cfgReg |= 0x1; // Failure of this write operation is fatal status = writeI2C((UInt8)kConfReg1, (UInt8 *)&cfgReg, 1); closeI2C(); return status; } // AppleLM87::getReading IOReturn AppleLM8x::getReading(UInt32 subAddress, SInt32 *value) { IOReturn status; UInt8 data; if (systemIsRestarting == TRUE) return kIOReturnOffline; status = openI2C(kLM8xBus); if(status != kIOReturnSuccess) { DLOG("AppleLM8x::getReading(0x%x) failed to open I2C bus.\n", kLM8xAddr<<1); return status; } status = readI2C((UInt8)subAddress, (UInt8 *) &data, 1); if(status != kIOReturnSuccess) { DLOG("AppleLM8x::getReading(0x%x) readI2C failed.\n", kLM8xAddr<<1); closeI2C(); return status; } closeI2C(); *value = (SInt32)data; return kIOReturnSuccess; } #pragma mark - #pragma mark *** Platform Functions *** #pragma mark - /******************************************************************************* * IOHWSensor entry point - callPlatformFunction() *******************************************************************************/ IOReturn AppleLM8x::callPlatformFunction ( const OSSymbol *functionName, bool waitForFunction, void *param1, void *param2, void *param3, void *param4 ) { UInt32 reg = (UInt32)param1; SInt32 *value = (SInt32 *)param2; DLOG ( "AppleLM8x::callPlatformFunction(0x%x) %s %s %08lx %08lx %08lx %08lx\n", kLM8xAddr<<1, functionName->getCStringNoCopy(), waitForFunction ? "TRUE" : "FALSE", (UInt32)param1, (UInt32)param2, (UInt32)param3, (UInt32)param4 ); if (functionName->isEqualTo(sGetSensorValueSym) == TRUE) { IOReturn status; if (systemIsRestarting == TRUE) return(kIOReturnOffline); for(int i = 0; i < LUNtableElement; i++) { if(reg == LUNtable[i].SubAddress) { status = getReading((UInt8)LUNtable[i].SubAddress, value); if(LUNtable[i].type == kTypeTemperature) { UInt8 reading = *value; *value = ( ( ( ( SInt8 ) reading ) << 16 ) * LUNtable[i].ConversionMultiple ); } if(LUNtable[i].type == kTypeADC) *value = (UInt32)(*value * LUNtable[i].ConversionMultiple); if(LUNtable[i].type == kTypeVoltage) *value = (UInt32)(*value * LUNtable[i].ConversionMultiple); return status; } } } return (super::callPlatformFunction(functionName, waitForFunction, param1, param2, param3, param4)); } IOReturn AppleLM8x::publishChildren(IOService *nub) { OSIterator *childIterator = NULL; IORegistryEntry *childEntry = NULL; IOService *childNub = NULL; IOReturn status; childIterator = nub->getChildIterator(gIODTPlane); if( childIterator != NULL ) { // Iterate through children and create nubs while ( ( childEntry = (IORegistryEntry *)( childIterator->getNextObject() ) ) != NULL ) { // Build Table status = buildEntryTable(childEntry); LUNtableElement++; // Publish child as IOService childNub = OSDynamicCast(IOService, OSMetaClass::allocClassWithName("IOService")); childNub->init(childEntry, gIODTPlane); childNub->attach(this); childNub->registerService(); // DLOG("AppleLM8x::publishChildren(0x%x) published child %s\n", kLM8xAddr<<1, childEntry->getName()); } childIterator->release(); } return kIOReturnSuccess; } IOReturn AppleLM8x::buildEntryTable(IORegistryEntry *child) { OSData *data; // Build Table // SensorID -- no longer required - eem /* data = OSDynamicCast(OSData, child->getProperty("sensor-id")); if(data) LUNtable[LUNtableElement].SensorID = *(UInt32 *)data->getBytesNoCopy(); */ // SubAddress data = OSDynamicCast(OSData, child->getProperty("reg")); if(data) LUNtable[LUNtableElement].SubAddress = *(UInt32 *)data->getBytesNoCopy(); // ConversionMultiple if (LUNtable[LUNtableElement].SubAddress == 0x20) // 2.5Vin or External Temperature 2 { data = OSDynamicCast(OSData, child->getProperty("device_type")); if (data) { char *ptr = (char *)data->getBytesNoCopy(); if(strcmp(ptr, "adc-sensor") == 0) // fix for broken DT { LUNtable[LUNtableElement].ConversionMultiple = k25VinMultiplier; LUNtable[LUNtableElement].type = kTypeADC; } else if(strcmp(ptr, "voltage-sensor") == 0) { LUNtable[LUNtableElement].ConversionMultiple = k25VinMultiplier; LUNtable[LUNtableElement].type = kTypeVoltage; } else { LUNtable[LUNtableElement].ConversionMultiple = 1; LUNtable[LUNtableElement].type = kTypeTemperature; } } } else if (LUNtable[LUNtableElement].SubAddress == 0x21) // Vccp1 { LUNtable[LUNtableElement].ConversionMultiple = Vccp1Multiplier; LUNtable[LUNtableElement].type = kTypeADC; } else if (LUNtable[LUNtableElement].SubAddress == 0x22) // Vcc { LUNtable[LUNtableElement].ConversionMultiple = kVccMultiplier; LUNtable[LUNtableElement].type = kTypeADC; } else if (LUNtable[LUNtableElement].SubAddress == 0x23) // 5V { LUNtable[LUNtableElement].ConversionMultiple = k5VinMultiplier; LUNtable[LUNtableElement].type = kTypeADC; } else if (LUNtable[LUNtableElement].SubAddress == 0x24) // 12V { LUNtable[LUNtableElement].ConversionMultiple = k12VinMultiplier; LUNtable[LUNtableElement].type = kTypeADC; } else if (LUNtable[LUNtableElement].SubAddress == 0x25) // Vccp2 { LUNtable[LUNtableElement].ConversionMultiple = Vccp2Multiplier; LUNtable[LUNtableElement].type = kTypeADC; } else if (LUNtable[LUNtableElement].SubAddress == 0x26) // External Temperature 1 { LUNtable[LUNtableElement].ConversionMultiple = 1; LUNtable[LUNtableElement].type = kTypeTemperature; } else if (LUNtable[LUNtableElement].SubAddress == 0x27) // Internal Temperature { LUNtable[LUNtableElement].ConversionMultiple = 1; LUNtable[LUNtableElement].type = kTypeTemperature; } else if (LUNtable[LUNtableElement].SubAddress == 0x28) // FAN1 or AIN1 { data = OSDynamicCast(OSData, child->getProperty("device_type")); if (data) { char *ptr = (char *)data->getBytesNoCopy(); if(strcmp(ptr, "adc-sensor") == 0) // fix for broken DT { LUNtable[LUNtableElement].ConversionMultiple = AIN1Multiplier; LUNtable[LUNtableElement].type = kTypeADC; } else if(strcmp(ptr, "voltage-sensor") == 0) { LUNtable[LUNtableElement].ConversionMultiple = AIN1Multiplier; LUNtable[LUNtableElement].type = kTypeVoltage; } else { LUNtable[LUNtableElement].ConversionMultiple = 0; // special case LUNtable[LUNtableElement].type = kTypeRPM; } } } else if (LUNtable[LUNtableElement].SubAddress == 0x29) // FAN2 or AIN2 { data = OSDynamicCast(OSData, child->getProperty("device_type")); if (data) { char *ptr = (char *)data->getBytesNoCopy(); if(strcmp(ptr, "adc-sensor") == 0) // fix for broken DT { LUNtable[LUNtableElement].ConversionMultiple = AIN2Multiplier; LUNtable[LUNtableElement].type = kTypeADC; } else if(strcmp(ptr, "voltage-sensor") == 0) { LUNtable[LUNtableElement].ConversionMultiple = AIN2Multiplier; LUNtable[LUNtableElement].type = kTypeVoltage; } else { LUNtable[LUNtableElement].ConversionMultiple = 0; // special case; LUNtable[LUNtableElement].type = kTypeRPM; } } } return kIOReturnSuccess; } #pragma mark - #pragma mark *** I2C *** #pragma mark - // Open and set up the i2c bus IOReturn AppleLM8x::openI2C(UInt8 id) { IOReturn status; DLOG("AppleLM8x::openI2C(0x%x) entered.\n", kLM8xAddr<<1); if (interface == NULL) { DLOG("AppleLM8x::openI2C(0x%x) interface is NULL.\n", kLM8xAddr<<1); return kIOReturnBadArgument; } // Open the interface status = interface->callPlatformFunction(sOpenI2Cbus, false, (void *)((UInt32)id), NULL, NULL, NULL); if (status != kIOReturnSuccess) { DLOG("AppleLM8x::openI2C(0x%x) failed on 'OpenI2Cbus' command.\n", kLM8xAddr<<1); return status; } // the i2c driver does not support well read in interrupt mode // so it is better to "go polling" (read does not timeout on errors // in interrupt mode). status = interface->callPlatformFunction(sSetPollingMode, false, (void *)TRUE, NULL, NULL, NULL); if (status != kIOReturnSuccess) { DLOG("AppleLM8x::openI2C(0x%x) failed to set polling mode, status = %08lx\n", kLM8xAddr<<1, (UInt32) status); return status; } return kIOReturnSuccess; } // Close the i2c bus IOReturn AppleLM8x::closeI2C( void ) { IOReturn status; DLOG("AppleLM8x::closeI2C(0x%x) entered.\n", kLM8xAddr<<1); if (interface == NULL) { DLOG("AppleLM8x::closeI2C(0x%x) interface is NULL.\n", kLM8xAddr<<1); return kIOReturnBadArgument; } status = interface->callPlatformFunction(sCloseI2Cbus, false, NULL, NULL, NULL, NULL); if (status != kIOReturnSuccess) { DLOG("AppleLM8x::closeI2C(0x%x) failed on 'closeI2Cbus' command.\n", kLM8xAddr<<1); return status; } return kIOReturnSuccess; } // Send "size" bytes in the i2c bus. IOReturn AppleLM8x::writeI2C(UInt8 subAddr, UInt8 *data, UInt16 size) { IOReturn status; UInt8 attempts = 0; DLOG("AppleLM8x::writeI2C(0x%x) entered.\n", kLM8xAddr<<1); if ( (interface == NULL) || (data == NULL) || (size == 0) ) { DLOG("AppleLM8x::writeI2C(0x%x) called with incorrect function parameters.\n", kLM8xAddr<<1); return kIOReturnBadArgument; } status = interface->callPlatformFunction(sSetStandardSubMode, false, NULL, NULL, NULL, NULL); if (status != kIOReturnSuccess) { DLOG("AppleLM8x::readI2C(0x%x) failed to set 'StandardSub' mode.\n", kLM8xAddr<<1); return status; } while(attempts < kTriesToAttempt) { status = interface->callPlatformFunction ( sWriteI2Cbus, false, (void *)((UInt32)kLM8xAddr), (void *)((UInt32)subAddr), (void *)data, (void *)((UInt32)size) ); if(status == kIOReturnSuccess) break; attempts++; IOSleep(10); // sleep 10 milliseconds } if(status != kIOReturnSuccess) { IOLog("AppleLM8x::writeI2C(0x%x) failed %d attempts of the 'writeI2C' command for subaddr 0x%x.\n", kLM8xAddr<<1, attempts, subAddr); return status; } DLOG("AppleLM8x::writeI2C(0x%x) wrote data = 0x%x\n", kLM8xAddr<<1, *data); return status; } // Read "size" bytes from the i2c bus. IOReturn AppleLM8x::readI2C(UInt8 subAddr, UInt8 *data, UInt16 size) { IOReturn status; UInt8 attempts = 0; DLOG("AppleLM8x::readI2C(0x%x) entered.\n", kLM8xAddr<<1); if ( (interface == NULL) || (data == NULL) || (size == 0) ) { DLOG("AppleLM8x::readI2C(0x%x) called with incorrect function parameters.\n", kLM8xAddr<<1); return kIOReturnBadArgument; } status = interface->callPlatformFunction(sSetCombinedMode, false, NULL, NULL, NULL, NULL); if (status != kIOReturnSuccess) { DLOG("AppleLM8x::readI2C(0x%x) failed to set 'Combined' mode.\n", kLM8xAddr<<1); return status; } while( attempts < kTriesToAttempt) { status = interface->callPlatformFunction ( sReadI2Cbus, false, (void *)((UInt32)kLM8xAddr), (void *)((UInt32)subAddr), (void *)data, (void *)((UInt32)size) ); // Occasionally we get a valid I2C read that contains bogus data. With the current // hardware, the bogus data is always 0xFF. We just a check for this, and retry // if necessary. if(*data == 0xFF) status = kIOReturnError; if(status == kIOReturnSuccess) break; attempts++; IOSleep(10); // sleep 10 milliseconds } if(*data == 0xFF) { IOLog("AppleLM8x::readI2C(0x%x) failed %d attempts of the 'readI2C' command for subaddr 0x%x (BOGUS DATA)\n", kLM8xAddr<<1, attempts, subAddr); return status; } if(status != kIOReturnSuccess) { IOLog("AppleLM8x::readI2C(0x%x) failed %d attempts of the 'readI2C' command for subaddr 0x%x.\n", kLM8xAddr<<1, attempts, subAddr); return status; } DLOG("AppleLM8x::readI2C(0x%x) retrieved data 0x%x from subaddr 0x%x\n", kLM8xAddr<<1, *data, subAddr); return status; } #pragma mark - #pragma mark *** Power Management *** #pragma mark - /******************************************************************************* * Power Management callbacks and handlers *******************************************************************************/ IOReturn AppleLM8x::setPowerState(unsigned long whatState, IOService *dontCare) { DLOG("AppleLM8x::setPowerState called with state:%lu\n", whatState); if ( (whatState == kLM8xOffState) ) { // No Power State systemIsRestarting = TRUE; // set flag to reflect shutting down state. if ( saveRegisters() != kIOReturnSuccess ) IOLog("AppleLM8x::powerStateWillChangeTo(0x%x) failed to save registers.\n", kLM8xAddr<<1); } if ( (whatState == kLM8xOnState) ) { // Full Power State if ( restoreRegisters() != kIOReturnSuccess ) IOLog("AppleLM8x::powerStateWillChangeTo(0x%x) failed to restore registers.\n", kLM8xAddr<<1); systemIsRestarting = FALSE; // set flag to reflect we are not shutting down. } return IOPMAckImplied; } IOReturn AppleLM8x::sysPowerDownHandler(void *target, void *refCon, UInt32 messageType, IOService *service, void *messageArgument, vm_size_t argSize ) { IOReturn status = kIOReturnSuccess; DLOG("AppleLM8x::sysPowerDownHandler called with 0x%x\n", messageType); switch (messageType) { case kIOMessageSystemWillSleep: break; case kIOMessageSystemWillPowerOff: // iokit_common_msg(0x250) case kIOMessageSystemWillRestart: // iokit_common_msg(0x310) systemIsRestarting = TRUE; // set flag to reflect shutting down state. break; default: status = kIOReturnUnsupported; break; } return status; } IOReturn AppleLM8x::saveRegisters() { IOReturn status; DLOG("AppleLM8x::saveRegisters(0x%x) entered.\n", kLM8xAddr<<1); status = openI2C(kLM8xBus); if(status != kIOReturnSuccess) { DLOG("AppleLM8x::saveRegisters(0x%x) failed to open I2C bus.\n", kLM8xAddr<<1); return status; } status = readI2C((UInt8)kChannelModeRegister, (UInt8 *)&savedRegisters.ChannelMode, 1); if(status != kIOReturnSuccess) { DLOG("AppleLM8x::saveRegisters(0x%x) readI2C failed.\n", kLM8xAddr<<1); closeI2C(); return status; } status = readI2C((UInt8)kConfReg1, (UInt8 *)&savedRegisters.Configuration1, 1); if(status != kIOReturnSuccess) { DLOG("AppleLM8x::saveRegisters(0x%x) readI2C failed.\n", kLM8xAddr<<1); closeI2C(); return status; } status = readI2C((UInt8)kConfReg2, (UInt8 *)&savedRegisters.Configuration2, 1); if(status != kIOReturnSuccess) { DLOG("AppleLM8x::saveRegisters(0x%x) readI2C failed.\n", kLM8xAddr<<1); closeI2C(); return status; } closeI2C(); return status; } IOReturn AppleLM8x::restoreRegisters() { IOReturn status; DLOG("AppleLM8x::restoreRegisters(0x%x) entered.\n", kLM8xAddr<<1); status = openI2C(kLM8xBus); if(status != kIOReturnSuccess) { DLOG("AppleLM8x::restoreRegisters(0x%x) failed to open I2C bus.\n", kLM8xAddr<<1); return status; } status = writeI2C((UInt8)kChannelModeRegister, (UInt8 *)&savedRegisters.ChannelMode, 1); if(status != kIOReturnSuccess) { DLOG("AppleLM8x::restoreRegisters(0x%x) writeI2C failed.\n", kLM8xAddr<<1); closeI2C(); return status; } status = writeI2C((UInt8)kConfReg2, (UInt8 *)&savedRegisters.Configuration2, 1); if(status != kIOReturnSuccess) { DLOG("AppleLM8x::restoreRegisters(0x%x) writeI2C failed.\n", kLM8xAddr<<1); closeI2C(); return status; } // restore Configuration Register 1 last, since it starts polling status = writeI2C((UInt8)kConfReg1, (UInt8 *)&savedRegisters.Configuration1, 1); if(status != kIOReturnSuccess) { DLOG("AppleLM8x::restoreRegisters(0x%x) writeI2C failed.\n", kLM8xAddr<<1); closeI2C(); return status; } closeI2C(); return status; }