/*
 * Copyright (c) 2011 Intel Corporation. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sub license, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
 * IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors:
 *    Shengquan Yuan  <shengquan.yuan@intel.com>
 *    Forrest Zhang  <forrest.zhang@intel.com>
 *
 */

#include "psb_buffer.h"

#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <wsbm/wsbm_manager.h>

#include "psb_def.h"
#include "psb_drv_debug.h"

#if PSB_MFLD_DUMMY_CODE
static VAStatus psb_buffer_offset_camerav4l2(psb_driver_data_p driver_data,
        psb_buffer_p buf,
        unsigned int v4l2_buf_offset,
        unsigned int *bo_offset
                                            )
{
    *bo_offset = v4l2_buf_offset;
    return VA_STATUS_SUCCESS;
}


static VAStatus psb_buffer_offset_cameraci(psb_driver_data_p driver_data,
        psb_buffer_p buf,
        unsigned int ci_frame_offset_or_handle,
        unsigned int *bo_offset
                                          )
{
    *bo_offset = ci_frame_offset_or_handle;

    return VA_STATUS_SUCCESS;
}


static int psb_buffer_info_ci(psb_driver_data_p driver_data)
{
    struct drm_lnc_video_getparam_arg arg;
    unsigned long camera_info[2] = {0, 0};
    int ret = 0;

    driver_data->camera_phyaddr = driver_data->camera_size = 0;

    arg.key = LNC_VIDEO_GETPARAM_CI_INFO;
    arg.value = (uint64_t)((unsigned long) & camera_info[0]);
    ret = drmCommandWriteRead(driver_data->drm_fd, driver_data->getParamIoctlOffset,
                              &arg, sizeof(arg));
    if (ret == 0) {
        driver_data->camera_phyaddr = camera_info[0];
        driver_data->camera_size = camera_info[1];
        drv_debug_msg(VIDEO_DEBUG_GENERAL, "CI region physical address = 0x%08x, size=%dK\n",
                                 driver_data->camera_phyaddr,  driver_data->camera_size / 1024);

        return ret;
    }

    drv_debug_msg(VIDEO_DEBUG_GENERAL, "CI region get_info failed\n");
    return ret;
}

/*
 * Allocate global BO which maps camear device memory as encode MMU memory
 * the global BO shared by several encode surfaces created from camear memory
 */
static VAStatus psb_buffer_init_camera(psb_driver_data_p driver_data)
{
    int ret = 0;

    /* hasn't grab camera device memory region
     * grab the whole 4M camera device memory
     */
    driver_data->camera_bo = calloc(1, sizeof(struct psb_buffer_s));
    if (driver_data->camera_bo == NULL)
        return VA_STATUS_ERROR_ALLOCATION_FAILED;

    drv_debug_msg(VIDEO_DEBUG_GENERAL, "Grab whole camera device memory\n");
    ret = psb_buffer_create(driver_data, driver_data->camera_size, psb_bt_camera, (psb_buffer_p) driver_data->camera_bo);

    if (ret != VA_STATUS_SUCCESS) {
        free(driver_data->camera_bo);
        driver_data->camera_bo = NULL;

        drv_debug_msg(VIDEO_DEBUG_ERROR, "Grab camera device memory failed\n");
    }

    return ret;
}


/*
 * Create one buffer from camera device memory
 * is_v4l2 means if the buffer is V4L2 buffer
 * id_or_ofs is CI frame ID (actually now is frame offset), or V4L2 buffer offset
 */
VAStatus psb_buffer_create_camera(psb_driver_data_p driver_data,
                                  psb_buffer_p buf,
                                  int is_v4l2,
                                  int id_or_ofs
                                 )
{
    VAStatus vaStatus;
    int ret = 0;
    unsigned int camera_offset = 0;

    if (driver_data->camera_bo  == NULL) {
        if (psb_buffer_info_ci(driver_data)) {
            drv_debug_msg(VIDEO_DEBUG_ERROR, "Can't get CI region information\n");
            return VA_STATUS_ERROR_UNKNOWN;
        }

        vaStatus = psb_buffer_init_camera(driver_data);
        if (vaStatus != VA_STATUS_SUCCESS) {
            drv_debug_msg(VIDEO_DEBUG_ERROR, "Grab camera device memory failed\n");
            return ret;
        }
    }

    /* reference the global camear BO */
    ret = psb_buffer_reference(driver_data, buf, (psb_buffer_p) driver_data->camera_bo);
    if (ret != VA_STATUS_SUCCESS) {
        drv_debug_msg(VIDEO_DEBUG_ERROR, "Reference camera device memory failed\n");
        return ret;
    }

    if (is_v4l2)
        ret = psb_buffer_offset_camerav4l2(driver_data, buf, id_or_ofs, &camera_offset);
    else
        ret = psb_buffer_offset_cameraci(driver_data, buf, id_or_ofs, &camera_offset);

    buf->buffer_ofs = camera_offset;

    return ret;
}

/*
 * Create one buffer from user buffer
 * id_or_ofs is CI frame ID (actually now is frame offset), or V4L2 buffer offset
 * user_ptr :virtual address of user buffer start.
 */
VAStatus psb_buffer_create_camera_from_ub(psb_driver_data_p driver_data,
        psb_buffer_p buf,
        int id_or_ofs,
        int size,
        const unsigned long * user_ptr)
{
    VAStatus vaStatus = VA_STATUS_SUCCESS;
    int allignment;
    uint32_t placement;
    int ret;

    buf->rar_handle = 0;
    buf->buffer_ofs = 0;
    buf->type = psb_bt_user_buffer;
    buf->user_ptr = (unsigned char *)user_ptr;
    buf->driver_data = driver_data;

    allignment = 4096;
    placement =  DRM_PSB_FLAG_MEM_MMU | WSBM_PL_FLAG_TT | WSBM_PL_FLAG_CACHED | WSBM_PL_FLAG_SHARED ;
    ret = LOCK_HARDWARE(driver_data);
    if (ret) {
        UNLOCK_HARDWARE(driver_data);
        vaStatus = VA_STATUS_ERROR_ALLOCATION_FAILED;
        DEBUG_FAILURE_RET;
        return vaStatus;
    }
    ret = wsbmGenBuffers(driver_data->main_pool, 1, &buf->drm_buf,
                         allignment, placement);
    if (!buf->drm_buf) {
        drv_debug_msg(VIDEO_DEBUG_ERROR, "failed to gen wsbm buffers\n");
        UNLOCK_HARDWARE(driver_data);
        return VA_STATUS_ERROR_ALLOCATION_FAILED;
    }

    /* here use the placement when gen buffer setted */
    ret = wsbmBODataUB(buf->drm_buf, size, NULL, NULL, 0, user_ptr);
    UNLOCK_HARDWARE(driver_data);
    if (ret) {
        drv_debug_msg(VIDEO_DEBUG_ERROR, "failed to alloc wsbm buffers\n");
        return VA_STATUS_ERROR_ALLOCATION_FAILED;
    }
    drv_debug_msg(VIDEO_DEBUG_GENERAL, "Create BO from user buffer 0x%08x (%d byte),BO GPU offset hint=0x%08x\n",
                             user_ptr, size, wsbmBOOffsetHint(buf->drm_buf));


    buf->pl_flags = placement;
    buf->status = psb_bs_ready;
    buf->wsbm_synccpu_flag = 0;

    return VA_STATUS_SUCCESS;
}
#endif

#ifdef ANDROID
#ifndef BAYTRAIL
static int psb_buffer_info_rar(psb_driver_data_p driver_data)
{
    struct drm_lnc_video_getparam_arg arg;
    unsigned long rar_info[2] = {0, 0};
    int ret = 0;

    driver_data->rar_phyaddr = driver_data->rar_size = 0;

    arg.key = LNC_VIDEO_GETPARAM_IMR_INFO;
    arg.value = (uint64_t)((unsigned long) & rar_info[0]);
    ret = drmCommandWriteRead(driver_data->drm_fd, driver_data->getParamIoctlOffset,
                              &arg, sizeof(arg));
    if (ret == 0) {
        driver_data->rar_phyaddr = rar_info[0];
        driver_data->rar_size = rar_info[1];
        driver_data->rar_size = driver_data->rar_size & 0xfffff000; /* page align */
        drv_debug_msg(VIDEO_DEBUG_GENERAL, "RAR region physical address = 0x%08x, size=%dK\n",
                                 driver_data->rar_phyaddr,  driver_data->rar_size / 1024);

        return ret;
    }

    drv_debug_msg(VIDEO_DEBUG_GENERAL, "RAR region get size failed\n");
    return ret;
}
#endif
#endif

#ifndef BAYTRAIL
static VAStatus psb_buffer_init_imr(psb_driver_data_p driver_data)
{
    int ret = 0;

    /* hasn't grab IMR device memory region
     * grab the whole IMR3 device memory
     */
    driver_data->rar_bo = calloc(1, sizeof(struct psb_buffer_s));
    if (driver_data->rar_bo == NULL)
        goto exit_error;

    drv_debug_msg(VIDEO_DEBUG_GENERAL, "Init IMR device\n");
#ifdef ANDROID
    if (psb_buffer_info_rar(driver_data)) {
        drv_debug_msg(VIDEO_DEBUG_ERROR, "Get IMR region size failed\n");
        goto exit_error;
    }
#else
    drv_debug_msg(VIDEO_DEBUG_ERROR, "NON ANDROID:Get IMR region size failed\n");
    goto exit_error;
#endif
    drv_debug_msg(VIDEO_DEBUG_GENERAL, "Grab whole camera device memory\n");
    ret = psb_buffer_create(driver_data, driver_data->rar_size, psb_bt_imr, (psb_buffer_p) driver_data->rar_bo);

    if (ret != VA_STATUS_SUCCESS) {
        drv_debug_msg(VIDEO_DEBUG_ERROR, "Grab IMR device memory failed\n");
        goto exit_error;
    }

    return VA_STATUS_SUCCESS;

exit_error:
    if (driver_data->rar_bo)
        free(driver_data->rar_bo);

    driver_data->rar_bo = NULL;

    return VA_STATUS_ERROR_ALLOCATION_FAILED;
}


/*
 * Reference one IMR buffer from offset
 * only used to reference a slice IMR buffer which is created outside of video driver
 */
VAStatus psb_buffer_reference_imr(psb_driver_data_p driver_data,
                                  uint32_t imr_offset,
                                  psb_buffer_p buf
                                 )
{
    VAStatus vaStatus;
    int ret;

    if (driver_data->rar_bo  == NULL) {
        vaStatus = psb_buffer_init_imr(driver_data);
        if (vaStatus != VA_STATUS_SUCCESS) {
            drv_debug_msg(VIDEO_DEBUG_ERROR, "IMR init failed!\n");
            return vaStatus;
        }
    }

    /* don't need to assign the offset to buffer
     * so that when destroy the buffer, we just
     * need to unreference
     */
    /* buf->imr_offset = imr_offset; */

    /* reference the global IMR BO */
    ret = psb_buffer_reference(driver_data, buf, (psb_buffer_p) driver_data->rar_bo);
    if (ret != VA_STATUS_SUCCESS) {
        drv_debug_msg(VIDEO_DEBUG_ERROR, "Reference IMR device memory failed\n");
        return ret;
    }

    buf->rar_handle = imr_offset;
    buf->buffer_ofs = imr_offset;

    /* reference the global IMR buffer, reset buffer type */
    buf->type = psb_bt_imr_slice; /* don't need to IMR_release */

    drv_debug_msg(VIDEO_DEBUG_GENERAL, "Reference IMR buffer, IMR region offset =0x%08x, IMR BO GPU offset hint=0x%08x\n",
                             imr_offset, wsbmBOOffsetHint(buf->drm_buf));

    return VA_STATUS_SUCCESS;
}
#endif