/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * 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 <hardware/exynos/ion.h>
#include "ExynosDisplayDrmInterfaceModule.h"
#include "ExynosPrimaryDisplayModule.h"

using namespace gs201;

/////////////////////////////////////////////////// ExynosDisplayDrmInterfaceModule //////////////////////////////////////////////////////////////////
ExynosDisplayDrmInterfaceModule::ExynosDisplayDrmInterfaceModule(ExynosDisplay *exynosDisplay)
  : gs101::ExynosDisplayDrmInterfaceModule(exynosDisplay)
{
}

ExynosDisplayDrmInterfaceModule::~ExynosDisplayDrmInterfaceModule()
{
    for (auto p: mCGCDataInfos) {
        if (p.second != nullptr)
            munmap(p.second, sizeCgcDmaLut);

        if (p.first > 0)
            close(p.first);
    }
}

int32_t ExynosDisplayDrmInterfaceModule::initDrmDevice(DrmDevice *drmDevice)
{
    int ret = gs101::ExynosDisplayDrmInterfaceModule::initDrmDevice(drmDevice);
    if (ret != NO_ERROR)
        return ret;

    /* create file descriptors for CGC DMA */
    int32_t fd;
    struct cgc_dma_lut *buf;

    int ionFd = exynos_ion_open();
    if (ionFd >= 0) {
        while (mCGCDataInfos.size() < sizeCgCDataInfo) {
            fd = exynos_ion_alloc(ionFd, sizeCgcDmaLut, EXYNOS_ION_HEAP_SYSTEM_MASK, 0);
            if (fd >= 0) {
                buf = (struct cgc_dma_lut *)mmap(0, sizeCgcDmaLut, PROT_READ | PROT_WRITE,
                                                 MAP_SHARED, fd, 0);
                if (buf == nullptr) {
                    ALOGE("Failed to map buffer for CGC_DMA LUT");
                    close(fd);
                    ret = -ENOMEM;
                    break;
                } else {
                    memset(buf, 0, sizeCgcDmaLut);
                    mCGCDataInfos.emplace_back(CGCDataInfo(fd, buf));
                }
            } else {
                ALOGE("Failed to allocate ION for CGC_DMA LUT");
                ret = -ENOMEM;
                break;
            }
        }

        exynos_ion_close(ionFd);
    } else
        ALOGE("Failed to open ION for CGC_DMA LUT");

    return ret;
}

int32_t ExynosDisplayDrmInterfaceModule::createCgcDMAFromIDqe(
        const GsInterfaceType::IDqe::CgcData &cgcData)
{
    if ((cgcData.config->r_values.size() != DRM_SAMSUNG_CGC_LUT_REG_CNT) ||
        (cgcData.config->g_values.size() != DRM_SAMSUNG_CGC_LUT_REG_CNT) ||
        (cgcData.config->b_values.size() != DRM_SAMSUNG_CGC_LUT_REG_CNT)) {
        ALOGE("CGC data size is not same (r: %zu, g: %zu: b: %zu)",
                cgcData.config->r_values.size(),
                cgcData.config->g_values.size(),
                cgcData.config->b_values.size());
        return -EINVAL;
    }

    if (iCGCDataInfo >= mCGCDataInfos.size()) {
        ALOGE("CGC Data Infos is empty");
        return -EINVAL;
    }

    struct cgc_dma_lut *buf = mCGCDataInfos.at(iCGCDataInfo).second;
    uint32_t i = 0;
    for (; i < (DRM_SAMSUNG_CGC_LUT_REG_CNT - 1); i++) {
        buf[i * 2].r_value = (uint16_t)(cgcData.config->r_values[i]);
        buf[i * 2].g_value = (uint16_t)(cgcData.config->g_values[i]);
        buf[i * 2].b_value = (uint16_t)(cgcData.config->b_values[i]);
        buf[i * 2 + 1].r_value = (uint16_t)(cgcData.config->r_values[i] >> 16);
        buf[i * 2 + 1].g_value = (uint16_t)(cgcData.config->g_values[i] >> 16);
        buf[i * 2 + 1].b_value = (uint16_t)(cgcData.config->b_values[i] >> 16);
    }
    buf[i * 2].r_value = (uint16_t)cgcData.config->r_values[i];
    buf[i * 2].g_value = (uint16_t)cgcData.config->g_values[i];
    buf[i * 2].b_value = (uint16_t)cgcData.config->b_values[i];

    return mCGCDataInfos.at(iCGCDataInfo).first;
}

int32_t ExynosDisplayDrmInterfaceModule::setCgcLutDmaProperty(
        const DrmProperty &prop,
        ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq)
{
    if (!prop.id())
        return NO_ERROR;

    ExynosDeviceModule* device = static_cast<ExynosDeviceModule*>(mExynosDisplay->mDevice);
	gs101::ColorManager* colorManager = device->getDisplayColorManager(mExynosDisplay);
	if (!colorManager) return NO_ERROR;
    const GsInterfaceType::IDqe &dqe = colorManager->getDqe();
    const GsInterfaceType::IDqe::CgcData &cgcData = dqe.Cgc();

    /* dirty bit is valid only if enable is true */
    if (!mForceDisplayColorSetting && cgcData.enable && !cgcData.dirty)
        return NO_ERROR;

    int32_t ret = 0;
    int32_t cgcLutFd = disabledCgc;

    if (cgcData.enable) {
        if (cgcData.config == nullptr) {
            ALOGE("no CGC config");
            return NO_ERROR;
        }

        cgcLutFd = createCgcDMAFromIDqe(cgcData);
        if (cgcLutFd < 0) {
            HWC_LOGE(mExynosDisplay, "%s: create CGC DMA FD fail", __func__);
            return cgcLutFd;
        }

        iCGCDataInfo = (iCGCDataInfo + 1) % sizeCgCDataInfo;
    }

    /* CGC Disabled information should not be delivered at every frame */
    if (cgcLutFd == disabledCgc && !mCgcEnabled)
        return NO_ERROR;

    /* CGC setting when cgc is enabled and dirty */
    if ((ret = drmReq.atomicAddProperty(mDrmCrtc->id(), prop, cgcLutFd, true)) < 0) {
        HWC_LOGE(mExynosDisplay, "%s: Fail to set cgc_dma_fd property", __func__);
        return ret;
    }
    dqe.Cgc().NotifyDataApplied();

    mCgcEnabled = (cgcLutFd != disabledCgc);

    return NO_ERROR;
}

int32_t ExynosDisplayDrmInterfaceModule::setDisplayColorSetting(
        ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq)
{
    if (!mForceDisplayColorSetting && !mColorSettingChanged)
        return NO_ERROR;

    int32_t ret = gs101::ExynosDisplayDrmInterfaceModule::setDisplayColorSetting(drmReq);
    if (ret != NO_ERROR)
        return ret;

    return setCgcLutDmaProperty(mDrmCrtc->cgc_lut_fd_property(), drmReq);
}

int32_t ExynosDisplayDrmInterfaceModule::setHistoPosProperty(
    const DrmProperty &prop,
    ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq) {
  if (!prop.id()) return NO_ERROR;

  int32_t ret = 0;

  if ((ret = drmReq.atomicAddProperty(
           mDrmCrtc->id(), prop, (uint64_t)mHistogramInfo->getHistogramPos(),
           true)) < 0) {
    HWC_LOGE(mExynosDisplay, "%s: Fail to set histogram position property",
             __func__);
    return ret;
  }

  return NO_ERROR;
}

int32_t ExynosDisplayDrmInterfaceModule::setDisplayHistogramSetting(
    ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq) {
  if ((isHistogramInfoRegistered() == false) || (isPrimary() == false)) return NO_ERROR;

  int32_t ret =
      gs101::ExynosDisplayDrmInterfaceModule::setDisplayHistogramSetting(
          drmReq);
  if (ret != NO_ERROR) return ret;

  ret = setHistoPosProperty(mDrmCrtc->histogram_position_property(), drmReq);

  return ret;
}

void ExynosDisplayDrmInterfaceModule::registerHistogramInfo(
        const std::shared_ptr<IDLHistogram> &info) {
  gs101::ExynosDisplayDrmInterfaceModule::registerHistogramInfo(info);
  mHistogramInfo = info;
}

//////////////////////////////////////////////////// ExynosPrimaryDisplayDrmInterfaceModule //////////////////////////////////////////////////////////////////
ExynosPrimaryDisplayDrmInterfaceModule::ExynosPrimaryDisplayDrmInterfaceModule(ExynosDisplay *exynosDisplay)
  : ExynosDisplayDrmInterfaceModule(exynosDisplay)
{
}

ExynosPrimaryDisplayDrmInterfaceModule::~ExynosPrimaryDisplayDrmInterfaceModule()
{
}