/*
 * Copyright (C) 2020, Samsung Electronics Co., Ltd.
 *
 * display_drv_engine.c
 * TUI LL display driver module
 *
 */

#include <cache.h>
#include <inttypes.h>
#include <string.h>
#include <tee_tui_low_api.h>

#include "board.h"
#include "display_drv_engine.h"
#include "disp_core.h"
#include "tuill_defs.h"
#include "tuill_client_drv.h"
#include "tuill_drv.h"
#include "tuill_log.h"
#include "tuiHal.h"

static struct FB_Data FB;

#define rgba_to_abgr(x) __builtin_bswap32(x)
static void fit_color_to_disp(void)
{
    /* RGB is default */
#if (TUI_COLOR_MODE == BGR_ORDER)
    uint32_t *display_buffer_tmp = (uint32_t *)(uintptr_t)FB.fb_virtual;
    const size_t pixels_count = FB.fb_size / sizeof(uint32_t);
    for (size_t i = 0; i < pixels_count; ++i) {
            display_buffer_tmp[i] = rgba_to_abgr(display_buffer_tmp[i]);
    }
#endif
}

#define abgr_to_rgba(x) __builtin_bswap32(x)
static void fit_color_to_lib()
{
    /* RGB is default */
#if (TUI_COLOR_MODE == BGR_ORDER)
    uint32_t *display_buffer_tmp = (uint32_t *)(uintptr_t)FB.fb_virtual;
    const size_t pixels_count = FB.fb_size / sizeof(uint32_t);
    for (size_t i = 0; i < pixels_count; ++i) {
            display_buffer_tmp[i] = abgr_to_rgba(display_buffer_tmp[i]);
    }
#endif
}

#ifndef TUI_MODEL_RANCHU
static int check_buffer_param(tuiDisplayBuffer_t *fb)
{
    uint64_t frame_buffer_pa = fb->physicalFb;
    uint64_t frame_buffer_size = fb->physicalFbLen;
    uint64_t work_buffer_pa = fb->physicalWb;
    uint64_t work_buffer_size = fb->physicalWbLen;
    uint64_t next_pos;
    uint64_t fb_size;
    uint64_t wb_size;

    syslog(LOG_DEBUG, "width             = %d\n", fb->width);
    syslog(LOG_DEBUG, "height            = %d\n", fb->height);
    syslog(LOG_DEBUG, "frame_buffer_pa   = 0x%" PRIx64 "\n", frame_buffer_pa);
    syslog(LOG_DEBUG, "frame_buffer_size = %" PRIu64 "\n", frame_buffer_size);
    syslog(LOG_DEBUG, "work_buffer_pa    = 0x%" PRIx64 "\n", work_buffer_pa);
    syslog(LOG_DEBUG, "work_buffer_size  = %" PRIu64 "\n", work_buffer_size);
    for (unsigned int i = 0; i < STUI_DISPLAY_INFO_SIZE; i++) {
        syslog(LOG_DEBUG, "lcd info[%u]	= %" PRIu64 "\n", i, fb->lcd_info[i]);
    }

    if (fb->width > 4096 || fb->height > 4096) {
        syslog(LOG_ERR, "too large width %d or height %d\n", fb->width, fb->height);
        return -TUILLE_GENERIC;
    }

    fb_size = fb->width * fb->height * (BOARD_BITSPERPIXEL >> 3);
    fb_size = STUI_ALIGN_UP(fb_size, FB_ALIGN);
    wb_size = fb->width * fb->height * (2 * (BOARD_BITSPERPIXEL >> 3) + 1);
    wb_size = STUI_ALIGN_UP(wb_size, FB_ALIGN);

    /* Check frame buffer address and size */
    if (!frame_buffer_pa || frame_buffer_size != fb_size) {
        syslog(LOG_ERR, "null frame buffer address or incorrect size 0x%" PRIu64 "\n", fb_size);
        return -TUILLE_GENERIC;
    }

    if (work_buffer_pa != 0) {
        if (__builtin_add_overflow(frame_buffer_pa, frame_buffer_size, &next_pos)) {
            syslog(LOG_ERR, "integer overflow\n");
            return -TUILLE_GENERIC;
        }

        /* Check WB begins exactly after the frame-buffer */
        if (work_buffer_pa != next_pos) {
            syslog(LOG_ERR, "WB is not right after FB\n");
            return -TUILLE_GENERIC;
        }

        /* Check WB size */
        if (work_buffer_size > wb_size) {
            syslog(LOG_ERR, "WB size is larger than expectation\n");
            return -TUILLE_GENERIC;
        }
    } else {
        /* No working buffer */
        fb->physicalWbLen = 0;
    }

    return 0;
}
#endif

static int32_t disp_income(void *user_data, struct tuill_buffer *buff)
{
    (void)user_data;
    TUILL_CALL_TRACE();
    struct tuill_internal_command rsp = {};
    struct tuill_internal_command *cmd = (struct tuill_internal_command *)buff->data;

    rsp.cmd = cmd->cmd | RESPONSE_FLAG;
    rsp.ret_code = -TUILLE_BAD_PARAMETERS;
    rsp.task_state = cmd->task_state;
    rsp.task_id = cmd->task_id;

    switch (cmd->cmd) {
#ifdef TUI_MODEL_RANCHU
    case TUILL_ICMD_OPEN_DRIVER: {
        syslog(LOG_DEBUG, "TUILL_ICMD_OPEN_DRIVER\n");
        rsp.ret_code = 0;
        drv_ctx.tuilldrv.tuill_state |= TEE_PERIPHERAL_FLAG_LOCKED;
        drv_send_state();
        break;
    }
    case TUILL_ICMD_CLOSE_DRIVER: {
        syslog(LOG_DEBUG, "TUILL_ICMD_CLOSE_DRIVER\n");
        rsp.ret_code = 0;
        drv_ctx.tuilldrv.tuill_state &= ~TEE_PERIPHERAL_FLAG_LOCKED;
        drv_send_state();
        break;
    }
#else
    case TUILL_ICMD_OPEN_DRIVER: {
        syslog(LOG_DEBUG, "TUILL_ICMD_OPEN_DRIVER\n");
        tuiDisplayBuffer_t _fb;

        if (drv_ctx.tuilldrv.tuill_state & TEE_PERIPHERAL_FLAG_LOCKED) {
            syslog(LOG_ERR, "Already opened.\n");
            rsp.ret_code = -TUILLE_BAD_STATE;
            break;
        }

        memcpy(&FB, &cmd->open_drivers_cmd.fb, sizeof(struct FB_Data));

        if (drv_video_init()) {
            syslog(LOG_ERR, "drv_video_init() FAILED\n\n");
            rsp.ret_code = -TUILLE_GENERIC;
            break;
        }

        _fb.width = FB.width;                   /* IN  */
        _fb.height = FB.height;                 /* IN  */
        _fb.physicalFb = FB.fb_physical;        /* IN  */
        _fb.physicalFbLen = FB.fb_size;         /* IN  */
        _fb.virtualFb = FB.fb_virtual;          /* OUT */
        _fb.physicalWb = FB.wb_physical;        /* IN  */
        _fb.physicalWbLen = FB.wb_size;         /* IN  */
        _fb.virtualWb = FB.wb_virtual;          /* OUT */
        _fb.physicaldisp = FB.disp_physical;    /* IN  */
        _fb.physicaldispLen = FB.disp_size;     /* IN  */
        memcpy(_fb.lcd_info, FB.lcd_info, sizeof(FB.lcd_info));    /* IN  */

        if (check_buffer_param(&_fb)) {
            rsp.ret_code = -TUILLE_BAD_PARAMETERS;
            syslog(LOG_ERR, "check_buffer_param() failed\n");
            break;
        }

        rsp.ret_code = tuiHalDisplayInitialize(&_fb);

        if (rsp.ret_code == E_TUI_HAL_BAD_PARAMETERS) {
            syslog(LOG_ERR, "tuiHalDisplayInitialize() failed, res = E_TUI_HAL_BAD_PARAMETERS\n");
            rsp.ret_code = -TUILLE_BAD_PARAMETERS;
            break;
        } else if (rsp.ret_code) {
            syslog(LOG_ERR, "tuiHalDisplayInitialize() failed, res = %d\n", rsp.ret_code);
            rsp.ret_code = -TUILLE_ACCESS_DENIED;
            break;
        }

        FB.fb_virtual = 0;
        FB.wb_virtual = 0;
        if (FB.fb_size) {
            FB.fb_virtual = (uintptr_t)drv_video_get_vaddr();
        }

        if (FB.wb_size) {
            FB.wb_virtual = (uintptr_t)tuiHalWBGetVaddr();
        }

        memcpy(&rsp.open_drivers_rsp.fb, &FB, sizeof(struct FB_Data));
        rsp.ret_code = 0;

        drv_ctx.tuilldrv.tuill_state |= TEE_PERIPHERAL_FLAG_LOCKED;
        drv_send_state();
        break;
    }
    case TUILL_ICMD_CLOSE_DRIVER:
        syslog(LOG_DEBUG, "TUILL_ICMD_CLOSE_DRIVER\n");

        if (!(drv_ctx.tuilldrv.tuill_state & TEE_PERIPHERAL_FLAG_LOCKED)) {
            syslog(LOG_ERR, "Already closed.\n");
            rsp.ret_code = -TUILLE_BAD_STATE;
            break;
        }

        rsp.ret_code = tuiHalDisplayUninitialize();
        if (rsp.ret_code) {
            syslog(LOG_ERR, "tuiHalDisplayUninitialize() failed, res = %d\n", rsp.ret_code);
            rsp.ret_code = -TUILLE_GENERIC;
        } else {
            drv_ctx.tuilldrv.tuill_state &= ~TEE_PERIPHERAL_FLAG_LOCKED;
            drv_send_state();
        }
        break;
#endif
    default:
        syslog(LOG_NOTICE, "Unknown command: %d\n", cmd->cmd);
        return -TUILLE_GENERIC;
    }

    return tuild_send_cmd(&rsp);
}

static int32_t disp_hangup(void *user_data)
{
    (void)user_data;
    TUILL_CALL_TRACE();
    /* TODO: add restoring after error code here */
    return 0;
}

void *disp_mmap(struct drv_info *filp, void *addr, size_t data_len, int prot, int flags,
                off_t offset)
{
    (void)filp;
    (void)addr;
    (void)prot;
    (void)flags;

    if (!(drv_ctx.tuilldrv.tuill_state & TEE_PERIPHERAL_FLAG_LOCKED)) {
        syslog(LOG_ERR, "Already closed.\n");
        return MAP_FAILED;
    }

    TUILL_CALL_TRACE();
    if (offset == 0 && data_len == FB.fb_size) {
        if (FB.fb_virtual) {
            syslog(LOG_DEBUG, "fb_virtual=%" PRIx64 "\n", FB.fb_virtual);
            return (void *)(intptr_t)FB.fb_virtual;
        }

        return MAP_FAILED;
    } else if (offset == (off_t)FB.fb_size && data_len == FB.wb_size) {
        if (FB.wb_virtual) {
            syslog(LOG_DEBUG, "wb_virtual=%" PRIx64 "\n", FB.wb_virtual);
            return (void *)(intptr_t)FB.wb_virtual;
        }

        return MAP_FAILED;
    }

    return MAP_FAILED;
}

int disp_ioctl(struct drv_info *info, int ioctl_cmd, struct ioctl_arg *arg)
{
    (void)info;
    (void)arg;
    int ret = 0;

    if (!(drv_ctx.tuilldrv.tuill_state & TEE_PERIPHERAL_FLAG_LOCKED)) {
        syslog(LOG_ERR, "TUI state is already closed.\n");
        return -TUILLE_ACCESS_DENIED;
    }

    switch (ioctl_cmd) {
    case TUILLDRV_IOCTL_BLIT_DISPLAY_SURFACE:
        fit_color_to_disp();
        syslog(LOG_DEBUG, "Processing TUILLDRV_IOCTL_BLIT_DISPLAY_SURFACE...\n");
#ifndef USE_NON_CACHED_FB
        syslog(LOG_DEBUG, "USE_NON_CACHED_FB\n");
        if (TEES_DcacheFlush((void *)(intptr_t)FB.fb_virtual, (size_t)FB.fb_size) != 0) {
            ret = -TUILLE_ACCESS_DENIED;
        } else if (drv_video_trigger() != 0) {
            ret = -TUILLE_ACCESS_DENIED;
        }

#else
        if (drv_video_trigger() != 0) {
            ret = -TUILLE_ACCESS_DENIED;
        }

#endif
        fit_color_to_lib();
        syslog(LOG_DEBUG, "TUILLDRV_IOCTL_BLIT_DISPLAY_SURFACE out ret=%d\n", ret);
        break;
    default:
        syslog(LOG_DEBUG, "Unknown cmd=%d\n", ioctl_cmd);
        ret = -TUILLE_BAD_PARAMETERS;
        break;
    }

    return ret;
}

struct socket_callbacks disp_cb = {
    .income = disp_income,
    .hangup = disp_hangup,
};
