/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials provided
*       with the distribution.
*     * Neither the name of The Linux Foundation nor the names of its
*       contributors may be used to endorse or promote products derived
*       from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/

#define LOG_TAG "QCameraPerf"

// To remove
#include <cutils/properties.h>

// System dependencies
#include <stdlib.h>
#include <dlfcn.h>

// Camera dependencies
#include "QCameraPerf.h"
#include "QCameraTrace.h"

extern "C" {
#include "mm_camera_dbg.h"
}

namespace qcamera {

/*===========================================================================
 * FUNCTION   : QCameraPerfLock constructor
 *
 * DESCRIPTION: initialize member variables
 *
 * PARAMETERS :
 *   None
 *
 * RETURN     : void
 *
 *==========================================================================*/
QCameraPerfLock::QCameraPerfLock() :
        perf_lock_acq(NULL),
        perf_lock_rel(NULL),
        mDlHandle(NULL),
        mPerfLockEnable(0),
        mPerfLockHandle(-1),
        mPerfLockHandleTimed(-1),
        mTimerSet(0),
        mPerfLockTimeout(0),
        mStartTimeofLock(0)
{
}

/*===========================================================================
 * FUNCTION   : QCameraPerfLock destructor
 *
 * DESCRIPTION: class desctructor
 *
 * PARAMETERS :
 *   None
 *
 * RETURN     : void
 *
 *==========================================================================*/
QCameraPerfLock::~QCameraPerfLock()
{
    lock_deinit();
}


/*===========================================================================
 * FUNCTION   : lock_init
 *
 * DESCRIPTION: opens the performance lib and initilizes the perf lock functions
 *
 * PARAMETERS :
 *   None
 *
 * RETURN     : void
 *
 *==========================================================================*/
void QCameraPerfLock::lock_init()
{
    const char *rc;
    char value[PROPERTY_VALUE_MAX];

    LOGD("E");
    Mutex::Autolock lock(mLock);

    // Clear the list of active power hints
    mActivePowerHints.clear();
    mCurrentPowerHint       = static_cast<power_hint_t>(0);
    mCurrentPowerHintEnable = false;

    property_get("persist.camera.perflock.enable", value, "1");
    mPerfLockEnable = atoi(value);
#ifdef HAS_MULTIMEDIA_HINTS
    if (hw_get_module(POWER_HARDWARE_MODULE_ID, (const hw_module_t **)&m_pPowerModule)) {
        LOGE("%s module not found", POWER_HARDWARE_MODULE_ID);
    }
#endif

    if (mPerfLockEnable) {
        perf_lock_acq = NULL;
        perf_lock_rel = NULL;
        mPerfLockHandle = -1;
        /* Retrieve name of vendor extension library */
        if (property_get("ro.vendor.extension_library", value, NULL) <= 0) {
            goto cleanup;
        }

        mDlHandle = dlopen(value, RTLD_NOW | RTLD_LOCAL);
        if (mDlHandle == NULL) {
            goto cleanup;
        }

        dlerror();

        perf_lock_acq = (int (*) (int, int, int[], int))dlsym(mDlHandle, "perf_lock_acq");
        if ((rc = dlerror()) != NULL) {
            LOGE("failed to perf_lock_acq function handle");
            goto cleanup;
        }

        perf_lock_rel = (int (*) (int))dlsym(mDlHandle, "perf_lock_rel");
        if ((rc = dlerror()) != NULL) {
            LOGE("failed to perf_lock_rel function handle");
            goto cleanup;
        }
        LOGD("X");
        return;

cleanup:
        perf_lock_acq  = NULL;
        perf_lock_rel  = NULL;
        mPerfLockEnable = 0;
        if (mDlHandle) {
            dlclose(mDlHandle);
            mDlHandle = NULL;
        }
    }
    LOGD("X");
}

/*===========================================================================
 * FUNCTION   : lock_deinit
 *
 * DESCRIPTION: deinitialize the perf lock parameters
 *
 * PARAMETERS :
 *   None
 *
 * RETURN     : void
 *
 *==========================================================================*/
void QCameraPerfLock::lock_deinit()
{
    Mutex::Autolock lock(mLock);
    if (mPerfLockEnable) {
        LOGD("E");

        if (mActivePowerHints.empty() == false) {
            // Disable the active power hint
            mCurrentPowerHint = *mActivePowerHints.begin();
            powerHintInternal(mCurrentPowerHint, false);
            mActivePowerHints.clear();
        }

        if ((NULL != perf_lock_rel) && (mPerfLockHandleTimed >= 0)) {
            (*perf_lock_rel)(mPerfLockHandleTimed);
        }

        if ((NULL != perf_lock_rel) && (mPerfLockHandle >= 0)) {
            (*perf_lock_rel)(mPerfLockHandle);
        }

        if (mDlHandle) {
            perf_lock_acq  = NULL;
            perf_lock_rel  = NULL;

            dlclose(mDlHandle);
            mDlHandle       = NULL;
        }
        mPerfLockEnable = 0;
        LOGD("X");
    }
}

/*===========================================================================
 * FUNCTION   : isTimerReset
 *
 * DESCRIPTION: Check if timout duration is reached
 *
 * PARAMETERS : None
 *
 * RETURN     : true if timeout reached
 *              false if timeout not reached
 *
 *==========================================================================*/
bool QCameraPerfLock::isTimerReset()
{
    Mutex::Autolock lock(mLock);
    if (mPerfLockEnable && mTimerSet) {
        nsecs_t timeDiff = systemTime() - mStartTimeofLock;
        if (ns2ms(timeDiff) > (uint32_t)mPerfLockTimeout) {
            resetTimer();
            return true;
        }
    }
    return false;
}

/*===========================================================================
 * FUNCTION   : resetTimer
 *
 * DESCRIPTION: Reset the timer used in timed perf lock
 *
 * PARAMETERS : None
 *
 * RETURN     : void
 *
 *==========================================================================*/
void QCameraPerfLock::resetTimer()
{
    mPerfLockTimeout = 0;
    mTimerSet = 0;
}

/*===========================================================================
 * FUNCTION   : start_timer
 *
 * DESCRIPTION: get the start of the timer
 *
 * PARAMETERS :
 *  @timer_val: timer duration in milliseconds
 *
 * RETURN     : int32_t type of status
 *              NO_ERROR  -- success
 *              none-zero failure code
 *
 *==========================================================================*/
void QCameraPerfLock::startTimer(uint32_t timer_val)
{
    mStartTimeofLock = systemTime();
    mTimerSet = 1;
    mPerfLockTimeout = timer_val;
}

/*===========================================================================
 * FUNCTION   : lock_acq_timed
 *
 * DESCRIPTION: Acquire the performance lock for the specified duration.
 *              If an existing lock timeout has not elapsed, extend the
 *              lock further for the specified duration
 *
 * PARAMETERS :
 *  @timer_val: lock duration
 *
 * RETURN     : int32_t type of status
 *              NO_ERROR  -- success
 *              none-zero failure code
 *
 *==========================================================================*/
int32_t QCameraPerfLock::lock_acq_timed(int32_t timer_val)
{
    int32_t ret = -1;

    LOGD("E");
    Mutex::Autolock lock(mLock);

    if (mPerfLockEnable) {
        int32_t perf_lock_params[] = {
                ALL_CPUS_PWR_CLPS_DIS,
                CPU0_MIN_FREQ_TURBO_MAX,
                CPU4_MIN_FREQ_TURBO_MAX
        };
        if (mTimerSet) {
            nsecs_t curElapsedTime = systemTime() - mStartTimeofLock;
            int32_t pendingTimeout = mPerfLockTimeout - ns2ms(curElapsedTime);
            timer_val += pendingTimeout;
        }
        startTimer(timer_val);

        // Disable power hint when acquiring the perf lock
        if (mCurrentPowerHintEnable) {
            LOGD("mCurrentPowerHintEnable %d" ,mCurrentPowerHintEnable);
            powerHintInternal(mCurrentPowerHint, false);
        }

        if ((NULL != perf_lock_acq) && (mPerfLockHandleTimed < 0)) {
            ret = (*perf_lock_acq)(mPerfLockHandleTimed, timer_val, perf_lock_params,
                    sizeof(perf_lock_params) / sizeof(int32_t));
            LOGD("ret %d", ret);
            if (ret < 0) {
                LOGE("failed to acquire lock");
            } else {
                mPerfLockHandleTimed = ret;
            }
        }
        LOGD("perf_handle_acq %d ", mPerfLockHandleTimed);
    }

    LOGD("X");
    return ret;
}

/*===========================================================================
 * FUNCTION   : lock_acq
 *
 * DESCRIPTION: acquire the performance lock
 *
 * PARAMETERS :
 *   None
 *
 * RETURN     : int32_t type of status
 *              NO_ERROR  -- success
 *              none-zero failure code
 *
 *==========================================================================*/
int32_t QCameraPerfLock::lock_acq()
{
    int32_t ret = -1;

    LOGD("E");
    Mutex::Autolock lock(mLock);

    if (mPerfLockEnable) {
        int32_t perf_lock_params[] = {
                ALL_CPUS_PWR_CLPS_DIS,
                CPU0_MIN_FREQ_TURBO_MAX,
                CPU4_MIN_FREQ_TURBO_MAX
        };

        // Disable power hint when acquiring the perf lock
        if (mCurrentPowerHintEnable) {
            powerHintInternal(mCurrentPowerHint, false);
        }

        if ((NULL != perf_lock_acq) && (mPerfLockHandle < 0)) {
            ret = (*perf_lock_acq)(mPerfLockHandle, ONE_SEC, perf_lock_params,
                    sizeof(perf_lock_params) / sizeof(int32_t));
            LOGD("ret %d", ret);
            if (ret < 0) {
                LOGE("failed to acquire lock");
            } else {
                mPerfLockHandle = ret;
            }
        }
        LOGD("perf_handle_acq %d ", mPerfLockHandle);
    }

    LOGD("X");
    return ret;
}

/*===========================================================================
 * FUNCTION   : lock_rel_timed
 *
 * DESCRIPTION: release the performance lock
 *
 * PARAMETERS :
 *   None
 *
 * RETURN     : int32_t type of status
 *              NO_ERROR  -- success
 *              none-zero failure code
 *
 *==========================================================================*/
int32_t QCameraPerfLock::lock_rel_timed()
{
    int ret = -1;
    Mutex::Autolock lock(mLock);
    if (mPerfLockEnable) {
        LOGD("E");
        if (mPerfLockHandleTimed < 0) {
            LOGW("mPerfLockHandle < 0,check if lock is acquired");
            return ret;
        }
        LOGD("perf_handle_rel %d ", mPerfLockHandleTimed);

        if ((NULL != perf_lock_rel) && (0 <= mPerfLockHandleTimed)) {
            ret = (*perf_lock_rel)(mPerfLockHandleTimed);
            if (ret < 0) {
                LOGE("failed to release lock");
            }
            mPerfLockHandleTimed = -1;
            resetTimer();
        }

        if ((mCurrentPowerHintEnable == 1) && (mTimerSet == 0)) {
            powerHintInternal(mCurrentPowerHint, mCurrentPowerHintEnable);
        }
        LOGD("X");
    }
    return ret;
}

/*===========================================================================
 * FUNCTION   : lock_rel
 *
 * DESCRIPTION: release the performance lock
 *
 * PARAMETERS :
 *   None
 *
 * RETURN     : int32_t type of status
 *              NO_ERROR  -- success
 *              none-zero failure code
 *
 *==========================================================================*/
int32_t QCameraPerfLock::lock_rel()
{
    int ret = -1;
    Mutex::Autolock lock(mLock);
    if (mPerfLockEnable) {
        LOGD("E");
        if (mPerfLockHandle < 0) {
            LOGW("mPerfLockHandle < 0,check if lock is acquired");
            return ret;
        }
        LOGD("perf_handle_rel %d ", mPerfLockHandle);

        if ((NULL != perf_lock_rel) && (0 <= mPerfLockHandle)) {
            ret = (*perf_lock_rel)(mPerfLockHandle);
            if (ret < 0) {
                LOGE("failed to release lock");
            }
            mPerfLockHandle = -1;
        }

        if (mCurrentPowerHintEnable == 1) {
            powerHintInternal(mCurrentPowerHint, mCurrentPowerHintEnable);
        }
        LOGD("X");
    }
    return ret;
}

/*===========================================================================
 * FUNCTION   : powerHintInternal
 *
 * DESCRIPTION: Sets the requested power hint and state to power HAL.
 *
 * PARAMETERS :
 * hint       : Power hint
 * enable     : Enable power hint if set to 1. Disable if set to 0.
 * RETURN     : void
 *
 *==========================================================================*/
void QCameraPerfLock::powerHintInternal(power_hint_t hint, bool enable)
{
#ifdef HAS_MULTIMEDIA_HINTS
    if (m_pPowerModule != NULL) {
        if (enable == true) {
            m_pPowerModule->powerHint(m_pPowerModule, hint, (void *)"state=1");
        } else {
            m_pPowerModule->powerHint(m_pPowerModule, hint, (void *)"state=0");
        }
    }
#endif
}

/*===========================================================================
 * FUNCTION   : powerHint
 *
 * DESCRIPTION: Updates the list containing active/enabled power hints.
 *              If needed, calls the internal powerHint function with
 *              requested power hint and state.
 * PARAMETERS :
 * hint       : Power hint
 * enable     : Enable power hint if set to 1. Disable if set to 0.
 * RETURN     : void
 *
 *==========================================================================*/
void QCameraPerfLock::powerHint(power_hint_t hint, bool enable)
{
#ifdef HAS_MULTIMEDIA_HINTS
    if (enable == true) {
        if ((hint != mCurrentPowerHint) || (enable != mCurrentPowerHintEnable)) {
            // Disable the current active power hint
            if (mCurrentPowerHintEnable == true) {
                powerHintInternal(mCurrentPowerHint, false);
            }
            // Push the new power hint at the head of the active power hint list
            mActivePowerHints.push_front(hint);

            // Set the new power hint
            mCurrentPowerHint       = hint;
            mCurrentPowerHintEnable = enable;
            powerHintInternal(hint, enable);
        }
    } else {
        // Remove the power hint from the list
        for (List<power_hint_t>::iterator it = mActivePowerHints.begin();
                it != mActivePowerHints.end(); ++it) {
            if (*it == hint) {
                if (it != mActivePowerHints.begin()) {
                    LOGW("Request to remove the previous power hint: %d instead of "
                            "currently active power hint: %d", static_cast<int>(hint),
                                                            static_cast<int>(mCurrentPowerHint));
                }
                mActivePowerHints.erase(it);
                break;
            }
        }

        if (hint == mCurrentPowerHint) {
            // Disable the power hint
            powerHintInternal(hint, false);

            // If the active power hint list is not empty,
            // restore the previous power hint from the head of the list
            if (mActivePowerHints.empty() == false) {
                mCurrentPowerHint       = *mActivePowerHints.begin();
                mCurrentPowerHintEnable = true;
                powerHintInternal(mCurrentPowerHint, true);
            } else {
                mCurrentPowerHint       = static_cast<power_hint_t>(0);
                mCurrentPowerHintEnable = false;
            }
        }
    }
#endif
}

}; // namespace qcamera