/*
 * Copyright 2015 Rockchip Electronics Co. LTD
 *
 * 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 <string.h>

#include "mpp_mem.h"
#include "mpp_env.h"
#include "mpp_debug.h"
#include "mpp_buffer.h"

#include "vpu.h"
#include "vpu_mem_legacy.h"

#define VPU_MEM_DBG_FUNCTION            (0x00000001)

static RK_U32 vpu_mem_debug = 0;

#define vpu_mem_dbg(flag, fmt, ...)     _mpp_dbg(vpu_mem_debug, flag, fmt, ## __VA_ARGS__)
#define vpu_mem_dbg_f(flag, fmt, ...)   _mpp_dbg_f(vpu_mem_debug, flag, fmt, ## __VA_ARGS__)

#define vpu_mem_dbg_func(fmt, ...)      vpu_mem_dbg_f(VPU_MEM_DBG_FUNCTION, fmt, ## __VA_ARGS__)

static RK_S32
commit_memory_handle(vpu_display_mem_pool *p, RK_S32 mem_hdl, RK_S32 size)
{
    MppBufferInfo info;
    vpu_display_mem_pool_impl *p_mempool = (vpu_display_mem_pool_impl *)p;

    vpu_mem_dbg_func("in  pool %p hnl %p size %d\n", p, mem_hdl, size);
    memset(&info, 0, sizeof(MppBufferInfo));
    info.type = MPP_BUFFER_TYPE_ION;
    info.fd = mem_hdl;
    info.size = size & 0x07ffffff;
    info.index = (size & 0xf8000000) >> 27;

    p_mempool->size = size;
    p_mempool->buff_size = size;

    mpp_buffer_commit(p_mempool->group, &info);
    vpu_mem_dbg_func("out pool %p fd %d\n", p, info.fd);
    return info.fd;
}

static void* get_free_memory_vpumem(vpu_display_mem_pool *p)
{
    MPP_RET ret = MPP_OK;
    MppBuffer buffer = NULL;
    VPUMemLinear_t *dmabuf = mpp_calloc(VPUMemLinear_t, 1);
    vpu_display_mem_pool_impl *p_mempool = (vpu_display_mem_pool_impl *)p;
    if (dmabuf == NULL) {
        return NULL;
    }
    vpu_mem_dbg_func("in  pool %p\n", p);
    ret = mpp_buffer_get(p_mempool->group, &buffer, p_mempool->size);
    if (MPP_OK != ret) {
        mpp_free(dmabuf);
        return NULL;
    }
    dmabuf->phy_addr = (RK_U32)mpp_buffer_get_fd(buffer);
    dmabuf->vir_addr = (RK_U32*)mpp_buffer_get_ptr(buffer);
    dmabuf->size = p_mempool->size;
    dmabuf->offset = (RK_U32*)buffer;
    vpu_mem_dbg_func("out pool %p ret %p fd %d size %d buffer %p\n", p, dmabuf,
                     dmabuf->phy_addr, dmabuf->size, buffer);
    return dmabuf;

}

static RK_S32 inc_used_memory_handle_ref(vpu_display_mem_pool *p, void * hdl)
{
    VPUMemLinear_t *dmabuf = (VPUMemLinear_t *)hdl;
    MppBuffer buffer = (MppBuffer)dmabuf->offset;
    vpu_mem_dbg_func("pool %p hnd %p buffer %p\n", p, hdl, buffer);
    if (buffer != NULL) {
        mpp_buffer_inc_ref(buffer);
    }

    (void)p;
    return MPP_OK;
}

static RK_S32 put_used_memory_handle(vpu_display_mem_pool *p, void *hdl)
{
    VPUMemLinear_t *dmabuf = (VPUMemLinear_t *)hdl;
    MppBuffer buf = (MppBuffer)dmabuf->offset;
    vpu_mem_dbg_func("pool %p hnd %p buffer %p\n", p, hdl, buf);
    if (buf != NULL) {
        mpp_buffer_put(buf);
        memset(dmabuf, 0, sizeof(VPUMemLinear_t));
    }
    (void)p;
    return MPP_OK;
}

static RK_S32 get_free_memory_num(vpu_display_mem_pool *p)
{
    vpu_display_mem_pool_impl *p_mempool = (vpu_display_mem_pool_impl *)p;
    RK_S32 ret = (p_mempool->group) ?
                 (mpp_buffer_group_unused(p_mempool->group)) : (0);

    vpu_mem_dbg_func("pool %p ret %d\n", p, ret);
    return ret;
}

static RK_S32 reset_vpu_mem_pool(vpu_display_mem_pool *p)
{
    vpu_display_mem_pool_impl *p_mempool = (vpu_display_mem_pool_impl *)p;
    mpp_buffer_group_clear(p_mempool->group);
    return 0;
}


vpu_display_mem_pool* open_vpu_memory_pool()
{
    vpu_display_mem_pool_impl *p_mempool =
        mpp_calloc(vpu_display_mem_pool_impl, 1);

    mpp_env_get_u32("vpu_mem_debug", &vpu_mem_debug, 0);
    vpu_mem_dbg_func("in  pool %p\n", p_mempool);

    if (NULL == p_mempool) {
        return NULL;
    }
    mpp_buffer_group_get_external(&p_mempool->group, MPP_BUFFER_TYPE_ION);
    if (NULL == p_mempool->group) {
        return NULL;
    }
    p_mempool->commit_hdl     = commit_memory_handle;
    p_mempool->get_free       = get_free_memory_vpumem;
    p_mempool->put_used       = put_used_memory_handle;
    p_mempool->inc_used       = inc_used_memory_handle_ref;
    p_mempool->reset          = reset_vpu_mem_pool;
    p_mempool->get_unused_num = get_free_memory_num;
    p_mempool->version        = 1;
    p_mempool->buff_size      = -1;

    vpu_mem_dbg_func("out pool %p group %p\n", p_mempool, p_mempool->group);
    return (vpu_display_mem_pool*)p_mempool;
}

void close_vpu_memory_pool(vpu_display_mem_pool *p)
{
    vpu_display_mem_pool_impl *p_mempool = (vpu_display_mem_pool_impl *)p;

    vpu_mem_dbg_func("pool %p group %p\n", p_mempool, p_mempool->group);
    mpp_buffer_group_put(p_mempool->group);
    mpp_free(p_mempool);
    return;
}

int create_vpu_memory_pool_allocator(vpu_display_mem_pool **ipool,
                                     int num, int size)
{
    vpu_display_mem_pool_impl *p_mempool =
        mpp_calloc(vpu_display_mem_pool_impl, 1);

    mpp_env_get_u32("vpu_mem_debug", &vpu_mem_debug, 0);
    vpu_mem_dbg_func("in  pool %p num %d size %d\n", p_mempool, num, size);

    if (NULL == p_mempool)
        return -1;

    mpp_buffer_group_get_internal(&p_mempool->group, MPP_BUFFER_TYPE_ION);
    mpp_buffer_group_limit_config(p_mempool->group, 0, num + 4);
    p_mempool->commit_hdl     = commit_memory_handle;
    p_mempool->get_free       = get_free_memory_vpumem;
    p_mempool->put_used       = put_used_memory_handle;
    p_mempool->inc_used       = inc_used_memory_handle_ref;
    p_mempool->reset          = reset_vpu_mem_pool;
    p_mempool->get_unused_num = get_free_memory_num;
    p_mempool->version        = 0;
    p_mempool->buff_size      = size;
    p_mempool->size           = size;
    *ipool = (vpu_display_mem_pool*)p_mempool;

    vpu_mem_dbg_func("out pool %p group %p\n", p_mempool, p_mempool->group);
    return 0;
}

void release_vpu_memory_pool_allocator(vpu_display_mem_pool *ipool)
{
    vpu_display_mem_pool_impl *p_mempool = (vpu_display_mem_pool_impl *)ipool;
    if (p_mempool == NULL)
        return;

    vpu_mem_dbg_func("pool %p group %p\n", p_mempool, p_mempool->group);

    if (p_mempool->group) {
        mpp_buffer_group_put(p_mempool->group);
        p_mempool->group = NULL;
    }

    vpu_mem_dbg_func("free %p\n", p_mempool);
    mpp_free(p_mempool);
    return;
}

RK_S32 VPUMemJudgeIommu()
{
    int ret = 0;

    if (VPUClientGetIOMMUStatus() > 0) {
        //mpp_err("media.used.iommu");
        ret = 1;
    }

    return ret;
}


RK_S32 VPUMallocLinear(VPUMemLinear_t *p, RK_U32 size)
{
    int ret = 0;
    MppBuffer buffer = NULL;
    ret = mpp_buffer_get(NULL, &buffer, size);
    if (ret != MPP_OK) {
        return -1;
    }
    p->phy_addr = (RK_U32)mpp_buffer_get_fd(buffer);
    p->vir_addr = (RK_U32*)mpp_buffer_get_ptr(buffer);
    p->size = size;
    p->offset = (RK_U32*)buffer;
    return 0;
}

RK_S32 VPUMallocLinearFromRender(VPUMemLinear_t *p, RK_U32 size, void *ctx)
{
    VPUMemLinear_t *dma_buf = NULL;
    vpu_display_mem_pool_impl *p_mempool = (vpu_display_mem_pool_impl *)ctx;
    if (ctx == NULL) {
        return VPUMallocLinear(p, size);
    }
    dma_buf = (VPUMemLinear_t *)
              p_mempool->get_free((vpu_display_mem_pool *)ctx);
    memset(p, 0, sizeof(VPUMemLinear_t));
    if (dma_buf != NULL) {
        if (dma_buf->size < size) {
            mpp_free(dma_buf);
            return -1;
        }
        memcpy(p, dma_buf, sizeof(VPUMemLinear_t));
        mpp_free(dma_buf);
        return 0;
    }
    return -1;
}

RK_S32 VPUFreeLinear(VPUMemLinear_t *p)
{
    if (p->offset != NULL) {
        put_used_memory_handle(NULL, p);
    }
    return 0;
}

RK_S32 VPUMemDuplicate(VPUMemLinear_t *dst, VPUMemLinear_t *src)
{
    MppBuffer buffer = (MppBuffer)src->offset;
    if (buffer != NULL) {
        mpp_buffer_inc_ref(buffer);
    }
    memcpy(dst, src, sizeof(VPUMemLinear_t));
    return 0;
}

RK_S32 VPUMemLink(VPUMemLinear_t *p)
{
    (void)p;
    return 0;
}

RK_S32 VPUMemFlush(VPUMemLinear_t *p)
{
    (void)p;
    return 0;
}

RK_S32 VPUMemClean(VPUMemLinear_t *p)
{
    (void)p;
    return 0;
}


RK_S32 VPUMemInvalidate(VPUMemLinear_t *p)
{
    (void)p;
    return 0;
}

RK_S32 VPUMemGetFD(VPUMemLinear_t *p)
{
    RK_S32 fd = 0;
    MppBuffer buffer = (MppBuffer)p->offset;
    fd = mpp_buffer_get_fd(buffer);
    return fd;
}

