// Copyright (c) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the License "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL " http://www.eclipse.org/legal/epl-v10.html ".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// os\kernelhwsrv\kernel\eka\drivers\power\smppower\sample_idlehandler\smpidlehandler.cpp
// implements a basic smp idle handler generic layer that can be derived from 
// to create platform specific SMP idle handlers

/**
 @file
 @prototype
*/
#ifndef __WINS__
#include <kernel/arm/arm.h>
#endif
#include "smpidlehandler.h"

#ifdef __SMP__

//-/-/-/-/-/-/-/-/-/ class DDSMPIdleHandler /-/-/-/-/-/-/-/-/-/

DSMPIdleHandler* gTheIdleHandler = NULL;

DSMPIdleHandler::DSMPIdleHandler ()
    :iAllCpusMask(TIdleSupport::AllCpusMask()),
     iStartAfterExtInitDfc(StartAfterExtIntDfcFn,this,Kern::SvMsgQue(),0),
     iInitialised(EFalse)
      {
      // singleton class
      __PM_IDLE_ASSERT_DEBUG(!gTheIdleHandler);
      gTheIdleHandler = this;
      }


/**
   To be called after construction in a thread context with interrupts enabled. Power extension entry point ideal
   @pre thread context ints enable no kernel locks or fast mutexes
   @param aGlobalIntDistAddress GIC address
   @param aBaseIntIfAddress GIC CPU interface address
 */
void DSMPIdleHandler::Initialise(TUint32 aGlobalIntDistAddress, TUint32 aBaseIntIfAddress)
    {
	TIdleSupport::SetupIdleSupport(aGlobalIntDistAddress,aBaseIntIfAddress);
    //Set Idle IPI to highest priority
	TIdleSupport::SetIdleIPIToHighestPriority();
    Arm::SetIdleHandler((TCpuIdleHandlerFn)DSMPIdleHandler::IdleHandler, this);
    iStartAfterExtInitDfc.Enque();
    }


/**
   Must be called when cores are enaged or retired
   @pre calling code must be outside idle handler, or in DoEnterIdle call
*/
void DSMPIdleHandler::ResetSyncPoints()
    {
    iIdleSync.Reset();
    iStage2.Reset();
    }


/**
   Called by idle handler inmediatelly after idle entry. Can be used for things such as checking 
   if a core is going to be retired. Can return EFalse to force the idle handler to return 
   at that point
   @param aCpuMask mask of current cpu
   @param aStage passed from kernel indicated things such core retiring or postamble
   @param aU points to some per-CPU uncached memory used for handshaking in 
       during power up/power down of a core for support of core retiring. This 
       memory is provided by baseport to the kernel via the VIB

   @return EFalse if the cpu should exit idle, ETrue otherwise
 */
TBool DSMPIdleHandler::DoEnterIdle(TInt aCpuMask, TInt aStage,  volatile TAny* aU)
    {
    return ETrue;
    }

 /**
    Called by idle handler all cpus to enter idle once all have entered idle but
    before NTimeQ::IdleTime is called to check time to next timer expiry. Can be used 
    for things such as idle timer pre-idle processing. In such cases only the last CPU
    should do the idle timer processing
    @param aCpuMask mask of current cpu
    @param aLastCPu indicates if this is last CPU.
 */
void DSMPIdleHandler::CpusHaveEnteredIdle(TInt aCpuMask, TInt aLastCpu)
    {
    }


/**
   Called after wakeup can be used for status restoring
   a sync point is placed after the call to this function
   This can be a good place to do idle tick restoration
   @param TInt aCpuMask indicates calling CPU
   @param aLastCPu indicates if this is last CPU.
   @see EnterLowPowerMode
*/
void DSMPIdleHandler::PostWakeup(TInt aCpuMask, TBool aLastCpu)
    {
    }

/**
   Called at exit of idle handler not synchronised in any way
   @param aExitPoint point at which you might exit the idle handler
   @param TInt aCpuMask indicates calling CPU
   @param aLastCPu indicates if this is last CPU.
*/
void DSMPIdleHandler::DoExitIdle(TIdleExit aExitPoint, TInt aCpuMask, TBool aLastCpu)
    {
    }

/**
   IdleHandler just calls DoIdle
*/
void DSMPIdleHandler::IdleHandler(TAny* /*aPtr*/, TInt aStage, volatile TAny* aU)
    {
    gTheIdleHandler->DoIdle(aStage,aU);
    }

/**
   Idle handling per se
*/
void DSMPIdleHandler::DoIdle(TInt aStage, volatile TAny* aU)
    {
    // wait after all extensions have initialsed before running
    if (!iInitialised) return;
    
    TInt c = NKern::CurrentCpu();
	TUint32 cMask = (1<<c);
	TUint32 allButC = (~cMask) & iAllCpusMask;

    if (!DoEnterIdle(cMask,aStage,aU)) return;

	if (TIdleSupport::SetLocalAndCheckSetGlobalIdle(cMask)) // check if last down
		{
        //LastCpuIdle:                               
        PMBTRACE4(KIdleEntry,KIdleEntryLastCpu,TIdleSupport::GetCpusIdleMask());
        // we have now managed to lock the system into all cpus waiting, by setting MSB we are now informing any leaving CPUs 
        // that we intend to take the system down
        // other CPUs with an STI so they can proceed with power down
        // when we get there, all other CPUs can be in of the following states:
        // 1. In the DoWFI of the section above (allButC!=_....) 
        // 2. They could have already got past the DoWFI due to another interrupt waking them up
        TIdleSupport::DoIdleIPI(allButC);          // wake all sleeping CPUs   
        PMBTRACE0(KSendIPI,0);
        SYNCPOINT(iIdleSync,0);  // After this point all should be awake and then other will clear the pending IPI 
        CpusHaveEnteredIdle(cMask,ETrue);
        iStage2.Reset();
        TIdleSupport::ResetLogic(); // 
        SYNCPOINT(iIdleSync,1);  // sync on IPI clearing

        // here do we have
        // a/ Interrupt pending?
        // b/ Timer coming up soon? 
        // if (a or b) just allow all idle handlers to return
        TInt nextTimer = NTimerQ::IdleTime();
        TBool exitNow = !GetLowPowerMode(nextTimer,iLowPowerMode);
        
        PMBTRACE8(KMisc,2,nextTimer,iLowPowerMode);
        
        if (TIdleSupport::IsIntPending() || exitNow)
            {
            // clear idling cpus this will cause all cpus to exit idle handler
            TIdleSupport::SetExitRequired(&iStage2);		
            }
        
        SYNCPOINT(iStage2,2);
        if(TIdleSupport::GetExitRequired())            
            {
            DoExitIdle(EExitBeforeLPM,cMask,ETrue);
            PMBTRACE4(KIdleeXit,KIdleeXitLastCpu1,TIdleSupport::GetCpusIdleMask());
            return;
            }
        
        // We enter LPM here!!!
        TBool exitRouseRequired = EnterLowPowerMode(iLowPowerMode,cMask,ETrue);

        TBool firstToWake = EFalse;

        if (exitRouseRequired)
            {
            firstToWake = TIdleSupport::FirstCoreAwake(cMask);
            if (firstToWake)
                {
                // we are probaly first to rouse so wake the others
                PMBTRACE0(KMisc,0x50);
                TIdleSupport::DoIdleIPI (allButC);
                }
            }

        PostWakeup(cMask,ETrue);
        PMBTRACE4(KMisc,3,exitRouseRequired);
        SYNCPOINT(iIdleSync,3);  
        if (exitRouseRequired)
            {
            if (!firstToWake)
                {
                TIdleSupport::ClearIdleIPI();
                PMBTRACE0(KMisc,0x51);
                }
            }
        DoExitIdle(EExitAfterLPM,cMask,ETrue);    
        PMBTRACE4(KMisc,0x52,TIdleSupport::IntPending()&0x1ff);
        PMBTRACE4(KIdleeXit,KIdleeXitLastCpu0,TIdleSupport::GetCpusIdleMask());
        }
	else
        {
        //NormalIdle:                               
        PMBTRACE4(KIdleEntry,KIdleEntryNormalCpu,TIdleSupport::GetCpusIdleMask());	
        TIdleSupport::DoWFI();	// CPU in WFI
        // here we could have woken up because of the Idle IPI or because of another interrupt
        if (!TIdleSupport::ClearLocalAndCheckGlobalIdle(cMask)) 
            {
            // not all other cores are idling we were woken up by some interrupt so exit
            DoExitIdle(EExitOtherCPUsNotIdle,cMask,EFalse);
            PMBTRACE4(KIdleeXit,KIdleeXitNormalCpu0,TIdleSupport::GetCpusIdleMask());
            return;
            }

        // we are all idling
        SYNCPOINT(iIdleSync,0x10);
        // clear IdleIPI, we could actually have another interrupt pending but 
        // we ignore this for now as it is checked later
        
        TIdleSupport::ClearIdleIPI(); 
        PMBTRACE0(KClearIPI,0x10);
        CpusHaveEnteredIdle(cMask,EFalse);
        SYNCPOINT(iIdleSync,0x11);

        if (TIdleSupport::IsIntPending())
				{
                // clear idling cpus this will cause all cpus to exit idle handler
                TIdleSupport::SetExitRequired(&iStage2);				           
				}

        SYNCPOINT(iStage2,0x12);			
        if (TIdleSupport::GetExitRequired())
            {
            DoExitIdle(EExitBeforeLPM,cMask,EFalse);
            PMBTRACE4(KIdleeXit,KIdleeXitNormalCpu1,TIdleSupport::GetCpusIdleMask());
            return;
            }

        // We enter LPM here!!!
        TBool exitRouseRequired = EnterLowPowerMode(iLowPowerMode,cMask,EFalse);

        TBool firstToWake = EFalse;
        
        if (exitRouseRequired)
            {
            firstToWake = TIdleSupport::FirstCoreAwake(cMask);
            if (firstToWake)
                {
                // we are probaly first to rouse so wake the others
                PMBTRACE0(KMisc,0x50);
                TIdleSupport::DoIdleIPI (allButC);
                }
            }

        PostWakeup(cMask,EFalse);
        PMBTRACE4(KMisc,3,exitRouseRequired);
        SYNCPOINT(iIdleSync,0x13);  
        if (exitRouseRequired)
            {
            if (!firstToWake)
                {
                TIdleSupport::ClearIdleIPI();
                PMBTRACE0(KMisc,0x51);
                }
            }
        
        DoExitIdle(EExitAfterLPM,cMask,EFalse);    
        PMBTRACE4(KMisc,0x52,TIdleSupport::IntPending()&0x1ff);
        PMBTRACE4(KIdleeXit,KIdleeXitNormalCpu0,TIdleSupport::GetCpusIdleMask());
        }
    }


/**
   Dfc queued on init to delay idle handler entry until after all
   extensions have initialised
*/
void DSMPIdleHandler::StartAfterExtIntDfcFn(TAny* aPtr)
    {
    DSMPIdleHandler* pD = (DSMPIdleHandler*) aPtr;
    pD->iInitialised = ETrue;
    }


#endif //__SMP__
