/*
// Copyright (c) 2014 Intel Corporation 
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/

#include <math.h>
#include <HwcTrace.h>
#include <Drm.h>
#include <Hwcomposer.h>
#include <PhysicalDevice.h>
#include <common/OverlayPlaneBase.h>
#include <common/TTMBufferMapper.h>
#include <common/GrallocSubBuffer.h>
#include <DisplayQuery.h>


// FIXME: remove it
#include <OMX_IVCommon.h>
#include <OMX_IntelVideoExt.h>

namespace android {
namespace intel {

OverlayPlaneBase::OverlayPlaneBase(int index, int disp)
    : DisplayPlane(index, PLANE_OVERLAY, disp),
      mTTMBuffers(),
      mActiveTTMBuffers(),
      mCurrent(0),
      mWsbm(0),
      mPipeConfig(0),
      mBobDeinterlace(0),
      mUseScaledBuffer(0)
{
    CTRACE();
    for (int i = 0; i < OVERLAY_BACK_BUFFER_COUNT; i++) {
        mBackBuffer[i] = 0;
    }
}

OverlayPlaneBase::~OverlayPlaneBase()
{
    CTRACE();
}

bool OverlayPlaneBase::initialize(uint32_t bufferCount)
{
    Drm *drm = Hwcomposer::getInstance().getDrm();
    CTRACE();

    // NOTE: use overlay's data buffer count for the overlay plane
    if (bufferCount < OVERLAY_DATA_BUFFER_COUNT) {
        ITRACE("override overlay buffer count from %d to %d",
             bufferCount, OVERLAY_DATA_BUFFER_COUNT);
        bufferCount = OVERLAY_DATA_BUFFER_COUNT;
    }
    if (!DisplayPlane::initialize(bufferCount)) {
        DEINIT_AND_RETURN_FALSE("failed to initialize display plane");
    }

    mTTMBuffers.setCapacity(bufferCount);
    mActiveTTMBuffers.setCapacity(MIN_DATA_BUFFER_COUNT);

    // init wsbm
    mWsbm = new Wsbm(drm->getDrmFd());
    if (!mWsbm || !mWsbm->initialize()) {
        DEINIT_AND_RETURN_FALSE("failed to create wsbm");
    }

    // create overlay back buffer
    for (int i = 0; i < OVERLAY_BACK_BUFFER_COUNT; i++) {
        mBackBuffer[i] = createBackBuffer();
        if (!mBackBuffer[i]) {
            DEINIT_AND_RETURN_FALSE("failed to create overlay back buffer");
        }
        // reset back buffer
        resetBackBuffer(i);
    }

    // disable overlay when created
    flush(PLANE_DISABLE);

    return true;
}

bool OverlayPlaneBase::isDisabled()
{
    RETURN_FALSE_IF_NOT_INIT();

    struct drm_psb_register_rw_arg arg;
    memset(&arg, 0, sizeof(struct drm_psb_register_rw_arg));

    arg.get_plane_state_mask = 1;
    arg.plane.type = DC_OVERLAY_PLANE;
    arg.plane.index = mIndex;
    // pass the pipe index to check its enabled status
    // now we can pass the device id directly since
    // their values are just equal
    arg.plane.ctx = mDevice; // not used in kernel

    Drm *drm = Hwcomposer::getInstance().getDrm();
    bool ret = drm->writeReadIoctl(DRM_PSB_REGISTER_RW, &arg, sizeof(arg));
    if (ret == false) {
        WTRACE("overlay plane query failed with error code %d", ret);
        return false;
    }

    DTRACE("overlay %d status %s on device %d, current device %d",
        mIndex, arg.plane.ctx ? "DISABLED" : "ENABLED", mDevice, mDevice);

    return arg.plane.ctx == PSB_DC_PLANE_DISABLED;
}

void OverlayPlaneBase::deinitialize()
{
    if (mTTMBuffers.size()) {
        invalidateBufferCache();
    }

    if (mActiveTTMBuffers.size() > 0) {
        invalidateActiveTTMBuffers();
    }

    // delete back buffer
    for (int i = 0; i < OVERLAY_BACK_BUFFER_COUNT; i++) {
        if (mBackBuffer[i]) {
            deleteBackBuffer(i);
            mBackBuffer[i] = NULL;
        }
    }
    DEINIT_AND_DELETE_OBJ(mWsbm);

    DisplayPlane::deinitialize();
}

void OverlayPlaneBase::invalidateBufferCache()
{
    // clear plane buffer cache
    DisplayPlane::invalidateBufferCache();
    invalidateTTMBuffers();
}

bool OverlayPlaneBase::assignToDevice(int disp)
{
    uint32_t pipeConfig = 0;

    RETURN_FALSE_IF_NOT_INIT();
    VTRACE("overlay %d assigned to disp %d", mIndex, disp);

    switch (disp) {
    case IDisplayDevice::DEVICE_EXTERNAL:
        pipeConfig = (0x2 << 6);
        break;
    case IDisplayDevice::DEVICE_PRIMARY:
    default:
        pipeConfig = 0;
        break;
    }

    // if pipe switching happened, then disable overlay first
    if (mPipeConfig != pipeConfig) {
        DTRACE("overlay %d switched from %d to %d", mIndex, mDevice, disp);
        disable();
    }

    mPipeConfig = pipeConfig;
    DisplayPlane::assignToDevice(disp);

    enable();

    return true;
}

void OverlayPlaneBase::setZOrderConfig(ZOrderConfig& zorderConfig,
                                            void *nativeConfig)
{
    CTRACE();

    // setup overlay z order
    int ovaZOrder = -1;
    int ovcZOrder = -1;
    for (size_t i = 0; i < zorderConfig.size(); i++) {
        DisplayPlane *plane = zorderConfig[i]->plane;
        if (plane->getType() == DisplayPlane::PLANE_OVERLAY) {
            if (plane->getIndex() == 0) {
                ovaZOrder = i;
            } else if (plane->getIndex() == 1) {
                ovcZOrder = i;
            }
        }
    }

    for (int i = 0; i < OVERLAY_BACK_BUFFER_COUNT; i++) {
        OverlayBackBufferBlk *backBuffer = mBackBuffer[i]->buf;
        if (!backBuffer)
            return;

        // force overlay c above overlay a
        if ((ovaZOrder >= 0) && (ovaZOrder < ovcZOrder)) {
            backBuffer->OCONFIG |= (1 << 15);
        } else {
            backBuffer->OCONFIG &= ~(1 << 15);
        }
    }
}

bool OverlayPlaneBase::reset()
{
    RETURN_FALSE_IF_NOT_INIT();

    DisplayPlane::reset();

    // invalidate active TTM buffers
    if (mActiveTTMBuffers.size() > 0) {
        invalidateActiveTTMBuffers();
    }

    // reset back buffers
    for (int i = 0; i < OVERLAY_BACK_BUFFER_COUNT; i++) {
        resetBackBuffer(i);
    }
    return true;
}

bool OverlayPlaneBase::enable()
{
    RETURN_FALSE_IF_NOT_INIT();
    for (int i = 0; i < OVERLAY_BACK_BUFFER_COUNT; i++) {
        OverlayBackBufferBlk *backBuffer = mBackBuffer[i]->buf;
        if (!backBuffer)
            return false;

        if (backBuffer->OCMD & 0x1)
            return true;

        backBuffer->OCMD |= 0x1;
    }

    // flush
    flush(PLANE_ENABLE);
    return true;
}

bool OverlayPlaneBase::disable()
{
    RETURN_FALSE_IF_NOT_INIT();
    for (int i = 0; i < OVERLAY_BACK_BUFFER_COUNT; i++) {
        OverlayBackBufferBlk *backBuffer = mBackBuffer[i]->buf;
        if (!backBuffer)
            return false;

        if (!(backBuffer->OCMD & 0x1))
            return true;

        backBuffer->OCMD &= ~0x1;
    }

    // flush
    flush(PLANE_DISABLE);
    return true;
}

OverlayBackBuffer* OverlayPlaneBase::createBackBuffer()
{
    CTRACE();

    // create back buffer
    OverlayBackBuffer *backBuffer = (OverlayBackBuffer *)malloc(sizeof(OverlayBackBuffer));
    if (!backBuffer) {
        ETRACE("failed to allocate back buffer");
        return 0;
    }


    int size = sizeof(OverlayBackBufferBlk);
    int alignment = 64 * 1024;
    void *wsbmBufferObject = 0;
    bool ret = mWsbm->allocateTTMBuffer(size, alignment, &wsbmBufferObject);
    if (ret == false) {
        ETRACE("failed to allocate TTM buffer");
        return 0;
    }

    void *virtAddr = mWsbm->getCPUAddress(wsbmBufferObject);
    uint32_t gttOffsetInPage = mWsbm->getGttOffset(wsbmBufferObject);

    backBuffer->buf = (OverlayBackBufferBlk *)virtAddr;
    backBuffer->gttOffsetInPage = gttOffsetInPage;
    backBuffer->bufObject = wsbmBufferObject;

    VTRACE("cpu %p, gtt %d", virtAddr, gttOffsetInPage);

    return backBuffer;
}

void OverlayPlaneBase::deleteBackBuffer(int buf)
{
    if (!mBackBuffer[buf])
        return;

    void *wsbmBufferObject = mBackBuffer[buf]->bufObject;
    bool ret = mWsbm->destroyTTMBuffer(wsbmBufferObject);
    if (ret == false) {
        WTRACE("failed to destroy TTM buffer");
    }
    // free back buffer
    free(mBackBuffer[buf]);
    mBackBuffer[buf] = 0;
}

void OverlayPlaneBase::resetBackBuffer(int buf)
{
    CTRACE();

    if (!mBackBuffer[buf] || !mBackBuffer[buf]->buf)
        return;

    OverlayBackBufferBlk *backBuffer = mBackBuffer[buf]->buf;

    memset(backBuffer, 0, sizeof(OverlayBackBufferBlk));

    // reset overlay
    backBuffer->OCLRC0 = (OVERLAY_INIT_CONTRAST << 18) |
                         (OVERLAY_INIT_BRIGHTNESS & 0xff);
    backBuffer->OCLRC1 = OVERLAY_INIT_SATURATION;
    backBuffer->DCLRKV = OVERLAY_INIT_COLORKEY;
    backBuffer->DCLRKM = OVERLAY_INIT_COLORKEYMASK;
    backBuffer->OCONFIG = 0;
    backBuffer->OCONFIG |= (0x1 << 3);
    backBuffer->OCONFIG |= (0x1 << 27);
    backBuffer->SCHRKEN &= ~(0x7 << 24);
    backBuffer->SCHRKEN |= 0xff;
}

BufferMapper* OverlayPlaneBase::getTTMMapper(BufferMapper& grallocMapper, struct VideoPayloadBuffer *payload)
{
    buffer_handle_t khandle;
    uint32_t w, h;
    uint32_t yStride, uvStride;
    stride_t stride;
    int srcX, srcY, srcW, srcH;
    int tmp;

    DataBuffer *buf;
    ssize_t index;
    TTMBufferMapper *mapper;
    bool ret;

    if (!payload) {
        ETRACE("invalid payload buffer");
        return 0;
    }

    srcX = grallocMapper.getCrop().x;
    srcY = grallocMapper.getCrop().y;
    srcW = grallocMapper.getCrop().w;
    srcH = grallocMapper.getCrop().h;

    // init ttm buffer
    if (mUseScaledBuffer) {
        khandle = payload->scaling_khandle;
    } else {
        khandle = payload->rotated_buffer_handle;
    }
    index = mTTMBuffers.indexOfKey(khandle);
    if (index < 0) {
        VTRACE("unmapped TTM buffer, will map it");

        if (mUseScaledBuffer) {
            w = payload->scaling_width;
            h = payload->scaling_height;
        } else {
            w = payload->rotated_width;
            h = payload->rotated_height;

            checkCrop(srcX, srcY, srcW, srcH, payload->coded_width, payload->coded_height);
        }

        uint32_t format = grallocMapper.getFormat();
        // this is for sw decode with tiled buffer in landscape mode
        if (payload->tiling)
            format = OMX_INTEL_COLOR_FormatYUV420PackedSemiPlanar_Tiled;

        // calculate stride
        switch (format) {
        case HAL_PIXEL_FORMAT_YV12:
        case HAL_PIXEL_FORMAT_I420:
            uint32_t yStride_align;
            yStride_align = DisplayQuery::getOverlayLumaStrideAlignment(grallocMapper.getFormat());
            if (yStride_align > 0)
            {
                yStride = align_to(align_to(w, 32), yStride_align);
            }
            else
            {
                yStride = align_to(align_to(w, 32), 64);
            }
            uvStride = align_to(yStride >> 1, 64);
            stride.yuv.yStride = yStride;
            stride.yuv.uvStride = uvStride;
            break;
        case HAL_PIXEL_FORMAT_NV12:
            yStride = align_to(align_to(w, 32), 64);
            uvStride = yStride;
            stride.yuv.yStride = yStride;
            stride.yuv.uvStride = uvStride;
            break;
        case OMX_INTEL_COLOR_FormatYUV420PackedSemiPlanar:
        case OMX_INTEL_COLOR_FormatYUV420PackedSemiPlanar_Tiled:
            if (mUseScaledBuffer) {
                stride.yuv.yStride = payload->scaling_luma_stride;
                stride.yuv.uvStride = payload->scaling_chroma_u_stride;
            } else {
               yStride = align_to(align_to(w, 32), 64);
               uvStride = yStride;
               stride.yuv.yStride = yStride;
               stride.yuv.uvStride = uvStride;
            }
            break;
        case HAL_PIXEL_FORMAT_YUY2:
        case HAL_PIXEL_FORMAT_UYVY:
            yStride = align_to((align_to(w, 32) << 1), 64);
            uvStride = 0;
            stride.yuv.yStride = yStride;
            stride.yuv.uvStride = uvStride;
            break;
        }

        DataBuffer buf(khandle);
        // update buffer
        buf.setStride(stride);
        buf.setWidth(w);
        buf.setHeight(h);
        buf.setCrop(srcX, srcY, srcW, srcH);
        buf.setFormat(format);

        // create buffer mapper
        bool res = false;
        do {
            mapper = new TTMBufferMapper(*mWsbm, buf);
            if (!mapper) {
                ETRACE("failed to allocate mapper");
                break;
            }
            // map ttm buffer
            ret = mapper->map();
            if (!ret) {
                ETRACE("failed to map");
                invalidateTTMBuffers();
                ret = mapper->map();
                if (!ret) {
                    ETRACE("failed to remap");
                    break;
                }
            }

            if (mTTMBuffers.size() >= OVERLAY_DATA_BUFFER_COUNT) {
                invalidateTTMBuffers();
            }

            // add mapper
            index = mTTMBuffers.add(khandle, mapper);
            if (index < 0) {
                ETRACE("failed to add TTMMapper");
                break;
            }

            // increase mapper refCount since it is added to mTTMBuffers
            mapper->incRef();
            res = true;
        } while (0);

        if (!res) {
            // error handling
            if (mapper) {
                mapper->unmap();
                delete mapper;
                mapper = NULL;
            }
            return 0;
        }
    } else {
        VTRACE("got mapper in saved ttm buffers");
        mapper = reinterpret_cast<TTMBufferMapper *>(mTTMBuffers.valueAt(index));
        if (mapper->getCrop().x != srcX || mapper->getCrop().y != srcY ||
            mapper->getCrop().w != srcW || mapper->getCrop().h != srcH) {
            if(!mUseScaledBuffer)
               checkCrop(srcX, srcY, srcW, srcH, payload->coded_width, payload->coded_height);
            mapper->setCrop(srcX, srcY, srcW, srcH);
        }
    }

    XTRACE();
    return mapper;
}

void OverlayPlaneBase::putTTMMapper(BufferMapper* mapper)
{
    if (!mapper)
        return;

    if (!mapper->decRef()) {
        // unmap it
        mapper->unmap();

        // destroy this mapper
        delete mapper;
    }
}

bool OverlayPlaneBase::isActiveTTMBuffer(BufferMapper *mapper)
{
    for (size_t i = 0; i < mActiveTTMBuffers.size(); i++) {
        BufferMapper *activeMapper = mActiveTTMBuffers.itemAt(i);
        if (!activeMapper)
            continue;
        if (activeMapper->getKey() == mapper->getKey())
            return true;
    }

    return false;
}

void OverlayPlaneBase::updateActiveTTMBuffers(BufferMapper *mapper)
{
    // unmap the first entry (oldest buffer)
    if (mActiveTTMBuffers.size() >= MAX_ACTIVE_TTM_BUFFERS) {
        BufferMapper *oldest = mActiveTTMBuffers.itemAt(0);
        putTTMMapper(oldest);
        mActiveTTMBuffers.removeAt(0);
    }

    // queue it to cached buffers
    if (!isActiveTTMBuffer(mapper)) {
        mapper->incRef();
        mActiveTTMBuffers.push_back(mapper);
    }
}

void OverlayPlaneBase::invalidateActiveTTMBuffers()
{
    BufferMapper* mapper;

    RETURN_VOID_IF_NOT_INIT();

    for (size_t i = 0; i < mActiveTTMBuffers.size(); i++) {
        mapper = mActiveTTMBuffers.itemAt(i);
        // unmap it
        putTTMMapper(mapper);
    }

    // clear recorded data buffers
    mActiveTTMBuffers.clear();
}

void OverlayPlaneBase::invalidateTTMBuffers()
{
    BufferMapper* mapper;
    for (size_t i = 0; i < mTTMBuffers.size(); i++) {
        mapper = mTTMBuffers.valueAt(i);
        // putTTMMapper removes mapper from cache
        putTTMMapper(mapper);
    }
    mTTMBuffers.clear();
}


bool OverlayPlaneBase::rotatedBufferReady(BufferMapper& mapper, BufferMapper* &rotatedMapper)
{
    struct VideoPayloadBuffer *payload;
    uint32_t format;

    // only NV12_VED has rotated buffer
    format = mapper.getFormat();
    if (format != OMX_INTEL_COLOR_FormatYUV420PackedSemiPlanar &&
        format != OMX_INTEL_COLOR_FormatYUV420PackedSemiPlanar_Tiled)
        return false;

    payload = (struct VideoPayloadBuffer *)mapper.getCpuAddress(SUB_BUFFER1);
    // check payload
    if (!payload) {
        ETRACE("no payload found");
        return false;
    }

    if (payload->force_output_method == FORCE_OUTPUT_GPU)
        return false;

    if (payload->client_transform != mTransform) {
        if (payload->surface_protected) {
            payload->hwc_timestamp = systemTime();
            payload->layer_transform = mTransform;
        }
        WTRACE("client is not ready");
        return false;
    }

    rotatedMapper = getTTMMapper(mapper, payload);
    return true;
}


bool OverlayPlaneBase::useOverlayRotation(BufferMapper& mapper)
{
    // by default overlay plane does not support rotation.
    return false;
}

bool OverlayPlaneBase::scaledBufferReady(BufferMapper& mapper, BufferMapper* &scaledMapper, VideoPayloadBuffer *payload)
{
    return false;
}

void OverlayPlaneBase::checkPosition(int& x, int& y, int& w, int& h)
{
    drmModeModeInfoPtr mode = &mModeInfo;

    if (mode->hdisplay == 0 || mode->vdisplay == 0)
        return;

    if (x < 0)
        x = 0;
    if (y < 0)
        y = 0;
    if ((x + w) > mode->hdisplay)
        w = mode->hdisplay - x;
    if ((y + h) > mode->vdisplay)
        h = mode->vdisplay - y;
}

void OverlayPlaneBase::checkCrop(int& srcX, int& srcY, int& srcW, int& srcH,
                               int coded_width, int coded_height)
{
    int tmp;

    if (mTransform)
        srcH >>= mBobDeinterlace;

    if (mTransform == HWC_TRANSFORM_ROT_90 || mTransform == HWC_TRANSFORM_ROT_270) {
        tmp = srcH;
        srcH = srcW;
        srcW = tmp;

        tmp = srcX;
        srcX = srcY;
        srcY = tmp;

        tmp = coded_width;
        coded_width = coded_height;
        coded_height = tmp;
    }

    // skip pading bytes in rotate buffer
    switch(mTransform) {
    case HWC_TRANSFORM_ROT_90:
        srcX = (coded_width >> mBobDeinterlace) - srcW - srcX;
        break;
    case HWC_TRANSFORM_ROT_180:
        srcX = coded_width - srcW - srcX;
        srcY = (coded_height >> mBobDeinterlace) - srcH - srcY;
        break;
    case HWC_TRANSFORM_ROT_270:
        srcY = coded_height - srcH - srcY;
        break;
    default:
        break;
    }
}


bool OverlayPlaneBase::bufferOffsetSetup(BufferMapper& mapper)
{
    CTRACE();

    OverlayBackBufferBlk *backBuffer = mBackBuffer[mCurrent]->buf;
    if (!backBuffer) {
        ETRACE("invalid back buffer");
        return false;
    }

    uint32_t format = mapper.getFormat();
    uint32_t gttOffsetInBytes = (mapper.getGttOffsetInPage(0) << 12);
    uint32_t yStride = mapper.getStride().yuv.yStride;
    uint32_t uvStride = mapper.getStride().yuv.uvStride;
    uint32_t w = mapper.getWidth();
    uint32_t h = mapper.getHeight();
    uint32_t srcX= mapper.getCrop().x;
    uint32_t srcY= mapper.getCrop().y;

    // clear original format setting
    backBuffer->OCMD &= ~(0xf << 10);
    backBuffer->OCMD &= ~OVERLAY_MEMORY_LAYOUT_TILED;

    // Y/U/V plane must be 4k bytes aligned.
    backBuffer->OSTART_0Y = gttOffsetInBytes;
    if (mIsProtectedBuffer) {
        // temporary workaround until vsync event logic is corrected.
        // it seems that overlay buffer update and renderring can be overlapped,
        // as such encryption bit may be cleared during HW rendering
        backBuffer->OSTART_0Y |= 0x01;
    }

    backBuffer->OSTART_0U = gttOffsetInBytes;
    backBuffer->OSTART_0V = gttOffsetInBytes;

    backBuffer->OSTART_1Y = backBuffer->OSTART_0Y;
    backBuffer->OSTART_1U = backBuffer->OSTART_0U;
    backBuffer->OSTART_1V = backBuffer->OSTART_0V;

    switch(format) {
    case HAL_PIXEL_FORMAT_YV12:    // YV12
        backBuffer->OBUF_0Y = 0;
        backBuffer->OBUF_0V = yStride * h;
        backBuffer->OBUF_0U = backBuffer->OBUF_0V + (uvStride * (h / 2));
        backBuffer->OCMD |= OVERLAY_FORMAT_PLANAR_YUV420;
        break;
    case HAL_PIXEL_FORMAT_I420:    // I420
        backBuffer->OBUF_0Y = 0;
        backBuffer->OBUF_0U = yStride * h;
        backBuffer->OBUF_0V = backBuffer->OBUF_0U + (uvStride * (h / 2));
        backBuffer->OCMD |= OVERLAY_FORMAT_PLANAR_YUV420;
        break;
    case HAL_PIXEL_FORMAT_NV12:    // NV12
        backBuffer->OBUF_0Y = 0;
        backBuffer->OBUF_0U = yStride * h;
        backBuffer->OBUF_0V = 0;
        backBuffer->OCMD |= OVERLAY_FORMAT_PLANAR_NV12_2;
        break;
    // NOTE: this is the decoded video format, align the height to 32B
    //as it's defined by video driver
    case OMX_INTEL_COLOR_FormatYUV420PackedSemiPlanar:    // Intel codec NV12
        backBuffer->OBUF_0Y = 0;
        backBuffer->OBUF_0U = yStride * align_to(h, 32);
        backBuffer->OBUF_0V = 0;
        backBuffer->OCMD |= OVERLAY_FORMAT_PLANAR_NV12_2;
        break;
    case OMX_INTEL_COLOR_FormatYUV420PackedSemiPlanar_Tiled:  //NV12_tiled
        backBuffer->OBUF_0Y = 0;
        backBuffer->OBUF_0U = yStride * align_to(h, 32);
        backBuffer->OBUF_0V = 0;
        backBuffer->OSTART_0U += yStride * align_to(h, 32);
        backBuffer->OSTART_0V += yStride * align_to(h, 32);
        backBuffer->OSTART_1U = backBuffer->OSTART_0U;
        backBuffer->OSTART_1V = backBuffer->OSTART_0V;
        backBuffer->OTILEOFF_0Y = srcX + (srcY << 16);
        backBuffer->OTILEOFF_1Y = backBuffer->OTILEOFF_0Y;
        backBuffer->OTILEOFF_0U = srcX + ((srcY / 2) << 16);
        backBuffer->OTILEOFF_1U = backBuffer->OTILEOFF_0U;
        backBuffer->OTILEOFF_0V = backBuffer->OTILEOFF_0U;
        backBuffer->OTILEOFF_1V = backBuffer->OTILEOFF_0U;
        backBuffer->OCMD |= OVERLAY_FORMAT_PLANAR_NV12_2;
        backBuffer->OCMD |= OVERLAY_MEMORY_LAYOUT_TILED;
        break;
    case HAL_PIXEL_FORMAT_YUY2:    // YUY2
        backBuffer->OBUF_0Y = 0;
        backBuffer->OBUF_0U = 0;
        backBuffer->OBUF_0V = 0;
        backBuffer->OCMD |= OVERLAY_FORMAT_PACKED_YUV422;
        backBuffer->OCMD |= OVERLAY_PACKED_ORDER_YUY2;
        break;
    case HAL_PIXEL_FORMAT_UYVY:    // UYVY
        backBuffer->OBUF_0Y = 0;
        backBuffer->OBUF_0U = 0;
        backBuffer->OBUF_0V = 0;
        backBuffer->OCMD |= OVERLAY_FORMAT_PACKED_YUV422;
        backBuffer->OCMD |= OVERLAY_PACKED_ORDER_UYVY;
        break;
    default:
        ETRACE("unsupported format %d", format);
        return false;
    }

    backBuffer->OBUF_0Y += srcY * yStride + srcX;
    backBuffer->OBUF_0V += (srcY / 2) * uvStride + srcX;
    backBuffer->OBUF_0U += (srcY / 2) * uvStride + srcX;
    backBuffer->OBUF_1Y = backBuffer->OBUF_0Y;
    backBuffer->OBUF_1U = backBuffer->OBUF_0U;
    backBuffer->OBUF_1V = backBuffer->OBUF_0V;

    VTRACE("done. offset (%d, %d, %d)",
          backBuffer->OBUF_0Y,
          backBuffer->OBUF_0U,
          backBuffer->OBUF_0V);
    return true;
}

uint32_t OverlayPlaneBase::calculateSWidthSW(uint32_t offset, uint32_t width)
{
    ATRACE("offset = %d, width = %d", offset, width);

    uint32_t swidth = ((offset + width + 0x3F) >> 6) - (offset >> 6);

    swidth <<= 1;
    swidth -= 1;

    return swidth;
}

bool OverlayPlaneBase::coordinateSetup(BufferMapper& mapper)
{
    CTRACE();

    OverlayBackBufferBlk *backBuffer = mBackBuffer[mCurrent]->buf;
    if (!backBuffer) {
        ETRACE("invalid back buffer");
        return false;
    }

    uint32_t swidthy = 0;
    uint32_t swidthuv = 0;
    uint32_t format = mapper.getFormat();
    uint32_t width = mapper.getCrop().w;
    uint32_t height = mapper.getCrop().h;
    uint32_t yStride = mapper.getStride().yuv.yStride;
    uint32_t uvStride = mapper.getStride().yuv.uvStride;
    uint32_t offsety = backBuffer->OBUF_0Y;
    uint32_t offsetu = backBuffer->OBUF_0U;

    switch (format) {
    case HAL_PIXEL_FORMAT_YV12:              // YV12
    case HAL_PIXEL_FORMAT_I420:              // I420
    case HAL_PIXEL_FORMAT_NV12:              // NV12
    case OMX_INTEL_COLOR_FormatYUV420PackedSemiPlanar:          // NV12
    case OMX_INTEL_COLOR_FormatYUV420PackedSemiPlanar_Tiled:    // NV12_tiled
        break;
    case HAL_PIXEL_FORMAT_YUY2:              // YUY2
    case HAL_PIXEL_FORMAT_UYVY:              // UYVY
        width <<= 1;
        break;
    default:
        ETRACE("unsupported format %d", format);
        return false;
    }

    if (width <= 0 || height <= 0) {
        ETRACE("invalid src dim");
        return false;
    }

    if (yStride <=0 && uvStride <= 0) {
        ETRACE("invalid source stride");
        return false;
    }

    backBuffer->SWIDTH = width | ((width / 2) << 16);
    swidthy = calculateSWidthSW(offsety, width);
    swidthuv = calculateSWidthSW(offsetu, width / 2);
    backBuffer->SWIDTHSW = (swidthy << 2) | (swidthuv << 18);
    backBuffer->SHEIGHT = height | ((height / 2) << 16);
    backBuffer->OSTRIDE = (yStride & (~0x3f)) | ((uvStride & (~0x3f)) << 16);

    XTRACE();

    return true;
}

bool OverlayPlaneBase::setCoeffRegs(double *coeff, int mantSize,
                                  coeffPtr pCoeff, int pos)
{
    int maxVal, icoeff, res;
    int sign;
    double c;

    sign = 0;
    maxVal = 1 << mantSize;
    c = *coeff;
    if (c < 0.0) {
        sign = 1;
        c = -c;
    }

    res = 12 - mantSize;
    if ((icoeff = (int)(c * 4 * maxVal + 0.5)) < maxVal) {
        pCoeff[pos].exponent = 3;
        pCoeff[pos].mantissa = icoeff << res;
        *coeff = (double)icoeff / (double)(4 * maxVal);
    } else if ((icoeff = (int)(c * 2 * maxVal + 0.5)) < maxVal) {
        pCoeff[pos].exponent = 2;
        pCoeff[pos].mantissa = icoeff << res;
        *coeff = (double)icoeff / (double)(2 * maxVal);
    } else if ((icoeff = (int)(c * maxVal + 0.5)) < maxVal) {
        pCoeff[pos].exponent = 1;
        pCoeff[pos].mantissa = icoeff << res;
        *coeff = (double)icoeff / (double)(maxVal);
    } else if ((icoeff = (int)(c * maxVal * 0.5 + 0.5)) < maxVal) {
        pCoeff[pos].exponent = 0;
        pCoeff[pos].mantissa = icoeff << res;
        *coeff = (double)icoeff / (double)(maxVal / 2);
    } else {
        // Coeff out of range
        return false;
    }

    pCoeff[pos].sign = sign;
    if (sign)
        *coeff = -(*coeff);
    return true;
}

void OverlayPlaneBase::updateCoeff(int taps, double fCutoff,
                                 bool isHoriz, bool isY,
                                 coeffPtr pCoeff)
{
    int i, j, j1, num, pos, mantSize;
    double pi = 3.1415926535, val, sinc, window, sum;
    double rawCoeff[MAX_TAPS * 32], coeffs[N_PHASES][MAX_TAPS];
    double diff;
    int tapAdjust[MAX_TAPS], tap2Fix;
    bool isVertAndUV;

    if (isHoriz)
        mantSize = 7;
    else
        mantSize = 6;

    isVertAndUV = !isHoriz && !isY;
    num = taps * 16;
    for (i = 0; i < num  * 2; i++) {
        val = (1.0 / fCutoff) * taps * pi * (i - num) / (2 * num);
        if (val == 0.0)
            sinc = 1.0;
        else
            sinc = sin(val) / val;

        // Hamming window
        window = (0.54 - 0.46 * cos(2 * i * pi / (2 * num - 1)));
        rawCoeff[i] = sinc * window;
    }

    for (i = 0; i < N_PHASES; i++) {
        // Normalise the coefficients
        sum = 0.0;
        for (j = 0; j < taps; j++) {
            pos = i + j * 32;
            sum += rawCoeff[pos];
        }
        for (j = 0; j < taps; j++) {
            pos = i + j * 32;
            coeffs[i][j] = rawCoeff[pos] / sum;
        }

        // Set the register values
        for (j = 0; j < taps; j++) {
            pos = j + i * taps;
            if ((j == (taps - 1) / 2) && !isVertAndUV)
                setCoeffRegs(&coeffs[i][j], mantSize + 2, pCoeff, pos);
            else
                setCoeffRegs(&coeffs[i][j], mantSize, pCoeff, pos);
        }

        tapAdjust[0] = (taps - 1) / 2;
        for (j = 1, j1 = 1; j <= tapAdjust[0]; j++, j1++) {
            tapAdjust[j1] = tapAdjust[0] - j;
            tapAdjust[++j1] = tapAdjust[0] + j;
        }

        // Adjust the coefficients
        sum = 0.0;
        for (j = 0; j < taps; j++)
            sum += coeffs[i][j];
        if (sum != 1.0) {
            for (j1 = 0; j1 < taps; j1++) {
                tap2Fix = tapAdjust[j1];
                diff = 1.0 - sum;
                coeffs[i][tap2Fix] += diff;
                pos = tap2Fix + i * taps;
                if ((tap2Fix == (taps - 1) / 2) && !isVertAndUV)
                    setCoeffRegs(&coeffs[i][tap2Fix], mantSize + 2, pCoeff, pos);
                else
                    setCoeffRegs(&coeffs[i][tap2Fix], mantSize, pCoeff, pos);

                sum = 0.0;
                for (j = 0; j < taps; j++)
                    sum += coeffs[i][j];
                if (sum == 1.0)
                    break;
            }
        }
    }
}

bool OverlayPlaneBase::scalingSetup(BufferMapper& mapper)
{
    int xscaleInt, xscaleFract, yscaleInt, yscaleFract;
    int xscaleIntUV, xscaleFractUV;
    int yscaleIntUV, yscaleFractUV;
    int deinterlace_factor = 1;
    // UV is half the size of Y -- YUV420
    int uvratio = 2;
    uint32_t newval;
    coeffRec xcoeffY[N_HORIZ_Y_TAPS * N_PHASES];
    coeffRec xcoeffUV[N_HORIZ_UV_TAPS * N_PHASES];
    int i, j, pos;
    bool scaleChanged = false;
    int x, y, w, h;

    OverlayBackBufferBlk *backBuffer = mBackBuffer[mCurrent]->buf;
    if (!backBuffer) {
        ETRACE("invalid back buffer");
        return false;
    }

    x = mPosition.x;
    y = mPosition.y;
    w = mPosition.w;
    h = mPosition.h;

    // check position
    checkPosition(x, y, w, h);
    VTRACE("final position (%d, %d, %d, %d)", x, y, w, h);

    if ((w <= 0) || (h <= 0)) {
         ETRACE("invalid dst width/height");
         return false;
    }

    // setup dst position
    backBuffer->DWINPOS = (y << 16) | x;
    backBuffer->DWINSZ = (h << 16) | w;

    uint32_t srcWidth = mapper.getCrop().w;
    uint32_t srcHeight = mapper.getCrop().h;
    uint32_t dstWidth = w;
    uint32_t dstHeight = h;

    if (mBobDeinterlace && !mTransform)
        deinterlace_factor = 2;

    VTRACE("src (%dx%d), dst (%dx%d)",
          srcWidth, srcHeight,
          dstWidth, dstHeight);

     // Y down-scale factor as a multiple of 4096
    if (srcWidth == dstWidth && srcHeight == dstHeight) {
        xscaleFract = (1 << 12);
        yscaleFract = (1 << 12)/deinterlace_factor;
    } else {
        xscaleFract = ((srcWidth - 1) << 12) / dstWidth;
        yscaleFract = ((srcHeight - 1) << 12) / (dstHeight * deinterlace_factor);
    }

    // Calculate the UV scaling factor
    xscaleFractUV = xscaleFract / uvratio;
    yscaleFractUV = yscaleFract / uvratio;

    // To keep the relative Y and UV ratios exact, round the Y scales
    // to a multiple of the Y/UV ratio.
    xscaleFract = xscaleFractUV * uvratio;
    yscaleFract = yscaleFractUV * uvratio;

    // Integer (un-multiplied) values
    xscaleInt = xscaleFract >> 12;
    yscaleInt = yscaleFract >> 12;

    xscaleIntUV = xscaleFractUV >> 12;
    yscaleIntUV = yscaleFractUV >> 12;

    // Check scaling ratio
    if (xscaleInt > INTEL_OVERLAY_MAX_SCALING_RATIO) {
        ETRACE("xscaleInt > %d", INTEL_OVERLAY_MAX_SCALING_RATIO);
        return false;
    }

    // shouldn't get here
    if (xscaleIntUV > INTEL_OVERLAY_MAX_SCALING_RATIO) {
        ETRACE("xscaleIntUV > %d", INTEL_OVERLAY_MAX_SCALING_RATIO);
        return false;
    }

    newval = (xscaleInt << 15) |
    ((xscaleFract & 0xFFF) << 3) | ((yscaleFract & 0xFFF) << 20);
    if (newval != backBuffer->YRGBSCALE) {
        scaleChanged = true;
        backBuffer->YRGBSCALE = newval;
    }

    newval = (xscaleIntUV << 15) | ((xscaleFractUV & 0xFFF) << 3) |
    ((yscaleFractUV & 0xFFF) << 20);
    if (newval != backBuffer->UVSCALE) {
        scaleChanged = true;
        backBuffer->UVSCALE = newval;
    }

    newval = yscaleInt << 16 | yscaleIntUV;
    if (newval != backBuffer->UVSCALEV) {
        scaleChanged = true;
        backBuffer->UVSCALEV = newval;
    }

    // Recalculate coefficients if the scaling changed
    // Only Horizontal coefficients so far.
    if (scaleChanged) {
        double fCutoffY;
        double fCutoffUV;

        fCutoffY = xscaleFract / 4096.0;
        fCutoffUV = xscaleFractUV / 4096.0;

        // Limit to between 1.0 and 3.0
        if (fCutoffY < MIN_CUTOFF_FREQ)
            fCutoffY = MIN_CUTOFF_FREQ;
        if (fCutoffY > MAX_CUTOFF_FREQ)
            fCutoffY = MAX_CUTOFF_FREQ;
        if (fCutoffUV < MIN_CUTOFF_FREQ)
            fCutoffUV = MIN_CUTOFF_FREQ;
        if (fCutoffUV > MAX_CUTOFF_FREQ)
            fCutoffUV = MAX_CUTOFF_FREQ;

        updateCoeff(N_HORIZ_Y_TAPS, fCutoffY, true, true, xcoeffY);
        updateCoeff(N_HORIZ_UV_TAPS, fCutoffUV, true, false, xcoeffUV);

        for (i = 0; i < N_PHASES; i++) {
            for (j = 0; j < N_HORIZ_Y_TAPS; j++) {
                pos = i * N_HORIZ_Y_TAPS + j;
                backBuffer->Y_HCOEFS[pos] =
                        (xcoeffY[pos].sign << 15 |
                         xcoeffY[pos].exponent << 12 |
                         xcoeffY[pos].mantissa);
            }
        }
        for (i = 0; i < N_PHASES; i++) {
            for (j = 0; j < N_HORIZ_UV_TAPS; j++) {
                pos = i * N_HORIZ_UV_TAPS + j;
                backBuffer->UV_HCOEFS[pos] =
                         (xcoeffUV[pos].sign << 15 |
                          xcoeffUV[pos].exponent << 12 |
                          xcoeffUV[pos].mantissa);
            }
        }
    }

    XTRACE();
    return true;
}

bool OverlayPlaneBase::colorSetup(BufferMapper& mapper)
{
    CTRACE();

    OverlayBackBufferBlk *backBuffer = mBackBuffer[mCurrent]->buf;
    if (!backBuffer) {
        ETRACE("invalid back buffer");
        return false;
    }

    uint32_t format = mapper.getFormat();
    if (format != OMX_INTEL_COLOR_FormatYUV420PackedSemiPlanar &&
        format != OMX_INTEL_COLOR_FormatYUV420PackedSemiPlanar_Tiled) {

        VTRACE("Not video layer, use default color setting");
        backBuffer->OCLRC0 = (OVERLAY_INIT_CONTRAST << 18) |
                         (OVERLAY_INIT_BRIGHTNESS & 0xff);
        backBuffer->OCLRC1 = OVERLAY_INIT_SATURATION;
        backBuffer->OCONFIG &= ~(1 << 5);

        return true;
    }

    struct VideoPayloadBuffer *payload;
    payload = (struct VideoPayloadBuffer *)mapper.getCpuAddress(SUB_BUFFER1);
    // check payload
    if (!payload) {
        ETRACE("no payload found");
        return false;
    }

    // BT.601 or BT.709
    backBuffer->OCONFIG &= ~(1 << 5);
    backBuffer->OCONFIG |= ((payload->csc_mode & 1) << 5);

    // no level expansion for video on HDMI
    if (payload->video_range || mPipeConfig == (0x2 << 6)) {
        // full range, no need to do level expansion
        backBuffer->OCLRC0 = 0x1000000;
        backBuffer->OCLRC1 = 0x80;
    } else {
        // level expansion for limited range
        backBuffer->OCLRC0 = (OVERLAY_INIT_CONTRAST << 18) |
                         (OVERLAY_INIT_BRIGHTNESS & 0xff);
        backBuffer->OCLRC1 = OVERLAY_INIT_SATURATION;
    }

    return true;
}

bool OverlayPlaneBase::setDataBuffer(BufferMapper& grallocMapper)
{
    BufferMapper *mapper;
    BufferMapper *videoBufferMapper = 0;
    bool ret;
    uint32_t format;

    RETURN_FALSE_IF_NOT_INIT();

    // get gralloc mapper
    mapper = &grallocMapper;
    format = grallocMapper.getFormat();
    if (format == OMX_INTEL_COLOR_FormatYUV420PackedSemiPlanar ||
        format == OMX_INTEL_COLOR_FormatYUV420PackedSemiPlanar_Tiled) {
        struct VideoPayloadBuffer *payload;
        payload = (struct VideoPayloadBuffer *)grallocMapper.getCpuAddress(SUB_BUFFER1);
        if (!payload) {
            ETRACE("invalid payload buffer");
            return 0;
        }

        mBobDeinterlace = payload->bob_deinterlace;

        int srcW, srcH;
        srcW = grallocMapper.getCrop().w - grallocMapper.getCrop().x;
        srcH = grallocMapper.getCrop().h - grallocMapper.getCrop().y;
        if ((srcW > INTEL_OVERLAY_MAX_WIDTH - 1) || (srcH > INTEL_OVERLAY_MAX_HEIGHT - 1)) {
            if (mTransform) {
                int x, y, w, h;
                x = mSrcCrop.x;
                y = mSrcCrop.y;
                w = mSrcCrop.w;
                h = mSrcCrop.h;
                setSourceCrop(0, 0, payload->scaling_width, payload->scaling_height);
                if (!useOverlayRotation(grallocMapper)) {
                    DTRACE("The scaled buffer will hit overlay rotation limitation, fall back to GLES");
                    setSourceCrop(x, y, w, h);
                    return false;
                }
            }

            if (!scaledBufferReady(grallocMapper, videoBufferMapper, payload)) {
                DTRACE("scaled buffer is not ready, fall back to GLES");
                return false;
            } else {
                videoBufferMapper->setFormat(OMX_INTEL_COLOR_FormatYUV420PackedSemiPlanar);
                mapper = videoBufferMapper;
            }
        }
    }

    if (!mUseScaledBuffer && mTransform && !useOverlayRotation(grallocMapper)) {
        if (!rotatedBufferReady(grallocMapper, videoBufferMapper)) {
            DTRACE("rotated buffer is not ready");
            return false;
        }

        if (!videoBufferMapper) {
            ETRACE("failed to get rotated buffer");
            return false;
        }
        mapper = videoBufferMapper;
    }

    OverlayBackBufferBlk *backBuffer = mBackBuffer[mCurrent]->buf;
    if (!backBuffer) {
        ETRACE("invalid back buffer");
        return false;
    }

    ret = bufferOffsetSetup(*mapper);
    if (ret == false) {
        ETRACE("failed to set up buffer offsets");
        return false;
    }

    ret = coordinateSetup(*mapper);
    if (ret == false) {
        ETRACE("failed to set up overlay coordinates");
        return false;
    }

    ret = scalingSetup(*mapper);
    if (ret == false) {
        ETRACE("failed to set up scaling parameters");
        return false;
    }

    backBuffer->OCMD |= 0x1;

    ret = colorSetup(grallocMapper);
    if (ret == false) {
        ETRACE("failed to set up color parameters");
        return false;
    }
    if (mBobDeinterlace && !mTransform) {
        backBuffer->OCMD |= BUF_TYPE_FIELD;
        backBuffer->OCMD &= ~FIELD_SELECT;
        backBuffer->OCMD |= FIELD0;
        backBuffer->OCMD &= ~(BUFFER_SELECT);
        backBuffer->OCMD |= BUFFER0;
    }

    // add to active ttm buffers if it's a rotated buffer
    if (videoBufferMapper) {
        updateActiveTTMBuffers(mapper);
    }

    mUseScaledBuffer = 0;
    return true;
}

} // namespace intel
} // namespace android