/*
* Copyright (c) 2013 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.
*/

#include "overlay.h"
#include "overlayWriteback.h"
#include "mdpWrapper.h"

#define SIZE_1M 0x00100000

namespace overlay {

//=========== class WritebackMem ==============================================
bool WritebackMem::manageMem(uint32_t size, bool isSecure) {
    if(mBuf.bufSz() == size) {
        return true;
    }
    if(mBuf.valid()) {
        if(!mBuf.close()) {
            ALOGE("%s error closing mem", __func__);
            return false;
        }
    }
    return alloc(size, isSecure);
}

bool WritebackMem::alloc(uint32_t size, bool isSecure) {
    if(!mBuf.open(NUM_BUFS, size, isSecure)){
        ALOGE("%s: Failed to open", __func__);
        mBuf.close();
        return false;
    }

    OVASSERT(MAP_FAILED != mBuf.addr(), "MAP failed");
    OVASSERT(mBuf.getFD() != -1, "getFd is -1");

    mCurrOffsetIndex = 0;
    for (uint32_t i = 0; i < NUM_BUFS; i++) {
        mOffsets[i] = i * size;
    }
    return true;
}

bool WritebackMem::dealloc() {
    bool ret = true;
    if(mBuf.valid()) {
        ret = mBuf.close();
    }
    return ret;
}

//=========== class Writeback =================================================
Writeback::Writeback() : mXres(0), mYres(0), mOpFmt(-1), mSecure(false) {
    int fbNum = Overlay::getFbForDpy(Overlay::DPY_WRITEBACK);
    if(!utils::openDev(mFd, fbNum, Res::fbPath, O_RDWR)) {
        ALOGE("%s failed to init %s", __func__, Res::fbPath);
        return;
    }
    startSession();
}

Writeback::~Writeback() {
    stopSession();
    if (!mFd.close()) {
        ALOGE("%s error closing fd", __func__);
    }
}

bool Writeback::startSession() {
    if(!mdp_wrapper::wbInitStart(mFd.getFD())) {
        ALOGE("%s failed", __func__);
        return false;
    }
    return true;
}

bool Writeback::stopSession() {
    if(mFd.valid()) {
        if(!Overlay::displayCommit(mFd.getFD())) {
            ALOGE("%s: displayCommit failed", __func__);
            return false;
        }
        if(!mdp_wrapper::wbStopTerminate(mFd.getFD())) {
            ALOGE("%s: wbStopTerminate failed", __func__);
            return false;
        }
    } else {
        ALOGE("%s Invalid fd", __func__);
        return false;
    }
    return true;
}

bool Writeback::configureDpyInfo(int xres, int yres) {
    if(mXres != xres || mYres != yres) {
        fb_var_screeninfo vinfo;
        memset(&vinfo, 0, sizeof(fb_var_screeninfo));
        if(!mdp_wrapper::getVScreenInfo(mFd.getFD(), vinfo)) {
            ALOGE("%s failed", __func__);
            return false;
        }
        vinfo.xres = xres;
        vinfo.yres = yres;
        vinfo.xres_virtual = xres;
        vinfo.yres_virtual = yres;
        vinfo.xoffset = 0;
        vinfo.yoffset = 0;
        if(!mdp_wrapper::setVScreenInfo(mFd.getFD(), vinfo)) {
            ALOGE("%s failed", __func__);
            return false;
        }
        mXres = xres;
        mYres = yres;
    }
    return true;
}

bool Writeback::configureMemory(uint32_t size) {
    if(!mWbMem.manageMem(size, mSecure)) {
        ALOGE("%s failed, memory failure", __func__);
        return false;
    }
    return true;
}

bool Writeback::queueBuffer(int opFd, uint32_t opOffset) {
    memset(&mFbData, 0, sizeof(struct msmfb_data));
    //Queue
    mFbData.offset = opOffset;
    mFbData.memory_id = opFd;
    mFbData.id = 0;
    mFbData.flags = 0;
    if(!mdp_wrapper::wbQueueBuffer(mFd.getFD(), mFbData)) {
        ALOGE("%s: queuebuffer failed", __func__);
        return false;
    }
    return true;
}

bool Writeback::dequeueBuffer() {
    //Dequeue
    mFbData.flags = MSMFB_WRITEBACK_DEQUEUE_BLOCKING;
    if(!mdp_wrapper::wbDequeueBuffer(mFd.getFD(), mFbData)) {
        ALOGE("%s: dequeuebuffer failed", __func__);
        return false;
    }
    return true;
}

bool Writeback::writeSync(int opFd, uint32_t opOffset) {
    if(!queueBuffer(opFd, opOffset)) {
        return false;
    }
    if(!Overlay::displayCommit(mFd.getFD())) {
        return false;
    }
    if(!dequeueBuffer()) {
        return false;
    }
    return true;
}

bool Writeback::writeSync() {
    mWbMem.useNextBuffer();
    return writeSync(mWbMem.getDstFd(), mWbMem.getOffset());
}

bool Writeback::setOutputFormat(int mdpFormat) {
    if(mdpFormat != mOpFmt) {
        struct msmfb_metadata metadata;
        memset(&metadata, 0 , sizeof(metadata));
        metadata.op = metadata_op_wb_format;
        metadata.data.mixer_cfg.writeback_format = mdpFormat;
        if (ioctl(mFd.getFD(), MSMFB_METADATA_SET, &metadata) < 0) {
            ALOGE("Error setting MDP Writeback format");
            return false;
        }
        mOpFmt = mdpFormat;
    }
    return true;
}

int Writeback::getOutputFormat() {
    if(mOpFmt < 0) {
        struct msmfb_metadata metadata;
        memset(&metadata, 0 , sizeof(metadata));
        metadata.op = metadata_op_wb_format;
        if (ioctl(mFd.getFD(), MSMFB_METADATA_GET, &metadata) < 0) {
            ALOGE("Error retrieving MDP Writeback format");
            return -1;
        }
        mOpFmt =  metadata.data.mixer_cfg.writeback_format;
    }
    return mOpFmt;
}

bool Writeback::setSecure(bool isSecure) {
    if(isSecure != mSecure) {
        // Call IOCTL to set WB interface as secure
        struct msmfb_metadata metadata;
        memset(&metadata, 0 , sizeof(metadata));
        metadata.op = metadata_op_wb_secure;
        metadata.data.secure_en = isSecure;
        if (ioctl(mFd.getFD(), MSMFB_METADATA_SET, &metadata) < 0) {
            ALOGE("Error setting MDP WB secure");
            return false;
        }
        mSecure = isSecure;
    }
    return true;
}

//static

Writeback *Writeback::getInstance() {
    if(sWb == NULL) {
        sWb = new Writeback();
    }
    sUsed = true;
    return sWb;
}

void Writeback::configDone() {
    if(sUsed == false && sWb) {
        delete sWb;
        sWb = NULL;
    }
}

void Writeback::clear() {
    sUsed = false;
    if(sWb) {
        delete sWb;
        sWb = NULL;
    }
}

bool Writeback::getDump(char *buf, size_t len) {
    if(sWb) {
        utils::getDump(buf, len, "WBData", sWb->mFbData);
        char outputBufferInfo[256];
        snprintf(outputBufferInfo, sizeof(outputBufferInfo),
                "OutputBuffer xres=%d yres=%d format=%s\n\n",
                sWb->getWidth(), sWb->getHeight(),
                utils::getFormatString(sWb->getOutputFormat()));
        strlcat(buf, outputBufferInfo, len);
        return true;
    }
    return false;
}

Writeback *Writeback::sWb = 0;
bool Writeback::sUsed = false;

} //namespace overlay
