/**
 * @brief  CAL file for Samsung DPP
 *
 * Copyright (c) 2012-2019, Samsung Electronics Co., Ltd.
 *
 * This software is proprietary of Samsung Electronics.
 * No part of this software, either material or conceptual may be copied
 * or distributed, transmitted, transcribed, stored in a retrieval system
 * or translated into any human or computer language in any form by any means,
 * electronic, mechanical, manual or otherwise, or disclosed to third parties
 * without the express written permission of Samsung Electronics.
 */

#include "board.h"
#include "bsp_common.h"
#include "dbg.h"
#include "decon.h"
#include "device.h"
#include "dpp_coef.h"
#include "dpp_common.h"
#include "regs-dpp.h"
#include "secmap.h"

/*************************************************/
/*************** INTERNAL FUNCTION ***************/
/*************************************************/

#define TIMEOUT           (20 * 1000) /* 20ms */
#define SECURE_IDMA_DPP   0

static uint32_t log_level = 0;

static uint32_t dma_read(uint32_t id, uint32_t reg_id)
{
    (void)id;

    return IDMA_SEC_REG(reg_id);
}

static void dma_com_write(uint32_t id, uint32_t reg_id, uint32_t val)
{
    (void)id;

    IDMA_COMMON_REG(reg_id) = val;

    if (log_level > 0) {
        dbgPrintf("[%s] off(0x%x)= 0x%x\n", __func__, reg_id, val);
    }
}

static void dma_write(uint32_t id, uint32_t reg_id, uint32_t val)
{
    (void)id;

    IDMA_SEC_REG(reg_id) = val;

    if (log_level > 0) {
        dbgPrintf("[%s] off(0x%x)= 0x%x\n", __func__, reg_id, val);
    }
}

static void dma_write_mask(uint32_t id, uint32_t reg_id, uint32_t val, uint32_t mask)
{
    uint32_t old = dma_read(id, reg_id);

    val = (val & mask) | (old & ~mask);
    dma_write(id, reg_id, val);
}

static uint32_t dpp_read(uint32_t id, uint32_t reg_id)
{
    (void)id;

    return DPP_SEC_REG(reg_id);
}

static void dpp_write(uint32_t id, uint32_t reg_id, uint32_t val)
{
    (void)id;

    DPP_SEC_REG(reg_id) = val;

    if (log_level > 0) {
        dbgPrintf("[%s] off(0x%x)= 0x%x\n", __func__, reg_id, val);
    }
}

static void dpp_write_mask(uint32_t id, uint32_t reg_id, uint32_t val, uint32_t mask)
{
    uint32_t old = dpp_read(id, reg_id);

    val = (val & mask) | (old & ~mask);
    dpp_write(id, reg_id, val);
}

/*************************************************/
/******* INTERNAL FUNCTION: H/W access API *******/
/*************************************************/

static uint32_t dma_reg_get_op_status(uint32_t id)
{
    uint32_t val;

    val = dma_read(id, IDMA_ENABLE);
    if (val & IDMA_OP_STATUS) {
        return OP_STATUS_BUSY;
    }
    return OP_STATUS_IDLE;
}

static int dpp_reg_wait_status_run(uint32_t id)
{
    uint32_t dma_status = 0;
    unsigned long delay_time = 10;
    unsigned long cnt = TIMEOUT / delay_time;

    while (cnt) {
        dma_status = dma_reg_get_op_status(id);

        if (dma_status != OP_STATUS_IDLE) {
            break;
        }

        cnt--;
        udelay(delay_time);
    }

    if (!cnt) {
        if (dma_status == OP_STATUS_IDLE) {
            dbgPrintf("[dma%d] timeout op_status to run\n", id);
        }
        return -1;
    }

    return 0;
}

static int dpp_reg_wait_status_idle(uint32_t id)
{
    uint32_t dma_status = 0;
    unsigned long delay_time = 10;
    unsigned long cnt = TIMEOUT / delay_time;

    while (cnt) {
        dma_status = dma_reg_get_op_status(id);

        if (dma_status != OP_STATUS_BUSY) {
            break;
        }

        cnt--;
        udelay(delay_time);
    }

    if (!cnt) {
        if (dma_status == OP_STATUS_BUSY) {
            dbgPrintf("[dma%d] timeout op_status to idle\n", id);
        }
        return -1;
    }

    return 0;
}

static void dpp_reg_set_irq_mask_all(uint32_t id, uint32_t en)
{
    uint32_t val = en ? ~0 : 0;

    dpp_write_mask(id, DPP_IRQ, val, DPP_ALL_IRQ_MASK);
}

static void dpp_reg_set_irq_enable(uint32_t id)
{
    dpp_write_mask(id, DPP_IRQ, ~0, DPP_IRQ_ENABLE);
}

static void dpp_reg_set_clock_gate_en_all(uint32_t id, uint32_t en)
{
    uint32_t val = en ? ~0 : 0;

    dpp_write_mask(id, DPP_ENABLE, val, DPP_ALL_CLOCK_GATE_EN_MASK);
}

static void dpp_reg_set_dynamic_gating_en_all(uint32_t id, uint32_t en)
{
    uint32_t val = en ? ~0 : 0;

    dpp_write_mask(id, DPP_DYNAMIC_GATING_EN, val, DPP_DG_EN_ALL);
}

static void dpp_reg_set_h_coef(uint32_t id, uint32_t h_ratio)
{
    int sc_ratio;

    if (h_ratio <= DPP_SC_RATIO_MAX) {
        sc_ratio = 0;
    } else if (h_ratio <= DPP_SC_RATIO_7_8) {
        sc_ratio = 1;
    } else if (h_ratio <= DPP_SC_RATIO_6_8) {
        sc_ratio = 2;
    } else if (h_ratio <= DPP_SC_RATIO_5_8) {
        sc_ratio = 3;
    } else if (h_ratio <= DPP_SC_RATIO_4_8) {
        sc_ratio = 4;
    } else if (h_ratio <= DPP_SC_RATIO_3_8) {
        sc_ratio = 5;
    } else {
        sc_ratio = 6;
    }

    for (int i = 0; i < 9; i++) {
        for (int j = 0; j < 8; j++) {
            for (int k = 0; k < 2; k++) {
                dpp_write(id, DPP_H_COEF(i, j, k), h_coef_8t[sc_ratio][i][j]);
            }
        }
    }
}

static void dpp_reg_set_v_coef(uint32_t id, uint32_t v_ratio)
{
    int sc_ratio;

    if (v_ratio <= DPP_SC_RATIO_MAX) {
        sc_ratio = 0;
    } else if (v_ratio <= DPP_SC_RATIO_7_8) {
        sc_ratio = 1;
    } else if (v_ratio <= DPP_SC_RATIO_6_8) {
        sc_ratio = 2;
    } else if (v_ratio <= DPP_SC_RATIO_5_8) {
        sc_ratio = 3;
    } else if (v_ratio <= DPP_SC_RATIO_4_8) {
        sc_ratio = 4;
    } else if (v_ratio <= DPP_SC_RATIO_3_8) {
        sc_ratio = 5;
    } else {
        sc_ratio = 6;
    }

    for (int i = 0; i < 9; i++) {
        for (int j = 0; j < 4; j++) {
            for (int k = 0; k < 2; k++) {
                dpp_write(id, DPP_V_COEF(i, j, k), v_coef_4t[sc_ratio][i][j]);
            }
        }
    }
}

static void dpp_reg_set_scale_ratio(uint32_t id)
{
    uint32_t h_ratio = (1 << 20);
    uint32_t v_ratio = (1 << 20);

    dpp_write_mask(id, DPP_MAIN_H_RATIO, DPP_H_RATIO(h_ratio),
                   DPP_H_RATIO_MASK);
    dpp_write_mask(id, DPP_MAIN_V_RATIO, DPP_V_RATIO(v_ratio),
                   DPP_V_RATIO_MASK);
    dpp_reg_set_h_coef(id, h_ratio);

    dpp_reg_set_v_coef(id, v_ratio);
}

static void dpp_reg_set_img_size(uint32_t id, uint32_t w, uint32_t h)
{
    uint32_t val;

    val = (DPP_IMG_HEIGHT(h) | DPP_IMG_WIDTH(w));
    dpp_write(id, DPP_IMG_SIZE, val);
}

static void dma_reg_set_in_base_addr(uint32_t id, uint32_t addr_y, uint32_t addr_c)
{
    dma_write(id, IDMA_IN_BASE_ADDR_Y, addr_y);
    dma_write(id, IDMA_IN_BASE_ADDR_C, addr_c);
}

static void dpp_reg_set_buf_addr(uint32_t id, uint32_t addr)
{
    dma_reg_set_in_base_addr(id, addr, 0);
}

static void idma_reg_set_irq_mask_all(uint32_t id, uint32_t en)
{
    uint32_t val = en ? ~0 : 0;

    dma_write_mask(id, IDMA_IRQ, val, IDMA_ALL_IRQ_MASK);
}

static void idma_reg_set_irq_enable(uint32_t id)
{
    dma_write_mask(id, IDMA_IRQ, ~0, IDMA_IRQ_ENABLE);
}

static void idma_reg_set_clock_gate_en_all(uint32_t id, uint32_t en)
{
    uint32_t val = en ? ~0 : 0;

    dma_write_mask(id, IDMA_ENABLE, val, IDMA_ALL_CLOCK_GATE_EN_MASK);
}

static void idma_reg_set_in_qos_lut(uint32_t id, uint32_t lut_id, uint32_t qos_t)
{
    uint32_t reg_id;

    if (lut_id == 0) {
        reg_id = DPU_DMA_QOS_LUT07_00;
    } else {
        reg_id = DPU_DMA_QOS_LUT15_08;
    }
    dma_com_write(id, reg_id, qos_t);
}

static void idma_reg_set_dynamic_gating_en_all(uint32_t id, uint32_t en)
{
    uint32_t val = en ? ~0 : 0;

    dma_write_mask(id, IDMA_DYNAMIC_GATING_EN, val, IDMA_DG_EN_ALL);
}

static void idma_reg_set_out_frame_alpha(uint32_t id, uint32_t alpha)
{
    dma_write_mask(id, IDMA_OUT_CON, IDMA_OUT_FRAME_ALPHA(alpha),
                   IDMA_OUT_FRAME_ALPHA_MASK);
}

static void idma_reg_set_coordinates(uint32_t id, uint32_t w, uint32_t h)
{
    dma_write(id, IDMA_SRC_OFFSET, IDMA_SRC_OFFSET_Y(0) | IDMA_SRC_OFFSET_X(0));
    dma_write(id, IDMA_SRC_SIZE, IDMA_SRC_HEIGHT(h) | IDMA_SRC_WIDTH(w));
    dma_write(id, IDMA_IMG_SIZE, IDMA_IMG_HEIGHT(h) | IDMA_IMG_WIDTH(w));
}
static void dma_dpp_reg_set_coordinates(uint32_t id, uint32_t w, uint32_t h)
{
    idma_reg_set_coordinates(id, w, h);
    dpp_reg_set_img_size(id, w, h);
}

static void idma_reg_set_format(uint32_t id, uint32_t fmt)
{
    dma_write_mask(id, IDMA_IN_CON, IDMA_IMG_FORMAT(fmt),
                   IDMA_IMG_FORMAT_MASK);
}

static void idma_reg_set_afbc(uint32_t id, uint32_t en)
{
    uint32_t val = en ? ~0 : 0;

    dma_write_mask(id, IDMA_IN_CON, val, IDMA_AFBC_EN);
}

static void dpp_reg_set_alpha_type(uint32_t id, uint32_t type)
{
    /* [type] 0=per-frame, 1=per-pixel */
    dpp_write_mask(id, DPP_IN_CON, DPP_ALPHA_SEL(type), DPP_ALPHA_SEL_MASK);
}

static void dpp_reg_set_format(uint32_t id, uint32_t fmt)
{
    dpp_write_mask(id, DPP_IN_CON, DPP_IMG_FORMAT(fmt), DPP_IMG_FORMAT_MASK);
}

static int dma_dpp_reg_set_format(uint32_t id, enum decon_pixel_format format)
{
    uint32_t fmt;
    uint32_t alpha_type = 0; /* 0: per-frame, 1: per-pixel */
    uint32_t fmt_type = 0;

    switch (format) {
    case DECON_PIXEL_FORMAT_ARGB_8888:
        fmt = IDMA_IMG_FORMAT_ARGB8888;
        fmt_type = DPP_IMG_FORMAT_ARGB8888;
        alpha_type = 1;
        break;
    case DECON_PIXEL_FORMAT_ABGR_8888:
        fmt = IDMA_IMG_FORMAT_ABGR8888;
        fmt_type = DPP_IMG_FORMAT_ARGB8888;
        alpha_type = 1;
        break;
    case DECON_PIXEL_FORMAT_RGBA_8888:
        fmt = IDMA_IMG_FORMAT_RGBA8888;
        fmt_type = DPP_IMG_FORMAT_ARGB8888;
        alpha_type = 1;
        break;
    case DECON_PIXEL_FORMAT_BGRA_8888:
        fmt = IDMA_IMG_FORMAT_BGRA8888;
        fmt_type = DPP_IMG_FORMAT_ARGB8888;
        alpha_type = 1;
        break;
    case DECON_PIXEL_FORMAT_XRGB_8888:
        fmt = IDMA_IMG_FORMAT_XRGB8888;
        fmt_type = DPP_IMG_FORMAT_ARGB8888;
        break;
    case DECON_PIXEL_FORMAT_XBGR_8888:
        fmt = IDMA_IMG_FORMAT_XBGR8888;
        fmt_type = DPP_IMG_FORMAT_ARGB8888;
        break;
    case DECON_PIXEL_FORMAT_RGBX_8888:
        fmt = IDMA_IMG_FORMAT_RGBX8888;
        fmt_type = DPP_IMG_FORMAT_ARGB8888;
        break;
    case DECON_PIXEL_FORMAT_BGRX_8888:
        fmt = IDMA_IMG_FORMAT_BGRX8888;
        fmt_type = DPP_IMG_FORMAT_ARGB8888;
        break;
    case DECON_PIXEL_FORMAT_RGB_565:
        fmt = IDMA_IMG_FORMAT_RGB565;
        fmt_type = DPP_IMG_FORMAT_ARGB8888;
        break;
    /* TODO: add ARGB1555 & ARGB4444 */
    case DECON_PIXEL_FORMAT_ARGB_2101010:
        fmt = IDMA_IMG_FORMAT_ARGB2101010;
        fmt_type = DPP_IMG_FORMAT_ARGB8101010;
        alpha_type = 1;
        break;
    case DECON_PIXEL_FORMAT_ABGR_2101010:
        fmt = IDMA_IMG_FORMAT_ABGR2101010;
        fmt_type = DPP_IMG_FORMAT_ARGB8101010;
        alpha_type = 1;
        break;
    case DECON_PIXEL_FORMAT_RGBA_1010102:
        fmt = IDMA_IMG_FORMAT_RGBA2101010;
        fmt_type = DPP_IMG_FORMAT_ARGB8101010;
        alpha_type = 1;
        break;
    case DECON_PIXEL_FORMAT_BGRA_1010102:
        fmt = IDMA_IMG_FORMAT_BGRA2101010;
        fmt_type = DPP_IMG_FORMAT_ARGB8101010;
        alpha_type = 1;
        break;

    case DECON_PIXEL_FORMAT_NV12:
    case DECON_PIXEL_FORMAT_NV12M:
        fmt = IDMA_IMG_FORMAT_YUV420_2P;
        fmt_type = DPP_IMG_FORMAT_YUV420_8P;
        break;
    case DECON_PIXEL_FORMAT_NV21:
    case DECON_PIXEL_FORMAT_NV21M:
    case DECON_PIXEL_FORMAT_NV12N:
        fmt = IDMA_IMG_FORMAT_YVU420_2P;
        fmt_type = DPP_IMG_FORMAT_YUV420_8P;
        break;

    case DECON_PIXEL_FORMAT_NV12N_10B:
        fmt = IDMA_IMG_FORMAT_YVU420_8P2;
        fmt_type = DPP_IMG_FORMAT_YUV420_8P2;
        break;
    case DECON_PIXEL_FORMAT_NV12M_P010:
        fmt = IDMA_IMG_FORMAT_YUV420_P010;
        fmt_type = DPP_IMG_FORMAT_YUV420_P010;
        break;
    case DECON_PIXEL_FORMAT_NV21M_P010:
        fmt = IDMA_IMG_FORMAT_YVU420_P010;
        fmt_type = DPP_IMG_FORMAT_YUV420_P010;
        break;
    case DECON_PIXEL_FORMAT_NV12M_S10B:
        fmt = IDMA_IMG_FORMAT_YVU420_8P2;
        fmt_type = DPP_IMG_FORMAT_YUV420_8P2;
        break;
    case DECON_PIXEL_FORMAT_NV21M_S10B:
        fmt = IDMA_IMG_FORMAT_YUV420_8P2;
        fmt_type = DPP_IMG_FORMAT_YUV420_8P2;
        break;
    case DECON_PIXEL_FORMAT_NV16:
        fmt = IDMA_IMG_FORMAT_YVU422_2P;
        fmt_type = DPP_IMG_FORMAT_YUV422_8P;
        break;
    case DECON_PIXEL_FORMAT_NV61:
        fmt = IDMA_IMG_FORMAT_YUV422_2P;
        fmt_type = DPP_IMG_FORMAT_YUV422_8P;
        break;
    default:
        dbgPrintf("Unsupported Format\n");
        return -1;
    }

    idma_reg_set_format(id, fmt);
    dpp_reg_set_alpha_type(id, alpha_type);
    dpp_reg_set_format(id, fmt_type);

    return 0;
}

static void idma_reg_clear_irq(uint32_t id, uint32_t irq)
{
    dma_write_mask(id, IDMA_IRQ, ~0, irq);
}

static void dpp_reg_clear_irq(uint32_t id, uint32_t irq)
{
    dpp_write_mask(id, DPP_IRQ, ~0, irq);
}

static void dpp_reg_set_linecnt(uint32_t id, uint32_t en)
{
    if (en) {
        dpp_write_mask(id, DPP_LINECNT_CON, DPP_LC_MODE(0) | DPP_LC_ENABLE(1),
                       DPP_LC_MODE_MASK | DPP_LC_ENABLE_MASK);
    } else {
        dpp_write_mask(id, DPP_LINECNT_CON, DPP_LC_ENABLE(0),
                       DPP_LC_ENABLE_MASK);
    }
}

static void dma_reg_set_secure_en(uint32_t id, uint32_t en)
{
    uint32_t val = en ? 0 : 1;

    dma_write(id, IDMA_MST_SECURITY, val);
    dma_write(id, IDMA_SLV_SECURITY, val);
}

static void dpp_reg_set_secure_en(uint32_t id, uint32_t en)
{
    uint32_t val = en ? 0 : ~0;

    dpp_write_mask(id, DPP_ENABLE, val, DPP_TZPC_FLAG);
}

static uint32_t g_width;
static uint32_t g_height;

/*************************************************/
/******* EXTERNAL FUNCTION: H/W access API *******/
/*************************************************/
void dpp_reg_init(uint32_t width, uint32_t height)
{
    uint32_t id = SECURE_IDMA_DPP;

    dbgPrintf("%s:%d +\n", __func__, __LINE__);
    g_width = width;
    g_height = height;

    /* IDMA setting */
    idma_reg_set_irq_mask_all(id, 0);
    idma_reg_set_irq_enable(id);
    idma_reg_set_clock_gate_en_all(id, 0);
    idma_reg_set_in_qos_lut(id, 0, 0x44444444);
    idma_reg_set_in_qos_lut(id, 1, 0x44444444);
    idma_reg_set_dynamic_gating_en_all(id, 0);
    idma_reg_set_out_frame_alpha(id, 0xFF);

    /* DPP setting */
    dpp_reg_set_irq_mask_all(id, 0);
    dpp_reg_set_irq_enable(id);
    dpp_reg_set_clock_gate_en_all(id, 0);
    dpp_reg_set_dynamic_gating_en_all(id, 0);
    dpp_reg_set_linecnt(id, 1);

    /* part of the dpp/dma configuration */
    dpp_reg_set_scale_ratio(id);
    dma_dpp_reg_set_coordinates(id, g_width, g_height);
    dma_dpp_reg_set_format(id, DECON_PIXEL_FORMAT_ABGR_8888);

    idma_reg_set_afbc(id, 0);
    dma_reg_set_secure_en(id, 1);
    dpp_reg_set_secure_en(id, 1);

    dbgPrintf("%s:%d -\n", __func__, __LINE__);
}

void dpp_reg_ready(uint32_t fb_addr)
{
    uint32_t id = SECURE_IDMA_DPP;

    /* TODO : resetted? if it was soft reset, then re-init */
    if (dpp_read(id, DPP_IMG_SIZE) == 0) {
        dpp_reg_init(g_width, g_height);
    }
    dpp_reg_set_buf_addr(id, fb_addr);
}

int dpp_reg_start(void)
{
    uint32_t id = SECURE_IDMA_DPP;

    return dpp_reg_wait_status_run(id);
}

int dpp_reg_wait_done(void)
{
    uint32_t id = SECURE_IDMA_DPP;

    return dpp_reg_wait_status_idle(id);
}

void dpp_reg_deinit(void)
{
    uint32_t id = SECURE_IDMA_DPP;

    dpp_reg_wait_status_idle(id);

    dma_reg_set_secure_en(id, 0);
    dpp_reg_set_secure_en(id, 0);

    idma_reg_clear_irq(id, IDMA_ALL_IRQ_CLEAR);
    idma_reg_set_irq_mask_all(id, 1);

    dpp_reg_clear_irq(id, DPP_ALL_IRQ_CLEAR);
    dpp_reg_set_irq_mask_all(id, 1);
}