/*
 * Copyright (c) 1998-2004 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 "i82557.h"

/**************************************************************************
* POWER-MANAGEMENT CODE
***************************************************************************
* These definitions and functions allow the driver to handle power state
* changes to support sleep and wake.
**************************************************************************/

// Two power states are supported by the driver, On and Off.

enum {
    k8255xPowerStateOff = 0,
    k8255xPowerStateOn,
    k8255xPowerStateCount
};

// An IOPMPowerState structure is added to the array for each supported
// power state. This array is used by the power management policy-maker
// to determine our capabilities and requirements for each power state.

static IOPMPowerState i8255xPowerStateArray[ k8255xPowerStateCount ] =
{
    { 1,0,0,0,0,0,0,0,0,0,0,0 },
    { 1,IOPMDeviceUsable,IOPMPowerOn,IOPMPowerOn,0,0,0,0,0,0,0,0 }
};

enum {
    kFiveSeconds = 5000000
};

//---------------------------------------------------------------------------
// handleSetPowerStateOff()
//
// The policy-maker has told the driver to turn off the device, and the
// driver has started a new thread to do this. This C function is the start
// of that thread. This function then calls itself through the command gate
// to gain exclusive access to the device.
//---------------------------------------------------------------------------

void handleSetPowerStateOff( thread_call_param_t param0,
                             thread_call_param_t param1 )
{
    Intel82557 * self = ( Intel82557 * ) param0;

    assert( self );

    if ( param1 == 0 )
    {
        self->getCommandGate()->runAction( (IOCommandGate::Action)
                                           handleSetPowerStateOff,
                                           (void *) 1 /* param1 */ );
    }
    else
    {
        self->setPowerStateOff();
        self->release();  // offset the retain in setPowerState()
    }
}

//---------------------------------------------------------------------------
// handleSetPowerStateOn()
//
// The policy-maker has told the driver to turn on the device, and the
// driver has started a new thread to do this. This C function is the start
// of that thread. This function then calls itself through the command gate
// to gain exclusive access to the device.
//---------------------------------------------------------------------------

void handleSetPowerStateOn( thread_call_param_t param0,
                            thread_call_param_t param1 )
{
    Intel82557 * self = ( Intel82557 * ) param0;

    assert( self );

    if ( param1 == 0 )
    {
        self->getCommandGate()->runAction( (IOCommandGate::Action)
                                           handleSetPowerStateOn,
                                           (void *) 1 /* param1 */ );
    }
    else
    {
        self->setPowerStateOn();
        self->release();  // offset the retain in setPowerState()
    }
}

//---------------------------------------------------------------------------
// registerWithPolicyMaker()
//
// The superclass invokes this function when it is time for the driver to
// register with the power management policy-maker of the device.
//
// The driver registers by passing to the policy-maker an array which
// describes the power states supported by the hardware and the driver.
//
// Argument:
//
// policyMaker - A pointer to the power management policy-maker of the
//               device.
//---------------------------------------------------------------------------

IOReturn Intel82557::registerWithPolicyMaker( IOService * policyMaker )
{
    IOReturn ret;

    // Initialize power management support state.

    pmPowerState  = k8255xPowerStateOn;
    pmPolicyMaker = policyMaker;

	// No cheating here. Decouple the driver's handling of power change
    // requests from the power management work loop thread. Not strictly
    // necessary for the work that this driver is doing currently, but
    // useful as an example of how to do things in general.
    //
    // Allocate thread callouts for asynchronous power on and power off.
    // Allocation failure is not detected here, but before each use in
    // the setPowerState() method.

    powerOffThreadCall = thread_call_allocate( 
                           (thread_call_func_t)  handleSetPowerStateOff,
                           (thread_call_param_t) this );

    powerOnThreadCall  = thread_call_allocate(
                           (thread_call_func_t)  handleSetPowerStateOn,
                           (thread_call_param_t) this );

    ret = pmPolicyMaker->registerPowerDriver( this,
                                              i8255xPowerStateArray,
                                              k8255xPowerStateCount );
    
    return ret;
}

//---------------------------------------------------------------------------
// setPowerState()
//
// The power management policy-maker for this device invokes this function
// to switch the power state of the device.
//
// Arguments:
//
// powerStateOrdinal - an index into the power state array for the state
//                     to switch to.
//
// policyMaker       - a pointer to the policy-maker.
//---------------------------------------------------------------------------

IOReturn Intel82557::setPowerState( unsigned long powerStateOrdinal,
                                    IOService *   policyMaker)
{
    // The default return value is for an implied acknowledgement.
    // If the appropriate thread wasn't allocated earlier in
    // registerWithPolicyMaker(), then this is what will be returned.

    IOReturn result = IOPMAckImplied;

    // There's nothing to do if our current power state is the one
    // we're being asked to change to.

    if ( pmPowerState == powerStateOrdinal )
    {
        return result;
    }

    switch ( powerStateOrdinal )
    {
        case k8255xPowerStateOff:

            // The driver is being told to turn off the device for some reason.
            // It saves whatever state and context it needs to and then shuts
            // off the device.
            //
            // It may take some time to turn off the HW, so the driver spawns
            // a thread to power down the device and returns immediately to
            // the policy-maker giving an upper bound on the time it will need
            // to complete the power state transition.

            if ( powerOffThreadCall )
            {
                // Prevent the object from being freed while a call is pending.
                // If thread_call_enter() returns TRUE, then a call is already
                // pending, and the extra retain is dropped.
                
                retain();
                if ( thread_call_enter( powerOffThreadCall ) == TRUE )
                {
                    release();
                }
                result = kFiveSeconds;
            }
            break;

        case k8255xPowerStateOn:

            // The driver is being told to turn on the device.  It does so
            // and then restores any state or context which it has previously
            // saved.
            //
            // It may take some time to turn on the HW, so the driver spawns
            // a thread to power up the device and returns immediately to
            // the policy-maker giving an upper bound on the time it will need
            // to complete the power state transition.

            if ( powerOnThreadCall )
            {
                // Prevent the object from being freed while a call is pending.
                // If thread_call_enter() returns TRUE, then a call is already
                // pending, and the extra retain is dropped.

                retain();
                if ( thread_call_enter( powerOnThreadCall ) == TRUE )
                {
                    release();
                }
                result = kFiveSeconds;
            }
            break;
        
        default:
            IOLog("%s: invalid power state (%ld)\n", getName(),
                  powerStateOrdinal);
            break;
    }

    return result;
}

//---------------------------------------------------------------------------
// setPowerStateOff()
//
// The policy-maker has told the driver to turn off the device, and this
// function is called by a new kernel thread, while holding the gate in
// the driver's work loop. Exclusive hardware access is assured.
//---------------------------------------------------------------------------

void Intel82557::setPowerStateOff(void)
{
    // At this point, all clients have been notified of the driver's
    // imminent transition to a power state that renders it "unusable".
    // And beacuse of the response to this notification by all clients,
    // the controller driver is guaranteed to be disabled when this
    // function is called.

    // If wake on Magic Packet support is active, then allow the device
    // to assert the PME# line on the PCI bus when a matching Magic Packet
    // is received, by setting the PME_En bit in the PMCSR register. The
    // assertion of the PME# signal should be sufficient to wake up the
    // machine from sleep.

    if ( pmPCICapPtr )
    {
        pciNub->configWrite16( kPCIPMCSR,
                               magicPacketEnabled ? 0x8103 : 0x8003 );
    }

    pmPowerState = k8255xPowerStateOff;

    // Since the driver returned a non-acknowledgement when called at
    // setPowerState(), it sends an ACK to the policy-maker here to
    // indicate that our power state transition is complete.

    pmPolicyMaker->acknowledgeSetPowerState();
}

//---------------------------------------------------------------------------
// setPowerStateOn()
//
// The policy-maker has told the driver to turn on the device, and this
// function is called by a new kernel thread, while holding the gate in
// the driver's work loop. Exclusive hardware access is assured.
//---------------------------------------------------------------------------

void Intel82557::setPowerStateOn()
{
    // Clear PME# assertion now that the device has regained full power.

    if ( pmPCICapPtr )
    {
        // Save and restore the PCI configuration registers on a D3 - D0
        // state transition. This is necessary when the state transition
        // occurs on a powered PCI bus, because the chip will perform an
        // internal reset which initializes its PCI config space (except
        // for the power management block). This is documented in the D3
        // state notes in the Intel 82558 Software Developer's Manual.

        pciNub->saveDeviceState();
        pciNub->configWrite16( kPCIPMCSR, 0x8000 );
        IOSleep(10);  // wait for internal reset completion
        pciNub->restoreDeviceState();
    }

    pmPowerState = k8255xPowerStateOn;

    // Since the driver returned a non-acknowledgement when called at
    // setPowerState(), it sends an ACK to the policy-maker here to
    // indicate that our power state transition is complete.

    pmPolicyMaker->acknowledgeSetPowerState();

    // With power restored, all clients will be notified that the driver
    // has became "usable". If a client wishes to use the driver, then the
    // driver can expect a call to its enable() method to start things off.
}

//---------------------------------------------------------------------------
// setWakeOnMagicPacket()
//
// Just before the driver is disabled by the network interface when the
// system is going into sleep, this function is called to instruct the
// driver to enable Magic Packet wakeup.
//
// This function records that Magic Packet wakeup should be enabled,
// and let setPowerStateOff() take care of the rest.
//---------------------------------------------------------------------------

IOReturn Intel82557::setWakeOnMagicPacket( bool active )
{
    magicPacketEnabled = active;
    return kIOReturnSuccess;
}

//---------------------------------------------------------------------------
// getPacketFilters()
//
// The driver reports whether the hardware supports Magic Packets by
// implementing this function defined by the superclass.
//---------------------------------------------------------------------------

IOReturn Intel82557::getPacketFilters( const OSSymbol * group,
                                       UInt32 *         filters ) const
{
	if ( ( group == gIOEthernetWakeOnLANFilterGroup ) &&
         ( magicPacketSupported ) )
	{
		*filters = kIOEthernetWakeOnMagicPacket;
		return kIOReturnSuccess;
	}

    // For any other filter groups, return the default set of filters
    // reported by IOEthernetController.

	return IOEthernetController::getPacketFilters( group, filters );
}
