/*
 * Copyright   2012-2019, Samsung Electronics Co. Ltd
 *  MinHo Kim <m8891.kim@samsung.co.kr>
 * 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.h"
#include "dpp_coef.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_write(uint32_t id, uint32_t reg_id, uint32_t val)
{
    (void)id;

    IDMA_SEC_REG(reg_id) = val;

    if (0 < log_level) {
        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_REG(reg_id);
}

static void dpp_write(uint32_t id, uint32_t reg_id, uint32_t val)
{
    (void)id;

    DPP_REG(reg_id) = val;

    if (0 < log_level) {
        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 *******/
/*************************************************/

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;
}

uint32_t dpp_reg_get_op_status(uint32_t id)
{
    uint32_t val;

    val = dpp_read(id, DPP_ENABLE);
    if (val & DPP_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;
}

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);
}

void dma_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);
}

void dma_reg_set_irq_enable(uint32_t id)
{
    dma_write_mask(id, IDMA_IRQ, ~0, IDMA_IRQ_ENABLE);
}

void dma_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);
}

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);
}

void dma_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 = IDMA_IN_QOS_LUT07_00;
    } else {
        reg_id = IDMA_IN_QOS_LUT15_08;
    }
    dma_write(id, reg_id, qos_t);
}

void dpp_reg_set_lookup_table(uint32_t id)
{
    dma_reg_set_in_qos_lut(id, 0, 0x44444444);
    dma_reg_set_in_qos_lut(id, 1, 0x44444444);
}

void dma_reg_set_dynamic_gating_en_all(uint32_t id, uint32_t en)
{
    uint32_t val, mask;

    val = en ? ~0 : 0;

    mask = IDMA_DG_EN_ALL;
    dma_write_mask(id, IDMA_DYNAMIC_GATING_EN, val, mask);
}

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);
}

void dpp_reg_set_dynamic_clock_gating(uint32_t id)
{
    dma_reg_set_dynamic_gating_en_all(id, 1);
    dpp_reg_set_dynamic_gating_en_all(id, 1);
}

void dma_reg_set_out_frame_alpha(uint32_t id, uint32_t alpha)
{
    uint32_t val;
    uint32_t mask;

    val = IDMA_OUT_FRAME_ALPHA(alpha);
    mask = IDMA_OUT_FRAME_ALPHA_MASK;
    dma_write_mask(id, IDMA_OUT_CON, val, mask);
}

void dpp_reg_set_plane_alpha_fixed(uint32_t id)
{
    dma_reg_set_out_frame_alpha(id, 0xFF);
}

void dpp_reg_set_secure_mode(uint32_t id, uint32_t en)
{
    uint32_t val = en ? ~0 : 0;

    dpp_write_mask(id, DPP_ENABLE, val, DPP_SECURE_MODE);
}

void dma_reg_set_secure_mode(uint32_t id, uint32_t en)
{
    uint32_t val = en ? ~0 : 0;

    dma_write_mask(id, IDMA_ENABLE, val, IDMA_SECURE_MODE);
}

void dpp_reg_set_h_ratio(uint32_t id, uint32_t h_ratio)
{
    uint32_t val;

    val = DPP_H_RATIO(h_ratio);
    dpp_write_mask(id, DPP_MAIN_H_RATIO, val, DPP_H_RATIO_MASK);
}

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]);
            }
        }
    }
}

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]);
            }
        }
    }
}

void dpp_reg_set_v_ratio(uint32_t id, uint32_t v_ratio)
{
    uint32_t val;

    val = DPP_V_RATIO(v_ratio);
    dpp_write_mask(id, DPP_MAIN_V_RATIO, val, DPP_V_RATIO_MASK);
}

void dpp_reg_set_scale_ratio(uint32_t id)
{
    uint32_t h_ratio = (1 << 20);
    uint32_t v_ratio = (1 << 20);

    dpp_reg_set_h_ratio(id, h_ratio);
    dpp_reg_set_h_coef(id, h_ratio);

    dpp_reg_set_v_ratio(id, v_ratio);
    dpp_reg_set_v_coef(id, v_ratio);
}

void dma_reg_set_buf_offset(uint32_t id, uint32_t x, uint32_t y)
{
    uint32_t val;
    val = (IDMA_SRC_OFFSET_Y(y) | IDMA_SRC_OFFSET_X(x));
    dma_write(id, IDMA_SRC_OFFSET, val);
}

void dma_reg_set_buf_size(uint32_t id, uint32_t w, uint32_t h)
{
    uint32_t val;
    val = (IDMA_SRC_HEIGHT(h) | IDMA_SRC_WIDTH(w));
    dma_write(id, IDMA_SRC_SIZE, val);
}

void dma_reg_set_img_size(uint32_t id, uint32_t w, uint32_t h)
{
    uint32_t val;
    val = (IDMA_IMG_HEIGHT(h) | IDMA_IMG_WIDTH(w));
    dma_write(id, IDMA_IMG_SIZE, val);
}

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);
}

void dpp_reg_set_size(uint32_t id, uint32_t w, uint32_t h)
{
    /* source offset */
    dma_reg_set_buf_offset(id, 0, 0);

    /* source full(alloc) size */
    dma_reg_set_buf_size(id, w, h);

    /* source cropped size */
    dma_reg_set_img_size(id, w, h);
    dpp_reg_set_img_size(id, w, h);
}

void dma_reg_set_img_format(uint32_t id, uint32_t fmt)
{
    uint32_t val;
    uint32_t mask;

    val = IDMA_IMG_FORMAT(fmt);
    mask = IDMA_IMG_FORMAT_MASK;
    dma_write_mask(id, IDMA_IN_CON, val, mask);
}

void dpp_reg_set_img_format(uint32_t id, uint32_t fmt)
{
    uint32_t val;
    val = DPP_IMG_FORMAT(fmt);
    dpp_write_mask(id, DPP_IN_CON, val, DPP_IMG_FORMAT_MASK);
}

int dpp_reg_set_format(uint32_t id, enum dpp_pixel_format format)
{
    uint32_t fmt;
    /* 0=ARGB, 1=YUV */
    uint32_t fmt_type = 0;
    /* [WB] chroma x-/y-offset when R2Y : [0, 4] */

    switch ((enum decon_pixel_format)format) {
    case DECON_PIXEL_FORMAT_ARGB_8888:
        fmt = IDMA_IMG_FORMAT_ARGB8888;
        break;
    case DECON_PIXEL_FORMAT_ABGR_8888:
        fmt = IDMA_IMG_FORMAT_ABGR8888;
        break;
    case DECON_PIXEL_FORMAT_RGBA_8888:
        fmt = IDMA_IMG_FORMAT_RGBA8888;
        break;
    case DECON_PIXEL_FORMAT_BGRA_8888:
        fmt = IDMA_IMG_FORMAT_BGRA8888;
        break;
    case DECON_PIXEL_FORMAT_XRGB_8888:
        fmt = IDMA_IMG_FORMAT_XRGB8888;
        break;
    case DECON_PIXEL_FORMAT_XBGR_8888:
        fmt = IDMA_IMG_FORMAT_XBGR8888;
        break;
    case DECON_PIXEL_FORMAT_RGBX_8888:
        fmt = IDMA_IMG_FORMAT_RGBX8888;
        break;
    case DECON_PIXEL_FORMAT_BGRX_8888:
        fmt = IDMA_IMG_FORMAT_BGRX8888;
        break;
    case DECON_PIXEL_FORMAT_RGB_565:
        fmt = IDMA_IMG_FORMAT_RGB565;
        break;
    /* TODO: add ARGB1555 & ARGB4444 */
    case DECON_PIXEL_FORMAT_NV12:
    case DECON_PIXEL_FORMAT_NV12M:
        fmt = IDMA_IMG_FORMAT_YUV420_2P;
        fmt_type = 1;
        break;
    case DECON_PIXEL_FORMAT_NV21:
    case DECON_PIXEL_FORMAT_NV21M:
    case DECON_PIXEL_FORMAT_NV12N:
        fmt = IDMA_IMG_FORMAT_YVU420_2P;
        fmt_type = 1;
        break;
    default:
        return -1;
    }

    dma_reg_set_img_format(id, fmt);
    dpp_reg_set_img_format(id, fmt_type);
    return 0;
}

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);
}

void dpp_reg_set_buf_addr(uint32_t id, uint32_t addr)
{
    dma_reg_set_in_base_addr(id, addr, 0);
}

void dma_reg_clear_irq_all(uint32_t id)
{
    uint32_t val = IDMA_ALL_IRQ_CLEAR;

    dma_write_mask(id, IDMA_IRQ, val, val);
}

void dpp_reg_set_irq_clear_all(uint32_t id)
{
    dpp_write_mask(id, DPP_IRQ, DPP_ALL_IRQ_CLEAR, DPP_ALL_IRQ_CLEAR);
}

void dma_reg_set_sw_reset(uint32_t id)
{
    dma_write_mask(id, IDMA_ENABLE, ~0, IDMA_SRSET);
}

void dpp_reg_set_sw_reset(uint32_t id)
{
    dpp_write_mask(id, DPP_ENABLE, ~0, DPP_SRSET);

}

int dpp_reg_wait_sw_reset_status(uint32_t id)
{
    uint32_t cfg = 0;
    unsigned long cnt = 100000;

    do {
        cfg = dpp_read(id, DPP_ENABLE);
        if (!(cfg & (DPP_SRSET))) {
            return 0;
        }
        udelay(10);
    } while (--cnt);

    dbgPrintf("[dpp] timeout sw reset %s \n", __func__);

    return -1;
}

int dma_reg_wait_sw_reset_status(uint32_t id)
{
    uint32_t cfg = 0;
    unsigned long cnt = 100000;

    do {
        cfg = dma_read(id, IDMA_ENABLE);
        if (!(cfg & (IDMA_SRSET))) {
            return 0;
        }
        udelay(10);
    } while (--cnt);

    dbgPrintf("[dma] timeout sw-reset %s\n", __func__);

    return -1;
}

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;

    /* dpp initialization */
    dma_reg_set_irq_mask_all(id, 1);
    dpp_reg_set_irq_mask_all(id, 1);

    dma_reg_set_clock_gate_en_all(id, 1);
    /* HACK */
    /*dpp_reg_set_clock_gate_en_all(id, 1); */

    dpp_reg_set_lookup_table(id);
    dpp_reg_set_dynamic_clock_gating(id);
    dpp_reg_set_plane_alpha_fixed(id);

    /* part of the dpp/dma configuration */
    dpp_reg_set_scale_ratio(id);
    dpp_reg_set_size(id, g_width, g_height);
    dpp_reg_set_format(id, PIXEL_FORMAT_ABGR_8888);
    dma_reg_set_secure_mode(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;
    uint32_t reset = 0;

    dpp_reg_wait_status_idle(id);
    dma_reg_set_secure_mode(id, 0);
    dma_reg_clear_irq_all(id);
    dpp_reg_set_irq_clear_all(id);

    dma_reg_set_irq_mask_all(id, 1);
    dpp_reg_set_irq_mask_all(id, 1);

    if (reset) {
        dma_reg_set_sw_reset(id);
        dpp_reg_set_sw_reset(id);

        if (dpp_reg_wait_sw_reset_status(id)
            && dma_reg_wait_sw_reset_status(id)) {
            return;
        }
    }
}