// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2016-2018, 2020, The Linux Foundation. All rights reserved.
 *
 */

#define pr_fmt(fmt)	"%s: " fmt, __func__

#include <linux/uaccess.h>
#include "mdss_fb.h"
#include "mdss_mdp.h"
#include "mdss_mdp_pp.h"
#include "mdss_mdp_pp_common.h"

#define IGC_DSPP_OP_MODE_EN BIT(0)
#define ENHIST_BIT_SHIFT 16
/* PA related define */

/* Offsets from DSPP/VIG base to PA block */
#define PA_DSPP_BLOCK_REG_OFF 0x800
#define PA_VIG_BLOCK_REG_OFF 0x1200

/* Offsets to various subblocks from PA block
 * in VIG/DSPP.
 */
#define PA_OP_MODE_REG_OFF 0x0
#define PA_HIST_REG_OFF 0x4
#define PA_LUTV_SWAP_REG_OFF 0x18
#define PA_HSIC_REG_OFF 0x1C
#define PA_DITHER_CTL_REG_OFF 0x2C
#define PA_PWL_HOLD_REG_OFF 0x40

/* Memory Color offsets */
#define PA_MEM_COL_REG_OFF 0x80
#define PA_MEM_SKIN_REG_OFF (PA_MEM_COL_REG_OFF)
#define PA_MEM_SKY_REG_OFF  (PA_MEM_SKIN_REG_OFF + \
				JUMP_REGISTERS_OFF(5))
#define PA_MEM_FOL_REG_OFF  (PA_MEM_SKY_REG_OFF + \
				JUMP_REGISTERS_OFF(5))
#define PA_MEM_SKIN_ADJUST_P2_REG_OFF (PA_MEM_FOL_REG_OFF + \
					JUMP_REGISTERS_OFF(5))
#define PA_MEM_SKY_ADJUST_P2_REG_OFF (PA_MEM_SKIN_ADJUST_P2_REG_OFF + \
					JUMP_REGISTERS_OFF(2))
#define PA_MEM_FOL_ADJUST_P2_REG_OFF (PA_MEM_SKY_ADJUST_P2_REG_OFF + \
					JUMP_REGISTERS_OFF(2))

#define PA_SZONE_REG_OFF 0x100
#define PA_LUTV_REG_OFF 0x200
#define PA_HIST_RAM_REG_OFF 0x400

#define PPB_GLOBAL_DITHER_REG_OFF 0x30E0
#define DITHER_MATRIX_LEN 16
#define DITHER_DEPTH_MAP_INDEX 9
static u32 dither_matrix[DITHER_MATRIX_LEN] = {
	15, 7, 13, 5, 3, 11, 1, 9, 12, 4, 14, 6, 0, 8, 2, 10};
static u32 dither_depth_map[DITHER_DEPTH_MAP_INDEX] = {
	0, 0, 0, 0, 0, 1, 2, 3, 3};

#define PA_DITHER_REG_OFF 0x2C

#define IGC_DITHER_STRENGTH_REG_OFF 0x7E0

/* histogram prototypes */
static int pp_get_hist_offset(u32 block, u32 *ctl_off);
static int pp_hist_set_config(char __iomem *base_addr,
		struct pp_sts_type *pp_sts, void *cfg_data,
		u32 block_type);
static int pp_hist_get_config(char __iomem *base_addr, void *cfg_data,
		u32 block_type, u32 disp_num);

/* PA LUT prototypes */
static int pp_hist_lut_get_config(char __iomem *base_addr, void *cfg_data,
			   u32 block_type, u32 disp_num);
static int pp_hist_lut_set_config(char __iomem *base_addr,
		struct pp_sts_type *pp_sts, void *cfg_data,
		u32 block_type);
static int pp_hist_lut_get_version(u32 *version);
static void pp_hist_lut_opmode_config(char __iomem *base_addr,
		struct pp_sts_type *pp_sts);

/* dither prototypes */
static int pp_dither_get_config(char __iomem *base_addr, void *cfg_data,
		u32 block_type, u32 disp_num);
static int pp_dither_set_config(char __iomem *base_addr,
		struct pp_sts_type *pp_sts, void *cfg_data,
		u32 block_type);
static int pp_dither_get_version(u32 *version);

/* PA prototypes */
static int pp_pa_set_config(char __iomem *base_addr,
		struct pp_sts_type *pp_sts, void *cfg_data,
		u32 block_type);
static int pp_pa_get_config(char __iomem *base_addr, void *cfg_data,
		u32 block_type, u32 disp_num);
static int pp_pa_get_version(u32 *version);
static void pp_pa_set_global_adj_regs(char __iomem *base_addr,
		struct mdp_pa_data_v1_7 *pa_data, u32 flag);
static void pp_pa_set_mem_col(char __iomem *base_addr,
		struct mdp_pa_data_v1_7 *pa_data, u32 flags);
static void pp_pa_set_six_zone(char __iomem *base_addr,
		struct mdp_pa_data_v1_7 *pa_data,
		u32 flags);
static void pp_pa_opmode_config(char __iomem *base_addr,
		struct pp_sts_type *pp_sts);

/* PA dither prototypes */
static int pp_pa_dither_get_version(u32 *version);
static int pp_pa_dither_set_config(char __iomem *base_addr,
		struct pp_sts_type *pp_sts, void *cfg_data,
		u32 block_type);

/* IGC prototypes */
static int pp_igc_set_config(char __iomem *base_addr,
		struct pp_sts_type *pp_sts, void *cfg_data,
		u32 block_type);
static int pp_igc_get_config(char __iomem *base_addr, void *cfg_data,
		u32 block_type, u32 disp_num);
static int pp_igc_get_version(u32 *version);
static int pp_igc_dither_set_strength(char __iomem *base_addr,
		struct pp_sts_type *pp_sts, void *cfg_data,
		u32 block_type);

static void pp_opmode_config(int location, struct pp_sts_type *pp_sts,
		u32 *opmode, int side);
static int pp_driver_init(struct mdp_pp_driver_ops *ops);

static struct mdss_pp_res_type_v3 config_data;

static int pp_driver_init(struct mdp_pp_driver_ops *ops)
{
	int i = 0;

	if (!ops->pp_ops[IGC].pp_set_config) {
		pr_err("IGC function is not set\n");
		return -EINVAL;
	}
	config_data.igc_set_config = ops->pp_ops[IGC].pp_set_config;
	for (i = 0; i < MDSS_BLOCK_DISP_NUM; i++) {
		config_data.igc_v3_data[i].c0_c1_data =
			&config_data.igc_table_c0_c1[i][0];
		config_data.igc_v3_data[i].c2_data =
			&config_data.igc_table_c2[i][0];
		config_data.igc_v3_data[i].len = IGC_LUT_ENTRIES;
		config_data.igc_v3_data[i].strength = 0;
		config_data.igc_v3_data[i].table_fmt = mdp_igc_rec_max;
	}
	return 0;
}

void *pp_get_driver_ops_v3(struct mdp_pp_driver_ops *ops)
{
	if (!ops) {
		pr_err("PP driver ops invalid %pK\n", ops);
		return ERR_PTR(-EINVAL);
	}

	if (pp_driver_init(ops))
		return ERR_PTR(-EINVAL);
	/* PA ops */
	ops->pp_ops[PA].pp_set_config = pp_pa_set_config;
	ops->pp_ops[PA].pp_get_config = pp_pa_get_config;
	ops->pp_ops[PA].pp_get_version = pp_pa_get_version;

	/* HIST_LUT ops */
	ops->pp_ops[HIST_LUT].pp_set_config = pp_hist_lut_set_config;
	ops->pp_ops[HIST_LUT].pp_get_config = pp_hist_lut_get_config;
	ops->pp_ops[HIST_LUT].pp_get_version = pp_hist_lut_get_version;

	/* HIST ops */
	ops->pp_ops[HIST].pp_set_config = pp_hist_set_config;
	ops->pp_ops[HIST].pp_get_config = pp_hist_get_config;
	ops->pp_ops[HIST].pp_get_version = NULL;

	/* Dither ops */
	ops->pp_ops[DITHER].pp_set_config = pp_dither_set_config;
	ops->pp_ops[DITHER].pp_get_config = pp_dither_get_config;
	ops->pp_ops[DITHER].pp_get_version = pp_dither_get_version;

	ops->pp_ops[PA_DITHER].pp_get_version = pp_pa_dither_get_version;
	ops->pp_ops[PA_DITHER].pp_set_config = pp_pa_dither_set_config;
	ops->pp_ops[PA_DITHER].pp_get_config = NULL;

	ops->pp_ops[IGC].pp_get_config = pp_igc_get_config;
	ops->pp_ops[IGC].pp_get_version = pp_igc_get_version;
	ops->pp_ops[IGC].pp_set_config = pp_igc_set_config;

	/* Set opmode pointers */
	ops->pp_opmode_config = pp_opmode_config;

	ops->get_hist_offset = pp_get_hist_offset;
	ops->gamut_clk_gate_en = NULL;
	ops->igc_set_dither_strength = pp_igc_dither_set_strength;

	return &config_data;
}

static int pp_get_hist_offset(u32 block, u32 *ctl_off)
{
	int ret = 0;

	if (!ctl_off) {
		pr_err("invalid params ctl_off %pK\n", ctl_off);
		return -EINVAL;
	}

	switch (block) {
	case SSPP_VIG:
		*ctl_off = PA_VIG_BLOCK_REG_OFF + PA_HIST_REG_OFF;
		break;
	case DSPP:
		*ctl_off = PA_DSPP_BLOCK_REG_OFF + PA_HIST_REG_OFF;
		break;
	default:
		pr_err("Invalid block type %d\n", block);
		ret = -EINVAL;
		break;
	}
	return ret;
}

static int pp_hist_set_config(char __iomem *base_addr,
		struct pp_sts_type *pp_sts, void *cfg_data, u32 block_type)
{
	u32 opmode = 0;
	struct pp_hist_col_info *hist_info = NULL;

	if (!base_addr || !cfg_data || !pp_sts) {
		pr_err("invalid params base_addr %pK cfg_data %pK pp_sts_type %pK\n",
		      base_addr, cfg_data, pp_sts);
		return -EINVAL;
	}

	if (block_type != DSPP) {
		pr_err("Invalid block type %d\n", block_type);
		return -EINVAL;
	}

	hist_info = (struct pp_hist_col_info *)cfg_data;
	opmode = readl_relaxed(base_addr + PA_DSPP_BLOCK_REG_OFF +
				PA_OP_MODE_REG_OFF);
	/* set the hist_en bit */
	if (hist_info->col_en) {
		pp_sts->hist_sts |= PP_STS_ENABLE;
		opmode |= BIT(16);
	} else {
		pp_sts->hist_sts &= ~PP_STS_ENABLE;
		opmode &= ~BIT(16);
	}

	writel_relaxed(opmode, base_addr + PA_DSPP_BLOCK_REG_OFF +
			PA_OP_MODE_REG_OFF);
	return 0;
}

static int pp_hist_get_config(char __iomem *base_addr, void *cfg_data,
			   u32 block_type, u32 disp_num)
{
	int i = 0;
	u32 sum = 0;
	struct pp_hist_col_info *hist_info = NULL;
	char __iomem *hist_addr;

	if (!base_addr || !cfg_data) {
		pr_err("invalid params base_addr %pK cfg_data %pK\n",
		       base_addr, cfg_data);
		return -EINVAL;
	}

	if (block_type != DSPP) {
		pr_err("Invalid block type %d\n", block_type);
		return -EINVAL;
	}

	hist_info = (struct pp_hist_col_info *) cfg_data;
	hist_addr = base_addr + PA_DSPP_BLOCK_REG_OFF + PA_HIST_RAM_REG_OFF;

	for (i = 0; i < HIST_V_SIZE; i++) {
		hist_info->data[i] = readl_relaxed(hist_addr) & REG_MASK(24);
		hist_addr += 0x4;
		sum += hist_info->data[i];
	}
	hist_info->hist_cnt_read++;
	return sum;
}

static int pp_hist_lut_get_config(char __iomem *base_addr, void *cfg_data,
			   u32 block_type, u32 disp_num)
{

	int ret = 0, i = 0;
	char __iomem *hist_lut_addr;
	u32 sz = 0, temp = 0, *data = NULL;
	struct mdp_hist_lut_data_v1_7 lut_data_v1_7;
	struct mdp_hist_lut_data_v1_7 *lut_data = &lut_data_v1_7;
	struct mdp_hist_lut_data *lut_cfg_data = NULL;

	if (!base_addr || !cfg_data) {
		pr_err("invalid params base_addr %pK cfg_data %pK\n",
		       base_addr, cfg_data);
		return -EINVAL;
	}

	if (block_type != DSPP) {
		pr_err("Invalid block type %d\n", block_type);
		return -EINVAL;
	}

	lut_cfg_data = (struct mdp_hist_lut_data *) cfg_data;
	if (!(lut_cfg_data->ops & MDP_PP_OPS_READ)) {
		pr_err("read ops not set for hist_lut %d\n", lut_cfg_data->ops);
		return 0;
	}
	if (lut_cfg_data->version != mdp_hist_lut_v1_7 ||
		!lut_cfg_data->cfg_payload) {
		pr_err("invalid hist_lut version %d payload %pK\n",
		       lut_cfg_data->version, lut_cfg_data->cfg_payload);
		return -EINVAL;
	}
	if (copy_from_user(lut_data, (void __user *) lut_cfg_data->cfg_payload,
		sizeof(*lut_data))) {
		pr_err("copy from user failed for lut_data\n");
		return -EFAULT;
	}
	if (lut_data->len != ENHIST_LUT_ENTRIES) {
		pr_err("invalid hist_lut len %d\n", lut_data->len);
		return -EINVAL;
	}
	sz = ENHIST_LUT_ENTRIES * sizeof(u32);
	if (!access_ok(VERIFY_WRITE, lut_data->data, sz)) {
		pr_err("invalid lut address for hist_lut sz %d\n", sz);
		return -EFAULT;
	}

	hist_lut_addr = base_addr + PA_DSPP_BLOCK_REG_OFF + PA_LUTV_REG_OFF;

	data = kzalloc(sz, GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	for (i = 0; i < ENHIST_LUT_ENTRIES; i += 2) {
		temp = readl_relaxed(hist_lut_addr);
		data[i] = temp & REG_MASK(10);
		data[i + 1] =
			(temp & REG_MASK_SHIFT(10, 16)) >> ENHIST_BIT_SHIFT;
		hist_lut_addr += 4;
	}
	if (copy_to_user(lut_data->data, data, sz)) {
		pr_err("failed to copy the hist_lut back to user\n");
		ret = -EFAULT;
	}
	kfree(data);
	return ret;
}

static int pp_hist_lut_set_config(char __iomem *base_addr,
		struct pp_sts_type *pp_sts, void *cfg_data,
		u32 block_type)
{
	int ret = 0, i = 0;
	u32 temp = 0;
	struct mdp_hist_lut_data *lut_cfg_data = NULL;
	struct mdp_hist_lut_data_v1_7 *lut_data = NULL;
	char __iomem *hist_lut_addr = NULL, *swap_addr = NULL;

	if (!base_addr || !cfg_data || !pp_sts) {
		pr_err("invalid params base_addr %pK cfg_data %pK pp_sts_type %pK\n",
		      base_addr, cfg_data, pp_sts);
		return -EINVAL;
	}

	if (block_type != DSPP) {
		pr_err("Invalid block type %d\n", block_type);
		return -EINVAL;
	}

	lut_cfg_data = (struct mdp_hist_lut_data *) cfg_data;
	if (lut_cfg_data->version != mdp_hist_lut_v1_7) {
		pr_err("invalid hist_lut version %d\n", lut_cfg_data->version);
		return -EINVAL;
	}

	if (!(lut_cfg_data->ops & ~(MDP_PP_OPS_READ))) {
		pr_err("only read ops set for lut\n");
		return ret;
	}
	if (lut_cfg_data->ops & MDP_PP_OPS_DISABLE ||
		!(lut_cfg_data->ops & MDP_PP_OPS_WRITE)) {
		pr_debug("non write ops set %d\n", lut_cfg_data->ops);
		goto hist_lut_set_sts;
	}
	lut_data = lut_cfg_data->cfg_payload;
	if (!lut_data) {
		pr_err("invalid hist_lut cfg_payload %pK\n", lut_data);
		return -EINVAL;
	}

	if (lut_data->len != ENHIST_LUT_ENTRIES || !lut_data->data) {
		pr_err("invalid hist_lut len %d data %pK\n",
		       lut_data->len, lut_data->data);
		return -EINVAL;
	}

	hist_lut_addr = base_addr + PA_DSPP_BLOCK_REG_OFF + PA_LUTV_REG_OFF;
	swap_addr = base_addr + PA_DSPP_BLOCK_REG_OFF + PA_LUTV_SWAP_REG_OFF;

	for (i = 0; i < ENHIST_LUT_ENTRIES; i += 2) {
		temp = (lut_data->data[i] & REG_MASK(10)) |
			((lut_data->data[i + 1] & REG_MASK(10))
			 << ENHIST_BIT_SHIFT);

		writel_relaxed(temp, hist_lut_addr);
		hist_lut_addr += 4;
	}

	writel_relaxed(1, swap_addr);

hist_lut_set_sts:
	if (lut_cfg_data->ops & MDP_PP_OPS_DISABLE) {
		pp_sts->enhist_sts &= ~(PP_STS_ENABLE | PP_STS_PA_LUT_FIRST);
	} else if (lut_cfg_data->ops & MDP_PP_OPS_ENABLE) {
		pp_sts->enhist_sts |= PP_STS_ENABLE;
		if (lut_cfg_data->hist_lut_first)
			pp_sts->enhist_sts |= PP_STS_PA_LUT_FIRST;
		else
			pp_sts->enhist_sts &= ~PP_STS_PA_LUT_FIRST;
	}

	pp_hist_lut_opmode_config(base_addr + PA_DSPP_BLOCK_REG_OFF, pp_sts);
	return ret;
}

static int pp_hist_lut_get_version(u32 *version)
{
	if (!version) {
		pr_err("invalid param version %pK\n", version);
		return -EINVAL;
	}
	*version = mdp_hist_lut_v1_7;
	return 0;
}

static void pp_hist_lut_opmode_config(char __iomem *base_addr,
		struct pp_sts_type *pp_sts)
{
	u32 opmode = 0;

	if (!base_addr || !pp_sts) {
		pr_err("invalid params base_addr %pK pp_sts_type %pK\n",
			base_addr, pp_sts);
		return;
	}
	opmode = readl_relaxed(base_addr + PA_OP_MODE_REG_OFF);

	/* set the hist_lutv_en and hist_lutv_first_en bits */
	if (pp_sts->enhist_sts & PP_STS_ENABLE) {
		opmode |= BIT(19) | BIT(20);
		opmode |= (pp_sts->enhist_sts & PP_STS_PA_LUT_FIRST) ?
				BIT(21) : 0;
	} else {
		opmode &= ~(BIT(19) | BIT(21));
		if (!(pp_sts->pa_sts & PP_STS_ENABLE))
			opmode &= ~BIT(20);
	}

	writel_relaxed(opmode, base_addr + PA_OP_MODE_REG_OFF);
}

static int pp_pa_set_config(char __iomem *base_addr,
		struct pp_sts_type *pp_sts, void *cfg_data,
		u32 block_type)
{
	struct mdp_pa_v2_cfg_data *pa_cfg_data = NULL;
	struct mdp_pa_data_v1_7 *pa_data = NULL;
	char __iomem *block_addr = NULL;

	if (!base_addr || !cfg_data || !pp_sts) {
		pr_err("invalid params base_addr %pK cfg_data %pK pp_sts_type %pK\n",
				base_addr, cfg_data, pp_sts);
		return -EINVAL;
	}
	if ((block_type != DSPP) && (block_type != SSPP_VIG)) {
		pr_err("Invalid block type %d\n", block_type);
		return -EINVAL;
	}

	pa_cfg_data = (struct mdp_pa_v2_cfg_data *) cfg_data;
	if (pa_cfg_data->version != mdp_pa_v1_7) {
		pr_err("invalid pa version %d\n", pa_cfg_data->version);
		return -EINVAL;
	}
	if (!(pa_cfg_data->flags & ~(MDP_PP_OPS_READ))) {
		pr_info("only read ops is set %d\n", pa_cfg_data->flags);
		return 0;
	}

	block_addr = base_addr +
		((block_type == DSPP) ? PA_DSPP_BLOCK_REG_OFF :
		 PA_VIG_BLOCK_REG_OFF);

	if (pa_cfg_data->flags & MDP_PP_OPS_DISABLE ||
		!(pa_cfg_data->flags & MDP_PP_OPS_WRITE)) {
		pr_debug("pa_cfg_data->flags = %d\n", pa_cfg_data->flags);
		goto pa_set_sts;
	}

	pa_data = pa_cfg_data->cfg_payload;
	if (!pa_data) {
		pr_err("invalid payload for pa %pK\n", pa_data);
		return -EINVAL;
	}

	pp_pa_set_global_adj_regs(block_addr, pa_data, pa_cfg_data->flags);
	pp_pa_set_mem_col(block_addr, pa_data, pa_cfg_data->flags);
	if (block_type == DSPP)
		pp_pa_set_six_zone(block_addr, pa_data, pa_cfg_data->flags);

pa_set_sts:
	pp_pa_set_sts(pp_sts, pa_data, pa_cfg_data->flags, block_type);
	pp_pa_opmode_config(block_addr, pp_sts);

	return 0;
}

static int pp_pa_get_config(char __iomem *base_addr, void *cfg_data,
		u32 block_type, u32 disp_num)
{
	return -ENOTSUPP;
}

static int pp_pa_get_version(u32 *version)
{
	if (!version) {
		pr_err("invalid param version\n");
		return -EINVAL;
	}
	*version = mdp_pa_v1_7;
	return 0;
}

static int pp_dither_get_config(char __iomem *base_addr, void *cfg_data,
		u32 block_type, u32 disp_num)
{
	return -ENOTSUPP;
}

static int pp_dither_set_config(char __iomem *base_addr,
		struct pp_sts_type *pp_sts, void *cfg_data,
		u32 block_type)
{
	int i = 0;
	u32 data;
	struct mdp_dither_cfg_data *dither_cfg_data = NULL;
	struct mdp_dither_data_v1_7 *dither_data = NULL;
	char __iomem *dither_opmode = NULL;

	if (!base_addr || !cfg_data || !pp_sts) {
		pr_err("invalid params base_addr %pK cfg_data %pK pp_sts_type %pK\n",
		      base_addr, cfg_data, pp_sts);
		return -EINVAL;
	}
	if (block_type != PPB)
		return -ENOTSUPP;
	dither_opmode = base_addr + PPB_GLOBAL_DITHER_REG_OFF;
	base_addr = dither_opmode + 4;

	dither_cfg_data = (struct mdp_dither_cfg_data *) cfg_data;

	if (dither_cfg_data->version != mdp_dither_v1_7) {
		pr_err("invalid dither version %d\n", dither_cfg_data->version);
		return -EINVAL;
	}

	if (dither_cfg_data->flags & MDP_PP_OPS_READ) {
		pr_err("Invalid context for read operation\n");
		return -EINVAL;
	}

	if (dither_cfg_data->flags & MDP_PP_OPS_DISABLE ||
		!(dither_cfg_data->flags & MDP_PP_OPS_WRITE)) {
		pr_debug("non write ops set %d\n", dither_cfg_data->flags);
		goto dither_set_sts;
	}

	dither_data = dither_cfg_data->cfg_payload;
	if (!dither_data) {
		pr_err("invalid payload for dither %pK\n", dither_data);
		return -EINVAL;
	}

	if ((dither_data->g_y_depth >= DITHER_DEPTH_MAP_INDEX) ||
		(dither_data->b_cb_depth >= DITHER_DEPTH_MAP_INDEX) ||
		(dither_data->r_cr_depth >= DITHER_DEPTH_MAP_INDEX)) {
		pr_err("invalid data for dither, g_y_depth %d y_cb_depth %d r_cr_depth %d\n",
			dither_data->g_y_depth, dither_data->b_cb_depth,
			dither_data->r_cr_depth);
		return -EINVAL;
	}
	data = dither_depth_map[dither_data->g_y_depth];
	data |= dither_depth_map[dither_data->b_cb_depth] << 2;
	data |= dither_depth_map[dither_data->r_cr_depth] << 4;
	data |= (dither_data->temporal_en) ? (1  << 8) : 0;
	writel_relaxed(data, base_addr);
	base_addr += 4;
	for (i = 0; i < DITHER_MATRIX_LEN; i += 4) {
		data = (dither_matrix[i] & REG_MASK(4)) |
			((dither_matrix[i + 1] & REG_MASK(4)) << 4) |
			((dither_matrix[i + 2] & REG_MASK(4)) << 8) |
			((dither_matrix[i + 3] & REG_MASK(4)) << 12);
		writel_relaxed(data, base_addr);
		base_addr += 4;
	}

dither_set_sts:
	pp_sts_set_split_bits(&pp_sts->dither_sts,
			dither_cfg_data->flags);
	if (dither_cfg_data->flags & MDP_PP_OPS_DISABLE) {
		pp_sts->dither_sts &= ~PP_STS_ENABLE;
		writel_relaxed(0, dither_opmode);
	} else if (dither_cfg_data->flags & MDP_PP_OPS_ENABLE) {
		pp_sts->dither_sts |= PP_STS_ENABLE;
		if (pp_sts_is_enabled(pp_sts->dither_sts, pp_sts->side_sts))
			writel_relaxed(BIT(0), dither_opmode);
	}
	return 0;
}

static int pp_dither_get_version(u32 *version)
{
	if (!version) {
		pr_err("invalid param version\n");
		return -EINVAL;
	}
	*version = mdp_dither_v1_7;
	return 0;
}

static void pp_opmode_config(int location, struct pp_sts_type *pp_sts,
		u32 *opmode, int side)
{
	if (!pp_sts || !opmode) {
		pr_err("Invalid pp_sts %pK or opmode %pK\n", pp_sts, opmode);
		return;
	}
	switch (location) {
	case SSPP_DMA:
		break;
	case SSPP_VIG:
		break;
	case DSPP:
		if (pp_sts_is_enabled(pp_sts->igc_sts, side))
			*opmode |= IGC_DSPP_OP_MODE_EN;
		break;
	case LM:
		if (pp_sts->argc_sts & PP_STS_ENABLE)
			pr_debug("pgc in LM enabled\n");
		break;
	default:
		pr_err("Invalid block type %d\n", location);
		break;
	}
}

static void pp_pa_set_global_adj_regs(char __iomem *base_addr,
		struct mdp_pa_data_v1_7 *pa_data, u32 flags)
{
	char __iomem *addr = NULL;

	addr = base_addr + PA_HSIC_REG_OFF;
	if (flags & MDP_PP_PA_HUE_ENABLE)
		writel_relaxed((pa_data->global_hue_adj &
					REG_MASK(12)), addr);
	addr += 4;
	if (flags & MDP_PP_PA_SAT_ENABLE)
		writel_relaxed((pa_data->global_sat_adj &
					REG_MASK(16)), addr);
	addr += 4;
	if (flags & MDP_PP_PA_VAL_ENABLE)
		writel_relaxed((pa_data->global_val_adj &
					REG_MASK(8)), addr);
	addr += 4;
	if (flags & MDP_PP_PA_CONT_ENABLE)
		writel_relaxed((pa_data->global_cont_adj &
					REG_MASK(8)), addr);
}

static void pp_pa_set_mem_col(char __iomem *base_addr,
		struct mdp_pa_data_v1_7 *pa_data, u32 flags)
{
	char __iomem *mem_col_base = NULL, *mem_col_p2 = NULL;
	struct mdp_pa_mem_col_data_v1_7 *mem_col_data = NULL;
	uint32_t mask = 0, hold = 0, hold_mask = 0;
	uint32_t hold_curr = 0;

	flags &= (MDP_PP_PA_SKIN_ENABLE | MDP_PP_PA_SKY_ENABLE |
			MDP_PP_PA_FOL_ENABLE);
	if (!flags)
		return;
	while (flags) {
		if (flags & MDP_PP_PA_SKIN_ENABLE) {
			flags &= ~MDP_PP_PA_SKIN_ENABLE;
			mem_col_base = base_addr + PA_MEM_SKIN_REG_OFF;
			mem_col_p2 = base_addr + PA_MEM_SKIN_ADJUST_P2_REG_OFF;
			mem_col_data = &pa_data->skin_cfg;
			hold |= pa_data->skin_cfg.sat_hold & REG_MASK(2);
			hold |= (pa_data->skin_cfg.val_hold & REG_MASK(2))
				<< 2;
			hold_mask |= REG_MASK(4);
		} else if (flags & MDP_PP_PA_SKY_ENABLE) {
			flags &= ~MDP_PP_PA_SKY_ENABLE;
			mem_col_base = base_addr + PA_MEM_SKY_REG_OFF;
			mem_col_p2 = base_addr + PA_MEM_SKY_ADJUST_P2_REG_OFF;
			mem_col_data = &pa_data->sky_cfg;
			hold |= (pa_data->sky_cfg.sat_hold & REG_MASK(2)) << 4;
			hold |= (pa_data->sky_cfg.val_hold & REG_MASK(2)) << 6;
			hold_mask |= REG_MASK_SHIFT(4, 4);
		} else if (flags & MDP_PP_PA_FOL_ENABLE) {
			flags &= ~MDP_PP_PA_FOL_ENABLE;
			mem_col_base = base_addr + PA_MEM_FOL_REG_OFF;
			mem_col_p2 = base_addr + PA_MEM_FOL_ADJUST_P2_REG_OFF;
			mem_col_data = &pa_data->fol_cfg;
			hold |= (pa_data->fol_cfg.sat_hold & REG_MASK(2)) << 8;
			hold |= (pa_data->fol_cfg.val_hold & REG_MASK(2)) << 10;
			hold_mask |= REG_MASK_SHIFT(4, 8);
		} else {
			break;
		}
		mask = REG_MASK_SHIFT(16, 16) | REG_MASK(11);
		writel_relaxed((mem_col_data->color_adjust_p0 & mask),
				mem_col_base);
		mem_col_base += 4;
		mask = U32_MAX;
		writel_relaxed((mem_col_data->color_adjust_p1 & mask),
				mem_col_base);
		mem_col_base += 4;
		mask = REG_MASK_SHIFT(11, 16) | REG_MASK(11);
		writel_relaxed((mem_col_data->hue_region & mask),
				mem_col_base);
		mem_col_base += 4;
		mask = REG_MASK(24);
		writel_relaxed((mem_col_data->sat_region & mask),
				mem_col_base);
		mem_col_base += 4;
		/* mask is same for val and sat */
		writel_relaxed((mem_col_data->val_region & mask),
				mem_col_base);
		mask = U32_MAX;
		writel_relaxed((mem_col_data->color_adjust_p2 & mask),
				mem_col_p2);
		mem_col_p2 += 4;
		writel_relaxed((mem_col_data->blend_gain & mask),
				mem_col_p2);
	}
	hold_curr = readl_relaxed(base_addr + PA_PWL_HOLD_REG_OFF) &
			REG_MASK(16);
	hold_curr &= ~hold_mask;
	hold = hold_curr | (hold & hold_mask);
	writel_relaxed(hold, (base_addr + PA_PWL_HOLD_REG_OFF));
}

static void pp_pa_set_six_zone(char __iomem *base_addr,
		struct mdp_pa_data_v1_7 *pa_data,
		u32 flags)
{
	char __iomem *addr = base_addr + PA_SZONE_REG_OFF;
	uint32_t mask_p0 = 0, mask_p1 = 0, hold = 0, hold_mask = 0;
	uint32_t hold_curr = 0;
	int i = 0;

	if (!(flags & MDP_PP_PA_SIX_ZONE_ENABLE))
		return;

	if (pa_data->six_zone_len != MDP_SIX_ZONE_LUT_SIZE ||
			!pa_data->six_zone_curve_p0 ||
			!pa_data->six_zone_curve_p1) {
		pr_err("Invalid six zone data: len %d curve_p0 %pK curve_p1 %pK\n",
				pa_data->six_zone_len,
				pa_data->six_zone_curve_p0,
				pa_data->six_zone_curve_p1);
		return;
	}
	mask_p0 = REG_MASK(12);
	mask_p1 = REG_MASK(12) | REG_MASK_SHIFT(12, 16);
	writel_relaxed((pa_data->six_zone_curve_p1[0] & mask_p1), addr + 4);
	/* Update the index to 0 and write value */
	writel_relaxed((pa_data->six_zone_curve_p0[0] & mask_p0) | BIT(26),
			addr);
	for (i = 1; i < MDP_SIX_ZONE_LUT_SIZE; i++) {
		writel_relaxed((pa_data->six_zone_curve_p1[i] & mask_p1),
				addr + 4);
		writel_relaxed((pa_data->six_zone_curve_p0[i] & mask_p0), addr);
	}
	addr += 8;
	writel_relaxed(pa_data->six_zone_thresh, addr);
	addr += 4;
	writel_relaxed(pa_data->six_zone_adj_p0 & REG_MASK(16), addr);
	addr += 4;
	writel_relaxed(pa_data->six_zone_adj_p1, addr);

	hold = (pa_data->six_zone_sat_hold & REG_MASK(2)) << 12;
	hold |= (pa_data->six_zone_val_hold & REG_MASK(2)) << 14;
	hold_mask = REG_MASK_SHIFT(4, 12);
	hold_curr = readl_relaxed(base_addr + PA_PWL_HOLD_REG_OFF) &
					REG_MASK(16);
	hold_curr &= ~hold_mask;
	hold = hold_curr | (hold & hold_mask);
	writel_relaxed(hold, (base_addr + PA_PWL_HOLD_REG_OFF));
}

static void pp_pa_opmode_config(char __iomem *base_addr,
		struct pp_sts_type *pp_sts)
{
	uint32_t opmode = 0;

	/* set the PA bits */
	if (pp_sts->pa_sts & PP_STS_ENABLE) {
		opmode |= BIT(20);

		if (pp_sts->pa_sts & PP_STS_PA_HUE_MASK)
			opmode |= BIT(25);
		if (pp_sts->pa_sts & PP_STS_PA_SAT_MASK)
			opmode |= BIT(26);
		if (pp_sts->pa_sts & PP_STS_PA_VAL_MASK)
			opmode |= BIT(27);
		if (pp_sts->pa_sts & PP_STS_PA_CONT_MASK)
			opmode |= BIT(28);
		if (pp_sts->pa_sts & PP_STS_PA_SAT_ZERO_EXP_EN)
			opmode |= BIT(1);
		if (pp_sts->pa_sts & PP_STS_PA_MEM_COL_SKIN_MASK)
			opmode |= BIT(5);
		if (pp_sts->pa_sts & PP_STS_PA_MEM_COL_FOL_MASK)
			opmode |= BIT(6);
		if (pp_sts->pa_sts & PP_STS_PA_MEM_COL_SKY_MASK)
			opmode |= BIT(7);
		if (pp_sts->pa_sts & PP_STS_PA_SIX_ZONE_HUE_MASK)
			opmode |= BIT(29);
		if (pp_sts->pa_sts & PP_STS_PA_SIX_ZONE_SAT_MASK)
			opmode |= BIT(30);
		if (pp_sts->pa_sts & PP_STS_PA_SIX_ZONE_VAL_MASK)
			opmode |= BIT(31);
		if (pp_sts->pa_sts & PP_STS_PA_MEM_PROT_HUE_EN)
			opmode |= BIT(22);
		if (pp_sts->pa_sts & PP_STS_PA_MEM_PROT_SAT_EN)
			opmode |= BIT(23);
		if (pp_sts->pa_sts & PP_STS_PA_MEM_PROT_VAL_EN)
			opmode |= BIT(24);
		if (pp_sts->pa_sts & PP_STS_PA_MEM_PROT_CONT_EN)
			opmode |= BIT(18);
		if (pp_sts->pa_sts & PP_STS_PA_MEM_PROT_BLEND_EN)
			opmode |= BIT(3);
		if (pp_sts->pa_sts & PP_STS_PA_MEM_PROT_SIX_EN)
			opmode |= BIT(17);
	}

	/* reset hist_en, hist_lutv_en and hist_lutv_first_en
	 * bits based on the pp_sts
	 */
	if (pp_sts->hist_sts & PP_STS_ENABLE)
		opmode |= BIT(16);
	if (pp_sts->enhist_sts & PP_STS_ENABLE)
		opmode |= BIT(19) | BIT(20);
	if (pp_sts->enhist_sts & PP_STS_PA_LUT_FIRST)
		opmode |= BIT(21);

	writel_relaxed(opmode, base_addr + PA_OP_MODE_REG_OFF);
}

static int pp_pa_dither_get_version(u32 *version)
{
	if (!version) {
		pr_err("invalid param version\n");
		return -EINVAL;
	}
	*version = mdp_dither_pa_v1_7;
	return 0;
}

static int pp_pa_dither_set_config(char __iomem *base_addr,
		struct pp_sts_type *pp_sts, void *cfg_data,
		u32 block_type)
{
	struct mdp_dither_cfg_data *dither_cfg_data = NULL;
	struct mdp_pa_dither_res_data_v1_7 *dither_data = NULL;
	u32 *pdata;
	u32 opmode = 0, data = 0, i = 0;
	char __iomem *opmode_addr = NULL, *matrix_addr = NULL;

	if (!base_addr || !cfg_data || !pp_sts) {
		pr_err("invalid params base_addr %pK cfg_data %pK pp_sts_type %pK\n",
				base_addr, cfg_data, pp_sts);
		return -EINVAL;
	}
	if (block_type != DSPP) {
		pr_err("Invalid block type %d\n", block_type);
		return -EINVAL;
	}

	dither_cfg_data = (struct mdp_dither_cfg_data *) cfg_data;
	if (dither_cfg_data->version != mdp_dither_pa_v1_7) {
		pr_err("invalid pa dither version %d\n",
			dither_cfg_data->version);
		return -EINVAL;
	}
	if (!(dither_cfg_data->flags & ~(MDP_PP_OPS_READ))) {
		pr_debug("only read ops is set %d\n", dither_cfg_data->flags);
		return 0;
	}
	opmode_addr = base_addr + PA_DSPP_BLOCK_REG_OFF + PA_DITHER_REG_OFF;
	if (dither_cfg_data->flags & MDP_PP_OPS_DISABLE ||
	    !(dither_cfg_data->flags & MDP_PP_OPS_WRITE)) {
		pr_debug("Disable pa dither/No write ops set flags %x\n",
			dither_cfg_data->flags);
		goto dither_set_sts;
	}
	matrix_addr = opmode_addr + 4;
	dither_data = (struct mdp_pa_dither_res_data_v1_7 *)
		dither_cfg_data->cfg_payload;
	if (!dither_data) {
		pr_err("invalid payload for dither\n");
		return -EINVAL;
	}
	pdata = dither_data->matrix_data;
	for (i = 0; i < MDP_DITHER_DATA_V1_7_SZ; i += 4) {
		data = (pdata[i] & REG_MASK(4)) |
			((pdata[i + 1] & REG_MASK(4)) << 4) |
			((pdata[i + 2] & REG_MASK(4)) << 8) |
			((pdata[i + 3] & REG_MASK(4)) << 12);
		writel_relaxed(data, matrix_addr);
		matrix_addr += 4;
	}
	opmode = BIT(0);
	opmode |= (dither_data->offset_en) ? BIT(1) : 0;
	opmode |= ((dither_data->strength) & REG_MASK(4)) << 4;

dither_set_sts:
	if (dither_cfg_data->flags & MDP_PP_OPS_DISABLE) {
		pp_sts->pa_dither_sts &= ~PP_STS_ENABLE;
		writel_relaxed(0, opmode_addr);
	} else if (dither_cfg_data->flags & MDP_PP_OPS_ENABLE) {
		pp_sts->pa_dither_sts |= PP_STS_ENABLE;
		writel_relaxed(opmode, opmode_addr);
	}
	return 0;
}

static int pp_igc_dither_set_strength(char __iomem *base_addr,
		struct pp_sts_type *pp_sts, void *cfg_data,
		u32 block_type)
{
	struct mdp_igc_lut_data *lut_cfg_data = cfg_data;
	struct mdp_igc_lut_data_config *v3_data = NULL;

	if (!base_addr || !cfg_data || (block_type != DSPP) || !pp_sts
		|| (lut_cfg_data->version != mdp_igc_v3)) {
		pr_err("invalid params base_addr %pK cfg_data %pK block_type %d igc version %d\n",
			base_addr, cfg_data, block_type, (lut_cfg_data ?
			lut_cfg_data->version : mdp_pp_unknown));
		return -EINVAL;
	}

	if ((lut_cfg_data->ops & MDP_PP_OPS_DISABLE) ||
		!(lut_cfg_data->ops & MDP_PP_OPS_WRITE))
		return 0;

	if (!lut_cfg_data->cfg_payload) {
		pr_err("invalid payload igc dither strenth\n");
		return -EINVAL;
	}
	v3_data = lut_cfg_data->cfg_payload;
	writel_relaxed(v3_data->strength,
			base_addr + IGC_DITHER_STRENGTH_REG_OFF);
	return 0;
}

static int pp_igc_set_config(char __iomem *base_addr,
		struct pp_sts_type *pp_sts, void *cfg_data,
		u32 block_type)
{
	struct mdp_igc_lut_data *lut_cfg_data = NULL, v17_cfg_data = {0};
	struct mdp_igc_lut_data_config *v3_data = NULL;
	struct mdp_igc_lut_data_v1_7 v17_lut_data = {0};
	int ret = 0;

	if (!base_addr || !pp_sts || !cfg_data || !config_data.igc_set_config) {
		pr_err("invalid payload base_addr %pK pp_sts %pK cfg_data %pK igc_set_config %pK\n",
			base_addr, pp_sts, cfg_data,
			config_data.igc_set_config);
		return -EINVAL;
	}
	lut_cfg_data = cfg_data;
	v3_data = lut_cfg_data->cfg_payload;
	if (v3_data) {
		v17_lut_data.c0_c1_data = v3_data->c0_c1_data;
		v17_lut_data.c2_data = v3_data->c2_data;
		v17_lut_data.len = v3_data->len;
		v17_lut_data.table_fmt = v3_data->table_fmt;
	}
	memcpy(&v17_cfg_data, lut_cfg_data, sizeof(v17_cfg_data));
	v17_cfg_data.version = mdp_igc_v1_7;
	ret = config_data.igc_set_config(base_addr, pp_sts, &v17_cfg_data,
					 block_type);
	return ret;
}

static int pp_igc_get_config(char __iomem *base_addr, void *cfg_data,
		u32 block_type, u32 disp_num)
{
	return -EINVAL;
}

static int pp_igc_get_version(u32 *version)
{
	if (!version) {
		pr_err("invalid param version\n");
		return -EINVAL;
	}
	*version = mdp_igc_v3;
	return 0;
}

