/*****************************************************************************
* Copyright (c) 2005 Digi International Inc., All Rights Reserved
* 
* This software contains proprietary and confidential information of Digi
* International Inc.  By accepting transfer of this copy, Recipient agrees
* to retain this software in confidence, to prevent disclosure to others,
* and to make no use of this software other than that for which it was
* delivered.  This is an unpublished copyrighted work of Digi International
* Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
* prohibited.
* 
* Restricted Rights Legend
*
* Use, duplication, or disclosure by the Government is subject to
* restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
* Technical Data and Computer Software clause at DFARS 252.227-7031 or
* subparagraphs (c)(1) and (2) of the Commercial Computer Software -
* Restricted Rights at 48 CFR 52.227-19, as applicable.
*
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
*
*
*  Description
*  ====================================================================
*  This file contains code that implements software support for the
*  power save features for ARM9 based platforms.
*
*/

#include <stdlib.h>
#include "Npttypes.h"
#include "errhndlr.h"
#include "scm_reg.h"
#include "efe_reg.h"
#include "tx_api.h"
#include "assert.h"
#include "ncc_init.h"
#include "powersave.h"
// #include "bsp.h"
#include "nagpio.h"
#include <nastatus.h>
#include <scm_reg.h>
#include <mc_isr.h>
#include "bsp_local.h"
#include <watchdog.h>

#if (PROCESSOR == ns9210) || (PROCESSOR == ns9215)

#include <uart_16750.h>
#include <mpmc_def.h>

#define VALID_TRIGGERS     (NAPD_I2CW | NAPD_SMWEA | NAPD_SMWEB | NAPD_SMWEC | NAPD_SMWED \
                           | NAPD_EWE | NAPD_EI0WE | NAPD_EI1WE | NAPD_EI2WE | NAPD_EI3WE \
                           | NAPD_SPIWE | NAPD_RTCWE)

#define IS_CLOCK_DIVISOR_VALID(a)  ((a == 1) || (a == 2) || (a == 4) || (a == 8) || (a == 16))

/* to store original values */
static unsigned char currentInterruptStatus[NA_MAX_INTERRUPTS];

/* used in catnap */
static unsigned long volatile naPdCatnapCCR  = 0;
static unsigned long volatile naPdCatnapPMR  = 0;
static unsigned naPdRefreshCount             = 0;
#define NAPD_CATNAP_CCR_MASK  0xFE000000

#else

#include "bum_reg.h"

#define VALID_TRIGGERS     (NAPD_I2CW | NAPD_SMWEB | NAPD_EWE | NAPD_PI3WE | NAPD_BBW | NAPD_EI0WE)

#endif

#define MAX_SERIAL_PORTS    4
#define GET_SEMAPHORE       if (tx_semaphore_get(&powersaveSemaphore, TX_WAIT_FOREVER) != TX_SUCCESS) customizeErrorHandler(ERROR_POWERSAVE, ERROR_POWERSAVE_SEMAPHORE);
#define PUT_SEMAPHORE       if (tx_semaphore_put(&powersaveSemaphore) != TX_SUCCESS) customizeErrorHandler(ERROR_POWERSAVE, ERROR_POWERSAVE_SEMAPHORE);

/*
 * This structure is used to build a linked list of shutdown
 * routines for drivers that will be called to gracefully shutdown
 * the system before powering down.
 */
typedef struct shutdownStruct
{
    struct shutdownStruct *next;
    WORD32 priority;
    NAPDFunctionType func;
} shutdownListType;

static shutdownListType shutdownList = {NULL, 0, NULL};

static TX_SEMAPHORE powersaveSemaphore;
static BOOLEAN initialized = FALSE;

/*ad*
 * The Power Save API can be used to run the processor in
 * <list type="ordered">
 * <item> Sleep mode: where clocks to processor and to other modules, 
 *        where user don't want to set wakeup triggers, are stopped </item>
 * <item> Catnap mode: where system clock is set to run at reduced speed to 
 *        save power </item> 
 * </list>
 *
 * Use the API, naPdSleep, to put the device in sleep mode as follows:
 * <list type="ordered">
 *  <item>Register applications and drivers that need to notified before the system
 *        powers down by calling naPdRegister.</item>
 *  <item>Setup the serial charecter match by calling naPdSetSerialTriggerWord
 *        for NS9360 and NS9750 processors OR by calling naPdSetSerialTriggerByte
 *        for NS9210 and NS9215 if you want to trigger on serial data </item>
 *  <item>Finally call naPdSleep to power down the system.  naPdSleep will
 *        call the functions registered previously with naPdRegister so that the
 *        system can shut down gracefully.  The CPU and peripherals
 *        internal to the chip are powered down after all the registered functions
 *        have been executed, except for those modules
 *        used to monitor trigger events.</item>
 *  <item>In case of NAPD_DEEP_SLEEP mode (only supported in NS9360) system will 
 *        cold reset when it detects the external trigger. But SDRAM will be intact.</item>
 *  <item>In case of NAPD_SLEEP_AND_RESET mode (only supported in NS9360 and NS9750) 
 *        when a trigger is detected, system will soft reset. But SDRAM will be intact.</item>
 *  <item>In case of NAPD_SLEEP_AND_RESUME mode (only supported in NS9210 and NS9215) 
 *        when a trigger is detected, system will resume from where it left off before
 *        entering sleep mode</item>
 * </list>
 *
 * Any of the following triggers can be used to wake up the system:
 * <list type="unordered">
 * <item>BBUS aggregate interrupt (only in NS9750 and NS9360)</item>
 * <item>I2C interrupt</item>
 * <item>Matching character is received by serial ports (all ports can be used in NS9210 and NS9215, only port B is used for NS9360 and NS9750)</item>
 * <item>SPI activity (only in NS9210 and NS9215)</item>
 * <item>RTC interrupt (only in NS9210 and NS9215)</item>
 * <item>Ethernet received packet</item>
 * <item>PCI interrupt 3 input goes low (only in NS9750)</item>
 * <item>External interrupt input goes low (all 4 interrupts can be used in NS9210 and NS9215, only EXT0 is used in NS9360 and NS9750)</item>
 * </list>
 *       
 * Use the API, naPdCatnapStart, to put the device in catnap mode and call 
 * naPdCatnapEnd to comeout of this mode. 
 *
 * The Power Save API cannot be used with the watchdog timer.
 *
 * @external
 * @category power_save_mode
 * @include "powersave.h"
 * @platform ns9360_a,ns9750_a,connectcore9c_a,connectcorewi9c_a,connectcore9p9360_a,connectme9210,connectcore9p9215_a
 * @library bsp
 * @since 6.1     
 * @overview Power Save Mode 
 *
*ad*/

#if (PROCESSOR == ns9210) || (PROCESSOR == ns9215)
/*
 * CPU wakeup ISR 
 */
static int naPdIsr(void *context)
{
    (void) context;

    /* we are back after sleep */
    narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, stopAndResetCpu, SCM_PMR_DISABLE);

    /* toggle wake interrupt clear */
    narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, wakeInterruptClear, SCM_PMR_ENABLE);
    narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, wakeInterruptClear, SCM_PMR_DISABLE);

    return 0;    
}

/* This will disable all enabled interrupts except CPU Wake interrupt */
static NaStatus disableInterrupts(void)
{
    int id = 0;

    for (id = 0; id < NA_MAX_INTERRUPTS; id++)
    {
        currentInterruptStatus[id] = 0;
        
        if (naIsInterruptEnabled(id))
        {
            /* CPU Wake interrupt already enabled? */
            if (id == CPU_WAKE_INTERRUPT) continue;
    
            currentInterruptStatus[id] = 1;
            if (naInterruptDisable(id) != SUCCESS)
                return NA_PD_FAILED_TO_SET_INTR;
        }
        else if (id == CPU_WAKE_INTERRUPT)
        {
            if (naInterruptEnable(id) != SUCCESS)
                return NA_PD_FAILED_TO_SET_INTR;
        }
    }

    return NA_SUCCESS;
}

/* This will enable all previously disabled interrupts */
static NaStatus enableInterrupts(WORD32 trigger)
{
    int id = 0;

    for (id = 0; id < NA_MAX_INTERRUPTS; id++)
    {
        if (currentInterruptStatus[id])
        {
            if (naInterruptEnable(id) != SUCCESS)
                return NA_PD_FAILED_TO_SET_INTR;
            currentInterruptStatus[id] = 0;
        }
        else if (id == CPU_WAKE_INTERRUPT)
        {
            if (naInterruptDisable(id) != SUCCESS)
                return NA_PD_FAILED_TO_SET_INTR;
        }        
    }

    if (trigger & NAPD_SERIAL_PORTA_TRIGGER)
    {
        /* toggle wakeup interrupt */
        narm_write_reg (NA_16750_ARM_WAKEUP, NA_16750_ARM_WAKEUP_getAddress(0), enable, 0);
        narm_write_reg (NA_16750_ARM_WAKEUP, NA_16750_ARM_WAKEUP_getAddress(0), enable, 1);
    }

    if (trigger & NAPD_SERIAL_PORTB_TRIGGER)
    {
        /* toggle wakeup interrupt */
        narm_write_reg (NA_16750_ARM_WAKEUP, NA_16750_ARM_WAKEUP_getAddress(1), enable, 0);
        narm_write_reg (NA_16750_ARM_WAKEUP, NA_16750_ARM_WAKEUP_getAddress(1), enable, 1);
    }

    if (trigger & NAPD_SERIAL_PORTC_TRIGGER)
    {
        /* toggle wakeup interrupt */
        narm_write_reg (NA_16750_ARM_WAKEUP, NA_16750_ARM_WAKEUP_getAddress(2), enable, 0);
        narm_write_reg (NA_16750_ARM_WAKEUP, NA_16750_ARM_WAKEUP_getAddress(2), enable, 1);
    }

    if (trigger & NAPD_SERIAL_PORTD_TRIGGER)
    {
        /* toggle wakeup interrupt */
        narm_write_reg (NA_16750_ARM_WAKEUP, NA_16750_ARM_WAKEUP_getAddress(3), enable, 0);
        narm_write_reg (NA_16750_ARM_WAKEUP, NA_16750_ARM_WAKEUP_getAddress(3), enable, 1);
    }

    return NA_SUCCESS;
}

/* set required wake triggers in power management register */
static void setWakeTriggers(WORD32 triggers)
{
    if (triggers & NAPD_ETHERNET_TRIGGER)
        narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, wakeOnEthernetPacket, SCM_PMR_ENABLE);

    if (triggers & NAPD_SERIAL_PORTA_TRIGGER)
        narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, wakeOnUartACharacterMatch, SCM_PMR_ENABLE);

    if (triggers & NAPD_SERIAL_PORTB_TRIGGER)
        narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, wakeOnUartBCharacterMatch, SCM_PMR_ENABLE);

    if (triggers & NAPD_SERIAL_PORTC_TRIGGER)
        narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, wakeOnUartCCharacterMatch, SCM_PMR_ENABLE);

    if (triggers & NAPD_SERIAL_PORTD_TRIGGER)
        narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, wakeOnUartDCharacterMatch, SCM_PMR_ENABLE);

    if (triggers & NAPD_SPI_TRIGGER)
        narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, wakeOnSpiActivity, SCM_PMR_ENABLE);

    if (triggers & NAPD_I2C_TRIGGER)
        narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, wakeOnI2cActivity, SCM_PMR_ENABLE);

    if (triggers & NAPD_RTC_TRIGGER)
        narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, wakeOnRtcInterrupt, SCM_PMR_ENABLE);

    if (triggers & NAPD_EXT_IRQ0_TRIGGER)
        narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, wakeOnExternalInterrupt0, SCM_PMR_ENABLE);

    if (triggers & NAPD_EXT_IRQ1_TRIGGER)
        narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, wakeOnExternalInterrupt1, SCM_PMR_ENABLE);

    if (triggers & NAPD_EXT_IRQ2_TRIGGER)
        narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, wakeOnExternalInterrupt2, SCM_PMR_ENABLE);

    if (triggers & NAPD_EXT_IRQ3_TRIGGER)
        narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, wakeOnExternalInterrupt3, SCM_PMR_ENABLE);
}

/*ad*
 * This function is only supported in NS9210 and NS9215 processors. If naPdSleep 
 * is used on this processor and serial triggers are selected, then the processor
 * will wake if data is received on a serial port that matches the value set by
 * this function. The received data must exactly match the value passed in 
 * matchData after the bits indicated in mask are ignored.
 *
 * Setting mask to 0xff causes the processor to wake when any data is received.
 * Setting mask to 0 and matchData to 0x65 causes the processor to wake only if 
 * 0x65 is received.
 *
 * This function is conditionally compiled for the NS9210 and NS9215 platforms.
 *
 * @since   7.3
 * @external
 * @category power_save_mode 
 * @platform connectme9210,connectcore9p9215_a
 *
 * @see @link naPdSleep
 * @see @link naPdWakeTriggers
 * @see @link NaStatusValues
 *
 * @param port        indicates which serial port
 * @param mask        masks off unwanted bits
 * @param matchData   value to compare serial input data against
 *
 * @return NA_SUCCESS       Value set
 * @return NA_PD_BAD_PORT   Port not supported
*ad*/
NaStatus naPdSetSerialTriggerByte(unsigned port, unsigned char mask, unsigned char matchData)
{
    NaStatus ret = NA_PD_BAD_PORT;
    unsigned long volatile * const addr = NA_16750_RECV_CHAR_MATCH_CONTROL_0_getAddress(port);

    if (port < MAX_SERIAL_PORTS)
    {
        narm_write_reg (NA_16750_RECV_CHAR_MATCH_CONTROL, addr, enable, 1);
        narm_write_reg (NA_16750_RECV_CHAR_MATCH_CONTROL, addr, mask, mask);
        narm_write_reg (NA_16750_RECV_CHAR_MATCH_CONTROL, addr, data, matchData);
        
        narm_write_reg (NA_16750_ARM_WAKEUP, NA_16750_ARM_WAKEUP_getAddress(port), enable, 1);

        ret = NA_SUCCESS;
    }

    return ret;
}

/* 
 * function returns the clock scale value based on the divisor value 
 */
static int clockScaleValue(unsigned clockDivisor)
{
    int i = 0;

    do
    {
        if (clockDivisor & 0x01)
            return i;
        clockDivisor >>= 1;
        i++;
    } while (i < 5);

    return 0; /* for safer side return 0 */
}

/*ad*
 * This function is only supported in NS9210 and NS9215 processors. This 
 * function divides down the processor clock by the value of 
 * slowClockDivisor, and adjusts the SDRAM refresh rate accordingly. When one
 * of the modules selected in the wakeTriggers paramater OR ethernet module 
 * generates an interrupt, the processor clock is reset to be divided down
 * by the value of fastClockDivisor. The slow clock divisor is restored when
 * the NET+OS master ISR handler completes.
 *
 * The slowClockDivisor must be set to a power of two between 1 and 16. A value 
 * 1 indicates the clock should run at full speed. The fastClockDivisor must be
 * set to a power of two between 1 and 4, because ethernet module has a
 * limitation on minimum speed in which it can run. 
 *
 * This function is conditionally compiled for the NS9210 and NS9215 platforms.
 *
 * @since   7.3
 * @external
 * @category power_save_mode 
 * @platform connectme9210,connectcore9p9215_a
 *
 * @see @link naPdCatnapEnd
 * @see @link naPdWakeTriggers
 * @see @link NaStatusValues
 *
 * @param wakeTriggers        specifies which events cause the fastClockDivisor to be used
 * @param slowClockDivisor    contains the clock divisor when idling (valid values are 1,2,4,8,16)
 * @param fastClockDivisor    contains the clock divisor to use for interesting events (valid values are 1,2,4)
 *
 * @return NA_SUCCESS               catnap mode started
 * @return NA_PD_BAD_DIVISOR        one or both the divisors is invalid
 * @return NA_PD_ALREADY_IN_CATNAP  already in catnap mode, so we cannot change the clock settings 
 *                                  without calling naPdCatnapEnd
*ad*/
NaStatus naPdCatnapStart(unsigned wakeTriggers, unsigned slowClockDivisor, unsigned fastClockDivisor)
{
    unsigned count;
    TX_INTERRUPT_SAVE_AREA;

    if (!IS_CLOCK_DIVISOR_VALID(slowClockDivisor) || !IS_CLOCK_DIVISOR_VALID(fastClockDivisor))
        return NA_PD_BAD_DIVISOR;

    /* ethernet require more than clk/8 speed to operate */
    if ((fastClockDivisor == 8) || (fastClockDivisor == 16))
        return NA_PD_BAD_DIVISOR;

    if (naPdAreWeInCatnapMode)
        return NA_PD_ALREADY_IN_CATNAP;
                 
    /* there may be some race condition, so it requires extra caution to 
       change the program flow below */
    TX_DISABLE;

    naPdCatnapCCR = *NA_SCM_CCR_ADDRESS & NAPD_CATNAP_CCR_MASK; 
    naPdCatnapPMR = *NA_SCM_PMR_ADDRESS;
    
    naPdSlowClockScale = clockScaleValue(slowClockDivisor);
    naPdFastClockScale = clockScaleValue(fastClockDivisor);

    count = narm_read_reg(NA_MPMCDynamicRefresh_REG, NA_MPMCDynamicRefresh_ADDRESS, REFRESH);
    naPdRefreshCount = count; /* to restore when catnap ends */

    /* let hardware control the clock */
    narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, hardwareClockScale, SCM_PMR_ENABLE);
                
    /* Adjust SDRAM refresh rate to compensate for slower clock rate */
    count = ((count/slowClockDivisor) > 1)?(count/slowClockDivisor):1;
    narm_write_reg (NA_MPMCDynamicRefresh_REG, NA_MPMCDynamicRefresh_ADDRESS, REFRESH, count);

    /* reduce the CPU clock equal to AHB clock */
    narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, cpuClockSelect, SCM_CCR_AHB_CLOCK);

    /* make sure ethernet module is enabled always */
    setWakeTriggers(wakeTriggers | NAPD_ETHERNET_TRIGGER);

    naPdFastClockCount    = 1; /* we need this set to 1 */
    naPdAreWeInCatnapMode = 1;

    naPdSetCatnapSlowClock();

    /* set the fast clock rate */
    narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, maxClockScaleControl, clockScaleValue(fastClockDivisor));

    TX_RESTORE;        
    
    return NA_SUCCESS;
}

/*ad*
 * This function is only supported on the NS9210 and NS9215 processors. 
 * Function restores the system clock to pre catnap mode speed and adjusts
 * the SDRAM refresh rate accordingly.
 *
 * This function is conditionally compiled for the NS9210 and NS9215 platforms.
 *
 * @since   7.3
 * @external
 * @category power_save_mode 
 * @platform connectme9210,connectcore9p9215_a
 *
 * @see @link naPdCatnapStart
 * @see @link NaStatusValues
 *
 * @return NA_SUCCESS               catnap mode ended
*ad*/
NaStatus naPdCatnapEnd(void)
{
    TX_INTERRUPT_SAVE_AREA;

    TX_DISABLE;        

    if (naPdAreWeInCatnapMode)
    {                 
        naPdAreWeInCatnapMode = 0;
        naPdSlowClockScale    = SCM_CCR_FULL_SPEED;
        naPdFastClockScale    = SCM_CCR_FULL_SPEED;

        /* put the clocks back to its original values */
        *NA_SCM_PMR_ADDRESS = naPdCatnapPMR;    
        naPdCatnapCCR |= (*NA_SCM_CCR_ADDRESS & ~NAPD_CATNAP_CCR_MASK);
        *NA_SCM_CCR_ADDRESS = naPdCatnapCCR; 
    
        /* Put SDRAM refresh rate to its original value */
        narm_write_reg (NA_MPMCDynamicRefresh_REG, NA_MPMCDynamicRefresh_ADDRESS, REFRESH, naPdRefreshCount);
    }

    TX_RESTORE;
            
    return NA_SUCCESS;
}

#else  /* of (PROCESSOR == ns9210) || (PROCESSOR == ns9215) */

/*ad*
 * This function is only supported in NS9750 and NS9360 processors. The port paramater
 * must always be set to 1. If naPdSleep() is used on these processors and the
 * NAPD_SERIAL_PORTB_TRIGGER is selected, then the processor will wake if data is 
 * received on port B and that data matches the value set by this function. 
 *
 * The serial match works as follows:
 * <list type="ordered">
 *  <item>Only serial port B is monitored for data.</item>
 *  <item>Only the first word of data (1 - 4 bytes) received on serial port B is checked.</item>
 *  <item>When the word is received, the valid bytes in it are compared to the corresponding 
 *        bytes in the ARM wake-up register.  A wake-up is triggered if all the compared bytes
 *        match.</item>
 * </list>
 *
 * @since   7.3
 * @external
 * @category power_save_mode 
 * @platform ns9360_a,ns9750_a,connectcore9c_a,connectcorewi9c_a,connectcore9p9360_a
 * @see @link naPdSleep
 * @see @link naPdWakeTriggers
 * @see @link NaStatusValues
 *
 * @param port        Always set to port 1 (port B)
 * @param matchData   Value to write into the ARM Wake-up Register.  The bytes in
 *                    this word are compared against the first word of data
 *                    received on serial port B after the system powers down.
 * @return NA_SUCCESS       Value set
 * @return NA_PD_BAD_PORT   Port was not set to 1
*ad*/
NaStatus naPdSetSerialTriggerWord(unsigned port, WORD32 matchData)
{
    NaStatus ret = NA_SUCCESS;

    if (port != 1)
        ret = NA_PD_BAD_PORT;
    else
        *NA_BUM_WAKE_ADDRESS = matchData;

    return ret;
}

static void storeDataInReservedArea(void)
{
    volatile naPowerSaveData_t *p_data = (naPowerSaveData_t *)NA_SLEEP_DATA_ADDRESS;

    /* store the current register values */
    p_data->ccr = *NA_SCM_CCR_ADDRESS;
    p_data->rsr = *NA_SCM_RSR_ADDRESS;

    /* store ethernet settings */
    p_data->eth_negotiable = (short)mii_check_negotiable();
}

#endif /* (PROCESSOR == ns9210) || (PROCESSOR == ns9215) */

/*ad*
 * This function is used to initialize the internal variables
 * used by the power save module.  Principly, the 
 * module's semaphore is created.
 *
 * @since   6.1
 * @category power_save_mode 
*ad*/
static void initModule(void)
{
    int ccode = tx_semaphore_create(&powersaveSemaphore, "Powersave Semaphore", 1);
    NA_ASSERT(ccode == TX_SUCCESS);

#if (PROCESSOR == ns9210) || (PROCESSOR == ns9215)
    ccode = naIsrInstall(CPU_WAKE_INTERRUPT, naPdIsr, NULL);
    NA_ASSERT(ccode == SUCCESS);
#endif

    initialized = TRUE;
}

/*
 * Disable clocks to all the modules which are not used for wakeup trigger
 */
static void disableClockToModules(WORD32 triggers)
{
    /*
     * Now make sure the H/W modules in the ASIC are shutdown by stopping their
     * clocks.  This step should be redundant since the shutdown routines we 
     * just called should have already done this.  Make sure that we don't turn 
     * off the clock for a module if we need to use it to trigger our reset.  
     */
    narm_write_reg(NA_EFE_GCR01_REG, NA_EFE_GCR01_ADDRESS, erxdma, EFE_GCR_DISABLE);
    narm_write_reg(NA_EFE_GCR01_REG, NA_EFE_GCR01_ADDRESS, etxdma, EFE_GCR_DISABLE);

#if !(PROCESSOR == ns9210) && !(PROCESSOR == ns9215)
    /* Disable the following bits if NO wakeup on BBUS aggregate interrupt */
    if ((triggers & NAPD_BBUS_INT_TRIGGER) == 0)
    {
        /* All these modules can be used to wakeup or reset the unit via BBUS */        
        if (((triggers & NAPD_SERIAL_TRIGGER) == 0) && ((triggers & NAPD_I2C_TRIGGER) == 0))
        {
            narm_write_reg(NA_SCM_RSR_REG, NA_SCM_RSR_ADDRESS, bbusTop, SCM_RSR_RESET_MODULE);
            narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, bbus, SCM_CCR_DISABLE_CLOCK);
            narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, bbusDma, SCM_CCR_DISABLE_CLOCK);
        }
    
        if ((triggers & NAPD_SERIAL_TRIGGER) == 0)
        {
            narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, serialAB, SCM_CCR_DISABLE_CLOCK);
        }
    
        if ((triggers & NAPD_I2C_TRIGGER) == 0)
        {
            narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, i2c, SCM_CCR_DISABLE_CLOCK);
        }
    
        narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, serialCD, SCM_CCR_DISABLE_CLOCK);
        narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, usbDevice, SCM_CCR_DISABLE_CLOCK);
        narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, usbHost, SCM_CCR_DISABLE_CLOCK);
        narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, 1284, SCM_CCR_DISABLE_CLOCK);
    }
    
    if ((triggers & NAPD_ETHERNET_TRIGGER) == 0)
    {
        narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, ethernetMac, SCM_CCR_DISABLE_CLOCK);
        narm_write_reg(NA_SCM_RSR_REG, NA_SCM_RSR_ADDRESS, ethernetMac, SCM_RSR_RESET_MODULE);
    }

    #if (PROCESSOR == ns9750)
    if ((triggers & NAPD_PCI_TRIGGER) == 0)
    {
        narm_write_reg(NA_SCM_RSR_REG, NA_SCM_RSR_ADDRESS, pci, SCM_RSR_RESET_MODULE);
        narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, pci, SCM_CCR_DISABLE_CLOCK);
        narm_write_reg(NA_SCM_RSR_REG, NA_SCM_RSR_ADDRESS, pciArbiter, SCM_RSR_RESET_MODULE);
        narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, pciArbiter, SCM_CCR_DISABLE_CLOCK);
    }

    narm_write_reg(NA_SCM_RSR_REG, NA_SCM_RSR_ADDRESS, jbig, SCM_RSR_RESET_MODULE);
    narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, jbig, SCM_CCR_DISABLE_CLOCK);
    #endif

    narm_write_reg(NA_SCM_RSR_REG, NA_SCM_RSR_ADDRESS, lcdController, SCM_RSR_RESET_MODULE);
    narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, lcdController, SCM_CCR_DISABLE_CLOCK);

#else /* for !(PROCESSOR == ns9210) && !(PROCESSOR == ns9215) */

    if ((triggers & NAPD_ETHERNET_TRIGGER) == 0)
        narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, ethernetMac, SCM_CCR_DISABLE_CLOCK);

    if ((triggers & NAPD_SERIAL_PORTA_TRIGGER) == 0)
        narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, uarta, SCM_CCR_DISABLE_CLOCK);

    if ((triggers & NAPD_SERIAL_PORTB_TRIGGER) == 0)
        narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, uartb, SCM_CCR_DISABLE_CLOCK);

    if ((triggers & NAPD_SERIAL_PORTC_TRIGGER) == 0)
        narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, uartc, SCM_CCR_DISABLE_CLOCK);

    if ((triggers & NAPD_SERIAL_PORTD_TRIGGER) == 0)
        narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, uartd, SCM_CCR_DISABLE_CLOCK);

    if ((triggers & NAPD_SPI_TRIGGER) == 0)
        narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, spi, SCM_CCR_DISABLE_CLOCK);

    if ((triggers & NAPD_I2C_TRIGGER) == 0)
        narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, i2c, SCM_CCR_DISABLE_CLOCK);

    if ((triggers & NAPD_RTC_TRIGGER) == 0)
        narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, rtc, SCM_CCR_DISABLE_CLOCK);

    /* disable rest of the clocks */
    narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, externalDma, SCM_CCR_DISABLE_CLOCK);
    narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, ioHub, SCM_CCR_DISABLE_CLOCK);
    narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, aes, SCM_CCR_DISABLE_CLOCK);
    narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, adc, SCM_CCR_DISABLE_CLOCK);
    narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, reserved1, SCM_CCR_DISABLE_CLOCK);
    narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, reserved2, SCM_CCR_DISABLE_CLOCK);
    narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, reserved3, SCM_CCR_DISABLE_CLOCK);
    narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, iop0, SCM_CCR_DISABLE_CLOCK);
    narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, iop1, SCM_CCR_DISABLE_CLOCK);

    /* disable all timers */
    *NA_SCM_TMCR_ADDRESS = 0;
#endif    
}

/*ad*
 * Some software modules and device drivers may need to shutdown gracefully
 * before the chip is powered down.  Use this function to register shutdown
 * routines for these modules.  The Power Save API calls these routines
 * just before powering down the chip.  The shutdown routines are called
 * in the order indicated by their priorities.  Functions with numerically 
 * larger priorities are called before functions with smaller ones.
 *
 * @since   6.1
 * @external
 * @category power_save_mode 
 *
 * @see @link naPdSleep
 * @see @link NAPD_OS_PRIORITY
 * @see @link NaStatusValues
 *
 * @param func      Pointer to function to call before powering down.
 * @param priority  Shutdown priority for function.  Functions with higher priority are
 *                  called before functions with lower priority.  All NET+OS modules 
 *                  are registered with priority level NAPD_OS_PRIORITY or lower.
 *                  Application functions should be registered with a higher priority
 *                  level than NAPD_OS_PRIORITY so that OS services will be available 
 *                  to them when they are called.
 *
 * @return NA_SUCCESS               Function has been registered.
 * @return NA_PD_OUT_OF_RESOURCES   Unable to register the function due to a resource issue.
 * @return NA_PD_NULL_POINTER       The function pointer was NULL.
*ad*/
NaStatus naPdRegister(NAPDFunctionType func, WORD32 priority)
{
    NaStatus result = NA_PD_NULL_POINTER;
    shutdownListType *newItem;
    
    NA_ASSERT(func != NULL);
    
    if (!initialized)
    {
        initModule();
    }
    
    GET_SEMAPHORE;
    
    if (func != NULL)
    {
        /*
         * Allocate a new item for the list of shutdown functions.
         */
        newItem = malloc(sizeof(shutdownListType));
        if (newItem == NULL)
        {
            result = NA_PD_OUT_OF_RESOURCES;
        }
        else
        {
            shutdownListType *item = &shutdownList;
            
            /*
             * Rifle through the list until we find a shutdown function
             * with a lower priority than the new one, or hit the end 
             * of the list.  Then add the new item into the list.
             */
            while (item->next)
            {
                if (item->next->priority < priority)
                {
                    break;
                }
                item = item->next;
            }
            newItem->func = func;
            newItem->priority = priority;
            newItem->next = item->next;
            item->next = newItem;
            result = NA_SUCCESS;
        }
    }
    
    PUT_SEMAPHORE;
    
    return result;
}

/*ad*
 * This function is used to stop the processor clock during idle periods. The
 * mode parameter determines how the processor will wakeup.
 *
 * <list type="ordered">
 * <item>NAPD_SLEEP_AND_RESET: The processor clock will be stopped, as well 
 *       as the clocks to all modules which have not been selected as wake 
 *       triggers. SDRAM will be placed in self-refresh mode. The processor 
 *       is reset when any of the wake triggers occurs. The NET+OS 
 *       startup code detects that it is waking up from a sleep and 
 *       performs the restart faster than if a power on reset had 
 *       occurred. It does this by running the application image 
 *       already in RAM rather than refreshing it by decompressing
 *       it from flash. It also suppresses the dialog and other 
 *       print statements in the start up code. The application 
 *       software also has to restart. 
 *       
 *       If the watchdog is enabled in reset mode then this API 
 *       will change it to interrupt mode before entering sleep mode. 
 *       If you have your own watchdog configuration function 
 *       then make sure your initial watchdog time is big enough so that 
 *       the value won't be zero when you configure the watchdog and 
 *       clear the watchdog interrupt twice before configuring the 
 *       watchdog. 
 *       
 *       This mode is only supported on NS9360 and NS9750 based platforms.</item>
 * <item>NAPD_DEEP_SLEEP: The processor clock will be stopped, as well as the
 *       clocks to all modules. SDRAM will be placed in self-refresh mode. 
 *       This sleep mode ends when external hardware resets the 
 *       processor. The restart operates the same as for the 
 *       NAPD_SLEEP_AND_RESET mode. The different in this mode 
 *       and NAPD_SLEEP_AND_RESET is that NAPD_SLEEP_AND_RESET 
 *       leaves some modules running inside of the ASIC to wait 
 *       for a wake event. This mode reduces power requirements 
 *       further by shutting down the entire processor and then 
 *       relies on external hardware to do the reset. 
 *       This mode requires external hardware and is only available 
 *       on NS9360 based platforms. </item>
 * <item>NAPD_SLEEP_AND_RESUME:  The processor clock will be stopped, as well
 *       as the clocks to all modules that are not selected as wake up 
 *       triggers. SDRAM is placed in self refresh mode. When a wake up 
 *       trigger occurs, SDRAM is restored and the processor resumes where it
 *       left off, after executing the CPU wake interrupt. This mode 
 *       is only supported on NS9215 based platforms.</item>
 * </list>
 *
 * @since   7.3
 * @external
 * @category power_save_mode 
 *
 * @see @link naPdRegister
 * @see @link naPdWakeTriggers
 * @see @link NaPdSleepMode_t
 * @see @link NaStatusValues
 *
 * @param mode          indicates which sleep mode will be used
 * @param wakeTriggers  determines what events can wakeup the processor
 *
 * @return NA_SUCCESS               NAPD_SLEEP_AND_RESUME mode was selected and came out of
 *                                  sleep mode successfully
 * @return "No return"              function never returns if NAPD_SLEEP_AND_RESET or
 *                                  NAPD_DEEP_SLEEP mode sleep succeeds
 * @return NA_PD_OUT_OF_RESOURCES   mode was set to NAPD_DEEP_SLEEP but the external hardware
 *                                  needed to support this mode was not detected 
 * @return NA_PD_INVALID_TRIGGER    unsupported wake trigger selected
 * @return NA_PD_NO_TRIGGER         mode was set to NAPD_SLEEP_AND_RESET or NAPD_SLEEP_AND_RESUME
 *                                  but wakeTrigger was set to zero
 * @return NA_PD_FAILED_TO_SET_INTR failed to enable or disable one or more interrupts
 * @return NA_PD_BAD_MODE           invalid mode was selected
 * @return NA_PD_WATCHDOG_ERROR     NAPD_SLEEP_AND_RESET mode was set, watchdog timer is enabled and 
 *                                  the function failed to switch the watchdog to interrupt mode 
 *
*ad*/
NaStatus naPdSleep(NaPdSleepMode_t mode, WORD32 wakeTriggers)
{
    int result = NA_SUCCESS;
    shutdownListType *module;
    
    /* check if mode is valid */
#if !(PROCESSOR == ns9210) && !(PROCESSOR == ns9215)   
    if ((mode != NAPD_DEEP_SLEEP) && (mode != NAPD_SLEEP_AND_RESET))
        return NA_PD_BAD_MODE;
#else
    if (mode != NAPD_SLEEP_AND_RESUME)
        return NA_PD_BAD_MODE;
#endif

    /* check if triggers are valid */
    if ((mode != NAPD_DEEP_SLEEP) && ((wakeTriggers & VALID_TRIGGERS) == 0))
        return NA_PD_INVALID_TRIGGER;

    if ((mode == NAPD_SLEEP_AND_RESET) && ((wakeTriggers & ~VALID_TRIGGERS) != 0))
        return NA_PD_INVALID_TRIGGER;

    if (!initialized)
        initModule();
        
    GET_SEMAPHORE;
    
    module = shutdownList.next;
    /*
     * Each device driver registered a shutdown function with this module during the
     * system startup.  Call these shutdown functions now so that the drivers can
     * shutdown gracefully.  Each item in the list contains a priority field.  Items
     * with larger priority values are shutdown before items with lower values.  The
     * list is sorted in priority order with the module with the highest priority 
     * first in the list.
     */
    while (module)
    {
        module->func(wakeTriggers);
        module = module->next;
    }

    PUT_SEMAPHORE;
    
    switch (mode)
    {
    #if !(PROCESSOR == ns9210) && !(PROCESSOR == ns9215)
        case NAPD_SLEEP_AND_RESET:
        {
            /* 
             * if watchdog is enabled in reset mode we need to put this in interrupt mode 
             * before entering sleep mode 
             */            
            if (naWatchdogSetInterruptMode() != WATCHDOG_SUCCESS)
            {
                result = NA_PD_WATCHDOG_ERROR;
                goto doneSleep;
            }

            /* save the current RSR and CCR values */
            storeDataInReservedArea();
            
            disableClockToModules(wakeTriggers);

            *NA_SCM_AAGCR_ADDRESS &= 1;             /* clear restart and debugger bits*/
        
            /* 
             * Put ourselves to sleep.
             * 
             *  1.  Clear the CSE bit in the RSR register in case it was left set 
             *      from a previous sleep sequence.
             *  2.  Set the wakeup triggers.  We have carefully chosen the bit values
             *      for the triggers to match the bits used in the H/W register, so
             *      we can just or in the value of wakeTriggers to the RSR.
             *  3.  Call the NAPDCpuSleep() function to start sleep mode.  This 
             *      function must be written in assembler because it has to reset
             *      the memory controller before going to sleep.
             */
            narm_write_reg(NA_SCM_RSR_REG, NA_SCM_RSR_ADDRESS, cpuSleep, 0);
            *NA_SCM_RSR_ADDRESS |= (wakeTriggers & VALID_TRIGGERS);
        
            NAPDCpuSleep();
        
            /* not intended to be here */
            result = NA_PD_OUT_OF_RESOURCES;
            goto doneSleep;
        }


        case NAPD_DEEP_SLEEP:
        {
            unsigned long addr, gpio_pin;

            /* save the current RSR and CCR values */
            storeDataInReservedArea();
            
            disableClockToModules(wakeTriggers);

            if (naGpioGetAddress(GPIO_PSAVE_CTRL, &addr, &gpio_pin))
                NAPDCpuDeepSleep(addr, 0x1 << gpio_pin);

            /* not intended to be here */
            result = NA_PD_OUT_OF_RESOURCES;
            break;            
        }
    #else  /* (PROCESSOR == ns9210) || (PROCESSOR == ns9215) */
        case NAPD_SLEEP_AND_RESUME:
        {
            unsigned long naPdCCR  = *NA_SCM_CCR_ADDRESS;
            unsigned long naPdPMR  = *NA_SCM_PMR_ADDRESS;
            unsigned long naPdTMCR = *NA_SCM_TMCR_ADDRESS;

            if ((wakeTriggers & NAPD_ETHERNET_TRIGGER) == 0)
                customizeMiiPhySleep();
            
            disableClockToModules(wakeTriggers);

            /* disable all the interrupts except CPU Wake interrupt */
            result = disableInterrupts();
            if (result != NA_SUCCESS)
                return result;

            setWakeTriggers(wakeTriggers);

            /* set SDRAM self refresh bit */
            narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, sdramSelfRefreshControl, SCM_PMR_ENABLE);
            
            /* let hardware control the clock */
            narm_write_reg(NA_SCM_PMR_REG, NA_SCM_PMR_ADDRESS, hardwareClockScale, SCM_PMR_ENABLE);
                        
            /* reduce the clock to minimum possible */
            narm_write_reg(NA_SCM_CCR_REG, NA_SCM_CCR_ADDRESS, clockScaleControl, SCM_CCR_DIV_BY_16);

            naPdCpuSleepAndResume(); /* put the processor in sleep mode */

            /* restore the register values */
            *NA_SCM_CCR_ADDRESS  = naPdCCR;
            *NA_SCM_PMR_ADDRESS  = naPdPMR;
            *NA_SCM_TMCR_ADDRESS = naPdTMCR;
        
            if ((wakeTriggers & NAPD_ETHERNET_TRIGGER) == 0)
                customizeMiiPhyWakeup();
            
            narm_write_reg(NA_EFE_GCR01_REG, NA_EFE_GCR01_ADDRESS, erxdma, EFE_GCR_ENABLE);
            narm_write_reg(NA_EFE_GCR01_REG, NA_EFE_GCR01_ADDRESS, etxdma, EFE_GCR_ENABLE);

            /* enable all other interrupts */
            if ((result = enableInterrupts(wakeTriggers)) != NA_SUCCESS)
                goto doneSleep;
            
            break;
        }
    #endif
        
        default:
            result = NA_PD_BAD_MODE;
            break;
    }
    
    doneSleep:
    return result;
}








