/*
 * Copyright (c) 1998-2000 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@
 */
 
#include <libkern/c++/OSContainers.h>
#include <IOKit/system.h>
#include <IOKit/IOLib.h>
#include <IOKit/graphics/IODisplay.h>
#include <IOKit/IODeviceTreeSupport.h>
#include <IOKit/IOPlatformExpert.h>
#include <IOKit/ndrvsupport/IONDRVFramebuffer.h>
#include <IOKit/assert.h>

#include "IVAD.h"

#ifndef kIODisplayTheatreModeKey
#define kIODisplayTheatreModeKey	"theatre-mode"
#endif
#ifndef kIODisplayTheatreModeWindowKey
#define kIODisplayTheatreModeWindowKey	"theatre-mode-window"
#endif

static IOReturn
ApplePMUSendMiscCommand( UInt32 command,
                        IOByteCount sendLength, UInt8 * sendBuffer,
                        IOByteCount * readLength, UInt8 * readBuffer );
IOReturn
AppleCudaWriteIIC( UInt8 address, const UInt8 * buffer, IOByteCount * count );
IOReturn
AppleCudaReadIIC( UInt8 address, UInt8 * buffer, IOByteCount * count );

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

class AppleOnboardDisplay : public IOService
{
    OSDeclareDefaultStructors(AppleOnboardDisplay);

private:
    IOFramebuffer * 	fFramebuffer;
    OSDictionary *  	fDisplayParams;
    IONotifier *	fNotifier;

    UInt32 		fIICAddress;

    IORegistryEntry * 	fIVAD;
    const OSSymbol *	fIVADPrefsKey;
    EEPROM *		fEEPROM;
    IVADUserPrefs *	fUserPrefs;
    UInt32		fModeIndex;
    bool		fUsesXPRAM;
    bool		fTheatreMode;

    bool (*fDoSet)( AppleOnboardDisplay * self,
                            OSDictionary * params, const OSSymbol * paramName,
                            UInt32 value );
    bool (*fDoUpdate)( AppleOnboardDisplay * self );

public:
    virtual bool start(	IOService * provider );
    virtual IOReturn setProperties( OSObject * properties );

private:
    static bool _displayHandler( void * target, void * ref,
                            IOService * newService );
    static IOReturn framebufferEvent( OSObject * self, void * ref,
                    IOFramebuffer *framebuffer, IOIndex event, void * info );
    bool displayHandler( IODisplay * display );

    bool addParameter( const OSSymbol * paramName, SInt32 min, SInt32 max );
    bool setParameter( const OSSymbol * paramName, SInt32 value );
    bool setForKey( OSDictionary * params, const OSSymbol * key, SInt32 value,
                    SInt32 min, SInt32 max );
    OSDictionary * getParams( const OSSymbol * sym,
                                SInt32 * value, SInt32 * min, SInt32 * max );
    static bool updateNone( AppleOnboardDisplay * self );
    // --
    static bool setATI( AppleOnboardDisplay * self,
                            OSDictionary * params, const OSSymbol * paramName,
                            UInt32 value );
    static bool updateATI( AppleOnboardDisplay * self );
    // --
    static bool setG3AIO( AppleOnboardDisplay * self,
                            OSDictionary * params, const OSSymbol * paramName,
                            UInt32 value );
    // --
    static bool setPMU( AppleOnboardDisplay * self,
                            OSDictionary * params, const OSSymbol * paramName,
                            UInt32 value );
    // --
    static bool setVCP( AppleOnboardDisplay * self,
                            OSDictionary * params, const OSSymbol * paramName,
                            UInt32 value );
    bool readVCPRange( UInt8 command, SInt32 * max, SInt32 * current );
    bool setupVCP( void );
    // --
    static bool setIVAD( AppleOnboardDisplay * self,
                            OSDictionary * params, const OSSymbol * paramName,
                            UInt32 value );
    static bool updateIVAD( AppleOnboardDisplay * self );
    bool setupIVAD( void );
    bool setTheatreMode( UInt32 newMode );
    bool setTheatreModeATI( IOGBounds * rect );
};

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#define super IOService

OSDefineMetaClassAndStructors(AppleOnboardDisplay, IOService);

const OSSymbol * gIODisplayParametersTheatreModeKey;
const OSSymbol * gIODisplayParametersTheatreModeWindowKey;

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

bool AppleOnboardDisplay::start( IOService * provider )
{
    OSObject *	notify;

    if( !super::start( provider))
	return( false);

    gIODisplayParametersTheatreModeKey = OSSymbol::withCString(kIODisplayTheatreModeKey);
    gIODisplayParametersTheatreModeWindowKey = OSSymbol::withCString(kIODisplayTheatreModeWindowKey);

    notify = addNotification( gIOMatchedNotification,
                            serviceMatching("IODisplay"), _displayHandler,
                            this, (void *) 0 );
    assert( notify );

    return( true );
}

bool AppleOnboardDisplay::_displayHandler( void * target, void * ref,
                            IOService * newService )
{
    return( ((AppleOnboardDisplay *)target)->displayHandler( 
                        (IODisplay *) newService ));
}

bool AppleOnboardDisplay::displayHandler( IODisplay * display )
{
    IODisplayConnect * 	connect;
    IOFramebuffer *	framebuffer;
    IOService *		dev;
    OSData *		data;
    OSNumber *		num;

    // only one onboard display is handled
    if( fFramebuffer)
        return( false );

    connect = display->getConnection();
    if( !connect)
        return( false );
    framebuffer = connect->getFramebuffer();
    dev = framebuffer->getProvider();
    fUsesXPRAM = true;

    if( (data = OSDynamicCast(OSData,
                    dev->getProperty("backlight-control")))
        && (data->isEqualTo("ati", 3) || (0 == strcmp("ATY,RageM_Lp", dev->getName()))) ) {

        fDisplayParams = OSDynamicCast( OSDictionary, getProperty("Backlight0"));
        fDoSet         = &AppleOnboardDisplay::setATI;
        fDoUpdate      = &AppleOnboardDisplay::updateATI;
        fUsesXPRAM     = false;

    } else if( (data = OSDynamicCast(OSData,
                    dev->getProperty("iic-address")))) {

        fIICAddress = *((UInt32 *) data->getBytesNoCopy());

        fIVAD = dev->childFromPath("ivad", gIODTPlane); // (iicAddress == iicIVAD)
        if( fIVAD) {

            fFramebuffer   = framebuffer;
            fIVADPrefsKey  = OSSymbol::withCString("geometry");
            fDisplayParams = OSDynamicCast( OSDictionary, getProperty("IVAD"));
            fDoSet         = &AppleOnboardDisplay::setIVAD;
            fDoUpdate      = &AppleOnboardDisplay::updateIVAD;
            setupIVAD();

        } else {
            fDisplayParams = OSDynamicCast( OSDictionary, getProperty("VCP"));
            fDoSet         = &AppleOnboardDisplay::setVCP;
            fDoUpdate      = &AppleOnboardDisplay::updateNone;
            if( !setupVCP())
                display = 0;
        }

    } else if( (data = OSDynamicCast(OSData,
                    dev->getProperty("ATY,AIO")))) {

        fDisplayParams = OSDynamicCast( OSDictionary, getProperty("G3AIO"));
        fDoSet         = &AppleOnboardDisplay::setG3AIO;
        fDoUpdate      = &AppleOnboardDisplay::updateNone;

    } else if( (0 == strcmp("ATY,264LTProA", dev->getName()))
             || (0 == strcmp("ATY,RageLTPro", dev->getName()))
             || (0 == strcmp("ATY,264LT-G", dev->getName()))
             || (0 == strcmp("ATY,264LTPro", dev->getName()))) {

        fDisplayParams = OSDynamicCast( OSDictionary, getProperty("Backlight1"));
        fDoSet         = &AppleOnboardDisplay::setPMU;
        fDoUpdate      = &AppleOnboardDisplay::updateNone;

    } else
        display = 0;


    if( display) {

        assert( fDisplayParams );

        attach( display );
        fFramebuffer = framebuffer;
        fNotifier = framebuffer->addFramebufferNotification(
                        &AppleOnboardDisplay::framebufferEvent, this, NULL );

        display->setProperty( gIODisplayParametersKey, fDisplayParams );
        num = OSNumber::withNumber( kAppleOnboardGUID, 64 );
        display->setProperty( gIODisplayGUIDKey, num);
        num->release();

        (*fDoUpdate)(this);

        if( fUsesXPRAM) {
            UInt8 bytes[2];
            SInt32 value, min, max;
            OSDictionary * params;
            IODTPlatformExpert * dtPlaform = OSDynamicCast( IODTPlatformExpert, getPlatform() );
            if( !dtPlaform
            || (kIOReturnSuccess != dtPlaform->readXPRAM( 0x50, (UInt8 *) bytes, 2 )))
                bytes[0] = bytes[1] = 0;

            if( (params = getParams( gIODisplayBrightnessKey, 0, &min, &max))) {
                value = (bytes[0] + (((UInt8) max) + ((UInt8) min)) / 2) & 0xff;
                setForKey( params, gIODisplayBrightnessKey, value, min, max);
            }
            if( (params = getParams( gIODisplayContrastKey, 0, &min, &max))) {
                value = (bytes[1] + ((UInt8) max)) & 0xff;
                setForKey( params, gIODisplayContrastKey, value, min, max);
            }
        }
    }

    return( true );
}

OSDictionary * AppleOnboardDisplay::getParams( const OSSymbol * sym,
                                            SInt32 * value, SInt32 * min, SInt32 * max )
{
    OSDictionary *	params;
    OSNumber *		num;

    params = OSDynamicCast( OSDictionary,
                            fDisplayParams->getObject(sym));
    if( params) do {

        if( value) {
            num = OSDynamicCast( OSNumber, params->getObject(gIODisplayValueKey));
            if( !num)
                continue;
            *value = num->unsigned32BitValue();
        }
        if( min) {
            num = OSDynamicCast( OSNumber, params->getObject(gIODisplayMinValueKey));
            if( !num)
                continue;
            *min = num->unsigned32BitValue();
        }
        if( max) {
            num = OSDynamicCast( OSNumber, params->getObject(gIODisplayMaxValueKey));
            if( !num)
                continue;
            *max = num->unsigned32BitValue();
        }
        return( params );

    } while( false );

    return( 0 );
}

bool AppleOnboardDisplay::setForKey( OSDictionary * params, const OSSymbol * sym,
                                    SInt32 value, SInt32 min, SInt32 max )
{
    OSNumber * num;
    SInt32 adjValue;
    bool ok;

	// invert rotation
	if( sym == gIODisplayRotationKey)
        adjValue = max - value + min;
    else
        adjValue = value;

    if( (ok = (*fDoSet)( this, params, sym, adjValue ))) {

        num = OSNumber::withNumber( value, 32 );
        params->setObject( gIODisplayValueKey, num );
        num->release();
    }

    return( ok );
}

IOReturn AppleOnboardDisplay::setProperties( OSObject * properties )
{
    OSSymbol *		sym;
    OSDictionary *	dict;
    OSDictionary *	params;
    OSNumber *		valueNum;
    OSObject *		valueObj;
    OSDictionary *	valueDict;
    OSIterator *	iter;
    SInt32		min, max, value;
    bool		doCommit = false;
    bool		allOK = true;
    bool		ok;

    if( !(dict = OSDynamicCast(OSDictionary, properties)))
        return( kIOReturnUnsupported );

    if( dict->getObject(gIODisplayParametersDefaultKey)) {
        (*fDoSet)( this, 0, gIODisplayParametersDefaultKey, 0 );
        (*fDoUpdate)(this);
        setProperties( fDisplayParams );
    }

    iter = OSCollectionIterator::withCollection( dict );
    if( iter) {
        for( ; (sym = (OSSymbol *) iter->getNextObject());
             allOK &= ok) {

            if( sym == gIODisplayParametersCommitKey) {
                ok = true;
                doCommit = true;
                continue;
            }
            if( sym == gIODisplayParametersDefaultKey) {
                ok = true;
                continue;
            }

            ok = false;
            if( sym == gIODisplayParametersTheatreModeWindowKey) {
                OSData * valueData = OSDynamicCast( OSData, dict->getObject(sym) );
                if( !valueData || (sizeof(IOGBounds) != valueData->getLength()))
                    continue;
                ok = setTheatreModeATI( (IOGBounds *) valueData->getBytesNoCopy() );
                continue;
            }
    
            if( 0 == (params = getParams( sym, 0, &min, &max)))
                continue;

            valueObj = dict->getObject(sym);
            if( !valueObj)
                continue;
            if( (valueDict = OSDynamicCast( OSDictionary, valueObj)))
                valueObj = valueDict->getObject( gIODisplayValueKey );
            valueNum = OSDynamicCast( OSNumber, valueObj );
            if( !valueNum)
                continue;
            value = valueNum->unsigned32BitValue();

            if( value < min)
                value = min;
            if( value > max)
                value = max;

            ok = setForKey( params, sym, value, min, max );
        }
        iter->release();
    }

    if( doCommit) {
        (*fDoSet)( this, 0, gIODisplayParametersCommitKey, 0 );

        if( fUsesXPRAM) {
            UInt8 bytes[2];
            IODTPlatformExpert * dtPlaform = OSDynamicCast( IODTPlatformExpert, getPlatform() );
            if( dtPlaform
            && (kIOReturnSuccess == dtPlaform->readXPRAM( 0x50, (UInt8 *) bytes, 2 ))) {
    
                if( (params = getParams( gIODisplayBrightnessKey, &value, &min, &max)))
                    bytes[0] = value - (min + max) / 2;

                if( (params = getParams( gIODisplayContrastKey, &value, &min, &max)))
                    bytes[1] = value - max;
                dtPlaform->writeXPRAM( 0x50, (UInt8 *) bytes, 2 );
            }
        }
    }

    return( allOK ? kIOReturnSuccess : kIOReturnError );
}

bool AppleOnboardDisplay::addParameter( const OSSymbol * paramName,
                                        SInt32 min, SInt32 max )
{
    OSDictionary *	paramDict;
    OSNumber *		num;
    bool 		ok = true;

    paramDict = (OSDictionary *) fDisplayParams->getObject(paramName);
    if( !paramDict)
        return( false );

    paramDict->setCapacityIncrement(1);
    num = OSNumber::withNumber( min, 32 );
    paramDict->setObject( gIODisplayMinValueKey, num);
    num->release();
    num = OSNumber::withNumber( max, 32 );
    paramDict->setObject( gIODisplayMaxValueKey, num);
    num->release();

    return( ok );
}

bool AppleOnboardDisplay::setParameter( const OSSymbol * paramName,
                                        SInt32 value )
{
    OSDictionary *	paramDict;
    OSNumber *		num;
    bool 		ok = true;

    paramDict = (OSDictionary *) fDisplayParams->getObject(paramName);
    if( !paramDict)
        return( false );

    // invert rotation
    if( paramName == gIODisplayRotationKey) {
        SInt32 min, max;
        getParams( paramName, NULL, &min, &max );
        value = max - value + min;
    }

    num = OSNumber::withNumber( value, 32 );
    paramDict->setObject( gIODisplayValueKey, num);
    num->release();

    return( ok );
}

IOReturn AppleOnboardDisplay::framebufferEvent( OSObject * _self, void * ref,
                    IOFramebuffer *framebuffer, IOIndex event, void * info )
{
    AppleOnboardDisplay *	self = (AppleOnboardDisplay *) _self;
    IOReturn 			err;

    switch(event) {

        case kIOFBNotifyDisplayModeDidChange:
            (*self->fDoUpdate)(self);
            self->setProperties( self->fDisplayParams );

        case kIOFBNotifyDisplayModeWillChange:
        case kIOFBNotifyWillSleep:
        case kIOFBNotifyDidWake:
            err = kIOReturnSuccess;
            break;

        default:
            err = kIOReturnUnsupported;
            break;
    }

    return( err);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

enum {
    kATIBacklightControl = 0xc0,
    kBacklightPowerOff	 = 0,
    kBacklightPowerOn    = 7
};

struct VDBacklightControlInfo {
    UInt32	backlightOnOff;
    UInt32	backlightLevel;
    UInt32	saveNVRAM;
};

bool AppleOnboardDisplay::setATI( AppleOnboardDisplay * self,
                            OSDictionary * params, const OSSymbol * paramName,
                            UInt32 value )
{
    IONDRVFramebuffer *		ndrv = OSDynamicCast( IONDRVFramebuffer, self->fFramebuffer);
    VDBacklightControlInfo	info;
    IOReturn			err;
    bool 			ok = false;

    if( !params)
        return( true );
    if( !ndrv)
        return( false );

    do {
        bzero(&info, sizeof(info));
        err = ndrv->doStatus( kATIBacklightControl, &info);
        if( err)
            continue;
        info.backlightLevel = value;
        info.saveNVRAM = 1;
        err = ndrv->doControl( kATIBacklightControl, &info);
        if( err)
            continue;
        ok = true;

    } while( false );

    return( ok );
}

bool AppleOnboardDisplay::updateATI( AppleOnboardDisplay * self )
{
    IONDRVFramebuffer * 	ndrv = OSDynamicCast( IONDRVFramebuffer, self->fFramebuffer);
    VDBacklightControlInfo	info;
    IOReturn			err;
    SInt32			value = 0xff;

    if( !ndrv)
        return( false );

    do {
        bzero(&info, sizeof(info));
        err = ndrv->doStatus( kATIBacklightControl, &info);
        if( err)
            continue;
        value = info.backlightLevel;
        info.saveNVRAM = 0;
        ndrv->doControl( kATIBacklightControl, &info);

    } while( false );

    self->setParameter( gIODisplayBrightnessKey, value );
    return( true );
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#if 1
#define 	heathrowBrightnessControl ((volatile UInt8 *)0xf3000032)
#define 	heathrowContrastControl ((volatile UInt8 *)0xf3000033)
#endif
#if 0
#define		defaultBrightness	144
#define		defaultContrast		183
#endif

bool AppleOnboardDisplay::setG3AIO( AppleOnboardDisplay * self,
                            OSDictionary * params, const OSSymbol * paramName,
                            UInt32 value )
{
    volatile UInt8 *	reg;

    if( !params)
        return( true );

    if( paramName == gIODisplayBrightnessKey)
        reg = heathrowBrightnessControl;
    else if( paramName == gIODisplayContrastKey)
        reg = heathrowContrastControl;
    else
        return( false );

    *reg = value;

    return( true );
}

bool AppleOnboardDisplay::updateNone( AppleOnboardDisplay * self )
{

//    self->setParameter( gIODisplayBrightnessKey, *heathrowBrightnessControl );
//    self->setParameter( gIODisplayContrastKey, *heathrowContrastControl );

    return( true );
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

enum {
    kPMUSetBrightness = 0x41
};

bool AppleOnboardDisplay::setPMU( AppleOnboardDisplay * self,
                            OSDictionary * params, const OSSymbol * paramName,
                            UInt32 value )
{
    IOByteCount unused;
    static UInt8 table[] = { 127,71,69,67,65,63,61,59,
                                58,56,54,52,50,48,46,44,
                                42,40,38,37,35,33,31,29,
                                27,25,23,21,19,18,16,14 };
    if( !params)
        return( true );

    ApplePMUSendMiscCommand( kPMUSetBrightness, 1, table + value, &unused, NULL );

    return( true );
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
enum {
    kGetVCPFeature		= 0x01,
    kReplyVCPFeature		= 0x02,
    kSetVCPFeature		= 0x03,
    kGetControlKeyMode		= 0x04,
    kReplyControlKeyMode	= 0x05,
    kSetControlKeyMode		= 0x06,
    kGetTimingReport		= 0x07,
    kGetKeyReport		= 0x08,
    kResetVCPFeature		= 0x09,
    kDisableVCPFeature		= 0x0A,
    kEnableVCPFeature		= 0x0B,
    kSaveCurrentSettings	= 0x0C
};

// VCP minor commands from LGE
enum {
    kDegaussCommand		= 0x00,
    kPresetDefaultCommand	= 0x04,
    kExtBrightnessCommand	= 0x10,
    kExtContrastCommand		= 0x12,
    kRedDriveCommand		= 0x16,
    kGreenDriveCommand		= 0x18,
    kBlueDriveCommand		= 0x1A,
    kHorizontalPositionCommand	= 0x20,
    kHorizontalSizeCommand	= 0x22,
    kPincushionCommand		= 0x24,
    kPincushionBalanceCommand	= 0x26,
    kVerticalPositionCommand	= 0x30,
    kVerticalSizeCommand	= 0x32,
    kParallelogramCommand	= 0x40,
    kTrapezoidCommand		= 0x42,
    kRotationCommand		= 0x44,
    kRedCutoffCommand		= 0x80,
    kGreenCutoffCommand		= 0x82,
    kBlueCutoffCommand		= 0x84,
    kSubContrastCommand		= 0x86,
    kSubBrightnessCommand	= 0x88,
    kABLCommand			= 0x8A,
    kPresetStartCommand		= 0xE0,
    kPresetEndCommand		= 0xE2,
    kClearDataCommand		= 0xE4,
    kSaveDataCommand		= 0xEA,
    kSaveColorCommand		= 0xEB,
    kSaveSystemDataCommand	= 0xEC
};

enum {
	kIICReadBufferSize	= 4,		// this many byte to read to get vcp feature
	kIICWriteBufferSize	= 7,
	kIICSourceAddress	= 0x50,
	kVCPCommandDataLength	= 0x84,
	kIICBogusSourceAddress	= 0xAF,	// special address that LGE check to not to go into master mode (Cuda can't do multi master mode)
	kMinMillisecondsBetweenIICCommands = 35	// Throttle to 30 times a second and round up a bit.
};

bool AppleOnboardDisplay::readVCPRange( UInt8 command, SInt32 * oMax, SInt32 * current )
{
    UInt8	buffer[16];
    IOByteCount count;
    SInt32	max;
    UInt32	retries;

    retries = 0;
    do {
        buffer[0] = kIICBogusSourceAddress;
        buffer[1] = kVCPCommandDataLength;
        buffer[2] = kSetVCPFeature;
        buffer[3] = command;
        buffer[4] = 0;
        buffer[5] = 0;
        buffer[6] = fIICAddress
                    ^ buffer[0]
                    ^ buffer[1]
                    ^ buffer[2]
                    ^ buffer[3];
    
        count = kIICWriteBufferSize;
        IOSleep( 100 ); 		// 100 in 9
        AppleCudaWriteIIC( fIICAddress, buffer, &count );
        bzero( buffer, sizeof( buffer));
        count = kIICReadBufferSize;
        IOSleep( 50 );		// 50 in 9
        AppleCudaReadIIC( fIICAddress + 1, buffer, &count );
        max = (buffer[0] << 8) | buffer[1];

    } while( (max == 0) && (retries++ < 10));

    *oMax = max;
    *current = (buffer[2] << 8) | buffer[3];

    return( max != 0 );
}

bool AppleOnboardDisplay::setupVCP( void )
{
    OSIterator *	iter;
    OSSymbol *		sym;
    OSDictionary *	params;
    OSNumber *		num;
    UInt32		command;
    SInt32	 	current, max;

    IOByteCount		count;
    IOReturn		err;

    current = 0;
    do {
        count = 0;
	IOSleep( 50 ); // 100 in 9
        err = AppleCudaWriteIIC( fIICAddress, (UInt8 *) &count, &count );
    } while( err && (current++ < 10));
    if( kIOReturnSuccess != err) {
        IOLog("VCP warmup fail (%x)\n", err);
        return( false );
    }

    iter = OSCollectionIterator::withCollection( fDisplayParams );
    if( iter) {
        while( (sym = (OSSymbol *) iter->getNextObject())) {

            if( sym == gIODisplayParametersCommitKey)
                continue;
            params = OSDynamicCast( OSDictionary, fDisplayParams->getObject(sym));
            if( !params)
                continue;
            num = (OSNumber *) params->getObject("reg");
            if( !num)
                continue;
            command = num->unsigned32BitValue();
            if( !readVCPRange( command, &max, &current ))
                continue;
            addParameter( sym, 0, max );
            setParameter( sym, current );
        }
        iter->release();
    }

    return( true );
}

bool AppleOnboardDisplay::setVCP( AppleOnboardDisplay * self,
                            OSDictionary * params, const OSSymbol * sym,
                            UInt32 value )
{
    OSNumber *	num;
    UInt8	buffer[16];
    IOByteCount count;
    bool	ok = true;
    UInt8	command;

    if( sym == gIODisplayParametersDefaultKey) {
        return( true );
    }

    if( !params)
        return( true );

    num = (OSNumber *) params->getObject("reg");
    if( !num)
        return( false );
    command = num->unsigned32BitValue();

    if( sym == gIODisplayParametersCommitKey)
        value = 0;

    buffer[0] = kIICSourceAddress;
    buffer[1] = kVCPCommandDataLength;
    buffer[2] = kSetVCPFeature;
    buffer[3] = command;
    buffer[4] = 0;
    buffer[5] = value;
    buffer[6] = self->fIICAddress
                ^ buffer[0]
                ^ buffer[1]
                ^ buffer[2]
                ^ buffer[3]
                ^ buffer[4]
                ^ buffer[5];

    if( sym == gIODisplayParametersCommitKey)
        IOSleep( 110 );
    else
        IOSleep( 50 ); // 100 in 9

    count = kIICWriteBufferSize;
    AppleCudaWriteIIC( self->fIICAddress, buffer, &count );

    if( sym == gIODisplayParametersCommitKey)
        IOSleep( 110 );

    return( ok );
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

enum {
    kATISetAuxWin	= 0x96,
    kATIGetAuxWin	= 0x96,
    kATISetFader	= 0x97,
    kATIGetFader	= 0x97,
    kAuxWinDelay	= 6,
    kAuxWinPolarity	= 0,
    kEnableAuxWinDelay  = 1
};

struct ATIAuxWinRec {
    UInt16	x;
    UInt16	y;
    UInt16	w;
    UInt16	h;
    UInt8	enable;
    UInt8	delay;
    UInt8	bppDelayEnable;
    UInt8	polarity;
};

struct ATIFaderRec {
    UInt8	enableFader;
};

/*  PMU99 I2C command equates (command $9A)*/
enum {
    kPMUI2CCmd			= 0x9A,

    kI2CStatusBus               = 0,                            /* pseudo bus used for status retrieval*/
    kSystemClockBus             = 1,
    kPowerSupplyBus             = 2,                            /* (IVAD is here)*/
    kSimpleI2CStream            = 0,
    kSubaddressI2CStream        = 1,
    kCombinedI2CStream          = 2,
    kI2CReplyPendingErr         = -4,
    kI2CTransactionErr          = -3,
    kI2CBusyErr                 = -2,
    kI2CParameterErr            = -1,
    kI2CNoErr                   = 0,
    kI2CReadData                = 1,
    kI2CDataBufSize             = 249
};

struct PMUI2CPB {
    UInt8                           bus;
    UInt8                           xferType;
    UInt8                           secondaryBusNum;
    UInt8                           address;
    UInt8                           subAddr;
    UInt8                           combAddr;
    UInt8                           dataCount;
    UInt8                           data[249];                  /* sizeof(PMUI2CPB) = 256*/
};
typedef struct PMUI2CPB                 PMUI2CPB;

#define MAXIICRETRYCOUNT	10
#define STATUS_DATAREAD		1
#define STATUS_OK			0
#define STATUS_BUSY			0xfe

static IOReturn
ApplePMUReadIIC( UInt8 bus, UInt8 address, UInt8 subAddress, UInt8 * readBuf, IOByteCount count )
{
    PMUI2CPB		iicPB;
    IOByteCount		readLen;
    UInt32		retries;		// loop counter for retry attempts
    IOReturn		ioResult;

    ioResult = kIOReturnError;			// assume error
    retries = MAXIICRETRYCOUNT;
    while (retries--) {

        iicPB.bus		= bus;
        iicPB.xferType		= kCombinedI2CStream;
        iicPB.secondaryBusNum	= 0;
        iicPB.address		= address;
        iicPB.subAddr		= subAddress;
        iicPB.combAddr		= 1 + address;
        iicPB.dataCount		= count - 1;

        readLen 		= count;
        readBuf[0] 		= 0xff;
        ApplePMUSendMiscCommand( kPMUI2CCmd, 8, (UInt8 *) &iicPB, &readLen, readBuf );
        if (readBuf[0] == STATUS_OK)
            break;				// if pb accepted, proceed to status/read phase

        IOSleep( 15 );
    }

    if (readBuf[0] == STATUS_OK) {

        retries = MAXIICRETRYCOUNT;
        while (retries--) {

            IOSleep( 15 );

            iicPB.bus		= kI2CStatusBus;
            readLen 		= count;
            readBuf[0]		= 0xff;
            ApplePMUSendMiscCommand( kPMUI2CCmd, 1, (UInt8 *) &iicPB, &readLen, readBuf );

            if (readBuf[0] == STATUS_DATAREAD) {
                ioResult = kIOReturnSuccess;
                break;
            }
        }
    }

    return( ioResult );
}

static IOReturn
ApplePMUWriteIIC( UInt8 bus, UInt8 address, UInt8 subAddress, UInt8 * buffer, IOByteCount count )
{
    PMUI2CPB		iicPB;
    IOByteCount		readLen;
    UInt8		readBuf[8];
    UInt32		retries;		// loop counter for retry attempts
    IOReturn		ioResult;

    ioResult = kIOReturnError;			// assume error

    retries = MAXIICRETRYCOUNT;
    while (retries--) {
        iicPB.bus		= bus;
        iicPB.xferType		= kSimpleI2CStream;
        iicPB.secondaryBusNum	= 0;
        iicPB.address		= address;
        iicPB.subAddr		= 0;
        iicPB.combAddr		= 0;
        iicPB.dataCount		= 2;
        iicPB.data[0]		= subAddress;
        bcopy( buffer, &iicPB.data[1], count );

        readLen 		= sizeof( readBuf );
        readBuf[0]		= 0xff;
        ApplePMUSendMiscCommand( kPMUI2CCmd, 8 + count, (UInt8 *) &iicPB, &readLen, readBuf );


        if (readBuf[0] == STATUS_OK)
            break;			// if pb accepted, proceed to status phase

        IOSleep( 15 );
    }

    if (readBuf[0] == STATUS_OK) {

        retries = MAXIICRETRYCOUNT;
        while (retries--) {

            IOSleep( 15 );

            // attempt to recover status

            iicPB.bus		= kI2CStatusBus;
            readLen 		= sizeof( readBuf );
            readBuf[0]		= 0xff;
            ApplePMUSendMiscCommand( kPMUI2CCmd, 1, (UInt8 *) &iicPB, &readLen, readBuf );

            if (readBuf[0] == STATUS_OK) {
                ioResult = kIOReturnSuccess;
                break;
            }
        }
    }

    return( ioResult );
}

bool AppleOnboardDisplay::updateIVAD( AppleOnboardDisplay * self )
{
    IOReturn 			err;
    IODisplayModeID		mode;
    IOIndex			depth;
    IOPixelInformation		pixInfo;

    err = self->fFramebuffer->getCurrentDisplayMode( &mode, &depth );
    if( kIOReturnSuccess == err)
        err = self->fFramebuffer->getPixelInformation( mode, depth,
                                kIOFBSystemAperture, &pixInfo );
    if( kIOReturnSuccess == err) switch( pixInfo.activeWidth ) {
        case 640:
            self->fModeIndex = 0;
            break;
        case 800:
        default:
            self->fModeIndex = 1;
            break;
        case 1024:
            self->fModeIndex = 2;
            break;
    }

    self->setParameter( gIODisplayContrastKey,
            self->fUserPrefs->contrast );
    self->setParameter( gIODisplayBrightnessKey,
            self->fUserPrefs->brightness );
    self->setParameter( gIODisplayHorizontalPositionKey,
            self->fUserPrefs->modeParams[self->fModeIndex].horizontalPosition ^ 0x7f );
    self->setParameter( gIODisplayHorizontalSizeKey,
            self->fUserPrefs->modeParams[self->fModeIndex].horizontalSize ^ 0x7f );
    self->setParameter( gIODisplayVerticalPositionKey,
            self->fUserPrefs->modeParams[self->fModeIndex].verticalPosition ^ 0x7f );
    self->setParameter( gIODisplayVerticalSizeKey,
            self->fUserPrefs->modeParams[self->fModeIndex].verticalSize );
    self->setParameter( gIODisplayTrapezoidKey,
            self->fUserPrefs->modeParams[self->fModeIndex].keystone );
    self->setParameter( gIODisplayPincushionKey,
            self->fUserPrefs->modeParams[self->fModeIndex].pincushion );
    self->setParameter( gIODisplayParallelogramKey,
            self->fUserPrefs->parallelogram  ^ 0x7f);
    self->setParameter( gIODisplayRotationKey,
            self->fUserPrefs->tilt);
    self->setParameter( gIODisplayParametersTheatreModeKey,
            self->fTheatreMode );

    return( true );
}

bool AppleOnboardDisplay::setupIVAD( void )
{
    UInt8	buffer[96 + 32];
    OSData *	data;
    UInt32	address;
    SInt32	i;
    bool	ok = false;

    if( 0 == (data = OSDynamicCast(OSData,
            fIVAD->getProperty("eeprom-address"))))
        return( false );

    address = *((UInt32 *) data->getBytesNoCopy());

    if( !fEEPROM)
        fEEPROM = IONew( EEPROM, 1 );
    if( !fUserPrefs)
        fUserPrefs = IONew( IVADUserPrefs, 1 );
    if( !fEEPROM || !fUserPrefs)
        return( false );

    bzero( buffer, sizeof(buffer) );
    for (i = 0; i < (6 * 16); i += 16) {
        if (kIOReturnSuccess == ApplePMUReadIIC(
                kPowerSupplyBus, address, i, buffer + i, 17 ))
            bcopy(1 + buffer + i, buffer + i, 16 );
    }

    bcopy( buffer, fEEPROM, sizeof(EEPROM) );

#if DEBUG
    IOLog("IVAD EEPROM(%x):\n", fEEPROM->id );
    for (int i = 0; i < (6 * 16); i++) {
        IOLog("%02x ", buffer[i]);
        if( 15 == (i & 15))
            IOLog("\n");
    }
#endif

    if( (data = OSDynamicCast(OSData,
            fIVAD->getProperty(fIVADPrefsKey))))
        bcopy( data->getBytesNoCopy(), fUserPrefs, sizeof(IVADUserPrefs));
    else {
        bcopy( fEEPROM->modeParams, fUserPrefs->modeParams, sizeof(fUserPrefs->modeParams));
        fUserPrefs->contrast	  = fEEPROM->stdParams.contrast;
        fUserPrefs->brightness	  = fEEPROM->stdParams.brightness;
        fUserPrefs->parallelogram = fEEPROM->stdParams.parallelogram;
        fUserPrefs->tilt	  = fEEPROM->stdParams.tilt;
    }

//    fDisplayParams->setCapacityIncrement(1);

    addParameter( gIODisplayContrastKey,
                    fEEPROM->userLimits.contrast.min,
                    fEEPROM->userLimits.contrast.max );
    addParameter( gIODisplayBrightnessKey,
                    fEEPROM->userLimits.brightness.min,
                    fEEPROM->userLimits.brightness.max );
    addParameter( gIODisplayHorizontalPositionKey,
                    fEEPROM->userLimits.horizontalPosition.min,
                    fEEPROM->userLimits.horizontalPosition.max );
    addParameter( gIODisplayHorizontalSizeKey,
                    fEEPROM->userLimits.horizontalSize.min,
                    fEEPROM->userLimits.horizontalSize.max );
    addParameter( gIODisplayVerticalPositionKey,
                    fEEPROM->userLimits.verticalPosition.min,
                    fEEPROM->userLimits.verticalPosition.max );
    addParameter( gIODisplayVerticalSizeKey,
                    fEEPROM->userLimits.verticalSize.min,
                    fEEPROM->userLimits.verticalSize.max );
    addParameter( gIODisplayTrapezoidKey,
                    fEEPROM->userLimits.keystone.min,
                    fEEPROM->userLimits.keystone.max );
    addParameter( gIODisplayPincushionKey,
                    fEEPROM->userLimits.pincushion.min,
                    fEEPROM->userLimits.pincushion.max );
    addParameter( gIODisplayParallelogramKey,
                    fEEPROM->userLimits.parallelogram.min,
                    fEEPROM->userLimits.parallelogram.max );
    addParameter( gIODisplayRotationKey,
                    fEEPROM->userLimits.tilt.min,
                    fEEPROM->userLimits.tilt.max );

    addParameter( gIODisplayParametersTheatreModeKey, 0, 1 );

    ATIFaderRec	fader;
    IONDRVFramebuffer * ndrv = OSDynamicCast( IONDRVFramebuffer, fFramebuffer);
    if( ndrv && (kIOReturnSuccess == ndrv->doStatus( kATIGetFader, &fader)))
        addParameter( gIODisplayParametersTheatreModeWindowKey, 0, 1 );

    return( ok );
}

bool AppleOnboardDisplay::setTheatreModeATI( IOGBounds * rect )
{
    IONDRVFramebuffer *		ndrv = OSDynamicCast( IONDRVFramebuffer, fFramebuffer);
    ATIFaderRec			fader;
    ATIAuxWinRec		auxWin;
    UInt32			newMode;
    bool 			ok = true;

    ok &= (kIOReturnSuccess == ndrv->doControl( kATISetFader, &fader));

    ok = (ndrv != 0);
    if( ok) do {

        newMode = (rect->maxx != rect->minx);
        auxWin.enable		= true;
        auxWin.delay		= kAuxWinDelay;
        auxWin.bppDelayEnable	= kEnableAuxWinDelay;
        fader.enableFader	= newMode;
        
        if( newMode) {
            auxWin.x		= rect->minx;
            auxWin.y		= rect->miny;
            auxWin.w		= rect->maxx - rect->minx;
            auxWin.h		= rect->maxy - rect->miny;
            auxWin.polarity	= kAuxWinPolarity;
            ok &= (kIOReturnSuccess == ndrv->doControl( kATISetAuxWin, &auxWin));
            ok &= (kIOReturnSuccess == ndrv->doControl( kATISetFader, &fader));

        } else {
            auxWin.x		= 0;
            auxWin.y		= 0;
            auxWin.w		= 1;
            auxWin.h		= 1;
            auxWin.polarity	= 0;
            ok &= (kIOReturnSuccess == ndrv->doControl( kATISetFader, &fader));
            ok &= (kIOReturnSuccess == ndrv->doControl( kATISetAuxWin, &auxWin));
        }
    } while( false );

    return( ok );
}


bool AppleOnboardDisplay::setTheatreMode( UInt32 newMode )
{
    enum { kTMRegCount = 9 };
    static const UInt8 regs[kTMRegCount] = {   kIVADContrast, kIVADBrightness,
                                                kIVADDrive1, kIVADDrive2, kIVADDrive3,
                                                kIVADCutoff1, kIVADCutoff2, kIVADCutoff3,
                                                kIVADVBLDCLevel };
    UInt8 values[kTMRegCount];
    bool 			ok = true;

    ok = true;
    newMode = (newMode != 0);
    if( newMode != fTheatreMode) {

        if( newMode) {

            values[0] = fEEPROM->briteParams.contrast;
            values[1] = fEEPROM->briteParams.brightness;
            values[2] = fEEPROM->briteParams.drive1;
            values[3] = fEEPROM->briteParams.drive2;
            values[4] = fEEPROM->briteParams.drive3;
            values[5] = fEEPROM->briteParams.cutoff1;
            values[6] = fEEPROM->briteParams.cutoff2;
            values[7] = fEEPROM->briteParams.cutoff3;
            values[8] = fEEPROM->stdParams.vblDCLevel
                    | (fEEPROM->briteParams.gainBoost ? kGainWin : 0);
        } else {
            values[0] = fUserPrefs->contrast;
            values[1] = fUserPrefs->brightness;
            values[2] = fEEPROM->stdParams.drive1;
            values[3] = fEEPROM->stdParams.drive2;
            values[4] = fEEPROM->stdParams.drive3;
            values[5] = fEEPROM->stdParams.cutoff1;
            values[6] = fEEPROM->stdParams.cutoff2;
            values[7] = fEEPROM->stdParams.cutoff3;
            values[8] = fEEPROM->stdParams.vblDCLevel;
        }

        for( int i = 0; i < kTMRegCount; i++)
            ok &= (kIOReturnSuccess == ApplePMUWriteIIC(
                    kPowerSupplyBus, fIICAddress, regs[i],
                    &values[i], sizeof(UInt8) ));
        if( ok)
            fTheatreMode = newMode;
    }
    return( ok );
}

bool AppleOnboardDisplay::setIVAD( AppleOnboardDisplay * self,
                            OSDictionary * params, const OSSymbol * sym,
                            UInt32 value )
{
    OSNumber *	num;
    UInt32	reg;
    UInt8	regValue;
    bool 	ok = false;

    if( sym == gIODisplayParametersDefaultKey) {
        bcopy( self->fEEPROM->modeParams, self->fUserPrefs->modeParams,
                    sizeof(self->fUserPrefs->modeParams));
        self->fUserPrefs->contrast	= self->fEEPROM->stdParams.contrast;
        self->fUserPrefs->brightness	= self->fEEPROM->stdParams.brightness;
        self->fUserPrefs->parallelogram = self->fEEPROM->stdParams.parallelogram;
        self->fUserPrefs->tilt	  	= self->fEEPROM->stdParams.tilt;
        return( true );

    } else if( sym == gIODisplayParametersCommitKey) {
        IODTPlatformExpert * dtPlaform = OSDynamicCast( IODTPlatformExpert, getPlatform() );
        OSData * data = OSData::withBytesNoCopy( self->fUserPrefs, sizeof(IVADUserPrefs));
        if( data && dtPlaform)
            ok = (kIOReturnSuccess ==
                dtPlaform->writeNVRAMProperty( self->fIVAD, self->fIVADPrefsKey, data ));
        if( data)
            data->release();
        return( ok );

    } else if( sym == gIODisplayParametersTheatreModeKey)
        return( self->setTheatreMode( value ));

    if( !params)
        return( true );

    num = (OSNumber *) params->getObject("reg");
    if( !num)
        return( false );
    reg = num->unsigned32BitValue();
    
    if( reg & 0x100)
        regValue = value ^ 0x7f;
    else
        regValue = value;

    ok = (kIOReturnSuccess == ApplePMUWriteIIC(
                kPowerSupplyBus, self->fIICAddress, reg & 0x1f,
                &regValue, sizeof(regValue) ));

    if( ok) {
        if( sym == gIODisplayContrastKey)
            self->fUserPrefs->contrast = regValue;
        else if( sym == gIODisplayBrightnessKey)
            self->fUserPrefs->brightness = regValue;
        else if( sym == gIODisplayHorizontalPositionKey)
            self->fUserPrefs->modeParams[self->fModeIndex].horizontalPosition = regValue;
        else if( sym == gIODisplayHorizontalSizeKey)
            self->fUserPrefs->modeParams[self->fModeIndex].horizontalSize = regValue;
        else if( sym == gIODisplayVerticalPositionKey)
            self->fUserPrefs->modeParams[self->fModeIndex].verticalPosition = regValue;
        else if( sym == gIODisplayVerticalSizeKey)
            self->fUserPrefs->modeParams[self->fModeIndex].verticalSize = regValue;
        else if( sym == gIODisplayTrapezoidKey)
            self->fUserPrefs->modeParams[self->fModeIndex].keystone = regValue;
        else if( sym == gIODisplayPincushionKey)
            self->fUserPrefs->modeParams[self->fModeIndex].pincushion = regValue;
        else if( sym == gIODisplayParallelogramKey)
            self->fUserPrefs->parallelogram = regValue;
        else if( sym == gIODisplayRotationKey)
            self->fUserPrefs->tilt = regValue;
    }

    return( ok );
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

static IOReturn
ApplePMUSendMiscCommand( UInt32 command,
                        IOByteCount sendLength, UInt8 * sendBuffer,
                        IOByteCount * readLength, UInt8 * readBuffer )
{
    struct SendMiscCommandParameterBlock {
        int command;  
        IOByteCount sLength;
        UInt8 *sBuffer;
        IOByteCount *rLength; 
        UInt8 *rBuffer;
    };
    IOReturn ret = kIOReturnError;
    static IOService * pmu;

    // Wait for the PMU to show up:
    if( !pmu)
        pmu = IOService::waitForService(IOService::serviceMatching("ApplePMU"));

    SendMiscCommandParameterBlock params = { command, sendLength, sendBuffer,
                                                      readLength, readBuffer };
    if( pmu)
        ret = pmu->callPlatformFunction( "sendMiscCommand", true,
                                         (void*)&params, NULL, NULL, NULL );
    return( ret );
}

