/*
 * Copyright (c) 2007-2008 Atheros Communications Inc.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/**
  *  The power saving manager is to save the power as much as possible.
  *  Generally speaking, it controls:
  *
  *         - when to sleep
  *         -
  *
  */
#include "cprecomp.h"

void zfPowerSavingMgrInit(zdev_t* dev)
{
    zmw_get_wlan_dev(dev);

    wd->sta.powerSaveMode = ZM_STA_PS_NONE;
    wd->sta.psMgr.state = ZM_PS_MSG_STATE_ACTIVE;
    wd->sta.psMgr.isSleepAllowed = 0;
    wd->sta.psMgr.maxSleepPeriods = 1;
    wd->sta.psMgr.ticks = 0;
    wd->sta.psMgr.sleepAllowedtick = 0;
}

static u16_t zfPowerSavingMgrHandlePsNone(zdev_t* dev, u8_t *isWakeUpRequired)
{
    u16_t ret = 0;
    zmw_get_wlan_dev(dev);

    switch(wd->sta.psMgr.state)
    {
        case ZM_PS_MSG_STATE_ACTIVE:
            *isWakeUpRequired = 0;
            break;

        case ZM_PS_MSG_STATE_T1:
        case ZM_PS_MSG_STATE_T2:
        case ZM_PS_MSG_STATE_SLEEP:
        default:
            *isWakeUpRequired = 1;
zm_debug_msg0("zfPowerSavingMgrHandlePsNone: Wake up now\n");
            if ( zfStaIsConnected(dev) )
            {
                zm_debug_msg0("zfPowerSavingMgrOnHandleT1 send Null data\n");
                //zfSendNullData(dev, 0);
                ret = 1;
            }

            wd->sta.psMgr.state = ZM_PS_MSG_STATE_ACTIVE;
            break;
    }
    return ret;
}

static void zfPowerSavingMgrHandlePs(zdev_t* dev)
{
    zmw_get_wlan_dev(dev);

    switch(wd->sta.psMgr.state)
    {
        case ZM_PS_MSG_STATE_ACTIVE:
            //zm_debug_msg0("zfPowerSavingMgrHandlePs: Prepare to sleep...\n");
            //wd->sta.psMgr.state = ZM_PS_MSG_STATE_T1;
            break;

        case ZM_PS_MSG_STATE_T1:
        case ZM_PS_MSG_STATE_T2:
        case ZM_PS_MSG_STATE_SLEEP:
        default:
            break;
    }
}

void zfPowerSavingMgrSetMode(zdev_t* dev, u8_t mode)
{
    u16_t sendNull = 0;
    u8_t isWakeUpRequired = 0;

    zmw_get_wlan_dev(dev);
    zmw_declare_for_critical_section();

    zm_debug_msg1("mode = ", mode);

    if (mode > ZM_STA_PS_LIGHT)
    {
        zm_debug_msg0("return - wrong power save mode");
        return;
    }

    zmw_enter_critical_section(dev);

    #if 1
    switch(mode)
    {
        case ZM_STA_PS_NONE:
            sendNull = zfPowerSavingMgrHandlePsNone(dev, &isWakeUpRequired);
            break;

        case ZM_STA_PS_FAST:
        case ZM_STA_PS_LIGHT:
            wd->sta.psMgr.maxSleepPeriods = 1;
            zfPowerSavingMgrHandlePs(dev);
            break;

        case ZM_STA_PS_MAX:
            wd->sta.psMgr.maxSleepPeriods = ZM_PS_MAX_SLEEP_PERIODS;
            zfPowerSavingMgrHandlePs(dev);
            break;
    }
    #else
    switch(wd->sta.psMgr.state)
    {
        case ZM_PS_MSG_STATE_ACTIVE:
            if ( mode != ZM_STA_PS_NONE )
            {
zm_debug_msg0("zfPowerSavingMgrSetMode: switch from ZM_PS_MSG_STATE_ACTIVE to ZM_PS_MSG_STATE_T1\n");
                // Stall the TX & start to wait the pending TX to be completed
                wd->sta.psMgr.state = ZM_PS_MSG_STATE_T1;
            }
            break;

        case ZM_PS_MSG_STATE_SLEEP:
            break;
    }
    #endif

    wd->sta.powerSaveMode = mode;
    zmw_leave_critical_section(dev);

    if ( isWakeUpRequired )
    {
        zfHpPowerSaveSetState(dev, 0);
        wd->sta.psMgr.tempWakeUp = 0;
    }

    if ( zfStaIsConnected(dev)
         && (wd->wlanMode == ZM_MODE_INFRASTRUCTURE) )
    {
        switch(mode)
        {
            case ZM_STA_PS_NONE:
                zfHpPowerSaveSetMode(dev, 0, 0, wd->beaconInterval);
                break;

            case ZM_STA_PS_FAST:
            case ZM_STA_PS_MAX:
            case ZM_STA_PS_LIGHT:
                zfHpPowerSaveSetMode(dev, 0, 1, wd->beaconInterval);
                break;

            default:
                zfHpPowerSaveSetMode(dev, 0, 0, wd->beaconInterval);
                break;
        }
    }

    if (sendNull == 1)
    {
        zfSendNullData(dev, 0);
    }

    return;
}

static void zfPowerSavingMgrNotifyPSToAP(zdev_t *dev)
{
    zmw_get_wlan_dev(dev);
    zmw_declare_for_critical_section();

    if ( (wd->sta.psMgr.tempWakeUp != 1)&&
         (wd->sta.psMgr.lastTxUnicastFrm != wd->commTally.txUnicastFrm ||
          wd->sta.psMgr.lastTxBroadcastFrm != wd->commTally.txBroadcastFrm ||
          wd->sta.psMgr.lastTxMulticastFrm != wd->commTally.txMulticastFrm) )
    {
        zmw_enter_critical_section(dev);
        wd->sta.psMgr.lastTxUnicastFrm = wd->commTally.txUnicastFrm;
        wd->sta.psMgr.lastTxBroadcastFrm = wd->commTally.txBroadcastFrm;
        wd->sta.psMgr.lastTxMulticastFrm = wd->commTally.txMulticastFrm;
        zmw_leave_critical_section(dev);

        zfSendNullData(dev, 1);
    }
}

static void zfPowerSavingMgrOnHandleT1(zdev_t* dev)
{
    zmw_get_wlan_dev(dev);
    zmw_declare_for_critical_section();

    // If the tx Q is not empty...return
    if ( zfIsVtxqEmpty(dev) == FALSE )
    {
        return;
    }

zm_debug_msg0("VtxQ is empty now...Check if HAL TXQ is empty\n");

    // The the HAL TX Q is not empty...return
    if ( zfHpGetFreeTxdCount(dev) != zfHpGetMaxTxdCount(dev) )
    {
        return;
    }

zm_debug_msg0("HAL TXQ is empty now...Could go to sleep...\n");

    zmw_enter_critical_section(dev);

    if (wd->sta.powerSaveMode == ZM_STA_PS_LIGHT)
    {
        if (wd->sta.ReceivedPktRatePerSecond > 200)
        {
            zmw_leave_critical_section(dev);
            return;
        }

        if ( zfStaIsConnected(dev)
             && (wd->wlanMode == ZM_MODE_INFRASTRUCTURE) )
        {
            if (wd->sta.psMgr.sleepAllowedtick) {
                wd->sta.psMgr.sleepAllowedtick--;
                zmw_leave_critical_section(dev);
                return;
            }
        }
    }

    wd->sta.psMgr.state = ZM_PS_MSG_STATE_T2;

    zmw_leave_critical_section(dev);

    // Send the Null pkt to AP to notify that I'm going to sleep
    if ( zfStaIsConnected(dev) )
    {
zm_debug_msg0("zfPowerSavingMgrOnHandleT1 send Null data\n");
        zfPowerSavingMgrNotifyPSToAP(dev);
    }

    // Stall the TX now
    // zfTxEngineStop(dev);
}

static void zfPowerSavingMgrOnHandleT2(zdev_t* dev)
{
    zmw_get_wlan_dev(dev);
    zmw_declare_for_critical_section();

    // Wait until the Null pkt is transmitted
    if ( zfHpGetFreeTxdCount(dev) != zfHpGetMaxTxdCount(dev) )
    {
        return;
    }

    zmw_enter_critical_section(dev);
    wd->sta.psMgr.state = ZM_PS_MSG_STATE_SLEEP;
    wd->sta.psMgr.lastTxUnicastFrm = wd->commTally.txUnicastFrm;
    wd->sta.psMgr.lastTxBroadcastFrm = wd->commTally.txBroadcastFrm;
    wd->sta.psMgr.lastTxMulticastFrm = wd->commTally.txMulticastFrm;
    zmw_leave_critical_section(dev);

    // Let CHIP sleep now
zm_debug_msg0("zfPowerSavingMgrOnHandleT2 zzzz....\n");
    zfHpPowerSaveSetState(dev, 1);
    wd->sta.psMgr.tempWakeUp = 0;
}

u8_t zfPowerSavingMgrIsSleeping(zdev_t *dev)
{
    u8_t isSleeping = FALSE;
    zmw_get_wlan_dev(dev);
    zmw_declare_for_critical_section();

    zmw_enter_critical_section(dev);
    if ( wd->sta.psMgr.state == ZM_PS_MSG_STATE_SLEEP ||
         wd->sta.psMgr.state == ZM_PS_MSG_STATE_T2)
    {
        isSleeping = TRUE;
    }
    zmw_leave_critical_section(dev);
    return isSleeping;
}

static u8_t zfPowerSavingMgrIsIdle(zdev_t *dev)
{
    u8_t isIdle = 0;

    zmw_get_wlan_dev(dev);
    zmw_declare_for_critical_section();

    zmw_enter_critical_section(dev);

    if ( zfStaIsConnected(dev) && wd->sta.psMgr.isSleepAllowed == 0 )
    {
        goto done;
    }

    if ( wd->sta.bChannelScan )
    {
        goto done;
    }

    if ( zfStaIsConnecting(dev) )
    {
        goto done;
    }

    if (wd->sta.powerSaveMode == ZM_STA_PS_LIGHT)
    {
        if (wd->sta.ReceivedPktRatePerSecond > 200)
        {
            goto done;
        }

        if ( zfStaIsConnected(dev)
             && (wd->wlanMode == ZM_MODE_INFRASTRUCTURE) )
        {
            if (wd->sta.psMgr.sleepAllowedtick) {
                wd->sta.psMgr.sleepAllowedtick--;
                goto done;
            }
        }
    }

    isIdle = 1;

done:
    zmw_leave_critical_section(dev);

    if ( zfIsVtxqEmpty(dev) == FALSE )
    {
        isIdle = 0;
    }

    return isIdle;
}

static void zfPowerSavingMgrSleepIfIdle(zdev_t *dev)
{
    u8_t isIdle;

    zmw_get_wlan_dev(dev);
    zmw_declare_for_critical_section();

    isIdle = zfPowerSavingMgrIsIdle(dev);

    if ( isIdle == 0 )
    {
        return;
    }

    zmw_enter_critical_section(dev);

    switch(wd->sta.powerSaveMode)
    {
        case ZM_STA_PS_NONE:
            break;

        case ZM_STA_PS_MAX:
        case ZM_STA_PS_FAST:
        case ZM_STA_PS_LIGHT:
            zm_debug_msg0("zfPowerSavingMgrSleepIfIdle: IDLE so slep now...\n");
            wd->sta.psMgr.state = ZM_PS_MSG_STATE_T1;
            break;
    }

    zmw_leave_critical_section(dev);
}

static void zfPowerSavingMgrDisconnectMain(zdev_t* dev)
{
    zmw_get_wlan_dev(dev);

#ifdef ZM_ENABLE_DISCONNECT_PS
    switch(wd->sta.psMgr.state)
    {
        case ZM_PS_MSG_STATE_ACTIVE:
            zfPowerSavingMgrSleepIfIdle(dev);
            break;

        case ZM_PS_MSG_STATE_SLEEP:
            break;

        case ZM_PS_MSG_STATE_T1:
            zfPowerSavingMgrOnHandleT1(dev);
            break;

        case ZM_PS_MSG_STATE_T2:
            zfPowerSavingMgrOnHandleT2(dev);
            break;
    }
#else
    zfPowerSavingMgrWakeup(dev);
#endif
}

static void zfPowerSavingMgrInfraMain(zdev_t* dev)
{
    zmw_get_wlan_dev(dev);

    switch(wd->sta.psMgr.state)
    {
        case ZM_PS_MSG_STATE_ACTIVE:
            zfPowerSavingMgrSleepIfIdle(dev);
            break;

        case ZM_PS_MSG_STATE_SLEEP:
            break;

        case ZM_PS_MSG_STATE_T1:
            zfPowerSavingMgrOnHandleT1(dev);
            break;

        case ZM_PS_MSG_STATE_T2:
            zfPowerSavingMgrOnHandleT2(dev);
            break;
    }
}

void zfPowerSavingMgrAtimWinExpired(zdev_t* dev)
{
    zmw_get_wlan_dev(dev);

//printk("zfPowerSavingMgrAtimWinExpired #1\n");
    if ( wd->sta.powerSaveMode == ZM_STA_PS_NONE )
    {
        return;
    }

//printk("zfPowerSavingMgrAtimWinExpired #2\n");
    // if we received any ATIM window from the others to indicate we have buffered data
    // at the other station, we can't go to sleep
    if ( wd->sta.recvAtim )
    {
        wd->sta.recvAtim = 0;
        zm_debug_msg0("Can't sleep due to receving ATIM window!");
        return;
    }

    // if we are the one to tx beacon during last beacon interval. we can't go to sleep
    // since we need to be alive to respond the probe request!
    if ( wd->sta.txBeaconInd )
    {
        zm_debug_msg0("Can't sleep due to just transmit a beacon!");
        return;
    }

    // If we buffer any data for the other stations. we could not go to sleep
    if ( wd->sta.ibssPrevPSDataCount != 0 )
    {
        zm_debug_msg0("Can't sleep due to buffering data for the others!");
        return;
    }

    // before sleeping, we still need to notify the others by transmitting null
    // pkt with power mgmt bit turned on.
    zfPowerSavingMgrOnHandleT1(dev);
}

static void zfPowerSavingMgrIBSSMain(zdev_t* dev)
{
    // wait for the end of
    // if need to wait to know if we are the one to transmit the beacon
    // during the beacon interval. If it's me, we can't go to sleep.

    zmw_get_wlan_dev(dev);

    switch(wd->sta.psMgr.state)
    {
        case ZM_PS_MSG_STATE_ACTIVE:
        case ZM_PS_MSG_STATE_SLEEP:
        case ZM_PS_MSG_STATE_T1:
            break;

        case ZM_PS_MSG_STATE_T2:
            zfPowerSavingMgrOnHandleT2(dev);
            break;
    }

    return;
}

#if 1
void zfPowerSavingMgrMain(zdev_t* dev)
{
    zmw_get_wlan_dev(dev);

    switch (wd->sta.adapterState)
    {
    case ZM_STA_STATE_DISCONNECT:
        zfPowerSavingMgrDisconnectMain(dev);
        break;
    case ZM_STA_STATE_CONNECTED:
        {
            if (wd->wlanMode == ZM_MODE_INFRASTRUCTURE) {
                zfPowerSavingMgrInfraMain(dev);
            } else if (wd->wlanMode == ZM_MODE_IBSS) {
                zfPowerSavingMgrIBSSMain(dev);
            }
        }
        break;
    case ZM_STA_STATE_CONNECTING:
    default:
        break;
    }
}
#else
void zfPowerSavingMgrMain(zdev_t* dev)
{
    zmw_get_wlan_dev(dev);

    if ( wd->wlanMode != ZM_MODE_INFRASTRUCTURE )
    {
        return;
    }

    switch(wd->sta.psMgr.state)
    {
        case ZM_PS_MSG_STATE_ACTIVE:
            goto check_sleep;
            break;

        case ZM_PS_MSG_STATE_SLEEP:
            goto sleeping;
            break;

        case ZM_PS_MSG_STATE_T1:
            zfPowerSavingMgrOnHandleT1(dev);
            break;

        case ZM_PS_MSG_STATE_T2:
            zfPowerSavingMgrOnHandleT2(dev);
            break;
    }

    return;

sleeping:
    return;

check_sleep:
    zfPowerSavingMgrSleepIfIdle(dev);
    return;
}
#endif

#ifdef ZM_ENABLE_POWER_SAVE
void zfPowerSavingMgrWakeup(zdev_t* dev)
{
    zmw_get_wlan_dev(dev);
    zmw_declare_for_critical_section();

//zm_debug_msg0("zfPowerSavingMgrWakeup");

    //if ( wd->sta.psMgr.state != ZM_PS_MSG_STATE_ACTIVE && ( zfPowerSavingMgrIsIdle(dev) == 0 ))
    if ( wd->sta.psMgr.state != ZM_PS_MSG_STATE_ACTIVE )
    {
        zmw_enter_critical_section(dev);

        wd->sta.psMgr.isSleepAllowed = 0;
        wd->sta.psMgr.state = ZM_PS_MSG_STATE_ACTIVE;

        if ( wd->sta.powerSaveMode > ZM_STA_PS_NONE )
            wd->sta.psMgr.tempWakeUp = 1;

        zmw_leave_critical_section(dev);

        // Wake up the CHIP now!!
        zfHpPowerSaveSetState(dev, 0);
    }
}
#else
void zfPowerSavingMgrWakeup(zdev_t* dev)
{
}
#endif

void zfPowerSavingMgrProcessBeacon(zdev_t* dev, zbuf_t* buf)
{
    u8_t   length, bitmap;
    u16_t  offset, n1, n2, q, r;
    zbuf_t* psBuf;

    zmw_get_wlan_dev(dev);
    zmw_declare_for_critical_section();

    if ( wd->sta.powerSaveMode == ZM_STA_PS_NONE  )
    //if ( wd->sta.psMgr.state != ZM_PS_MSG_STATE_SLEEP )
    {
        return;
    }

    wd->sta.psMgr.isSleepAllowed = 1;

    if ( (offset=zfFindElement(dev, buf, ZM_WLAN_EID_TIM)) != 0xffff )
    {
        length = zmw_rx_buf_readb(dev, buf, offset+1);

        if ( length > 3 )
        {
            n1 = zmw_rx_buf_readb(dev, buf, offset+4) & (~ZM_BIT_0);
            n2 = length + n1 - 4;
            q = wd->sta.aid >> 3;
            r = wd->sta.aid & 7;

            if ((q >= n1) && (q <= n2))
            {
                bitmap = zmw_rx_buf_readb(dev, buf, offset+5+q-n1);

                if ( (bitmap >> r) &  ZM_BIT_0 )
                {
                    //if ( wd->sta.powerSaveMode == ZM_STA_PS_FAST )
                    if ( 0 )
                    {
                        wd->sta.psMgr.state = ZM_PS_MSG_STATE_S1;
                        //zfSendPSPoll(dev);
                        zfSendNullData(dev, 0);
                    }
                    else
                    {
                        if ((wd->sta.qosInfo&0xf) != 0xf)
                        {
                            /* send ps-poll */
                            //printk("zfSendPSPoll #1\n");

                            wd->sta.psMgr.isSleepAllowed = 0;

                            switch (wd->sta.powerSaveMode)
                            {
                            case ZM_STA_PS_MAX:
                            case ZM_STA_PS_FAST:
                                //zm_debug_msg0("wake up and send PS-Poll\n");
                                zfSendPSPoll(dev);
                                break;
                            case ZM_STA_PS_LIGHT:
                                zm_debug_msg0("wake up and send null data\n");

                                zmw_enter_critical_section(dev);
                                wd->sta.psMgr.sleepAllowedtick = 400;
                                zmw_leave_critical_section(dev);

                                zfSendNullData(dev, 0);
                                break;
                            }

                            wd->sta.psMgr.tempWakeUp = 0;
                        }
                    }
                }
            }
        }
    }

    while ((psBuf = zfQueueGet(dev, wd->sta.uapsdQ)) != NULL)
    {
        zfTxSendEth(dev, psBuf, 0, ZM_EXTERNAL_ALLOC_BUF, 0);
    }

    //printk("zfPowerSavingMgrProcessBeacon #1\n");
    zfPowerSavingMgrMain(dev);
}

void zfPowerSavingMgrConnectNotify(zdev_t *dev)
{
    zmw_get_wlan_dev(dev);

    if ( wd->wlanMode == ZM_MODE_INFRASTRUCTURE )
    {
        switch(wd->sta.powerSaveMode)
        {
            case ZM_STA_PS_NONE:
                zfHpPowerSaveSetMode(dev, 0, 0, wd->beaconInterval);
                break;

            case ZM_STA_PS_FAST:
            case ZM_STA_PS_MAX:
            case ZM_STA_PS_LIGHT:
                zfHpPowerSaveSetMode(dev, 0, 1, wd->beaconInterval);
                break;

            default:
                zfHpPowerSaveSetMode(dev, 0, 0, wd->beaconInterval);
                break;
        }
    }
}

void zfPowerSavingMgrPreTBTTInterrupt(zdev_t *dev)
{
    zmw_get_wlan_dev(dev);
    zmw_declare_for_critical_section();

    /* disable TBTT interrupt when change from connection to disconnect */
    if (zfStaIsDisconnect(dev)) {
        zfHpPowerSaveSetMode(dev, 0, 0, 0);
        zfPowerSavingMgrWakeup(dev);
        return;
    }

    zmw_enter_critical_section(dev);
    wd->sta.psMgr.ticks++;

    if ( wd->sta.psMgr.ticks < wd->sta.psMgr.maxSleepPeriods )
    {
        zmw_leave_critical_section(dev);
        return;
    }
    else
    {
        wd->sta.psMgr.ticks = 0;
    }

    zmw_leave_critical_section(dev);

    zfPowerSavingMgrWakeup(dev);
}

/* Leave an empty line below to remove warning message on some compiler */

