/* Copyright (c) 2015, 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"

#include <cutils/properties.h>
#include <stdlib.h>
#include <utils/Log.h>
#include "QCameraPerf.h"

#ifdef CDBG
#undef CDBG
#endif //#ifdef CDBG
#define CDBG(fmt, args...) ALOGD_IF(gCamHalLogLevel >= 2, fmt, ##args)

#ifdef CDBG_HIGH
#undef CDBG_HIGH
#endif //#ifdef CDBG_HIGH
#define CDBG_HIGH(fmt, args...) ALOGD_IF(gCamHalLogLevel >= 1, fmt, ##args)


namespace qcamera {

extern volatile uint32_t gCamHalLogLevel;

/*===========================================================================
 * 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];
    int len;

    CDBG("%s E", __func__);
    Mutex::Autolock lock(mLock);

    property_get("persist.camera.perflock.enable", value, "1");
    mPerfLockEnable = atoi(value);
    mCurrentPowerHintEnable = 0;
#ifdef HAS_MULTIMEDIA_HINTS
    if (hw_get_module(POWER_HARDWARE_MODULE_ID, (const hw_module_t **)&m_pPowerModule)) {
        ALOGE("%s: %s module not found", __func__, 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) {
            ALOGE("%s: failed to perf_lock_acq function handle", __func__);
            goto cleanup;
        }

        perf_lock_rel = (int (*) (int))dlsym(mDlHandle, "perf_lock_rel");
        if ((rc = dlerror()) != NULL) {
            ALOGE("%s: failed to perf_lock_rel function handle", __func__);
            goto cleanup;
        }
        CDBG("%s X", __func__);
        return;

cleanup:
        perf_lock_acq  = NULL;
        perf_lock_rel  = NULL;
        mPerfLockEnable = 0;
        if (mDlHandle) {
            dlclose(mDlHandle);
            mDlHandle = NULL;
        }
    }
    CDBG("%s X", __func__);
}

/*===========================================================================
 * FUNCTION   : lock_deinit
 *
 * DESCRIPTION: deinitialize the perf lock parameters
 *
 * PARAMETERS :
 *   None
 *
 * RETURN     : void
 *
 *==========================================================================*/
void QCameraPerfLock::lock_deinit()
{
    Mutex::Autolock lock(mLock);
    if (mPerfLockEnable) {
        CDBG("%s E", __func__);
        if (mDlHandle) {
            perf_lock_acq  = NULL;
            perf_lock_rel  = NULL;

            dlclose(mDlHandle);
            mDlHandle       = NULL;
        }
        mPerfLockEnable = 0;
        CDBG("%s X", __func__);
    }
}

/*===========================================================================
 * 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) {
            mTimerSet = 0;
            return true;
        }
    }
    return false;
}

/*===========================================================================
 * 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;

    CDBG("%s E", __func__);
    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) {
            CDBG_HIGH("%s mCurrentPowerHintEnable %d", __func__ ,mCurrentPowerHintEnable);
            powerHintInternal(mCurrentPowerHint, 0);
        }

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

    CDBG("%s X", __func__);
    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;

    CDBG("%s E", __func__);
    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, 0);
        }

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

    CDBG("%s X", __func__);
    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) {
        CDBG("%s E", __func__);
        if (mPerfLockHandleTimed < 0) {
            ALOGE("%s: mPerfLockHandle < 0,check if lock is acquired", __func__);
            return ret;
        }
        CDBG("%s perf_handle_rel %d ",__func__, mPerfLockHandleTimed);

        if ((NULL != perf_lock_rel) && (0 <= mPerfLockHandleTimed)) {
            ret = (*perf_lock_rel)(mPerfLockHandleTimed);
            if (ret < 0) {
                ALOGE("%s: failed to release lock", __func__);
            }
            mPerfLockHandleTimed = -1;
        }

        if ((mCurrentPowerHintEnable == 1) && (mTimerSet == 0)) {
            powerHintInternal(mCurrentPowerHint, mCurrentPowerHintEnable);
        }
        CDBG("%s X", __func__);
    }
    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) {
        CDBG("%s E", __func__);
        if (mPerfLockHandle < 0) {
            ALOGE("%s: mPerfLockHandle < 0,check if lock is acquired", __func__);
            return ret;
        }
        CDBG("%s perf_handle_rel %d ",__func__, mPerfLockHandle);

        if ((NULL != perf_lock_rel) && (0 <= mPerfLockHandle)) {
            ret = (*perf_lock_rel)(mPerfLockHandle);
            if (ret < 0) {
                ALOGE("%s: failed to release lock", __func__);
            }
            mPerfLockHandle = -1;
        }

        if ((mCurrentPowerHintEnable == 1) && (mTimerSet == 0)) {
            powerHintInternal(mCurrentPowerHint, mCurrentPowerHintEnable);
        }
        CDBG("%s X", __func__);
    }
    return ret;
}

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

/*===========================================================================
 * FUNCTION   : powerHint
 *
 * 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::powerHint(power_hint_t hint, uint32_t enable)
{
#ifdef HAS_MULTIMEDIA_HINTS
    if (mCurrentPowerHintEnable) {
        //disable previous hint
        powerHintInternal(mCurrentPowerHint, 0);
    }
    powerHintInternal(hint, enable);

    mCurrentPowerHint       = hint;
    mCurrentPowerHintEnable = enable;
#endif
}

}; // namespace qcamera