blob: 242be60352703fc79c6d4082feafb3ec6d321e09 [file] [log] [blame]
/*
* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include "mdss_fb.h"
#include "mdss_mdp.h"
#include <linux/uaccess.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
struct mdp_csc_cfg mdp_csc_convert[MDSS_MDP_MAX_CSC] = {
[MDSS_MDP_CSC_RGB2RGB] = {
0,
{
0x0200, 0x0000, 0x0000,
0x0000, 0x0200, 0x0000,
0x0000, 0x0000, 0x0200,
},
{ 0x0, 0x0, 0x0,},
{ 0x0, 0x0, 0x0,},
{ 0x0, 0xff, 0x0, 0xff, 0x0, 0xff,},
{ 0x0, 0xff, 0x0, 0xff, 0x0, 0xff,},
},
[MDSS_MDP_CSC_YUV2RGB] = {
0,
{
0x0254, 0x0000, 0x0331,
0x0254, 0xff37, 0xfe60,
0x0254, 0x0409, 0x0000,
},
{ 0xfff0, 0xff80, 0xff80,},
{ 0x0, 0x0, 0x0,},
{ 0x0, 0xff, 0x0, 0xff, 0x0, 0xff,},
{ 0x0, 0xff, 0x0, 0xff, 0x0, 0xff,},
},
[MDSS_MDP_CSC_RGB2YUV] = {
0,
{
0x0083, 0x0102, 0x0032,
0x1fb5, 0x1f6c, 0x00e1,
0x00e1, 0x1f45, 0x1fdc
},
{ 0x0, 0x0, 0x0,},
{ 0x0010, 0x0080, 0x0080,},
{ 0x0, 0xff, 0x0, 0xff, 0x0, 0xff,},
{ 0x0010, 0x00eb, 0x0010, 0x00f0, 0x0010, 0x00f0,},
},
[MDSS_MDP_CSC_YUV2YUV] = {
0,
{
0x0200, 0x0000, 0x0000,
0x0000, 0x0200, 0x0000,
0x0000, 0x0000, 0x0200,
},
{ 0x0, 0x0, 0x0,},
{ 0x0, 0x0, 0x0,},
{ 0x0, 0xff, 0x0, 0xff, 0x0, 0xff,},
{ 0x0, 0xff, 0x0, 0xff, 0x0, 0xff,},
},
};
#define CSC_MV_OFF 0x0
#define CSC_BV_OFF 0x2C
#define CSC_LV_OFF 0x14
#define CSC_POST_OFF 0xC
#define MDSS_BLOCK_DISP_NUM (MDP_BLOCK_MAX - MDP_LOGICAL_BLOCK_DISP_0)
#define IGC_LUT_ENTRIES 256
#define GC_LUT_SEGMENTS 16
#define ENHIST_LUT_ENTRIES 256
#define HIST_V_SIZE 256
#define HIST_WAIT_TIMEOUT(frame) ((60 * HZ * (frame)) / 1000)
/* hist collect state */
enum {
HIST_UNKNOWN,
HIST_IDLE,
HIST_RESET,
HIST_START,
HIST_READY,
};
struct pp_hist_col_info {
u32 col_state;
u32 col_en;
u32 read_request;
u32 hist_cnt_read;
u32 hist_cnt_sent;
u32 frame_cnt;
u32 is_kick_ready;
struct completion comp;
u32 data[HIST_V_SIZE];
};
static u32 dither_matrix[16] = {
15, 7, 13, 5, 3, 11, 1, 9, 12, 4, 14, 6, 0, 8, 2, 10};
static u32 dither_depth_map[9] = {
0, 0, 0, 0, 0, 1, 2, 3, 3};
#define GAMUT_T0_SIZE 125
#define GAMUT_T1_SIZE 100
#define GAMUT_T2_SIZE 80
#define GAMUT_T3_SIZE 100
#define GAMUT_T4_SIZE 100
#define GAMUT_T5_SIZE 80
#define GAMUT_T6_SIZE 64
#define GAMUT_T7_SIZE 80
#define GAMUT_TOTAL_TABLE_SIZE (GAMUT_T0_SIZE + GAMUT_T1_SIZE + \
GAMUT_T2_SIZE + GAMUT_T3_SIZE + GAMUT_T4_SIZE + \
GAMUT_T5_SIZE + GAMUT_T6_SIZE + GAMUT_T7_SIZE)
struct pp_sts_type {
u32 pa_sts;
u32 pcc_sts;
u32 igc_sts;
u32 igc_tbl_idx;
u32 argc_sts;
u32 enhist_sts;
u32 dither_sts;
u32 gamut_sts;
u32 pgc_sts;
};
#define PP_FLAGS_DIRTY_PA 0x1
#define PP_FLAGS_DIRTY_PCC 0x2
#define PP_FLAGS_DIRTY_IGC 0x4
#define PP_FLAGS_DIRTY_ARGC 0x8
#define PP_FLAGS_DIRTY_ENHIST 0x10
#define PP_FLAGS_DIRTY_DITHER 0x20
#define PP_FLAGS_DIRTY_GAMUT 0x40
#define PP_FLAGS_DIRTY_HIST_COL 0x80
#define PP_FLAGS_DIRTY_PGC 0x100
#define PP_STS_ENABLE 0x1
#define PP_STS_GAMUT_FIRST 0x2
struct mdss_pp_res_type {
/* logical info */
u32 pp_disp_flags[MDSS_BLOCK_DISP_NUM];
u32 igc_lut_c0c1[MDSS_BLOCK_DISP_NUM][IGC_LUT_ENTRIES];
u32 igc_lut_c2[MDSS_BLOCK_DISP_NUM][IGC_LUT_ENTRIES];
struct mdp_ar_gc_lut_data
gc_lut_r[MDSS_BLOCK_DISP_NUM][GC_LUT_SEGMENTS];
struct mdp_ar_gc_lut_data
gc_lut_g[MDSS_BLOCK_DISP_NUM][GC_LUT_SEGMENTS];
struct mdp_ar_gc_lut_data
gc_lut_b[MDSS_BLOCK_DISP_NUM][GC_LUT_SEGMENTS];
u32 enhist_lut[MDSS_BLOCK_DISP_NUM][ENHIST_LUT_ENTRIES];
struct mdp_pa_cfg pa_disp_cfg[MDSS_BLOCK_DISP_NUM];
struct mdp_pcc_cfg_data pcc_disp_cfg[MDSS_BLOCK_DISP_NUM];
struct mdp_igc_lut_data igc_disp_cfg[MDSS_BLOCK_DISP_NUM];
struct mdp_pgc_lut_data argc_disp_cfg[MDSS_BLOCK_DISP_NUM];
struct mdp_pgc_lut_data pgc_disp_cfg[MDSS_BLOCK_DISP_NUM];
struct mdp_hist_lut_data enhist_disp_cfg[MDSS_BLOCK_DISP_NUM];
struct mdp_dither_cfg_data dither_disp_cfg[MDSS_BLOCK_DISP_NUM];
struct mdp_gamut_cfg_data gamut_disp_cfg[MDSS_BLOCK_DISP_NUM];
uint16_t gamut_tbl[MDSS_BLOCK_DISP_NUM][GAMUT_TOTAL_TABLE_SIZE];
struct pp_hist_col_info
*hist_col[MDSS_BLOCK_DISP_NUM][MDSS_MDP_MAX_DSPP];
u32 hist_data[MDSS_BLOCK_DISP_NUM][HIST_V_SIZE];
/* physical info */
struct pp_sts_type pp_dspp_sts[MDSS_MDP_MAX_DSPP];
struct pp_hist_col_info dspp_hist[MDSS_MDP_MAX_DSPP];
};
static DEFINE_MUTEX(mdss_pp_mutex);
static DEFINE_SPINLOCK(mdss_hist_lock);
static DEFINE_MUTEX(mdss_mdp_hist_mutex);
static struct mdss_pp_res_type *mdss_pp_res;
static void pp_hist_read(u32 v_base, struct pp_hist_col_info *hist_info);
static void pp_update_pcc_regs(u32 offset,
struct mdp_pcc_cfg_data *cfg_ptr);
static void pp_update_igc_lut(struct mdp_igc_lut_data *cfg,
u32 offset, u32 blk_idx);
static void pp_update_gc_one_lut(u32 offset,
struct mdp_ar_gc_lut_data *lut_data);
static void pp_update_argc_lut(u32 offset,
struct mdp_pgc_lut_data *config);
static void pp_update_hist_lut(u32 offset, struct mdp_hist_lut_data *cfg);
static void pp_pa_config(unsigned long flags, u32 base,
struct pp_sts_type *pp_sts,
struct mdp_pa_cfg *pa_config);
static void pp_pcc_config(unsigned long flags, u32 base,
struct pp_sts_type *pp_sts,
struct mdp_pcc_cfg_data *pcc_config);
static void pp_igc_config(unsigned long flags, u32 base,
struct pp_sts_type *pp_sts,
struct mdp_igc_lut_data *igc_config,
u32 pipe_num);
static void pp_enhist_config(unsigned long flags, u32 base,
struct pp_sts_type *pp_sts,
struct mdp_hist_lut_data *enhist_cfg);
int mdss_mdp_csc_setup_data(u32 block, u32 blk_idx, u32 tbl_idx,
struct mdp_csc_cfg *data)
{
int i, ret = 0;
u32 *off, base, val = 0;
if (data == NULL) {
pr_err("no csc matrix specified\n");
return -EINVAL;
}
switch (block) {
case MDSS_MDP_BLOCK_SSPP:
if (blk_idx < MDSS_MDP_SSPP_RGB0) {
base = MDSS_MDP_REG_SSPP_OFFSET(blk_idx);
if (tbl_idx == 1)
base += MDSS_MDP_REG_VIG_CSC_1_BASE;
else
base += MDSS_MDP_REG_VIG_CSC_0_BASE;
} else {
ret = -EINVAL;
}
break;
case MDSS_MDP_BLOCK_WB:
if (blk_idx < MDSS_MDP_MAX_WRITEBACK) {
base = MDSS_MDP_REG_WB_OFFSET(blk_idx) +
MDSS_MDP_REG_WB_CSC_BASE;
} else {
ret = -EINVAL;
}
break;
default:
ret = -EINVAL;
break;
}
if (ret != 0) {
pr_err("unsupported block id for csc\n");
return ret;
}
off = (u32 *) (base + CSC_MV_OFF);
for (i = 0; i < 9; i++) {
if (i & 0x1) {
val |= data->csc_mv[i] << 16;
MDSS_MDP_REG_WRITE(off, val);
off++;
} else {
val = data->csc_mv[i];
}
}
MDSS_MDP_REG_WRITE(off, val); /* COEFF_33 */
off = (u32 *) (base + CSC_BV_OFF);
for (i = 0; i < 3; i++) {
MDSS_MDP_REG_WRITE(off, data->csc_pre_bv[i]);
MDSS_MDP_REG_WRITE((u32 *)(((u32)off) + CSC_POST_OFF),
data->csc_post_bv[i]);
off++;
}
off = (u32 *) (base + CSC_LV_OFF);
for (i = 0; i < 6; i += 2) {
val = (data->csc_pre_lv[i] << 8) | data->csc_pre_lv[i+1];
MDSS_MDP_REG_WRITE(off, val);
val = (data->csc_post_lv[i] << 8) | data->csc_post_lv[i+1];
MDSS_MDP_REG_WRITE((u32 *)(((u32)off) + CSC_POST_OFF), val);
off++;
}
return ret;
}
int mdss_mdp_csc_setup(u32 block, u32 blk_idx, u32 tbl_idx, u32 csc_type)
{
struct mdp_csc_cfg *data;
if (csc_type >= MDSS_MDP_MAX_CSC) {
pr_err("invalid csc matrix index %d\n", csc_type);
return -ERANGE;
}
pr_debug("csc type=%d blk=%d idx=%d tbl=%d\n", csc_type,
block, blk_idx, tbl_idx);
data = &mdp_csc_convert[csc_type];
return mdss_mdp_csc_setup_data(block, blk_idx, tbl_idx, data);
}
static void pp_gamut_config(struct mdp_gamut_cfg_data *gamut_cfg,
u32 base, u32 *gamut_sts)
{
u32 offset;
int i, j;
if (gamut_cfg->flags & MDP_PP_OPS_WRITE) {
offset = base + MDSS_MDP_REG_DSPP_GAMUT_BASE;
for (i = 0; i < MDP_GAMUT_TABLE_NUM; i++) {
for (j = 0; j < gamut_cfg->tbl_size[i]; j++)
MDSS_MDP_REG_WRITE(offset,
(u32)gamut_cfg->r_tbl[i][j]);
offset += 4;
}
for (i = 0; i < MDP_GAMUT_TABLE_NUM; i++) {
for (j = 0; j < gamut_cfg->tbl_size[i]; j++)
MDSS_MDP_REG_WRITE(offset,
(u32)gamut_cfg->g_tbl[i][j]);
offset += 4;
}
for (i = 0; i < MDP_GAMUT_TABLE_NUM; i++) {
for (j = 0; j < gamut_cfg->tbl_size[i]; j++)
MDSS_MDP_REG_WRITE(offset,
(u32)gamut_cfg->b_tbl[i][j]);
offset += 4;
}
if (gamut_cfg->gamut_first)
*gamut_sts |= PP_STS_GAMUT_FIRST;
}
if (gamut_cfg->flags & MDP_PP_OPS_DISABLE)
*gamut_sts &= ~PP_STS_ENABLE;
else if (gamut_cfg->flags & MDP_PP_OPS_ENABLE)
*gamut_sts |= PP_STS_ENABLE;
}
static void pp_pa_config(unsigned long flags, u32 base,
struct pp_sts_type *pp_sts,
struct mdp_pa_cfg *pa_config)
{
if (flags & PP_FLAGS_DIRTY_PA) {
if (pa_config->flags & MDP_PP_OPS_WRITE) {
MDSS_MDP_REG_WRITE(base, pa_config->hue_adj);
base += 4;
MDSS_MDP_REG_WRITE(base, pa_config->sat_adj);
base += 4;
MDSS_MDP_REG_WRITE(base, pa_config->val_adj);
base += 4;
MDSS_MDP_REG_WRITE(base, pa_config->cont_adj);
}
if (pa_config->flags & MDP_PP_OPS_DISABLE)
pp_sts->pa_sts &= ~PP_STS_ENABLE;
else if (pa_config->flags & MDP_PP_OPS_ENABLE)
pp_sts->pa_sts |= PP_STS_ENABLE;
}
}
static void pp_pcc_config(unsigned long flags, u32 base,
struct pp_sts_type *pp_sts,
struct mdp_pcc_cfg_data *pcc_config)
{
if (flags & PP_FLAGS_DIRTY_PCC) {
if (pcc_config->ops & MDP_PP_OPS_WRITE)
pp_update_pcc_regs(base, pcc_config);
if (pcc_config->ops & MDP_PP_OPS_DISABLE)
pp_sts->pcc_sts &= ~PP_STS_ENABLE;
else if (pcc_config->ops & MDP_PP_OPS_ENABLE)
pp_sts->pcc_sts |= PP_STS_ENABLE;
}
}
static void pp_igc_config(unsigned long flags, u32 base,
struct pp_sts_type *pp_sts,
struct mdp_igc_lut_data *igc_config,
u32 pipe_num)
{
u32 tbl_idx;
if (flags & PP_FLAGS_DIRTY_IGC) {
if (igc_config->ops & MDP_PP_OPS_WRITE)
pp_update_igc_lut(igc_config, base, pipe_num);
if (igc_config->ops & MDP_PP_IGC_FLAG_ROM0) {
pp_sts->pcc_sts |= PP_STS_ENABLE;
tbl_idx = 1;
} else if (igc_config->ops & MDP_PP_IGC_FLAG_ROM1) {
pp_sts->pcc_sts |= PP_STS_ENABLE;
tbl_idx = 2;
} else {
tbl_idx = 0;
}
pp_sts->igc_tbl_idx = tbl_idx;
if (igc_config->ops & MDP_PP_OPS_DISABLE)
pp_sts->igc_sts &= ~PP_STS_ENABLE;
else if (igc_config->ops & MDP_PP_OPS_ENABLE)
pp_sts->igc_sts |= PP_STS_ENABLE;
}
}
static void pp_enhist_config(unsigned long flags, u32 base,
struct pp_sts_type *pp_sts,
struct mdp_hist_lut_data *enhist_cfg)
{
if (flags & PP_FLAGS_DIRTY_ENHIST) {
if (enhist_cfg->ops & MDP_PP_OPS_WRITE)
pp_update_hist_lut(base, enhist_cfg);
if (enhist_cfg->ops & MDP_PP_OPS_DISABLE)
pp_sts->enhist_sts &= ~PP_STS_ENABLE;
else if (enhist_cfg->ops & MDP_PP_OPS_ENABLE)
pp_sts->enhist_sts |= PP_STS_ENABLE;
}
}
static int pp_vig_pipe_setup(struct mdss_mdp_pipe *pipe, u32 *op)
{
struct pp_sts_type pp_sts;
u32 opmode = 0, base = 0;
unsigned long flags = 0;
pr_debug("pnum=%x\n", pipe->num);
if ((pipe->flags & MDP_OVERLAY_PP_CFG_EN) &&
(pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_CSC_CFG)) {
opmode |= !!(pipe->pp_cfg.csc_cfg.flags &
MDP_CSC_FLAG_ENABLE) << 17;
opmode |= !!(pipe->pp_cfg.csc_cfg.flags &
MDP_CSC_FLAG_YUV_IN) << 18;
opmode |= !!(pipe->pp_cfg.csc_cfg.flags &
MDP_CSC_FLAG_YUV_OUT) << 19;
/*
* TODO: Allow pipe to be programmed whenever new CSC is
* applied (i.e. dirty bit)
*/
if (pipe->play_cnt == 0)
mdss_mdp_csc_setup_data(MDSS_MDP_BLOCK_SSPP,
pipe->num, 1, &pipe->pp_cfg.csc_cfg);
} else {
if (pipe->src_fmt->is_yuv)
opmode |= (0 << 19) | /* DST_DATA=RGB */
(1 << 18) | /* SRC_DATA=YCBCR */
(1 << 17); /* CSC_1_EN */
/*
* TODO: Needs to be part of dirty bit logic: if there is a
* previously configured pipe need to re-configure CSC matrix
*/
if (pipe->play_cnt == 0) {
mdss_mdp_csc_setup(MDSS_MDP_BLOCK_SSPP, pipe->num, 1,
MDSS_MDP_CSC_YUV2RGB);
}
}
if (pipe->flags & MDP_OVERLAY_PP_CFG_EN) {
if (pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_PA_CFG) {
flags = PP_FLAGS_DIRTY_PA;
base = MDSS_MDP_REG_SSPP_OFFSET(pipe->num) +
MDSS_MDP_REG_VIG_PA_BASE;
pp_sts.pa_sts = 0;
pp_pa_config(flags, base, &pp_sts,
&pipe->pp_cfg.pa_cfg);
if (pp_sts.pa_sts & PP_STS_ENABLE)
opmode |= (1 << 4); /* PA_EN */
}
}
*op = opmode;
return 0;
}
int mdss_mdp_pipe_pp_setup(struct mdss_mdp_pipe *pipe, u32 *op)
{
int ret = 0;
if (!pipe)
return -ENODEV;
if (pipe->type == MDSS_MDP_PIPE_TYPE_VIG)
ret = pp_vig_pipe_setup(pipe, op);
else if (pipe->type == MDSS_MDP_PIPE_TYPE_RGB)
ret = -EINVAL;
else if (pipe->type == MDSS_MDP_PIPE_TYPE_DMA)
ret = -EINVAL;
return ret;
}
static int pp_mixer_setup(u32 disp_num, struct mdss_mdp_ctl *ctl,
struct mdss_mdp_mixer *mixer)
{
u32 flags, offset, dspp_num, opmode = 0;
struct mdp_pgc_lut_data *pgc_config;
struct pp_sts_type *pp_sts;
dspp_num = mixer->num;
/* no corresponding dspp */
if ((mixer->type != MDSS_MDP_MIXER_TYPE_INTF) ||
(dspp_num >= MDSS_MDP_MAX_DSPP))
return 0;
if (disp_num < MDSS_BLOCK_DISP_NUM)
flags = mdss_pp_res->pp_disp_flags[disp_num];
else
flags = 0;
pp_sts = &mdss_pp_res->pp_dspp_sts[dspp_num];
/* GC_LUT is in layer mixer */
if (flags & PP_FLAGS_DIRTY_ARGC) {
pgc_config = &mdss_pp_res->argc_disp_cfg[disp_num];
if (pgc_config->flags & MDP_PP_OPS_WRITE) {
offset = MDSS_MDP_REG_LM_OFFSET(disp_num) +
MDSS_MDP_REG_LM_GC_LUT_BASE;
pp_update_argc_lut(offset, pgc_config);
}
if (pgc_config->flags & MDP_PP_OPS_DISABLE)
pp_sts->argc_sts &= ~PP_STS_ENABLE;
else if (pgc_config->flags & MDP_PP_OPS_ENABLE)
pp_sts->argc_sts |= PP_STS_ENABLE;
ctl->flush_bits |= BIT(6) << dspp_num; /* LAYER_MIXER */
}
/* update LM opmode if LM needs flush */
if ((pp_sts->argc_sts & PP_STS_ENABLE) &&
(ctl->flush_bits & (BIT(6) << dspp_num))) {
offset = MDSS_MDP_REG_LM_OFFSET(dspp_num) +
MDSS_MDP_REG_LM_OP_MODE;
opmode = MDSS_MDP_REG_READ(offset);
opmode |= (1 << 0); /* GC_LUT_EN */
MDSS_MDP_REG_WRITE(offset, opmode);
}
return 0;
}
static int pp_dspp_setup(u32 disp_num, struct mdss_mdp_ctl *ctl,
struct mdss_mdp_mixer *mixer)
{
u32 flags, base, offset, dspp_num, opmode = 0;
struct mdp_dither_cfg_data *dither_cfg;
struct pp_hist_col_info *hist_info;
struct mdp_pgc_lut_data *pgc_config;
struct pp_sts_type *pp_sts;
u32 data, col_state;
unsigned long flag;
int i;
dspp_num = mixer->num;
/* no corresponding dspp */
if ((mixer->type != MDSS_MDP_MIXER_TYPE_INTF) ||
(dspp_num >= MDSS_MDP_MAX_DSPP))
return 0;
base = MDSS_MDP_REG_DSPP_OFFSET(dspp_num);
hist_info = &mdss_pp_res->dspp_hist[dspp_num];
if (hist_info->col_en) {
/* HIST_EN & AUTO_CLEAR */
opmode |= (1 << 16) | (1 << 17);
mutex_lock(&mdss_mdp_hist_mutex);
spin_lock_irqsave(&mdss_hist_lock, flag);
col_state = hist_info->col_state;
if (hist_info->is_kick_ready &&
((col_state == HIST_IDLE) ||
((false == hist_info->read_request) &&
col_state == HIST_READY))) {
/* Kick off collection */
MDSS_MDP_REG_WRITE(base +
MDSS_MDP_REG_DSPP_HIST_CTL_BASE, 1);
hist_info->col_state = HIST_START;
}
hist_info->is_kick_ready = true;
spin_unlock_irqrestore(&mdss_hist_lock, flag);
mutex_unlock(&mdss_mdp_hist_mutex);
}
if (disp_num < MDSS_BLOCK_DISP_NUM)
flags = mdss_pp_res->pp_disp_flags[disp_num];
else
flags = 0;
/* nothing to update */
if ((!flags) && (!(hist_info->col_en)))
return 0;
pp_sts = &mdss_pp_res->pp_dspp_sts[dspp_num];
pp_pa_config(flags, base + MDSS_MDP_REG_DSPP_PA_BASE, pp_sts,
&mdss_pp_res->pa_disp_cfg[disp_num]);
pp_pcc_config(flags, base + MDSS_MDP_REG_DSPP_PCC_BASE, pp_sts,
&mdss_pp_res->pcc_disp_cfg[disp_num]);
pp_igc_config(flags, MDSS_MDP_REG_IGC_DSPP_BASE, pp_sts,
&mdss_pp_res->igc_disp_cfg[disp_num], dspp_num);
pp_enhist_config(flags, base + MDSS_MDP_REG_DSPP_HIST_LUT_BASE,
pp_sts, &mdss_pp_res->enhist_disp_cfg[disp_num]);
if (pp_sts->pa_sts & PP_STS_ENABLE)
opmode |= (1 << 20); /* PA_EN */
if (pp_sts->pcc_sts & PP_STS_ENABLE)
opmode |= (1 << 4); /* PCC_EN */
if (pp_sts->igc_sts & PP_STS_ENABLE) {
opmode |= (1 << 0) | /* IGC_LUT_EN */
(pp_sts->igc_tbl_idx << 1);
}
if (pp_sts->enhist_sts & PP_STS_ENABLE) {
opmode |= (1 << 19) | /* HIST_LUT_EN */
(1 << 20); /* PA_EN */
if (!(pp_sts->pa_sts & PP_STS_ENABLE)) {
/* Program default value */
offset = base + MDSS_MDP_REG_DSPP_PA_BASE;
MDSS_MDP_REG_WRITE(offset, 0);
MDSS_MDP_REG_WRITE(offset + 4, 0);
MDSS_MDP_REG_WRITE(offset + 8, 0);
MDSS_MDP_REG_WRITE(offset + 12, 0);
}
}
if (flags & PP_FLAGS_DIRTY_DITHER) {
dither_cfg = &mdss_pp_res->dither_disp_cfg[disp_num];
if (dither_cfg->flags & MDP_PP_OPS_WRITE) {
offset = base + MDSS_MDP_REG_DSPP_DITHER_DEPTH;
MDSS_MDP_REG_WRITE(offset,
dither_depth_map[dither_cfg->g_y_depth] |
(dither_depth_map[dither_cfg->b_cb_depth] << 2) |
(dither_depth_map[dither_cfg->r_cr_depth] << 4));
offset += 0x14;
for (i = 0; i << 16; i += 4) {
data = dither_matrix[i] |
(dither_matrix[i + 1] << 4) |
(dither_matrix[i + 2] << 8) |
(dither_matrix[i + 3] << 12);
MDSS_MDP_REG_WRITE(offset, data);
offset += 4;
}
}
if (dither_cfg->flags & MDP_PP_OPS_DISABLE)
pp_sts->dither_sts &= ~PP_STS_ENABLE;
else if (dither_cfg->flags & MDP_PP_OPS_ENABLE)
pp_sts->dither_sts |= PP_STS_ENABLE;
}
if (pp_sts->dither_sts & PP_STS_ENABLE)
opmode |= (1 << 8); /* DITHER_EN */
if (flags & PP_FLAGS_DIRTY_GAMUT)
pp_gamut_config(&mdss_pp_res->gamut_disp_cfg[disp_num],
base, &pp_sts->gamut_sts);
if (pp_sts->gamut_sts & PP_STS_ENABLE) {
opmode |= (1 << 23); /* GAMUT_EN */
if (pp_sts->gamut_sts & PP_STS_GAMUT_FIRST)
opmode |= (1 << 24); /* GAMUT_ORDER */
}
if (flags & PP_FLAGS_DIRTY_PGC) {
pgc_config = &mdss_pp_res->pgc_disp_cfg[disp_num];
if (pgc_config->flags & MDP_PP_OPS_WRITE) {
offset = base + MDSS_MDP_REG_DSPP_GC_BASE;
pp_update_argc_lut(offset, pgc_config);
}
if (pgc_config->flags & MDP_PP_OPS_DISABLE)
pp_sts->pgc_sts &= ~PP_STS_ENABLE;
else if (pgc_config->flags & MDP_PP_OPS_ENABLE)
pp_sts->pgc_sts |= PP_STS_ENABLE;
}
if (pp_sts->pgc_sts & PP_STS_ENABLE)
opmode |= (1 << 22);
MDSS_MDP_REG_WRITE(base + MDSS_MDP_REG_DSPP_OP_MODE, opmode);
ctl->flush_bits |= BIT(13 + dspp_num); /* DSPP */
return 0;
}
int mdss_mdp_pp_setup(struct mdss_mdp_ctl *ctl)
{
u32 disp_num;
if ((!ctl->mfd) || (!mdss_pp_res))
return -EINVAL;
/* treat fb_num the same as block logical id*/
disp_num = ctl->mfd->index;
mutex_lock(&mdss_pp_mutex);
if (ctl->mixer_left) {
pp_mixer_setup(disp_num, ctl, ctl->mixer_left);
pp_dspp_setup(disp_num, ctl, ctl->mixer_left);
}
if (ctl->mixer_right) {
pp_mixer_setup(disp_num, ctl, ctl->mixer_right);
pp_dspp_setup(disp_num, ctl, ctl->mixer_right);
}
/* clear dirty flag */
if (disp_num < MDSS_BLOCK_DISP_NUM)
mdss_pp_res->pp_disp_flags[disp_num] = 0;
mutex_unlock(&mdss_pp_mutex);
return 0;
}
int mdss_mdp_pp_init(struct device *dev)
{
int ret = 0;
int i;
u32 offset;
uint32_t data[ENHIST_LUT_ENTRIES];
mutex_lock(&mdss_pp_mutex);
if (!mdss_pp_res) {
mdss_pp_res = devm_kzalloc(dev, sizeof(*mdss_pp_res),
GFP_KERNEL);
if (mdss_pp_res == NULL) {
pr_err("%s mdss_pp_res allocation failed!", __func__);
ret = -ENOMEM;
}
for (i = 0; i < ENHIST_LUT_ENTRIES; i++)
data[i] = i;
/* Initialize Histogram LUT for all DSPPs */
for (i = 0; i < MDSS_MDP_MAX_DSPP; i++) {
offset = MDSS_MDP_REG_DSPP_OFFSET(i) +
MDSS_MDP_REG_DSPP_HIST_LUT_BASE;
mdss_pp_res->enhist_disp_cfg[i].data = data;
pp_update_hist_lut(offset,
&mdss_pp_res->enhist_disp_cfg[i]);
}
}
mutex_unlock(&mdss_pp_mutex);
return ret;
}
void mdss_mdp_pp_term(struct device *dev)
{
if (!mdss_pp_res) {
mutex_lock(&mdss_pp_mutex);
devm_kfree(dev, mdss_pp_res);
mdss_pp_res = NULL;
mutex_unlock(&mdss_pp_mutex);
}
}
static int pp_get_dspp_num(u32 disp_num, u32 *dspp_num)
{
int i;
u32 mixer_cnt;
u32 mixer_id[MDSS_MDP_MAX_LAYERMIXER];
mixer_cnt = mdss_mdp_get_ctl_mixers(disp_num, mixer_id);
if (!mixer_cnt)
return -EPERM;
/* only read the first mixer */
for (i = 0; i < mixer_cnt; i++) {
if (mixer_id[i] < MDSS_MDP_MAX_DSPP)
break;
}
if (i >= mixer_cnt)
return -EPERM;
*dspp_num = mixer_id[i];
return 0;
}
int mdss_mdp_pa_config(struct mdp_pa_cfg_data *config, u32 *copyback)
{
int ret = 0;
u32 pa_offset, disp_num, dspp_num = 0;
if ((config->block < MDP_LOGICAL_BLOCK_DISP_0) ||
(config->block >= MDP_BLOCK_MAX))
return -EINVAL;
mutex_lock(&mdss_pp_mutex);
disp_num = config->block - MDP_LOGICAL_BLOCK_DISP_0;
if (config->pa_data.flags & MDP_PP_OPS_READ) {
ret = pp_get_dspp_num(disp_num, &dspp_num);
if (ret) {
pr_err("%s, no dspp connects to disp %d",
__func__, disp_num);
goto pa_config_exit;
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
pa_offset = MDSS_MDP_REG_DSPP_OFFSET(dspp_num) +
MDSS_MDP_REG_DSPP_PA_BASE;
config->pa_data.hue_adj = MDSS_MDP_REG_READ(pa_offset);
pa_offset += 4;
config->pa_data.sat_adj = MDSS_MDP_REG_READ(pa_offset);
pa_offset += 4;
config->pa_data.val_adj = MDSS_MDP_REG_READ(pa_offset);
pa_offset += 4;
config->pa_data.cont_adj = MDSS_MDP_REG_READ(pa_offset);
*copyback = 1;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
} else {
mdss_pp_res->pa_disp_cfg[disp_num] = config->pa_data;
mdss_pp_res->pp_disp_flags[disp_num] |= PP_FLAGS_DIRTY_PA;
}
pa_config_exit:
mutex_unlock(&mdss_pp_mutex);
return ret;
}
static void pp_read_pcc_regs(u32 offset,
struct mdp_pcc_cfg_data *cfg_ptr)
{
cfg_ptr->r.c = MDSS_MDP_REG_READ(offset);
cfg_ptr->g.c = MDSS_MDP_REG_READ(offset + 4);
cfg_ptr->b.c = MDSS_MDP_REG_READ(offset + 8);
offset += 0x10;
cfg_ptr->r.r = MDSS_MDP_REG_READ(offset);
cfg_ptr->g.r = MDSS_MDP_REG_READ(offset + 4);
cfg_ptr->b.r = MDSS_MDP_REG_READ(offset + 8);
offset += 0x10;
cfg_ptr->r.g = MDSS_MDP_REG_READ(offset);
cfg_ptr->g.g = MDSS_MDP_REG_READ(offset + 4);
cfg_ptr->b.g = MDSS_MDP_REG_READ(offset + 8);
offset += 0x10;
cfg_ptr->r.b = MDSS_MDP_REG_READ(offset);
cfg_ptr->g.b = MDSS_MDP_REG_READ(offset + 4);
cfg_ptr->b.b = MDSS_MDP_REG_READ(offset + 8);
offset += 0x10;
cfg_ptr->r.rr = MDSS_MDP_REG_READ(offset);
cfg_ptr->g.rr = MDSS_MDP_REG_READ(offset + 4);
cfg_ptr->b.rr = MDSS_MDP_REG_READ(offset + 8);
offset += 0x10;
cfg_ptr->r.rg = MDSS_MDP_REG_READ(offset);
cfg_ptr->g.rg = MDSS_MDP_REG_READ(offset + 4);
cfg_ptr->b.rg = MDSS_MDP_REG_READ(offset + 8);
offset += 0x10;
cfg_ptr->r.rb = MDSS_MDP_REG_READ(offset);
cfg_ptr->g.rb = MDSS_MDP_REG_READ(offset + 4);
cfg_ptr->b.rb = MDSS_MDP_REG_READ(offset + 8);
offset += 0x10;
cfg_ptr->r.gg = MDSS_MDP_REG_READ(offset);
cfg_ptr->g.gg = MDSS_MDP_REG_READ(offset + 4);
cfg_ptr->b.gg = MDSS_MDP_REG_READ(offset + 8);
offset += 0x10;
cfg_ptr->r.gb = MDSS_MDP_REG_READ(offset);
cfg_ptr->g.gb = MDSS_MDP_REG_READ(offset + 4);
cfg_ptr->b.gb = MDSS_MDP_REG_READ(offset + 8);
offset += 0x10;
cfg_ptr->r.bb = MDSS_MDP_REG_READ(offset);
cfg_ptr->g.bb = MDSS_MDP_REG_READ(offset + 4);
cfg_ptr->b.bb = MDSS_MDP_REG_READ(offset + 8);
offset += 0x10;
cfg_ptr->r.rgb_0 = MDSS_MDP_REG_READ(offset);
cfg_ptr->g.rgb_0 = MDSS_MDP_REG_READ(offset + 4);
cfg_ptr->b.rgb_0 = MDSS_MDP_REG_READ(offset + 8);
offset += 0x10;
cfg_ptr->r.rgb_1 = MDSS_MDP_REG_READ(offset);
cfg_ptr->g.rgb_1 = MDSS_MDP_REG_READ(offset + 4);
cfg_ptr->b.rgb_1 = MDSS_MDP_REG_READ(offset + 8);
}
static void pp_update_pcc_regs(u32 offset,
struct mdp_pcc_cfg_data *cfg_ptr)
{
MDSS_MDP_REG_WRITE(offset, cfg_ptr->r.c);
MDSS_MDP_REG_WRITE(offset + 4, cfg_ptr->g.c);
MDSS_MDP_REG_WRITE(offset + 8, cfg_ptr->b.c);
offset += 0x10;
MDSS_MDP_REG_WRITE(offset, cfg_ptr->r.r);
MDSS_MDP_REG_WRITE(offset + 4, cfg_ptr->g.r);
MDSS_MDP_REG_WRITE(offset + 8, cfg_ptr->b.r);
offset += 0x10;
MDSS_MDP_REG_WRITE(offset, cfg_ptr->r.g);
MDSS_MDP_REG_WRITE(offset + 4, cfg_ptr->g.g);
MDSS_MDP_REG_WRITE(offset + 8, cfg_ptr->b.g);
offset += 0x10;
MDSS_MDP_REG_WRITE(offset, cfg_ptr->r.b);
MDSS_MDP_REG_WRITE(offset + 4, cfg_ptr->g.b);
MDSS_MDP_REG_WRITE(offset + 8, cfg_ptr->b.b);
offset += 0x10;
MDSS_MDP_REG_WRITE(offset, cfg_ptr->r.rr);
MDSS_MDP_REG_WRITE(offset + 4, cfg_ptr->g.rr);
MDSS_MDP_REG_WRITE(offset + 8, cfg_ptr->b.rr);
offset += 0x10;
MDSS_MDP_REG_WRITE(offset, cfg_ptr->r.rg);
MDSS_MDP_REG_WRITE(offset + 4, cfg_ptr->g.rg);
MDSS_MDP_REG_WRITE(offset + 8, cfg_ptr->b.rg);
offset += 0x10;
MDSS_MDP_REG_WRITE(offset, cfg_ptr->r.rb);
MDSS_MDP_REG_WRITE(offset + 4, cfg_ptr->g.rb);
MDSS_MDP_REG_WRITE(offset + 8, cfg_ptr->b.rb);
offset += 0x10;
MDSS_MDP_REG_WRITE(offset, cfg_ptr->r.gg);
MDSS_MDP_REG_WRITE(offset + 4, cfg_ptr->g.gg);
MDSS_MDP_REG_WRITE(offset + 8, cfg_ptr->b.gg);
offset += 0x10;
MDSS_MDP_REG_WRITE(offset, cfg_ptr->r.gb);
MDSS_MDP_REG_WRITE(offset + 4, cfg_ptr->g.gb);
MDSS_MDP_REG_WRITE(offset + 8, cfg_ptr->b.gb);
offset += 0x10;
MDSS_MDP_REG_WRITE(offset, cfg_ptr->r.bb);
MDSS_MDP_REG_WRITE(offset + 4, cfg_ptr->g.bb);
MDSS_MDP_REG_WRITE(offset + 8, cfg_ptr->b.bb);
offset += 0x10;
MDSS_MDP_REG_WRITE(offset, cfg_ptr->r.rgb_0);
MDSS_MDP_REG_WRITE(offset + 4, cfg_ptr->g.rgb_0);
MDSS_MDP_REG_WRITE(offset + 8, cfg_ptr->b.rgb_0);
offset += 0x10;
MDSS_MDP_REG_WRITE(offset, cfg_ptr->r.rgb_1);
MDSS_MDP_REG_WRITE(offset + 4, cfg_ptr->g.rgb_1);
MDSS_MDP_REG_WRITE(offset + 8, cfg_ptr->b.rgb_1);
}
int mdss_mdp_pcc_config(struct mdp_pcc_cfg_data *config, u32 *copyback)
{
int ret = 0;
u32 base, disp_num, dspp_num = 0;
if ((config->block < MDP_LOGICAL_BLOCK_DISP_0) ||
(config->block >= MDP_BLOCK_MAX))
return -EINVAL;
mutex_lock(&mdss_pp_mutex);
disp_num = config->block - MDP_LOGICAL_BLOCK_DISP_0;
if (config->ops & MDP_PP_OPS_READ) {
ret = pp_get_dspp_num(disp_num, &dspp_num);
if (ret) {
pr_err("%s, no dspp connects to disp %d",
__func__, disp_num);
goto pcc_config_exit;
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
base = MDSS_MDP_REG_DSPP_OFFSET(dspp_num) +
MDSS_MDP_REG_DSPP_PCC_BASE;
pp_read_pcc_regs(base, config);
*copyback = 1;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
} else {
mdss_pp_res->pcc_disp_cfg[disp_num] = *config;
mdss_pp_res->pp_disp_flags[disp_num] |= PP_FLAGS_DIRTY_PCC;
}
pcc_config_exit:
mutex_unlock(&mdss_pp_mutex);
return ret;
}
static void pp_read_igc_lut(struct mdp_igc_lut_data *cfg,
u32 offset, u32 blk_idx)
{
int i;
u32 data;
/* INDEX_UPDATE & VALUE_UPDATEN */
data = (3 << 24) | (((~(1 << blk_idx)) & 0x7) << 28);
MDSS_MDP_REG_WRITE(offset, data);
for (i = 0; i < cfg->len; i++)
cfg->c0_c1_data[i] = MDSS_MDP_REG_READ(offset) & 0xFFF;
offset += 0x4;
MDSS_MDP_REG_WRITE(offset, data);
for (i = 0; i < cfg->len; i++)
cfg->c0_c1_data[i] |= (MDSS_MDP_REG_READ(offset) & 0xFFF) << 16;
offset += 0x4;
MDSS_MDP_REG_WRITE(offset, data);
for (i = 0; i < cfg->len; i++)
cfg->c2_data[i] = MDSS_MDP_REG_READ(offset) & 0xFFF;
}
static void pp_update_igc_lut(struct mdp_igc_lut_data *cfg,
u32 offset, u32 blk_idx)
{
int i;
u32 data;
/* INDEX_UPDATE */
data = (1 << 25) | (((~(1 << blk_idx)) & 0x7) << 28);
MDSS_MDP_REG_WRITE(offset, (cfg->c0_c1_data[0] & 0xFFF) | data);
/* disable index update */
data &= ~(1 << 25);
for (i = 1; i < cfg->len; i++)
MDSS_MDP_REG_WRITE(offset, (cfg->c0_c1_data[i] & 0xFFF) | data);
offset += 0x4;
data |= (1 << 25);
MDSS_MDP_REG_WRITE(offset, ((cfg->c0_c1_data[0] >> 16) & 0xFFF) | data);
data &= ~(1 << 25);
for (i = 1; i < cfg->len; i++)
MDSS_MDP_REG_WRITE(offset,
((cfg->c0_c1_data[i] >> 16) & 0xFFF) | data);
offset += 0x4;
data |= (1 << 25);
MDSS_MDP_REG_WRITE(offset, (cfg->c2_data[0] & 0xFFF) | data);
data &= ~(1 << 25);
for (i = 1; i < cfg->len; i++)
MDSS_MDP_REG_WRITE(offset, (cfg->c2_data[i] & 0xFFF) | data);
}
int mdss_mdp_igc_lut_config(struct mdp_igc_lut_data *config, u32 *copyback)
{
int ret = 0;
u32 tbl_idx, igc_offset, disp_num, dspp_num = 0;
struct mdp_igc_lut_data local_cfg;
if ((config->block < MDP_LOGICAL_BLOCK_DISP_0) ||
(config->block >= MDP_BLOCK_MAX))
return -EINVAL;
mutex_lock(&mdss_pp_mutex);
disp_num = config->block - MDP_LOGICAL_BLOCK_DISP_0;
if (config->ops & MDP_PP_OPS_READ) {
ret = pp_get_dspp_num(disp_num, &dspp_num);
if (ret) {
pr_err("%s, no dspp connects to disp %d",
__func__, disp_num);
goto igc_config_exit;
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
if (config->ops & MDP_PP_IGC_FLAG_ROM0)
tbl_idx = 1;
else if (config->ops & MDP_PP_IGC_FLAG_ROM1)
tbl_idx = 2;
else
tbl_idx = 0;
igc_offset = MDSS_MDP_REG_IGC_DSPP_BASE + (0x10 * tbl_idx);
local_cfg = *config;
local_cfg.c0_c1_data =
&mdss_pp_res->igc_lut_c0c1[disp_num][0];
local_cfg.c2_data =
&mdss_pp_res->igc_lut_c2[disp_num][0];
pp_read_igc_lut(&local_cfg, igc_offset, dspp_num);
if (copy_to_user(config->c0_c1_data, local_cfg.c2_data,
config->len * sizeof(u32))) {
ret = -EFAULT;
goto igc_config_exit;
}
if (copy_to_user(config->c2_data, local_cfg.c0_c1_data,
config->len * sizeof(u32))) {
ret = -EFAULT;
goto igc_config_exit;
}
*copyback = 1;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
} else {
if (copy_from_user(&mdss_pp_res->igc_lut_c0c1[disp_num][0],
config->c0_c1_data, config->len * sizeof(u32))) {
ret = -EFAULT;
goto igc_config_exit;
}
if (copy_from_user(&mdss_pp_res->igc_lut_c2[disp_num][0],
config->c2_data, config->len * sizeof(u32))) {
ret = -EFAULT;
goto igc_config_exit;
}
mdss_pp_res->igc_disp_cfg[disp_num] = *config;
mdss_pp_res->igc_disp_cfg[disp_num].c0_c1_data =
&mdss_pp_res->igc_lut_c0c1[disp_num][0];
mdss_pp_res->igc_disp_cfg[disp_num].c2_data =
&mdss_pp_res->igc_lut_c2[disp_num][0];
mdss_pp_res->pp_disp_flags[disp_num] |= PP_FLAGS_DIRTY_IGC;
}
igc_config_exit:
mutex_unlock(&mdss_pp_mutex);
return ret;
}
static void pp_update_gc_one_lut(u32 offset,
struct mdp_ar_gc_lut_data *lut_data)
{
int i, start_idx;
start_idx = (MDSS_MDP_REG_READ(offset) >> 16) & 0xF;
for (i = start_idx; i < GC_LUT_SEGMENTS; i++)
MDSS_MDP_REG_WRITE(offset, lut_data[i].x_start);
for (i = 0; i < start_idx; i++)
MDSS_MDP_REG_WRITE(offset, lut_data[i].x_start);
offset += 4;
start_idx = (MDSS_MDP_REG_READ(offset) >> 16) & 0xF;
for (i = start_idx; i < GC_LUT_SEGMENTS; i++)
MDSS_MDP_REG_WRITE(offset, lut_data[i].slope);
for (i = 0; i < start_idx; i++)
MDSS_MDP_REG_WRITE(offset, lut_data[i].slope);
offset += 4;
start_idx = (MDSS_MDP_REG_READ(offset) >> 16) & 0xF;
for (i = start_idx; i < GC_LUT_SEGMENTS; i++)
MDSS_MDP_REG_WRITE(offset, lut_data[i].offset);
for (i = 0; i < start_idx; i++)
MDSS_MDP_REG_WRITE(offset, lut_data[i].offset);
}
static void pp_update_argc_lut(u32 offset, struct mdp_pgc_lut_data *config)
{
pp_update_gc_one_lut(offset, config->r_data);
offset += 0x10;
pp_update_gc_one_lut(offset, config->g_data);
offset += 0x10;
pp_update_gc_one_lut(offset, config->b_data);
}
static void pp_read_gc_one_lut(u32 offset,
struct mdp_ar_gc_lut_data *gc_data)
{
int i, start_idx, data;
data = MDSS_MDP_REG_READ(offset);
start_idx = (data >> 16) & 0xF;
gc_data[start_idx].x_start = data & 0xFFF;
for (i = start_idx + 1; i < GC_LUT_SEGMENTS; i++) {
data = MDSS_MDP_REG_READ(offset);
gc_data[i].x_start = data & 0xFFF;
}
for (i = 0; i < start_idx; i++) {
data = MDSS_MDP_REG_READ(offset);
gc_data[i].x_start = data & 0xFFF;
}
offset += 4;
data = MDSS_MDP_REG_READ(offset);
start_idx = (data >> 16) & 0xF;
gc_data[start_idx].slope = data & 0x7FFF;
for (i = start_idx + 1; i < GC_LUT_SEGMENTS; i++) {
data = MDSS_MDP_REG_READ(offset);
gc_data[i].slope = data & 0x7FFF;
}
for (i = 0; i < start_idx; i++) {
data = MDSS_MDP_REG_READ(offset);
gc_data[i].slope = data & 0x7FFF;
}
offset += 4;
data = MDSS_MDP_REG_READ(offset);
start_idx = (data >> 16) & 0xF;
gc_data[start_idx].offset = data & 0x7FFF;
for (i = start_idx + 1; i < GC_LUT_SEGMENTS; i++) {
data = MDSS_MDP_REG_READ(offset);
gc_data[i].offset = data & 0x7FFF;
}
for (i = 0; i < start_idx; i++) {
data = MDSS_MDP_REG_READ(offset);
gc_data[i].offset = data & 0x7FFF;
}
}
static int pp_read_argc_lut(struct mdp_pgc_lut_data *config, u32 offset)
{
int ret = 0;
pp_read_gc_one_lut(offset, config->r_data);
offset += 0x10;
pp_read_gc_one_lut(offset, config->g_data);
offset += 0x10;
pp_read_gc_one_lut(offset, config->b_data);
return ret;
}
/* Note: Assumes that its inputs have been checked by calling function */
static void pp_update_hist_lut(u32 offset, struct mdp_hist_lut_data *cfg)
{
int i;
for (i = 0; i < ENHIST_LUT_ENTRIES; i++)
MDSS_MDP_REG_WRITE(offset, cfg->data[i]);
/* swap */
MDSS_MDP_REG_WRITE(offset + 4, 1);
}
int mdss_mdp_argc_config(struct mdp_pgc_lut_data *config, u32 *copyback)
{
int ret = 0;
u32 argc_offset = 0, disp_num, dspp_num = 0;
struct mdp_pgc_lut_data local_cfg;
struct mdp_pgc_lut_data *pgc_ptr;
u32 tbl_size;
if ((PP_BLOCK(config->block) < MDP_LOGICAL_BLOCK_DISP_0) ||
(PP_BLOCK(config->block) >= MDP_BLOCK_MAX))
return -EINVAL;
mutex_lock(&mdss_pp_mutex);
disp_num = PP_BLOCK(config->block) - MDP_LOGICAL_BLOCK_DISP_0;
switch (config->block & MDSS_PP_LOCATION_MASK) {
case MDSS_PP_LM_CFG:
argc_offset = MDSS_MDP_REG_LM_OFFSET(dspp_num) +
MDSS_MDP_REG_LM_GC_LUT_BASE;
pgc_ptr = &mdss_pp_res->argc_disp_cfg[disp_num];
mdss_pp_res->pp_disp_flags[disp_num] |=
PP_FLAGS_DIRTY_ARGC;
break;
case MDSS_PP_DSPP_CFG:
argc_offset = MDSS_MDP_REG_DSPP_OFFSET(dspp_num) +
MDSS_MDP_REG_DSPP_GC_BASE;
pgc_ptr = &mdss_pp_res->pgc_disp_cfg[disp_num];
mdss_pp_res->pp_disp_flags[disp_num] |=
PP_FLAGS_DIRTY_PGC;
break;
default:
goto argc_config_exit;
break;
}
tbl_size = GC_LUT_SEGMENTS * sizeof(struct mdp_ar_gc_lut_data);
if (config->flags & MDP_PP_OPS_READ) {
ret = pp_get_dspp_num(disp_num, &dspp_num);
if (ret) {
pr_err("%s, no dspp connects to disp %d",
__func__, disp_num);
goto argc_config_exit;
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
local_cfg = *config;
local_cfg.r_data =
&mdss_pp_res->gc_lut_r[disp_num][0];
local_cfg.g_data =
&mdss_pp_res->gc_lut_g[disp_num][0];
local_cfg.b_data =
&mdss_pp_res->gc_lut_b[disp_num][0];
pp_read_argc_lut(&local_cfg, argc_offset);
if (copy_to_user(config->r_data,
&mdss_pp_res->gc_lut_r[disp_num][0], tbl_size)) {
ret = -EFAULT;
goto argc_config_exit;
}
if (copy_to_user(config->g_data,
&mdss_pp_res->gc_lut_g[disp_num][0], tbl_size)) {
ret = -EFAULT;
goto argc_config_exit;
}
if (copy_to_user(config->b_data,
&mdss_pp_res->gc_lut_b[disp_num][0], tbl_size)) {
ret = -EFAULT;
goto argc_config_exit;
}
*copyback = 1;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
} else {
if (copy_from_user(&mdss_pp_res->gc_lut_r[disp_num][0],
config->r_data, tbl_size)) {
ret = -EFAULT;
goto argc_config_exit;
}
if (copy_from_user(&mdss_pp_res->gc_lut_g[disp_num][0],
config->g_data, tbl_size)) {
ret = -EFAULT;
goto argc_config_exit;
}
if (copy_from_user(&mdss_pp_res->gc_lut_b[disp_num][0],
config->b_data, tbl_size)) {
ret = -EFAULT;
goto argc_config_exit;
}
*pgc_ptr = *config;
pgc_ptr->r_data =
&mdss_pp_res->gc_lut_r[disp_num][0];
pgc_ptr->g_data =
&mdss_pp_res->gc_lut_g[disp_num][0];
pgc_ptr->b_data =
&mdss_pp_res->gc_lut_b[disp_num][0];
}
argc_config_exit:
mutex_unlock(&mdss_pp_mutex);
return ret;
}
int mdss_mdp_hist_lut_config(struct mdp_hist_lut_data *config, u32 *copyback)
{
int i, ret = 0;
u32 hist_offset, disp_num, dspp_num = 0;
if ((config->block < MDP_LOGICAL_BLOCK_DISP_0) ||
(config->block >= MDP_BLOCK_MAX))
return -EINVAL;
mutex_lock(&mdss_pp_mutex);
disp_num = config->block - MDP_LOGICAL_BLOCK_DISP_0;
if (config->ops & MDP_PP_OPS_READ) {
ret = pp_get_dspp_num(disp_num, &dspp_num);
if (ret) {
pr_err("%s, no dspp connects to disp %d",
__func__, disp_num);
goto enhist_config_exit;
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
hist_offset = MDSS_MDP_REG_DSPP_OFFSET(dspp_num) +
MDSS_MDP_REG_DSPP_HIST_LUT_BASE;
for (i = 0; i < ENHIST_LUT_ENTRIES; i++)
mdss_pp_res->enhist_lut[disp_num][i] =
MDSS_MDP_REG_READ(hist_offset);
if (copy_to_user(config->data,
&mdss_pp_res->enhist_lut[disp_num][0],
ENHIST_LUT_ENTRIES * sizeof(u32))) {
ret = -EFAULT;
goto enhist_config_exit;
}
*copyback = 1;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
} else {
if (copy_from_user(&mdss_pp_res->enhist_lut[disp_num][0],
config->data, ENHIST_LUT_ENTRIES * sizeof(u32))) {
ret = -EFAULT;
goto enhist_config_exit;
}
mdss_pp_res->enhist_disp_cfg[disp_num] = *config;
mdss_pp_res->enhist_disp_cfg[disp_num].data =
&mdss_pp_res->enhist_lut[disp_num][0];
mdss_pp_res->pp_disp_flags[disp_num] |= PP_FLAGS_DIRTY_ENHIST;
}
enhist_config_exit:
mutex_unlock(&mdss_pp_mutex);
return ret;
}
int mdss_mdp_dither_config(struct mdp_dither_cfg_data *config, u32 *copyback)
{
u32 disp_num;
if ((config->block < MDP_LOGICAL_BLOCK_DISP_0) ||
(config->block >= MDP_BLOCK_MAX))
return -EINVAL;
if (config->flags & MDP_PP_OPS_READ)
return -ENOTSUPP;
mutex_lock(&mdss_pp_mutex);
disp_num = config->block - MDP_LOGICAL_BLOCK_DISP_0;
mdss_pp_res->dither_disp_cfg[disp_num] = *config;
mdss_pp_res->pp_disp_flags[disp_num] |= PP_FLAGS_DIRTY_DITHER;
mutex_unlock(&mdss_pp_mutex);
return 0;
}
int mdss_mdp_gamut_config(struct mdp_gamut_cfg_data *config, u32 *copyback)
{
int i, j, size_total = 0, ret = 0;
u32 offset, disp_num, dspp_num = 0;
uint16_t *tbl_off;
struct mdp_gamut_cfg_data local_cfg;
if ((config->block < MDP_LOGICAL_BLOCK_DISP_0) ||
(config->block >= MDP_BLOCK_MAX))
return -EINVAL;
for (i = 0; i < MDP_GAMUT_TABLE_NUM; i++)
size_total += config->tbl_size[i];
if (size_total != GAMUT_TOTAL_TABLE_SIZE)
return -EINVAL;
mutex_lock(&mdss_pp_mutex);
disp_num = config->block - MDP_LOGICAL_BLOCK_DISP_0;
if (config->flags & MDP_PP_OPS_READ) {
ret = pp_get_dspp_num(disp_num, &dspp_num);
if (ret) {
pr_err("%s, no dspp connects to disp %d",
__func__, disp_num);
goto gamut_config_exit;
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
offset = MDSS_MDP_REG_DSPP_OFFSET(dspp_num) +
MDSS_MDP_REG_DSPP_GAMUT_BASE;
for (i = 0; i < MDP_GAMUT_TABLE_NUM; i++) {
for (j = 0; j < config->tbl_size[i]; j++)
config->r_tbl[i][j] =
(u16)MDSS_MDP_REG_READ(offset);
offset += 4;
}
for (i = 0; i < MDP_GAMUT_TABLE_NUM; i++) {
for (j = 0; j < config->tbl_size[i]; j++)
config->g_tbl[i][j] =
(u16)MDSS_MDP_REG_READ(offset);
offset += 4;
}
for (i = 0; i < MDP_GAMUT_TABLE_NUM; i++) {
for (j = 0; j < config->tbl_size[i]; j++)
config->b_tbl[i][j] =
(u16)MDSS_MDP_REG_READ(offset);
offset += 4;
}
*copyback = 1;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
} else {
local_cfg = *config;
tbl_off = mdss_pp_res->gamut_tbl[disp_num];
for (i = 0; i < MDP_GAMUT_TABLE_NUM; i++) {
local_cfg.r_tbl[i] = tbl_off;
if (copy_from_user(tbl_off, config->r_tbl[i],
config->tbl_size[i] * sizeof(uint16_t))) {
ret = -EFAULT;
goto gamut_config_exit;
}
tbl_off += local_cfg.tbl_size[i];
}
for (i = 0; i < MDP_GAMUT_TABLE_NUM; i++) {
local_cfg.g_tbl[i] = tbl_off;
if (copy_from_user(tbl_off, config->g_tbl[i],
config->tbl_size[i] * sizeof(uint16_t))) {
ret = -EFAULT;
goto gamut_config_exit;
}
tbl_off += local_cfg.tbl_size[i];
}
for (i = 0; i < MDP_GAMUT_TABLE_NUM; i++) {
local_cfg.b_tbl[i] = tbl_off;
if (copy_from_user(tbl_off, config->b_tbl[i],
config->tbl_size[i] * sizeof(uint16_t))) {
ret = -EFAULT;
goto gamut_config_exit;
}
tbl_off += local_cfg.tbl_size[i];
}
mdss_pp_res->gamut_disp_cfg[disp_num] = local_cfg;
mdss_pp_res->pp_disp_flags[disp_num] |= PP_FLAGS_DIRTY_GAMUT;
}
gamut_config_exit:
mutex_unlock(&mdss_pp_mutex);
return ret;
}
static void pp_hist_read(u32 v_base, struct pp_hist_col_info *hist_info)
{
int i, i_start;
u32 data;
data = MDSS_MDP_REG_READ(v_base);
i_start = data >> 24;
hist_info->data[i_start] = data & 0xFFFFFF;
for (i = i_start + 1; i < HIST_V_SIZE; i++)
hist_info->data[i] = MDSS_MDP_REG_READ(v_base) & 0xFFFFFF;
for (i = 0; i < i_start - 1; i++)
hist_info->data[i] = MDSS_MDP_REG_READ(v_base) & 0xFFFFFF;
hist_info->hist_cnt_read++;
}
int mdss_mdp_histogram_start(struct mdp_histogram_start_req *req)
{
u32 ctl_base, done_shift_bit;
struct pp_hist_col_info *hist_info;
int i, ret = 0;
u32 disp_num, dspp_num = 0;
u32 mixer_cnt, mixer_id[MDSS_MDP_MAX_LAYERMIXER];
unsigned long flag;
if ((req->block < MDP_LOGICAL_BLOCK_DISP_0) ||
(req->block >= MDP_BLOCK_MAX))
return -EINVAL;
mutex_lock(&mdss_mdp_hist_mutex);
disp_num = req->block - MDP_LOGICAL_BLOCK_DISP_0;
mixer_cnt = mdss_mdp_get_ctl_mixers(disp_num, mixer_id);
if (!mixer_cnt) {
pr_err("%s, no dspp connects to disp %d",
__func__, disp_num);
ret = -EPERM;
goto hist_start_exit;
}
if (mixer_cnt >= MDSS_MDP_MAX_DSPP) {
pr_err("%s, Too many dspp connects to disp %d",
__func__, mixer_cnt);
ret = -EPERM;
goto hist_start_exit;
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
for (i = 0; i < mixer_cnt; i++) {
dspp_num = mixer_id[i];
hist_info = &mdss_pp_res->dspp_hist[dspp_num];
done_shift_bit = (dspp_num * 4) + 12;
/* check if it is idle */
if (hist_info->col_en) {
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
pr_info("%s Hist collection has already been enabled %d",
__func__, dspp_num);
goto hist_start_exit;
}
spin_lock_irqsave(&mdss_hist_lock, flag);
hist_info->frame_cnt = req->frame_cnt;
init_completion(&hist_info->comp);
hist_info->hist_cnt_read = 0;
hist_info->hist_cnt_sent = 0;
hist_info->read_request = false;
hist_info->col_state = HIST_RESET;
hist_info->col_en = true;
hist_info->is_kick_ready = false;
spin_unlock_irqrestore(&mdss_hist_lock, flag);
mdss_pp_res->hist_col[disp_num][i] =
&mdss_pp_res->dspp_hist[dspp_num];
mdss_mdp_hist_irq_enable(3 << done_shift_bit);
ctl_base = MDSS_MDP_REG_DSPP_OFFSET(dspp_num) +
MDSS_MDP_REG_DSPP_HIST_CTL_BASE;
MDSS_MDP_REG_WRITE(ctl_base + 8, req->frame_cnt);
/* Kick out reset start */
MDSS_MDP_REG_WRITE(ctl_base + 4, 1);
}
for (i = mixer_cnt; i < MDSS_MDP_MAX_DSPP; i++)
mdss_pp_res->hist_col[disp_num][i] = 0;
mdss_pp_res->pp_disp_flags[disp_num] |= PP_FLAGS_DIRTY_HIST_COL;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
hist_start_exit:
mutex_unlock(&mdss_mdp_hist_mutex);
return ret;
}
int mdss_mdp_histogram_stop(u32 block)
{
int i, ret = 0;
u32 dspp_num, disp_num, ctl_base, done_bit;
struct pp_hist_col_info *hist_info;
u32 mixer_cnt, mixer_id[MDSS_MDP_MAX_LAYERMIXER];
unsigned long flag;
if ((block < MDP_LOGICAL_BLOCK_DISP_0) ||
(block >= MDP_BLOCK_MAX))
return -EINVAL;
mutex_lock(&mdss_mdp_hist_mutex);
disp_num = block - MDP_LOGICAL_BLOCK_DISP_0;
mixer_cnt = mdss_mdp_get_ctl_mixers(disp_num, mixer_id);
if (!mixer_cnt) {
pr_err("%s, no dspp connects to disp %d",
__func__, disp_num);
ret = -EPERM;
goto hist_stop_exit;
}
if (mixer_cnt >= MDSS_MDP_MAX_DSPP) {
pr_err("%s, Too many dspp connects to disp %d",
__func__, mixer_cnt);
ret = -EPERM;
goto hist_stop_exit;
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
for (i = 0; i < mixer_cnt; i++) {
dspp_num = mixer_id[i];
hist_info = &mdss_pp_res->dspp_hist[dspp_num];
done_bit = 3 << ((dspp_num * 4) + 12);
ctl_base = MDSS_MDP_REG_DSPP_OFFSET(dspp_num) +
MDSS_MDP_REG_DSPP_HIST_CTL_BASE;
if (hist_info->col_en == false) {
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
goto hist_stop_exit;
}
complete_all(&hist_info->comp);
spin_lock_irqsave(&mdss_hist_lock, flag);
hist_info->col_en = false;
hist_info->col_state = HIST_UNKNOWN;
hist_info->is_kick_ready = false;
spin_unlock_irqrestore(&mdss_hist_lock, flag);
mdss_mdp_hist_irq_disable(done_bit);
MDSS_MDP_REG_WRITE(ctl_base, (1 << 1));/* cancel */
}
for (i = 0; i < MDSS_MDP_MAX_DSPP; i++)
mdss_pp_res->hist_col[disp_num][i] = 0;
mdss_pp_res->pp_disp_flags[disp_num] |= PP_FLAGS_DIRTY_HIST_COL;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
hist_stop_exit:
mutex_unlock(&mdss_mdp_hist_mutex);
return ret;
}
int mdss_mdp_hist_collect(struct fb_info *info,
struct mdp_histogram_data *hist, u32 *hist_data_addr)
{
int i, j, wait_ret, ret = 0;
u32 timeout, v_base;
struct pp_hist_col_info *hist_info;
u32 dspp_num, disp_num, ctl_base;
u32 mixer_cnt, mixer_id[MDSS_MDP_MAX_LAYERMIXER];
unsigned long flag;
if ((hist->block < MDP_LOGICAL_BLOCK_DISP_0) ||
(hist->block >= MDP_BLOCK_MAX))
return -EINVAL;
mutex_lock(&mdss_mdp_hist_mutex);
disp_num = hist->block - MDP_LOGICAL_BLOCK_DISP_0;
mixer_cnt = mdss_mdp_get_ctl_mixers(disp_num, mixer_id);
if (!mixer_cnt) {
pr_err("%s, no dspp connects to disp %d",
__func__, disp_num);
ret = -EPERM;
goto hist_collect_exit;
}
if (mixer_cnt >= MDSS_MDP_MAX_DSPP) {
pr_err("%s, Too many dspp connects to disp %d",
__func__, mixer_cnt);
ret = -EPERM;
goto hist_collect_exit;
}
hist_info = &mdss_pp_res->dspp_hist[0];
for (i = 0; i < mixer_cnt; i++) {
dspp_num = mixer_id[i];
hist_info = &mdss_pp_res->dspp_hist[dspp_num];
ctl_base = MDSS_MDP_REG_DSPP_OFFSET(dspp_num) +
MDSS_MDP_REG_DSPP_HIST_CTL_BASE;
if ((hist_info->col_en == 0) ||
(hist_info->col_state == HIST_UNKNOWN)) {
ret = -EINVAL;
goto hist_collect_exit;
}
spin_lock_irqsave(&mdss_hist_lock, flag);
/* wait for hist done if cache has no data */
if (hist_info->col_state != HIST_READY) {
hist_info->read_request = true;
spin_unlock_irqrestore(&mdss_hist_lock, flag);
timeout = HIST_WAIT_TIMEOUT(hist_info->frame_cnt);
mutex_unlock(&mdss_mdp_hist_mutex);
wait_ret = wait_for_completion_killable_timeout(
&(hist_info->comp), timeout);
mutex_lock(&mdss_mdp_hist_mutex);
if (wait_ret == 0) {
ret = -ETIMEDOUT;
spin_lock_irqsave(&mdss_hist_lock, flag);
pr_debug("bin collection timedout, state %d",
hist_info->col_state);
/*
* When the histogram has timed out (usually
* underrun) change the SW state back to idle
* since histogram hardware will have done the
* same. Histogram data also needs to be
* cleared in this case, which is done by the
* histogram being read (triggered by READY
* state, which also moves the histogram SW back
* to IDLE).
*/
hist_info->col_state = HIST_READY;
spin_unlock_irqrestore(&mdss_hist_lock, flag);
} else if (wait_ret < 0) {
ret = -EINTR;
pr_debug("%s: bin collection interrupted",
__func__);
goto hist_collect_exit;
}
if (hist_info->col_state != HIST_READY) {
ret = -ENODATA;
pr_debug("%s: collection state is not ready: %d",
__func__, hist_info->col_state);
goto hist_collect_exit;
}
} else {
spin_unlock_irqrestore(&mdss_hist_lock, flag);
}
spin_lock_irqsave(&mdss_hist_lock, flag);
if (hist_info->col_state == HIST_READY) {
spin_unlock_irqrestore(&mdss_hist_lock, flag);
v_base = ctl_base + 0x1C;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
pp_hist_read(v_base, hist_info);
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
spin_lock_irqsave(&mdss_hist_lock, flag);
hist_info->read_request = false;
hist_info->col_state = HIST_IDLE;
}
spin_unlock_irqrestore(&mdss_hist_lock, flag);
}
if (mixer_cnt > 1) {
memset(&mdss_pp_res->hist_data[disp_num][0],
0, HIST_V_SIZE * sizeof(u32));
for (i = 0; i < mixer_cnt; i++) {
dspp_num = mixer_id[i];
hist_info = &mdss_pp_res->dspp_hist[dspp_num];
for (j = 0; j < HIST_V_SIZE; j++)
mdss_pp_res->hist_data[disp_num][i] +=
hist_info->data[i];
}
*hist_data_addr = (u32)&mdss_pp_res->hist_data[disp_num][0];
} else {
*hist_data_addr = (u32)hist_info->data;
}
hist_info->hist_cnt_sent++;
hist_collect_exit:
mutex_unlock(&mdss_mdp_hist_mutex);
return ret;
}
void mdss_mdp_hist_intr_done(u32 isr)
{
u32 isr_blk, blk_idx;
struct pp_hist_col_info *hist_info;
isr &= 0x333333;
while (isr != 0) {
if (isr & 0xFFF000) {
if (isr & 0x3000) {
blk_idx = 0;
isr_blk = (isr >> 12) & 0x3;
isr &= ~0x3000;
} else if (isr & 0x30000) {
blk_idx = 1;
isr_blk = (isr >> 16) & 0x3;
isr &= ~0x30000;
} else {
blk_idx = 2;
isr_blk = (isr >> 20) & 0x3;
isr &= ~0x300000;
}
hist_info = &mdss_pp_res->dspp_hist[blk_idx];
} else {
if (isr & 0x3) {
blk_idx = 0;
isr_blk = isr & 0x3;
isr &= ~0x3;
} else if (isr & 0x30) {
blk_idx = 1;
isr_blk = (isr >> 4) & 0x3;
isr &= ~0x30;
} else {
blk_idx = 2;
isr_blk = (isr >> 8) & 0x3;
isr &= ~0x300;
}
/* SSPP block, not support yet*/
continue;
}
/* Histogram Done Interrupt */
if ((isr_blk & 0x1) &&
(hist_info->col_en)) {
spin_lock(&mdss_hist_lock);
hist_info->col_state = HIST_READY;
spin_unlock(&mdss_hist_lock);
if (hist_info->read_request)
complete(&hist_info->comp);
}
/* Histogram Reset Done Interrupt */
if ((isr_blk & 0x2) &&
(hist_info->col_en)) {
spin_lock(&mdss_hist_lock);
hist_info->col_state = HIST_IDLE;
spin_unlock(&mdss_hist_lock);
}
};
}