M7350/kernel/drivers/video/msm/mdss/mdss_mdp_pp.c

3401 lines
94 KiB
C
Raw Normal View History

2024-09-09 08:52:07 +00:00
/*
* 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 HIST_WAIT_TIMEOUT(frame) ((75 * HZ * (frame)) / 1000)
/* hist collect state */
enum {
HIST_UNKNOWN,
HIST_IDLE,
HIST_RESET,
HIST_START,
HIST_READY,
};
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};
static u32 igc_limited[IGC_LUT_ENTRIES] = {
16777472, 17826064, 18874656, 19923248,
19923248, 20971840, 22020432, 23069024,
24117616, 25166208, 26214800, 26214800,
27263392, 28311984, 29360576, 30409168,
31457760, 32506352, 32506352, 33554944,
34603536, 35652128, 36700720, 37749312,
38797904, 38797904, 39846496, 40895088,
41943680, 42992272, 44040864, 45089456,
45089456, 46138048, 47186640, 48235232,
49283824, 50332416, 51381008, 51381008,
52429600, 53478192, 54526784, 55575376,
56623968, 57672560, 58721152, 58721152,
59769744, 60818336, 61866928, 62915520,
63964112, 65012704, 65012704, 66061296,
67109888, 68158480, 69207072, 70255664,
71304256, 71304256, 72352848, 73401440,
74450032, 75498624, 76547216, 77595808,
77595808, 78644400, 79692992, 80741584,
81790176, 82838768, 83887360, 83887360,
84935952, 85984544, 87033136, 88081728,
89130320, 90178912, 90178912, 91227504,
92276096, 93324688, 94373280, 95421872,
96470464, 96470464, 97519056, 98567648,
99616240, 100664832, 101713424, 102762016,
102762016, 103810608, 104859200, 105907792,
106956384, 108004976, 109053568, 109053568,
110102160, 111150752, 112199344, 113247936,
114296528, 115345120, 115345120, 116393712,
117442304, 118490896, 119539488, 120588080,
121636672, 121636672, 122685264, 123733856,
124782448, 125831040, 126879632, 127928224,
127928224, 128976816, 130025408, 131074000,
132122592, 133171184, 134219776, 135268368,
135268368, 136316960, 137365552, 138414144,
139462736, 140511328, 141559920, 141559920,
142608512, 143657104, 144705696, 145754288,
146802880, 147851472, 147851472, 148900064,
149948656, 150997248, 152045840, 153094432,
154143024, 154143024, 155191616, 156240208,
157288800, 158337392, 159385984, 160434576,
160434576, 161483168, 162531760, 163580352,
164628944, 165677536, 166726128, 166726128,
167774720, 168823312, 169871904, 170920496,
171969088, 173017680, 173017680, 174066272,
175114864, 176163456, 177212048, 178260640,
179309232, 179309232, 180357824, 181406416,
182455008, 183503600, 184552192, 185600784,
185600784, 186649376, 187697968, 188746560,
189795152, 190843744, 191892336, 191892336,
192940928, 193989520, 195038112, 196086704,
197135296, 198183888, 198183888, 199232480,
200281072, 201329664, 202378256, 203426848,
204475440, 204475440, 205524032, 206572624,
207621216, 208669808, 209718400, 210766992,
211815584, 211815584, 212864176, 213912768,
214961360, 216009952, 217058544, 218107136,
218107136, 219155728, 220204320, 221252912,
222301504, 223350096, 224398688, 224398688,
225447280, 226495872, 227544464, 228593056,
229641648, 230690240, 230690240, 231738832,
232787424, 233836016, 234884608, 235933200,
236981792, 236981792, 238030384, 239078976,
240127568, 241176160, 242224752, 243273344,
243273344, 244321936, 245370528, 246419120};
#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)
#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_FLAGS_DIRTY_SHARP 0x200
#define PP_STS_ENABLE 0x1
#define PP_STS_GAMUT_FIRST 0x2
#define PP_AD_STATE_INIT 0x2
#define PP_AD_STATE_CFG 0x4
#define PP_AD_STATE_DATA 0x8
#define PP_AD_STATE_RUN 0x10
#define PP_AD_STATE_VSYNC 0x20
#define PP_AD_STATE_BL_LIN 0x40
#define PP_AD_STATE_IS_INITCFG(st) (((st) & PP_AD_STATE_INIT) &&\
((st) & PP_AD_STATE_CFG))
#define PP_AD_STATE_IS_READY(st) (((st) & PP_AD_STATE_INIT) &&\
((st) & PP_AD_STATE_CFG) &&\
((st) & PP_AD_STATE_DATA))
#define PP_AD_STS_DIRTY_INIT 0x2
#define PP_AD_STS_DIRTY_CFG 0x4
#define PP_AD_STS_DIRTY_DATA 0x8
#define PP_AD_STS_DIRTY_VSYNC 0x10
#define PP_AD_STS_IS_DIRTY(sts) (((sts) & PP_AD_STS_DIRTY_INIT) ||\
((sts) & PP_AD_STS_DIRTY_CFG))
/* Bits 0 and 1 */
#define MDSS_AD_INPUT_AMBIENT (0x03)
/* Bits 3 and 7 */
#define MDSS_AD_INPUT_STRENGTH (0x88)
/*
* Check data by shifting by mode to see if it matches to the
* MDSS_AD_INPUT_* bitfields
*/
#define MDSS_AD_MODE_DATA_MATCH(mode, data) ((1 << (mode)) & (data))
#define MDSS_AD_RUNNING_AUTO_BL(ad) (((ad)->state & PP_AD_STATE_RUN) &&\
((ad)->cfg.mode == MDSS_AD_MODE_AUTO_BL))
#define MDSS_AD_RUNNING_AUTO_STR(ad) (((ad)->state & PP_AD_STATE_RUN) &&\
((ad)->cfg.mode == MDSS_AD_MODE_AUTO_STR))
#define SHARP_STRENGTH_DEFAULT 32
#define SHARP_EDGE_THR_DEFAULT 112
#define SHARP_SMOOTH_THR_DEFAULT 8
#define SHARP_NOISE_THR_DEFAULT 2
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];
u32 hist_data[MDSS_BLOCK_DISP_NUM][HIST_V_SIZE];
/* physical info */
struct pp_sts_type pp_disp_sts[MDSS_BLOCK_DISP_NUM];
struct pp_hist_col_info dspp_hist[MDSS_MDP_MAX_DSPP];
};
static DEFINE_MUTEX(mdss_pp_mutex);
static struct mdss_pp_res_type *mdss_pp_res;
static void pp_hist_read(char __iomem *v_base,
struct pp_hist_col_info *hist_info);
static int pp_histogram_setup(u32 *op, u32 block, struct mdss_mdp_mixer *mix);
static int pp_histogram_disable(struct pp_hist_col_info *hist_info,
u32 done_bit, char __iomem *ctl_base);
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(char __iomem *base,
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, char __iomem *base,
struct pp_sts_type *pp_sts,
struct mdp_hist_lut_data *enhist_cfg);
static void pp_sharp_config(char __iomem *offset,
struct pp_sts_type *pp_sts,
struct mdp_sharp_cfg *sharp_config);
static int mdss_ad_init_checks(struct msm_fb_data_type *mfd);
static struct mdss_ad_info *mdss_mdp_get_ad(struct msm_fb_data_type *mfd);
static int pp_update_ad_input(struct msm_fb_data_type *mfd);
static void pp_ad_vsync_handler(struct mdss_mdp_ctl *ctl, ktime_t t);
static void pp_ad_cfg_write(struct mdss_ad_info *ad);
static void pp_ad_init_write(struct mdss_ad_info *ad);
static void pp_ad_input_write(struct mdss_ad_info *ad, u32 bl_lvl);
static int mdss_mdp_ad_setup(struct msm_fb_data_type *mfd);
static void pp_ad_cfg_lut(char __iomem *offset, u32 *data);
static u32 last_sts, last_state;
int mdss_mdp_csc_setup_data(u32 block, u32 blk_idx, u32 tbl_idx,
struct mdp_csc_cfg *data)
{
int i, ret = 0;
char __iomem *base, *off;
u32 val = 0;
struct mdss_data_type *mdata;
struct mdss_mdp_pipe *pipe;
struct mdss_mdp_ctl *ctl;
if (data == NULL) {
pr_err("no csc matrix specified\n");
return -EINVAL;
}
mdata = mdss_mdp_get_mdata();
switch (block) {
case MDSS_MDP_BLOCK_SSPP:
if (blk_idx < mdata->nvig_pipes) {
pipe = mdata->vig_pipes + blk_idx;
base = pipe->base;
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 < mdata->nctl) {
ctl = mdata->ctl_off + blk_idx;
base = ctl->wb_base + 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 = base + CSC_MV_OFF;
for (i = 0; i < 9; i++) {
if (i & 0x1) {
val |= data->csc_mv[i] << 16;
writel_relaxed(val, off);
off += sizeof(u32 *);
} else {
val = data->csc_mv[i];
}
}
writel_relaxed(val, off); /* COEFF_33 */
off = base + CSC_BV_OFF;
for (i = 0; i < 3; i++) {
writel_relaxed(data->csc_pre_bv[i], off);
writel_relaxed(data->csc_post_bv[i], off + CSC_POST_OFF);
off += sizeof(u32 *);
}
off = base + CSC_LV_OFF;
for (i = 0; i < 6; i += 2) {
val = (data->csc_pre_lv[i] << 8) | data->csc_pre_lv[i+1];
writel_relaxed(val, off);
val = (data->csc_post_lv[i] << 8) | data->csc_post_lv[i+1];
writel_relaxed(val, off + CSC_POST_OFF);
off += sizeof(u32 *);
}
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, struct pp_sts_type *pp_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)
pp_sts->gamut_sts |= PP_STS_GAMUT_FIRST;
}
if (gamut_cfg->flags & MDP_PP_OPS_DISABLE)
pp_sts->gamut_sts &= ~PP_STS_ENABLE;
else if (gamut_cfg->flags & MDP_PP_OPS_ENABLE)
pp_sts->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, char __iomem *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;
}
}
/*the below function doesn't do error checking on the input params*/
static void pp_sharp_config(char __iomem *base,
struct pp_sts_type *pp_sts,
struct mdp_sharp_cfg *sharp_config)
{
if (sharp_config->flags & MDP_PP_OPS_WRITE) {
writel_relaxed(sharp_config->strength, base);
base += 4;
writel_relaxed(sharp_config->edge_thr, base);
base += 4;
writel_relaxed(sharp_config->smooth_thr, base);
base += 4;
writel_relaxed(sharp_config->noise_thr, base);
}
if (sharp_config->flags & MDP_PP_OPS_DISABLE)
pp_sts->sharp_sts &= ~PP_STS_ENABLE;
else if (sharp_config->flags & MDP_PP_OPS_ENABLE)
pp_sts->sharp_sts |= PP_STS_ENABLE;
}
static int pp_vig_pipe_setup(struct mdss_mdp_pipe *pipe, u32 *op)
{
u32 opmode = 0, base = 0;
unsigned long flags = 0;
char __iomem *offset;
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);
}
}
pp_histogram_setup(&opmode, MDSS_PP_SSPP_CFG | pipe->num, pipe->mixer);
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_pa_config(flags, base, &pipe->pp_res.pp_sts,
&pipe->pp_cfg.pa_cfg);
if (pipe->pp_res.pp_sts.pa_sts & PP_STS_ENABLE)
opmode |= (1 << 4); /* PA_EN */
}
if (pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_HIST_LUT_CFG) {
pp_enhist_config(PP_FLAGS_DIRTY_ENHIST,
pipe->base + MDSS_MDP_REG_VIG_HIST_LUT_BASE,
&pipe->pp_res.pp_sts,
&pipe->pp_cfg.hist_lut_cfg);
}
}
if (pipe->pp_res.pp_sts.enhist_sts & PP_STS_ENABLE) {
/* Enable HistLUT and PA */
opmode |= BIT(10) | BIT(4);
if (!(pipe->pp_res.pp_sts.pa_sts & PP_STS_ENABLE)) {
/* Program default value */
offset = pipe->base + MDSS_MDP_REG_VIG_PA_BASE;
writel_relaxed(0, offset);
writel_relaxed(0, offset + 4);
writel_relaxed(0, offset + 8);
writel_relaxed(0, offset + 12);
}
}
*op = opmode;
return 0;
}
static int mdss_mdp_leading_zero(u32 num)
{
u32 bit = 0x80000000;
int i;
for (i = 0; i < 32; i++) {
if (bit & num)
return i;
bit >>= 1;
}
return i;
}
static u32 mdss_mdp_scale_phase_step(int f_num, u32 src, u32 dst)
{
u32 val, s;
int n;
n = mdss_mdp_leading_zero(src);
if (n > f_num)
n = f_num;
s = src << n; /* maximum to reduce lose of resolution */
val = s / dst;
if (n < f_num) {
n = f_num - n;
val <<= n;
val |= ((s % dst) << n) / dst;
}
return val;
}
static int mdss_mdp_scale_setup(struct mdss_mdp_pipe *pipe)
{
u32 scale_config = 0;
u32 phasex_step = 0, phasey_step = 0;
u32 chroma_sample;
u32 filter_mode;
struct mdss_data_type *mdata;
u32 src_w, src_h;
mdata = mdss_mdp_get_mdata();
if (mdata->mdp_rev >= MDSS_MDP_HW_REV_102 && pipe->src_fmt->is_yuv)
filter_mode = MDSS_MDP_SCALE_FILTER_CA;
else
filter_mode = MDSS_MDP_SCALE_FILTER_BIL;
if (pipe->type == MDSS_MDP_PIPE_TYPE_DMA) {
if (pipe->dst.h != pipe->src.h || pipe->dst.w != pipe->src.w) {
pr_err("no scaling supported on dma pipe\n");
return -EINVAL;
} else {
return 0;
}
}
src_w = pipe->src.w >> pipe->horz_deci;
src_h = pipe->src.h >> pipe->vert_deci;
chroma_sample = pipe->src_fmt->chroma_sample;
if (pipe->flags & MDP_SOURCE_ROTATED_90) {
if (chroma_sample == MDSS_MDP_CHROMA_H1V2)
chroma_sample = MDSS_MDP_CHROMA_H2V1;
else if (chroma_sample == MDSS_MDP_CHROMA_H2V1)
chroma_sample = MDSS_MDP_CHROMA_H1V2;
}
if (!(pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_SHARP_CFG)) {
pipe->pp_cfg.sharp_cfg.flags = MDP_PP_OPS_ENABLE |
MDP_PP_OPS_WRITE;
pipe->pp_cfg.sharp_cfg.strength = SHARP_STRENGTH_DEFAULT;
pipe->pp_cfg.sharp_cfg.edge_thr = SHARP_EDGE_THR_DEFAULT;
pipe->pp_cfg.sharp_cfg.smooth_thr = SHARP_SMOOTH_THR_DEFAULT;
pipe->pp_cfg.sharp_cfg.noise_thr = SHARP_NOISE_THR_DEFAULT;
}
if ((pipe->src_fmt->is_yuv) &&
!((pipe->dst.w < src_w) || (pipe->dst.h < src_h))) {
pp_sharp_config(pipe->base +
MDSS_MDP_REG_VIG_QSEED2_SHARP,
&pipe->pp_res.pp_sts,
&pipe->pp_cfg.sharp_cfg);
}
if ((src_h != pipe->dst.h) ||
(pipe->pp_res.pp_sts.sharp_sts & PP_STS_ENABLE) ||
(chroma_sample == MDSS_MDP_CHROMA_420) ||
(chroma_sample == MDSS_MDP_CHROMA_H1V2)) {
pr_debug("scale y - src_h=%d dst_h=%d\n", src_h, pipe->dst.h);
if ((src_h / MAX_DOWNSCALE_RATIO) > pipe->dst.h) {
pr_err("too much downscaling height=%d->%d",
src_h, pipe->dst.h);
return -EINVAL;
}
scale_config |= MDSS_MDP_SCALEY_EN;
if (pipe->type == MDSS_MDP_PIPE_TYPE_VIG) {
u32 chr_dst_h = pipe->dst.h;
if (!pipe->vert_deci &&
((chroma_sample == MDSS_MDP_CHROMA_420) ||
(chroma_sample == MDSS_MDP_CHROMA_H1V2)))
chr_dst_h *= 2; /* 2x upsample chroma */
if (src_h <= pipe->dst.h) {
scale_config |= /* G/Y, A */
(filter_mode << 10) |
(MDSS_MDP_SCALE_FILTER_NEAREST << 18);
} else
scale_config |= /* G/Y, A */
(MDSS_MDP_SCALE_FILTER_PCMN << 10) |
(MDSS_MDP_SCALE_FILTER_PCMN << 18);
if (src_h <= chr_dst_h)
scale_config |= /* CrCb */
(MDSS_MDP_SCALE_FILTER_BIL << 14);
else
scale_config |= /* CrCb */
(MDSS_MDP_SCALE_FILTER_PCMN << 14);
phasey_step = mdss_mdp_scale_phase_step(
PHASE_STEP_SHIFT, src_h, chr_dst_h);
writel_relaxed(phasey_step, pipe->base +
MDSS_MDP_REG_VIG_QSEED2_C12_PHASESTEPY);
} else {
if (src_h <= pipe->dst.h)
scale_config |= /* RGB, A */
(MDSS_MDP_SCALE_FILTER_BIL << 10) |
(MDSS_MDP_SCALE_FILTER_NEAREST << 18);
else
scale_config |= /* RGB, A */
(MDSS_MDP_SCALE_FILTER_PCMN << 10) |
(MDSS_MDP_SCALE_FILTER_PCMN << 18);
}
phasey_step = mdss_mdp_scale_phase_step(
PHASE_STEP_SHIFT, src_h, pipe->dst.h);
}
if ((src_w != pipe->dst.w) ||
(pipe->pp_res.pp_sts.sharp_sts & PP_STS_ENABLE) ||
(chroma_sample == MDSS_MDP_CHROMA_420) ||
(chroma_sample == MDSS_MDP_CHROMA_H2V1)) {
pr_debug("scale x - src_w=%d dst_w=%d\n", src_w, pipe->dst.w);
if ((src_w / MAX_DOWNSCALE_RATIO) > pipe->dst.w) {
pr_err("too much downscaling width=%d->%d",
src_w, pipe->dst.w);
return -EINVAL;
}
scale_config |= MDSS_MDP_SCALEX_EN;
if (pipe->type == MDSS_MDP_PIPE_TYPE_VIG) {
u32 chr_dst_w = pipe->dst.w;
if (!pipe->horz_deci &&
((chroma_sample == MDSS_MDP_CHROMA_420) ||
(chroma_sample == MDSS_MDP_CHROMA_H2V1)))
chr_dst_w *= 2; /* 2x upsample chroma */
if (src_w <= pipe->dst.w) {
scale_config |= /* G/Y, A */
(filter_mode << 8) |
(MDSS_MDP_SCALE_FILTER_NEAREST << 16);
} else
scale_config |= /* G/Y, A */
(MDSS_MDP_SCALE_FILTER_PCMN << 8) |
(MDSS_MDP_SCALE_FILTER_PCMN << 16);
if (src_w <= chr_dst_w)
scale_config |= /* CrCb */
(MDSS_MDP_SCALE_FILTER_BIL << 12);
else
scale_config |= /* CrCb */
(MDSS_MDP_SCALE_FILTER_PCMN << 12);
phasex_step = mdss_mdp_scale_phase_step(
PHASE_STEP_SHIFT, src_w, chr_dst_w);
writel_relaxed(phasex_step, pipe->base +
MDSS_MDP_REG_VIG_QSEED2_C12_PHASESTEPX);
} else {
if (src_w <= pipe->dst.w)
scale_config |= /* RGB, A */
(MDSS_MDP_SCALE_FILTER_BIL << 8) |
(MDSS_MDP_SCALE_FILTER_NEAREST << 16);
else
scale_config |= /* RGB, A */
(MDSS_MDP_SCALE_FILTER_PCMN << 8) |
(MDSS_MDP_SCALE_FILTER_PCMN << 16);
}
phasex_step = mdss_mdp_scale_phase_step(
PHASE_STEP_SHIFT, src_w, pipe->dst.w);
}
writel_relaxed(scale_config, pipe->base +
MDSS_MDP_REG_SCALE_CONFIG);
writel_relaxed(phasex_step, pipe->base +
MDSS_MDP_REG_SCALE_PHASE_STEP_X);
writel_relaxed(phasey_step, pipe->base +
MDSS_MDP_REG_SCALE_PHASE_STEP_Y);
return 0;
}
int mdss_mdp_pipe_pp_setup(struct mdss_mdp_pipe *pipe, u32 *op)
{
int ret = 0;
if (!pipe)
return -ENODEV;
ret = mdss_mdp_scale_setup(pipe);
if (ret)
return -EINVAL;
if (pipe->type == MDSS_MDP_PIPE_TYPE_VIG)
ret = pp_vig_pipe_setup(pipe, op);
return ret;
}
void mdss_mdp_pipe_sspp_term(struct mdss_mdp_pipe *pipe)
{
u32 done_bit;
struct pp_hist_col_info *hist_info;
char __iomem *ctl_base;
if (!pipe && pipe->pp_res.hist.col_en) {
done_bit = 3 << (pipe->num * 4);
hist_info = &pipe->pp_res.hist;
ctl_base = pipe->base +
MDSS_MDP_REG_VIG_HIST_CTL_BASE;
pp_histogram_disable(hist_info, done_bit, ctl_base);
}
memset(&pipe->pp_cfg, 0, sizeof(struct mdp_overlay_pp_params));
memset(&pipe->pp_res, 0, sizeof(struct mdss_pipe_pp_res));
}
int mdss_mdp_pipe_sspp_setup(struct mdss_mdp_pipe *pipe, u32 *op)
{
int ret = 0;
unsigned long flags = 0;
u32 pipe_base;
u32 pipe_num;
if (pipe == NULL)
return -EINVAL;
/*
* TODO: should this function be responsible for masking multiple
* pipes to be written in dual pipe case?
* if so, requires rework of update_igc_lut
*/
switch (pipe->type) {
case MDSS_MDP_PIPE_TYPE_VIG:
pipe_base = MDSS_MDP_REG_IGC_VIG_BASE;
pipe_num = pipe->num - MDSS_MDP_SSPP_VIG0;
break;
case MDSS_MDP_PIPE_TYPE_RGB:
pipe_base = MDSS_MDP_REG_IGC_RGB_BASE;
pipe_num = pipe->num - MDSS_MDP_SSPP_RGB0;
break;
case MDSS_MDP_PIPE_TYPE_DMA:
pipe_base = MDSS_MDP_REG_IGC_DMA_BASE;
pipe_num = pipe->num - MDSS_MDP_SSPP_DMA0;
break;
default:
return -EINVAL;
}
if (pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_IGC_CFG) {
flags |= PP_FLAGS_DIRTY_IGC;
pp_igc_config(flags, pipe_base, &pipe->pp_res.pp_sts,
&pipe->pp_cfg.igc_cfg, pipe_num);
}
if (pipe->pp_res.pp_sts.igc_sts & PP_STS_ENABLE)
*op |= (1 << 16); /* IGC_LUT_EN */
return ret;
}
static int pp_mixer_setup(u32 disp_num,
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;
struct mdss_mdp_ctl *ctl;
dspp_num = mixer->num;
if (!mixer || !mixer->ctl)
return -EINVAL;
ctl = mixer->ctl;
/* 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_disp_sts[disp_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 char __iomem *mdss_mdp_get_dspp_addr_off(u32 dspp_num)
{
struct mdss_data_type *mdata;
struct mdss_mdp_mixer *mixer;
mdata = mdss_mdp_get_mdata();
if (mdata->nmixers_intf <= dspp_num) {
pr_err("Invalid dspp_num=%d", dspp_num);
return ERR_PTR(-EINVAL);
}
mixer = mdata->mixer_intf + dspp_num;
return mixer->dspp_base;
}
/* Assumes that function will be called from within clock enabled space*/
static int pp_histogram_setup(u32 *op, u32 block, struct mdss_mdp_mixer *mix)
{
int ret = -EINVAL;
char __iomem *base;
u32 op_flags, kick_base, col_state;
struct mdss_data_type *mdata;
struct mdss_mdp_pipe *pipe;
struct pp_hist_col_info *hist_info;
unsigned long flag;
if (mix && (PP_LOCAT(block) == MDSS_PP_DSPP_CFG)) {
/* HIST_EN & AUTO_CLEAR */
op_flags = BIT(16) | BIT(17);
hist_info = &mdss_pp_res->dspp_hist[mix->num];
base = mdss_mdp_get_dspp_addr_off(PP_BLOCK(block));
kick_base = MDSS_MDP_REG_DSPP_HIST_CTL_BASE;
} else if (PP_LOCAT(block) == MDSS_PP_SSPP_CFG) {
mdata = mdss_mdp_get_mdata();
pipe = mdss_mdp_pipe_get(mdata, BIT(PP_BLOCK(block)));
if (IS_ERR_OR_NULL(pipe)) {
pr_debug("pipe DNE (%d)", (u32) BIT(PP_BLOCK(block)));
ret = -ENODEV;
goto error;
}
/* HIST_EN & AUTO_CLEAR */
op_flags = BIT(8) + BIT(9);
hist_info = &pipe->pp_res.hist;
base = pipe->base;
kick_base = MDSS_MDP_REG_VIG_HIST_CTL_BASE;
mdss_mdp_pipe_unmap(pipe);
} else {
pr_warn("invalid histogram location (%d)", block);
goto error;
}
if (hist_info->col_en) {
*op |= op_flags;
mutex_lock(&hist_info->hist_mutex);
spin_lock_irqsave(&hist_info->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 */
writel_relaxed(1, base + kick_base);
hist_info->col_state = HIST_START;
}
spin_unlock_irqrestore(&hist_info->hist_lock, flag);
mutex_unlock(&hist_info->hist_mutex);
}
ret = 0;
error:
return ret;
}
static int pp_dspp_setup(u32 disp_num, struct mdss_mdp_mixer *mixer)
{
u32 flags, base, offset, dspp_num, opmode = 0;
struct mdp_dither_cfg_data *dither_cfg;
struct mdp_pgc_lut_data *pgc_config;
struct pp_sts_type *pp_sts;
u32 data;
char __iomem *basel;
int i, ret = 0;
struct mdss_data_type *mdata;
struct mdss_mdp_ctl *ctl;
u32 mixer_cnt;
u32 mixer_id[MDSS_MDP_INTF_MAX_LAYERMIXER];
if (!mixer || !mixer->ctl || !mixer->ctl->mdata)
return -EINVAL;
ctl = mixer->ctl;
mdata = ctl->mdata;
dspp_num = mixer->num;
/* no corresponding dspp */
if ((mixer->type != MDSS_MDP_MIXER_TYPE_INTF) ||
(dspp_num >= MDSS_MDP_MAX_DSPP))
return -EINVAL;
base = MDSS_MDP_REG_DSPP_OFFSET(dspp_num);
basel = mdss_mdp_get_dspp_addr_off(dspp_num);
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
ret = pp_histogram_setup(&opmode, MDSS_PP_DSPP_CFG | dspp_num, mixer);
if (ret)
goto dspp_exit;
if (disp_num < MDSS_BLOCK_DISP_NUM)
flags = mdss_pp_res->pp_disp_flags[disp_num];
else
flags = 0;
mixer_cnt = mdss_mdp_get_ctl_mixers(disp_num, mixer_id);
if (dspp_num < mdata->nad_cfgs && (mixer_cnt != 2) &&
ctl->mfd->panel_info->type != MIPI_CMD_PANEL) {
ret = mdss_mdp_ad_setup(ctl->mfd);
if (ret < 0)
pr_warn("ad_setup(dspp%d) returns %d", dspp_num, ret);
}
/* call calibration specific processing here */
if (ctl->mfd->calib_mode)
goto flush_exit;
/* nothing to update */
if ((!flags) && (!(opmode)) && (ret <= 0))
goto dspp_exit;
ret = 0;
pp_sts = &mdss_pp_res->pp_disp_sts[disp_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, basel + 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);
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);
flush_exit:
writel_relaxed(opmode, basel + MDSS_MDP_REG_DSPP_OP_MODE);
mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_FLUSH, BIT(13 + dspp_num));
wmb();
dspp_exit:
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
return ret;
}
int mdss_mdp_pp_setup(struct mdss_mdp_ctl *ctl)
{
int ret = 0;
if ((!ctl->mfd) || (!mdss_pp_res))
return -EINVAL;
/* TODO: have some sort of reader/writer lock to prevent unclocked
* access while display power is toggled */
if (!ctl->mfd->panel_power_on) {
ret = -EPERM;
goto error;
}
mutex_lock(&ctl->mfd->lock);
ret = mdss_mdp_pp_setup_locked(ctl);
mutex_unlock(&ctl->mfd->lock);
error:
return ret;
}
/* call only when holding and mfd->lock */
int mdss_mdp_pp_setup_locked(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->mixer_left);
pp_dspp_setup(disp_num, ctl->mixer_left);
}
if (ctl->mixer_right) {
pp_mixer_setup(disp_num, ctl->mixer_right);
pp_dspp_setup(disp_num, 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;
}
/*
* Set dirty and write bits on features that were enabled so they will be
* reconfigured
*/
int mdss_mdp_pp_resume(struct mdss_mdp_ctl *ctl, u32 dspp_num)
{
u32 flags = 0, disp_num, bl;
struct pp_sts_type pp_sts;
struct mdss_ad_info *ad;
struct mdss_data_type *mdata = ctl->mdata;
if (dspp_num >= MDSS_MDP_MAX_DSPP) {
pr_warn("invalid dspp_num");
return -EINVAL;
}
disp_num = ctl->mfd->index;
if (dspp_num < mdata->nad_cfgs) {
ad = &mdata->ad_cfgs[dspp_num];
if (PP_AD_STATE_CFG & ad->state)
pp_ad_cfg_write(ad);
if (PP_AD_STATE_INIT & ad->state)
pp_ad_init_write(ad);
if (PP_AD_STATE_DATA & ad->state) {
bl = ctl->mfd->bl_level;
ad->last_bl = bl;
if (ad->state & PP_AD_STATE_BL_LIN) {
bl = ad->bl_lin[bl >> ad->bl_bright_shift];
bl = bl << ad->bl_bright_shift;
}
pp_ad_input_write(ad, bl);
}
if ((PP_AD_STATE_VSYNC & ad->state) && ad->calc_itr)
ctl->add_vsync_handler(ctl, &ad->handle);
}
pp_sts = mdss_pp_res->pp_disp_sts[disp_num];
if (pp_sts.pa_sts & PP_STS_ENABLE) {
flags |= PP_FLAGS_DIRTY_PA;
if (!(mdss_pp_res->pa_disp_cfg[disp_num].flags
& MDP_PP_OPS_DISABLE))
mdss_pp_res->pa_disp_cfg[disp_num].flags |=
MDP_PP_OPS_WRITE;
}
if (pp_sts.pcc_sts & PP_STS_ENABLE) {
flags |= PP_FLAGS_DIRTY_PCC;
if (!(mdss_pp_res->pcc_disp_cfg[disp_num].ops
& MDP_PP_OPS_DISABLE))
mdss_pp_res->pcc_disp_cfg[disp_num].ops |=
MDP_PP_OPS_WRITE;
}
if (pp_sts.igc_sts & PP_STS_ENABLE) {
flags |= PP_FLAGS_DIRTY_IGC;
if (!(mdss_pp_res->igc_disp_cfg[disp_num].ops
& MDP_PP_OPS_DISABLE))
mdss_pp_res->igc_disp_cfg[disp_num].ops |=
MDP_PP_OPS_WRITE;
}
if (pp_sts.argc_sts & PP_STS_ENABLE) {
flags |= PP_FLAGS_DIRTY_ARGC;
if (!(mdss_pp_res->argc_disp_cfg[disp_num].flags
& MDP_PP_OPS_DISABLE))
mdss_pp_res->argc_disp_cfg[disp_num].flags |=
MDP_PP_OPS_WRITE;
}
if (pp_sts.enhist_sts & PP_STS_ENABLE) {
flags |= PP_FLAGS_DIRTY_ENHIST;
if (!(mdss_pp_res->enhist_disp_cfg[disp_num].ops
& MDP_PP_OPS_DISABLE))
mdss_pp_res->enhist_disp_cfg[disp_num].ops |=
MDP_PP_OPS_WRITE;
}
if (pp_sts.dither_sts & PP_STS_ENABLE) {
flags |= PP_FLAGS_DIRTY_DITHER;
if (!(mdss_pp_res->dither_disp_cfg[disp_num].flags
& MDP_PP_OPS_DISABLE))
mdss_pp_res->dither_disp_cfg[disp_num].flags |=
MDP_PP_OPS_WRITE;
}
if (pp_sts.gamut_sts & PP_STS_ENABLE) {
flags |= PP_FLAGS_DIRTY_GAMUT;
if (!(mdss_pp_res->gamut_disp_cfg[disp_num].flags
& MDP_PP_OPS_DISABLE))
mdss_pp_res->gamut_disp_cfg[disp_num].flags |=
MDP_PP_OPS_WRITE;
}
if (pp_sts.pgc_sts & PP_STS_ENABLE) {
flags |= PP_FLAGS_DIRTY_PGC;
if (!(mdss_pp_res->pgc_disp_cfg[disp_num].flags
& MDP_PP_OPS_DISABLE))
mdss_pp_res->pgc_disp_cfg[disp_num].flags |=
MDP_PP_OPS_WRITE;
}
mdss_pp_res->pp_disp_flags[disp_num] |= flags;
return 0;
}
int mdss_mdp_pp_init(struct device *dev)
{
int i, ret = 0;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
struct mdss_mdp_pipe *vig;
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 < MDSS_MDP_MAX_DSPP; i++) {
mutex_init(&mdss_pp_res->dspp_hist[i].hist_mutex);
spin_lock_init(&mdss_pp_res->dspp_hist[i].hist_lock);
}
}
if (mdata) {
vig = mdata->vig_pipes;
for (i = 0; i < mdata->nvig_pipes; i++) {
mutex_init(&vig[i].pp_res.hist.hist_mutex);
spin_lock_init(&vig[i].pp_res.hist.hist_lock);
}
}
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_INTF_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 mdss_mdp_ctl *ctl, struct mdp_pa_cfg_data *config,
u32 *copyback)
{
int ret = 0;
u32 pa_offset, disp_num, dspp_num = 0;
if (!ctl)
return -EINVAL;
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);
if (!ret)
mdss_mdp_pp_setup(ctl);
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 mdss_mdp_ctl *ctl,
struct mdp_pcc_cfg_data *config,
u32 *copyback)
{
int ret = 0;
u32 base, disp_num, dspp_num = 0;
if (!ctl)
return -EINVAL;
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);
if (!ret)
mdss_mdp_pp_setup(ctl);
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_limited_lut_igc_config(struct mdss_mdp_ctl *ctl)
{
int ret = 0;
u32 copyback = 0;
u32 copy_from_kernel = 1;
struct mdp_igc_lut_data config;
if (!ctl)
return -EINVAL;
config.len = IGC_LUT_ENTRIES;
config.ops = MDP_PP_OPS_WRITE | MDP_PP_OPS_ENABLE;
config.block = (ctl->mfd->index) + MDP_LOGICAL_BLOCK_DISP_0;
config.c0_c1_data = igc_limited;
config.c2_data = igc_limited;
ret = mdss_mdp_igc_lut_config(ctl, &config, &copyback,
copy_from_kernel);
return ret;
}
int mdss_mdp_igc_lut_config(struct mdss_mdp_ctl *ctl,
struct mdp_igc_lut_data *config,
u32 *copyback, u32 copy_from_kernel)
{
int ret = 0;
u32 tbl_idx, igc_offset, disp_num, dspp_num = 0;
struct mdp_igc_lut_data local_cfg;
if (!ctl)
return -EINVAL;
if ((config->block < MDP_LOGICAL_BLOCK_DISP_0) ||
(config->block >= MDP_BLOCK_MAX))
return -EINVAL;
if (config->len != IGC_LUT_ENTRIES)
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;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
goto igc_config_exit;
}
if (copy_to_user(config->c2_data, local_cfg.c0_c1_data,
config->len * sizeof(u32))) {
ret = -EFAULT;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
goto igc_config_exit;
}
*copyback = 1;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
} else {
if (copy_from_kernel) {
memcpy(&mdss_pp_res->igc_lut_c0c1[disp_num][0],
config->c0_c1_data, config->len * sizeof(u32));
memcpy(&mdss_pp_res->igc_lut_c2[disp_num][0],
config->c2_data, config->len * sizeof(u32));
} 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);
if (!ret)
mdss_mdp_pp_setup(ctl);
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(char __iomem *offset,
struct mdp_hist_lut_data *cfg)
{
int i;
for (i = 0; i < ENHIST_LUT_ENTRIES; i++)
writel_relaxed(cfg->data[i], offset);
/* swap */
if (PP_LOCAT(cfg->block) == MDSS_PP_DSPP_CFG)
writel_relaxed(1, offset + 4);
else
writel_relaxed(1, offset + 16);
}
int mdss_mdp_argc_config(struct mdss_mdp_ctl *ctl,
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 (!ctl)
return -EINVAL;
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 (PP_LOCAT(config->block)) {
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)) {
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
ret = -EFAULT;
goto argc_config_exit;
}
if (copy_to_user(config->g_data,
&mdss_pp_res->gc_lut_g[disp_num][0], tbl_size)) {
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
ret = -EFAULT;
goto argc_config_exit;
}
if (copy_to_user(config->b_data,
&mdss_pp_res->gc_lut_b[disp_num][0], tbl_size)) {
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
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);
if (!ret)
mdss_mdp_pp_setup(ctl);
return ret;
}
int mdss_mdp_hist_lut_config(struct mdss_mdp_ctl *ctl,
struct mdp_hist_lut_data *config,
u32 *copyback)
{
int i, ret = 0;
u32 hist_offset, disp_num, dspp_num = 0;
if (!ctl)
return -EINVAL;
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;
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))) {
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
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);
if (!ret)
mdss_mdp_pp_setup(ctl);
return ret;
}
int mdss_mdp_dither_config(struct mdss_mdp_ctl *ctl,
struct mdp_dither_cfg_data *config,
u32 *copyback)
{
u32 disp_num;
if (!ctl)
return -EINVAL;
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);
mdss_mdp_pp_setup(ctl);
return 0;
}
int mdss_mdp_gamut_config(struct mdss_mdp_ctl *ctl,
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 (!ctl)
return -EINVAL;
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);
if (!ret)
mdss_mdp_pp_setup(ctl);
return ret;
}
static void pp_hist_read(char __iomem *v_base,
struct pp_hist_col_info *hist_info)
{
int i, i_start;
u32 data;
data = readl_relaxed(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] = readl_relaxed(v_base) & 0xFFFFFF;
for (i = 0; i < i_start - 1; i++)
hist_info->data[i] = readl_relaxed(v_base) & 0xFFFFFF;
hist_info->hist_cnt_read++;
}
/* Assumes that relevant clocks are enabled */
static int pp_histogram_enable(struct pp_hist_col_info *hist_info,
struct mdp_histogram_start_req *req,
u32 shift_bit, char __iomem *ctl_base)
{
unsigned long flag;
int ret = 0;
mutex_lock(&hist_info->hist_mutex);
/* check if it is idle */
if (hist_info->col_en) {
pr_info("%s Hist collection has already been enabled %d",
__func__, (u32) ctl_base);
ret = -EINVAL;
goto exit;
}
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->hist_cnt_time = 0;
spin_lock_irqsave(&hist_info->hist_lock, flag);
hist_info->read_request = false;
hist_info->col_state = HIST_RESET;
hist_info->col_en = true;
spin_unlock_irqrestore(&hist_info->hist_lock, flag);
hist_info->is_kick_ready = false;
mdss_mdp_hist_irq_enable(3 << shift_bit);
writel_relaxed(req->frame_cnt, ctl_base + 8);
/* Kick out reset start */
writel_relaxed(1, ctl_base + 4);
exit:
mutex_unlock(&hist_info->hist_mutex);
return ret;
}
int mdss_mdp_histogram_start(struct mdss_mdp_ctl *ctl,
struct mdp_histogram_start_req *req)
{
u32 done_shift_bit;
char __iomem *ctl_base;
struct pp_hist_col_info *hist_info;
int i, ret = 0;
u32 disp_num, dspp_num = 0;
u32 mixer_cnt, mixer_id[MDSS_MDP_INTF_MAX_LAYERMIXER];
struct mdss_mdp_pipe *pipe;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
if (!ctl)
return -EINVAL;
if ((PP_BLOCK(req->block) < MDP_LOGICAL_BLOCK_DISP_0) ||
(PP_BLOCK(req->block) >= MDP_BLOCK_MAX))
return -EINVAL;
disp_num = PP_BLOCK(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_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_exit;
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
if (PP_LOCAT(req->block) == MDSS_PP_SSPP_CFG) {
i = MDSS_PP_ARG_MASK & req->block;
if (!i) {
ret = -EINVAL;
pr_warn("Must pass pipe arguments, %d", i);
goto hist_exit;
}
for (i = 0; i < MDSS_PP_ARG_NUM; i++) {
if (!PP_ARG(i, req->block))
continue;
pipe = mdss_mdp_pipe_get(mdata, BIT(i));
if (IS_ERR_OR_NULL(pipe))
continue;
if (!pipe || pipe->num > MDSS_MDP_SSPP_VIG2) {
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
ret = -EINVAL;
pr_warn("Invalid Hist pipe (%d)", i);
goto hist_exit;
}
done_shift_bit = (pipe->num * 4);
hist_info = &pipe->pp_res.hist;
ctl_base = pipe->base +
MDSS_MDP_REG_VIG_HIST_CTL_BASE;
ret = pp_histogram_enable(hist_info, req,
done_shift_bit, ctl_base);
mdss_mdp_pipe_unmap(pipe);
}
} else if (PP_LOCAT(req->block) == MDSS_PP_DSPP_CFG) {
for (i = 0; i < mixer_cnt; i++) {
dspp_num = mixer_id[i];
done_shift_bit = (dspp_num * 4) + 12;
hist_info = &mdss_pp_res->dspp_hist[dspp_num];
ctl_base = mdss_mdp_get_dspp_addr_off(dspp_num) +
MDSS_MDP_REG_DSPP_HIST_CTL_BASE;
ret = pp_histogram_enable(hist_info, req,
done_shift_bit, ctl_base);
mdss_pp_res->pp_disp_flags[disp_num] |=
PP_FLAGS_DIRTY_HIST_COL;
}
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
hist_exit:
if (!ret && (PP_LOCAT(req->block) == MDSS_PP_DSPP_CFG)) {
mdss_mdp_pp_setup(ctl);
/* wait for a frame to let histrogram enable itself */
/* TODO add hysteresis value to be able to remove this sleep */
usleep(41666);
for (i = 0; i < mixer_cnt; i++) {
dspp_num = mixer_id[i];
hist_info = &mdss_pp_res->dspp_hist[dspp_num];
mutex_lock(&hist_info->hist_mutex);
hist_info->is_kick_ready = true;
mutex_unlock(&hist_info->hist_mutex);
}
} else if (!ret) {
for (i = 0; i < MDSS_PP_ARG_NUM; i++) {
if (!PP_ARG(i, req->block))
continue;
pr_info("PP_ARG(%d) = %d", i, PP_ARG(i, req->block));
pipe = mdss_mdp_pipe_get(mdata, BIT(i));
if (IS_ERR_OR_NULL(pipe))
continue;
hist_info = &pipe->pp_res.hist;
hist_info->is_kick_ready = true;
mdss_mdp_pipe_unmap(pipe);
}
}
return ret;
}
static int pp_histogram_disable(struct pp_hist_col_info *hist_info,
u32 done_bit, char __iomem *ctl_base)
{
int ret = 0;
unsigned long flag;
mutex_lock(&hist_info->hist_mutex);
if (hist_info->col_en == false) {
pr_debug("Histogram already disabled (%d)", (u32) ctl_base);
ret = -EINVAL;
goto exit;
}
complete_all(&hist_info->comp);
spin_lock_irqsave(&hist_info->hist_lock, flag);
hist_info->col_en = false;
hist_info->col_state = HIST_UNKNOWN;
spin_unlock_irqrestore(&hist_info->hist_lock, flag);
hist_info->is_kick_ready = false;
mdss_mdp_hist_irq_disable(done_bit);
writel_relaxed(BIT(1), ctl_base);/* cancel */
ret = 0;
exit:
mutex_unlock(&hist_info->hist_mutex);
return ret;
}
int mdss_mdp_histogram_stop(struct mdss_mdp_ctl *ctl, u32 block)
{
int i, ret = 0;
char __iomem *ctl_base;
u32 dspp_num, disp_num, done_bit;
struct pp_hist_col_info *hist_info;
u32 mixer_cnt, mixer_id[MDSS_MDP_INTF_MAX_LAYERMIXER];
struct mdss_mdp_pipe *pipe;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
if (!ctl)
return -EINVAL;
if ((PP_BLOCK(block) < MDP_LOGICAL_BLOCK_DISP_0) ||
(PP_BLOCK(block) >= MDP_BLOCK_MAX))
return -EINVAL;
disp_num = PP_BLOCK(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);
if (PP_LOCAT(block) == MDSS_PP_SSPP_CFG) {
i = MDSS_PP_ARG_MASK & block;
if (!i) {
pr_warn("Must pass pipe arguments, %d", i);
goto hist_stop_clk;
}
for (i = 0; i < MDSS_PP_ARG_NUM; i++) {
if (!PP_ARG(i, block))
continue;
pipe = mdss_mdp_pipe_get(mdata, BIT(i));
if (IS_ERR_OR_NULL(pipe) ||
pipe->num > MDSS_MDP_SSPP_VIG2) {
pr_warn("Invalid Hist pipe (%d)", i);
continue;
}
done_bit = 3 << (pipe->num * 4);
hist_info = &pipe->pp_res.hist;
ctl_base = pipe->base +
MDSS_MDP_REG_VIG_HIST_CTL_BASE;
ret = pp_histogram_disable(hist_info, done_bit,
ctl_base);
mdss_mdp_pipe_unmap(pipe);
if (ret)
goto hist_stop_clk;
}
} else if (PP_LOCAT(block) == MDSS_PP_DSPP_CFG) {
for (i = 0; i < mixer_cnt; i++) {
dspp_num = mixer_id[i];
done_bit = 3 << ((dspp_num * 4) + 12);
hist_info = &mdss_pp_res->dspp_hist[dspp_num];
ctl_base = mdss_mdp_get_dspp_addr_off(dspp_num) +
MDSS_MDP_REG_DSPP_HIST_CTL_BASE;
ret = pp_histogram_disable(hist_info, done_bit,
ctl_base);
if (ret)
goto hist_stop_clk;
mdss_pp_res->pp_disp_flags[disp_num] |=
PP_FLAGS_DIRTY_HIST_COL;
}
}
hist_stop_clk:
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
hist_stop_exit:
if (!ret && (PP_LOCAT(block) == MDSS_PP_DSPP_CFG))
mdss_mdp_pp_setup(ctl);
return ret;
}
static int pp_hist_collect(struct mdss_mdp_ctl *ctl,
struct mdp_histogram_data *hist,
struct pp_hist_col_info *hist_info,
char __iomem *ctl_base)
{
int wait_ret, ret = 0;
u32 timeout;
char __iomem *v_base;
unsigned long flag;
struct mdss_pipe_pp_res *res;
struct mdss_mdp_pipe *pipe;
mutex_lock(&hist_info->hist_mutex);
if ((hist_info->col_en == 0) ||
(hist_info->col_state == HIST_UNKNOWN)) {
ret = -EINVAL;
goto hist_collect_exit;
}
spin_lock_irqsave(&hist_info->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(&hist_info->hist_lock, flag);
timeout = HIST_WAIT_TIMEOUT(hist_info->frame_cnt);
mutex_unlock(&hist_info->hist_mutex);
/* flush updates before wait*/
if (PP_LOCAT(hist->block) == MDSS_PP_DSPP_CFG)
mdss_mdp_pp_setup(ctl);
if (PP_LOCAT(hist->block) == MDSS_PP_SSPP_CFG) {
res = container_of(hist_info, struct mdss_pipe_pp_res,
hist);
pipe = container_of(res, struct mdss_mdp_pipe, pp_res);
pipe->params_changed++;
}
wait_ret = wait_for_completion_killable_timeout(
&(hist_info->comp), timeout);
mutex_lock(&hist_info->hist_mutex);
if (wait_ret == 0) {
ret = -ETIMEDOUT;
spin_lock_irqsave(&hist_info->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->hist_cnt_time++;
hist_info->col_state = HIST_READY;
spin_unlock_irqrestore(&hist_info->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: state is not ready: %d",
__func__, hist_info->col_state);
goto hist_collect_exit;
}
} else {
spin_unlock_irqrestore(&hist_info->hist_lock, flag);
}
spin_lock_irqsave(&hist_info->hist_lock, flag);
if (hist_info->col_state == HIST_READY) {
spin_unlock_irqrestore(&hist_info->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(&hist_info->hist_lock, flag);
hist_info->read_request = false;
hist_info->col_state = HIST_IDLE;
}
spin_unlock_irqrestore(&hist_info->hist_lock, flag);
hist_collect_exit:
mutex_unlock(&hist_info->hist_mutex);
return ret;
}
int mdss_mdp_hist_collect(struct mdss_mdp_ctl *ctl,
struct mdp_histogram_data *hist)
{
int i, j, off, ret = 0;
struct pp_hist_col_info *hist_info;
u32 dspp_num, disp_num;
char __iomem *ctl_base;
u32 hist_cnt, mixer_id[MDSS_MDP_INTF_MAX_LAYERMIXER];
u32 *hist_concat = NULL;
u32 *hist_data_addr;
u32 pipe_cnt = 0;
u32 pipe_num = MDSS_MDP_SSPP_VIG0;
struct mdss_mdp_pipe *pipe;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
if (!ctl)
return -EINVAL;
if ((PP_BLOCK(hist->block) < MDP_LOGICAL_BLOCK_DISP_0) ||
(PP_BLOCK(hist->block) >= MDP_BLOCK_MAX))
return -EINVAL;
disp_num = PP_BLOCK(hist->block) - MDP_LOGICAL_BLOCK_DISP_0;
hist_cnt = mdss_mdp_get_ctl_mixers(disp_num, mixer_id);
if (!hist_cnt) {
pr_err("%s, no dspp connects to disp %d",
__func__, disp_num);
ret = -EPERM;
goto hist_collect_exit;
}
if (hist_cnt >= MDSS_MDP_MAX_DSPP) {
pr_err("%s, Too many dspp connects to disp %d",
__func__, hist_cnt);
ret = -EPERM;
goto hist_collect_exit;
}
if (PP_LOCAT(hist->block) == MDSS_PP_DSPP_CFG) {
hist_info = &mdss_pp_res->dspp_hist[disp_num];
for (i = 0; i < hist_cnt; i++) {
dspp_num = mixer_id[i];
hist_info = &mdss_pp_res->dspp_hist[dspp_num];
ctl_base = mdss_mdp_get_dspp_addr_off(dspp_num) +
MDSS_MDP_REG_DSPP_HIST_CTL_BASE;
ret = pp_hist_collect(ctl, hist, hist_info, ctl_base);
if (ret)
goto hist_collect_exit;
}
if (hist_cnt > 1) {
if (hist->bin_cnt != HIST_V_SIZE) {
pr_err("User not expecting size %d output",
HIST_V_SIZE);
ret = -EINVAL;
goto hist_collect_exit;
}
hist_concat = kmalloc(HIST_V_SIZE * sizeof(u32),
GFP_KERNEL);
if (!hist_concat) {
ret = -ENOMEM;
goto hist_collect_exit;
}
memset(hist_concat, 0, HIST_V_SIZE * sizeof(u32));
for (i = 0; i < hist_cnt; i++) {
dspp_num = mixer_id[i];
hist_info = &mdss_pp_res->dspp_hist[dspp_num];
mutex_lock(&hist_info->hist_mutex);
for (j = 0; j < HIST_V_SIZE; j++)
hist_concat[i] += hist_info->data[i];
mutex_unlock(&hist_info->hist_mutex);
}
hist_data_addr = hist_concat;
} else {
hist_data_addr = hist_info->data;
}
hist_info = &mdss_pp_res->dspp_hist[disp_num];
hist_info->hist_cnt_sent++;
} else if (PP_LOCAT(hist->block) == MDSS_PP_SSPP_CFG) {
hist_cnt = MDSS_PP_ARG_MASK & hist->block;
if (!hist_cnt) {
pr_warn("Must pass pipe arguments, %d", hist_cnt);
goto hist_collect_exit;
}
/* Find the first pipe requested */
for (i = 0; i < MDSS_PP_ARG_NUM; i++) {
if (PP_ARG(i, hist_cnt)) {
pipe_num = i;
break;
}
}
pipe = mdss_mdp_pipe_get(mdata, BIT(pipe_num));
if (IS_ERR_OR_NULL(pipe)) {
pr_warn("Invalid starting hist pipe, %d", pipe_num);
ret = -ENODEV;
goto hist_collect_exit;
}
hist_info = &pipe->pp_res.hist;
mdss_mdp_pipe_unmap(pipe);
for (i = pipe_num; i < MDSS_PP_ARG_NUM; i++) {
if (!PP_ARG(i, hist->block))
continue;
pipe_cnt++;
pipe = mdss_mdp_pipe_get(mdata, BIT(i));
if (IS_ERR_OR_NULL(pipe) ||
pipe->num > MDSS_MDP_SSPP_VIG2) {
pr_warn("Invalid Hist pipe (%d)", i);
continue;
}
hist_info = &pipe->pp_res.hist;
ctl_base = pipe->base +
MDSS_MDP_REG_VIG_HIST_CTL_BASE;
ret = pp_hist_collect(ctl, hist, hist_info, ctl_base);
mdss_mdp_pipe_unmap(pipe);
if (ret)
goto hist_collect_exit;
}
if (pipe_cnt > 1) {
if (hist->bin_cnt != (HIST_V_SIZE * pipe_cnt)) {
pr_err("User not expecting size %d output",
pipe_cnt * HIST_V_SIZE);
ret = -EINVAL;
goto hist_collect_exit;
}
hist_concat = kmalloc(HIST_V_SIZE * pipe_cnt *
sizeof(u32), GFP_KERNEL);
if (!hist_concat) {
ret = -ENOMEM;
goto hist_collect_exit;
}
memset(hist_concat, 0, pipe_cnt * HIST_V_SIZE *
sizeof(u32));
for (i = pipe_num; i < MDSS_PP_ARG_NUM; i++) {
if (!PP_ARG(i, hist->block))
continue;
pipe = mdss_mdp_pipe_get(mdata, BIT(i));
hist_info = &pipe->pp_res.hist;
off = HIST_V_SIZE * i;
mutex_lock(&hist_info->hist_mutex);
for (j = off; j < off + HIST_V_SIZE; j++)
hist_concat[j] =
hist_info->data[j - off];
hist_info->hist_cnt_sent++;
mutex_unlock(&hist_info->hist_mutex);
mdss_mdp_pipe_unmap(pipe);
}
hist_data_addr = hist_concat;
} else {
hist_data_addr = hist_info->data;
}
} else {
pr_info("No Histogram at location %d", PP_LOCAT(hist->block));
goto hist_collect_exit;
}
ret = copy_to_user(hist->c0, hist_data_addr, sizeof(u32) *
hist->bin_cnt);
hist_collect_exit:
kfree(hist_concat);
return ret;
}
void mdss_mdp_hist_intr_done(u32 isr)
{
u32 isr_blk, blk_idx;
struct pp_hist_col_info *hist_info = NULL;
struct mdss_mdp_pipe *pipe;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
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 = MDSS_MDP_SSPP_VIG0;
isr_blk = isr & 0x3;
isr &= ~0x3;
} else if (isr & 0x30) {
blk_idx = MDSS_MDP_SSPP_VIG1;
isr_blk = (isr >> 4) & 0x3;
isr &= ~0x30;
} else {
blk_idx = MDSS_MDP_SSPP_VIG2;
isr_blk = (isr >> 8) & 0x3;
isr &= ~0x300;
}
pipe = mdss_mdp_pipe_search(mdata, BIT(blk_idx));
if (IS_ERR_OR_NULL(pipe)) {
pr_debug("pipe DNE, %d", blk_idx);
continue;
}
hist_info = &pipe->pp_res.hist;
}
/* Histogram Done Interrupt */
if (hist_info && (isr_blk & 0x1) &&
(hist_info->col_en)) {
spin_lock(&hist_info->hist_lock);
hist_info->col_state = HIST_READY;
spin_unlock(&hist_info->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(&hist_info->hist_lock);
hist_info->col_state = HIST_IDLE;
spin_unlock(&hist_info->hist_lock);
}
};
}
#define MDSS_AD_MAX_MIXERS 1
static int mdss_ad_init_checks(struct msm_fb_data_type *mfd)
{
u32 mixer_id[MDSS_MDP_INTF_MAX_LAYERMIXER];
u32 mixer_num;
u32 ret = -EINVAL;
int i = 0;
struct mdss_data_type *mdata = mfd_to_mdata(mfd);
if (!mfd || !mdata)
return ret;
if (mdata->nad_cfgs == 0) {
pr_debug("Assertive Display not supported by device");
return -ENODEV;
}
if (mfd->panel_info->type == MIPI_CMD_PANEL) {
pr_debug("Command panel not supported");
return -EINVAL;
}
mixer_num = mdss_mdp_get_ctl_mixers(mfd->index, mixer_id);
if (!mixer_num || mixer_num > MDSS_AD_MAX_MIXERS) {
pr_err("invalid mixer_num, %d", mixer_num);
return ret;
}
do {
if (mixer_id[i] >= mdata->nad_cfgs) {
pr_err("invalid mixer input, %d", mixer_id[i]);
return ret;
}
i++;
} while (i < mixer_num);
return mixer_id[0];
}
static struct mdss_ad_info *mdss_mdp_get_ad(struct msm_fb_data_type *mfd)
{
int ad_num;
struct mdss_data_type *mdata;
struct mdss_ad_info *ad = NULL;
mdata = mfd_to_mdata(mfd);
ad_num = mdss_ad_init_checks(mfd);
if (ad_num >= 0)
ad = &mdata->ad_cfgs[ad_num];
return ad;
}
static int pp_update_ad_input(struct msm_fb_data_type *mfd)
{
struct mdss_ad_info *ad;
struct mdss_ad_input input;
struct mdss_mdp_ctl *ctl;
if (!mfd)
return -EINVAL;
ctl = mfd_to_ctl(mfd);
if (!ctl)
return -EINVAL;
ad = mdss_mdp_get_ad(mfd);
if (!ad || ad->cfg.mode == MDSS_AD_MODE_AUTO_BL)
return -EINVAL;
pr_debug("backlight level changed (%d), trigger update to AD",
mfd->bl_level);
input.mode = ad->cfg.mode;
if (MDSS_AD_MODE_DATA_MATCH(ad->cfg.mode, MDSS_AD_INPUT_AMBIENT))
input.in.amb_light = ad->ad_data;
else
input.in.strength = ad->ad_data;
/* call to ad_input will trigger backlight read */
return mdss_mdp_ad_input(mfd, &input, 0);
}
int mdss_mdp_ad_config(struct msm_fb_data_type *mfd,
struct mdss_ad_init_cfg *init_cfg)
{
struct mdss_ad_info *ad;
struct mdss_mdp_ctl *ctl;
int lin_ret = -1, inv_ret = -1, ret = 0;
u32 ratio_temp, shift = 0;
ad = mdss_mdp_get_ad(mfd);
if (!ad)
return -EINVAL;
mutex_lock(&ad->lock);
if (init_cfg->ops & MDP_PP_AD_INIT) {
memcpy(&ad->init, &init_cfg->params.init,
sizeof(struct mdss_ad_init));
if (init_cfg->params.init.bl_lin_len == AD_BL_LIN_LEN) {
lin_ret = copy_from_user(&ad->bl_lin,
init_cfg->params.init.bl_lin,
AD_BL_LIN_LEN * sizeof(uint32_t));
inv_ret = copy_from_user(&ad->bl_lin_inv,
init_cfg->params.init.bl_lin_inv,
AD_BL_LIN_LEN * sizeof(uint32_t));
if (lin_ret || inv_ret)
ret = -ENOMEM;
ratio_temp = mfd->panel_info->bl_max / AD_BL_LIN_LEN;
while (ratio_temp > 0) {
ratio_temp = ratio_temp >> 1;
shift++;
}
ad->bl_bright_shift = shift;
} else if (init_cfg->params.init.bl_lin_len) {
ret = -EINVAL;
}
if (!lin_ret && !inv_ret)
ad->state |= PP_AD_STATE_BL_LIN;
else
ad->state &= !PP_AD_STATE_BL_LIN;
ad->sts |= PP_AD_STS_DIRTY_INIT;
} else if (init_cfg->ops & MDP_PP_AD_CFG) {
memcpy(&ad->cfg, &init_cfg->params.cfg,
sizeof(struct mdss_ad_cfg));
/*
* TODO: specify panel independent range of input from cfg,
* scale input backlight_scale to panel bl_max's range
*/
ad->cfg.backlight_scale = mfd->panel_info->bl_max;
ad->sts |= PP_AD_STS_DIRTY_CFG;
}
if (!ret && (init_cfg->ops & MDP_PP_OPS_DISABLE)) {
ad->sts &= ~PP_STS_ENABLE;
mutex_unlock(&ad->lock);
cancel_work_sync(&ad->calc_work);
mutex_lock(&ad->lock);
ad->mfd = NULL;
} else if (!ret && (init_cfg->ops & MDP_PP_OPS_ENABLE)) {
ad->sts |= PP_STS_ENABLE;
ad->mfd = mfd;
}
mutex_unlock(&ad->lock);
ctl = mfd_to_ctl(mfd);
if (!ret)
mdss_mdp_pp_setup(ctl);
return ret;
}
int mdss_mdp_ad_input(struct msm_fb_data_type *mfd,
struct mdss_ad_input *input, int wait) {
int ret = 0;
struct mdss_ad_info *ad;
struct mdss_mdp_ctl *ctl;
u32 bl;
ad = mdss_mdp_get_ad(mfd);
if (!ad)
return -EINVAL;
mutex_lock(&ad->lock);
if ((!PP_AD_STATE_IS_INITCFG(ad->state) &&
!PP_AD_STS_IS_DIRTY(ad->sts)) &&
!input->mode == MDSS_AD_MODE_CALIB) {
pr_warn("AD not initialized or configured.");
ret = -EPERM;
goto error;
}
switch (input->mode) {
case MDSS_AD_MODE_AUTO_BL:
case MDSS_AD_MODE_AUTO_STR:
if (!MDSS_AD_MODE_DATA_MATCH(ad->cfg.mode,
MDSS_AD_INPUT_AMBIENT)) {
ret = -EINVAL;
goto error;
}
ad->ad_data_mode = MDSS_AD_INPUT_AMBIENT;
ad->ad_data = input->in.amb_light;
ad->calc_itr = ad->cfg.stab_itr;
ad->sts |= PP_AD_STS_DIRTY_VSYNC;
ad->sts |= PP_AD_STS_DIRTY_DATA;
break;
case MDSS_AD_MODE_TARG_STR:
case MDSS_AD_MODE_MAN_STR:
if (!MDSS_AD_MODE_DATA_MATCH(ad->cfg.mode,
MDSS_AD_INPUT_STRENGTH)) {
ret = -EINVAL;
goto error;
}
ad->ad_data_mode = MDSS_AD_INPUT_STRENGTH;
ad->ad_data = input->in.strength;
ad->calc_itr = ad->cfg.stab_itr;
ad->sts |= PP_AD_STS_DIRTY_VSYNC;
ad->sts |= PP_AD_STS_DIRTY_DATA;
break;
case MDSS_AD_MODE_CALIB:
wait = 0;
if (mfd->calib_mode) {
bl = input->in.calib_bl;
if (bl >= AD_BL_LIN_LEN) {
pr_warn("calib_bl 255 max!");
break;
}
mutex_unlock(&ad->lock);
mutex_lock(&mfd->bl_lock);
MDSS_BRIGHT_TO_BL(bl, bl, mfd->panel_info->bl_max,
MDSS_MAX_BL_BRIGHTNESS);
mdss_fb_set_backlight(mfd, bl);
mutex_unlock(&mfd->bl_lock);
mutex_lock(&ad->lock);
} else {
pr_warn("should be in calib mode");
}
break;
default:
pr_warn("invalid default %d", input->mode);
ret = -EINVAL;
goto error;
}
error:
mutex_unlock(&ad->lock);
if (!ret) {
if (wait) {
mutex_lock(&ad->lock);
init_completion(&ad->comp);
mutex_unlock(&ad->lock);
}
ctl = mfd_to_ctl(mfd);
mdss_mdp_pp_setup(ctl);
if (wait) {
ret = wait_for_completion_interruptible_timeout(
&ad->comp, HIST_WAIT_TIMEOUT(1));
if (ret == 0)
ret = -ETIMEDOUT;
else if (ret > 0)
input->output = ad->last_str;
}
}
return ret;
}
static void pp_ad_input_write(struct mdss_ad_info *ad, u32 bl_lvl)
{
char __iomem *base = ad->base;
switch (ad->cfg.mode) {
case MDSS_AD_MODE_AUTO_BL:
writel_relaxed(ad->ad_data, base + MDSS_MDP_REG_AD_AL);
break;
case MDSS_AD_MODE_AUTO_STR:
writel_relaxed(bl_lvl, base + MDSS_MDP_REG_AD_BL);
writel_relaxed(ad->ad_data, base + MDSS_MDP_REG_AD_AL);
break;
case MDSS_AD_MODE_TARG_STR:
writel_relaxed(bl_lvl, base + MDSS_MDP_REG_AD_BL);
writel_relaxed(ad->ad_data, base + MDSS_MDP_REG_AD_TARG_STR);
break;
case MDSS_AD_MODE_MAN_STR:
writel_relaxed(bl_lvl, base + MDSS_MDP_REG_AD_BL);
writel_relaxed(ad->ad_data, base + MDSS_MDP_REG_AD_STR_MAN);
break;
default:
pr_warn("Invalid mode! %d", ad->cfg.mode);
break;
}
}
static void pp_ad_init_write(struct mdss_ad_info *ad)
{
u32 temp;
char __iomem *base = ad->base;
writel_relaxed(ad->init.i_control[0] & 0x1F,
base + MDSS_MDP_REG_AD_CON_CTRL_0);
writel_relaxed(ad->init.i_control[1] << 8,
base + MDSS_MDP_REG_AD_CON_CTRL_1);
temp = ad->init.white_lvl << 16;
temp |= ad->init.black_lvl & 0xFFFF;
writel_relaxed(temp, base + MDSS_MDP_REG_AD_BW_LVL);
writel_relaxed(ad->init.var, base + MDSS_MDP_REG_AD_VAR);
writel_relaxed(ad->init.limit_ampl, base + MDSS_MDP_REG_AD_AMP_LIM);
writel_relaxed(ad->init.i_dither, base + MDSS_MDP_REG_AD_DITH);
temp = ad->init.slope_max << 8;
temp |= ad->init.slope_min & 0xFF;
writel_relaxed(temp, base + MDSS_MDP_REG_AD_SLOPE);
writel_relaxed(ad->init.dither_ctl, base + MDSS_MDP_REG_AD_DITH_CTRL);
writel_relaxed(ad->init.format, base + MDSS_MDP_REG_AD_CTRL_0);
writel_relaxed(ad->init.auto_size, base + MDSS_MDP_REG_AD_CTRL_1);
temp = ad->init.frame_w << 16;
temp |= ad->init.frame_h & 0xFFFF;
writel_relaxed(temp, base + MDSS_MDP_REG_AD_FRAME_SIZE);
temp = ad->init.logo_v << 8;
temp |= ad->init.logo_h & 0xFF;
writel_relaxed(temp, base + MDSS_MDP_REG_AD_LOGO_POS);
pp_ad_cfg_lut(base + MDSS_MDP_REG_AD_LUT_FI, ad->init.asym_lut);
pp_ad_cfg_lut(base + MDSS_MDP_REG_AD_LUT_CC, ad->init.color_corr_lut);
}
#define MDSS_PP_AD_DEF_CALIB 0x6E
static void pp_ad_cfg_write(struct mdss_ad_info *ad)
{
char __iomem *base = ad->base;
u32 temp, temp_calib = MDSS_PP_AD_DEF_CALIB;
switch (ad->cfg.mode) {
case MDSS_AD_MODE_AUTO_BL:
temp = ad->cfg.backlight_max << 16;
temp |= ad->cfg.backlight_min & 0xFFFF;
writel_relaxed(temp, base + MDSS_MDP_REG_AD_BL_MINMAX);
writel_relaxed(ad->cfg.amb_light_min,
base + MDSS_MDP_REG_AD_AL_MIN);
temp = ad->cfg.filter[1] << 16;
temp |= ad->cfg.filter[0] & 0xFFFF;
writel_relaxed(temp, base + MDSS_MDP_REG_AD_AL_FILT);
case MDSS_AD_MODE_AUTO_STR:
pp_ad_cfg_lut(base + MDSS_MDP_REG_AD_LUT_AL,
ad->cfg.al_calib_lut);
writel_relaxed(ad->cfg.strength_limit,
base + MDSS_MDP_REG_AD_STR_LIM);
temp = ad->cfg.calib[3] << 16;
temp |= ad->cfg.calib[2] & 0xFFFF;
writel_relaxed(temp, base + MDSS_MDP_REG_AD_CALIB_CD);
writel_relaxed(ad->cfg.t_filter_recursion,
base + MDSS_MDP_REG_AD_TFILT_CTRL);
temp_calib = ad->cfg.calib[0] & 0xFFFF;
case MDSS_AD_MODE_TARG_STR:
temp = ad->cfg.calib[1] << 16;
temp |= temp_calib;
writel_relaxed(temp, base + MDSS_MDP_REG_AD_CALIB_AB);
case MDSS_AD_MODE_MAN_STR:
writel_relaxed(ad->cfg.backlight_scale,
base + MDSS_MDP_REG_AD_BL_MAX);
writel_relaxed(ad->cfg.mode, base + MDSS_MDP_REG_AD_MODE_SEL);
pr_debug("stab_itr = %d", ad->cfg.stab_itr);
break;
default:
break;
}
}
static void pp_ad_vsync_handler(struct mdss_mdp_ctl *ctl, ktime_t t)
{
struct mdss_data_type *mdata = ctl->mdata;
struct mdss_ad_info *ad;
if (ctl->mixer_left && ctl->mixer_left->num < mdata->nad_cfgs) {
ad = &mdata->ad_cfgs[ctl->mixer_left->num];
queue_work(mdata->ad_calc_wq, &ad->calc_work);
}
}
#define MDSS_PP_AD_BYPASS_DEF 0x101
static int mdss_mdp_ad_setup(struct msm_fb_data_type *mfd)
{
int ret = 0;
struct mdss_ad_info *ad;
struct mdss_mdp_ctl *ctl = mfd_to_ctl(mfd);
char __iomem *base;
u32 bypass = MDSS_PP_AD_BYPASS_DEF, bl;
ad = mdss_mdp_get_ad(mfd);
if (!ad)
return -EINVAL;
base = ad->base;
mutex_lock(&ad->lock);
if (ad->sts != last_sts || ad->state != last_state) {
last_sts = ad->sts;
last_state = ad->state;
pr_debug("begining: ad->sts = 0x%08x, state = 0x%08x", ad->sts,
ad->state);
}
if (!PP_AD_STS_IS_DIRTY(ad->sts) &&
(ad->sts & PP_AD_STS_DIRTY_DATA)) {
/*
* Write inputs to regs when the data has been updated or
* Assertive Display is up and running as long as there are
* no updates to AD init or cfg
*/
ad->sts &= ~PP_AD_STS_DIRTY_DATA;
ad->state |= PP_AD_STATE_DATA;
bl = 0;
if (MDSS_AD_RUNNING_AUTO_STR(ad) || ad->last_bl == 0) {
mutex_lock(&mfd->bl_lock);
bl = mfd->bl_level;
if (bl != ad->last_bl) {
ad->last_bl = bl;
ad->calc_itr = ad->cfg.stab_itr;
ad->sts |= PP_AD_STS_DIRTY_VSYNC;
}
if (ad->state & PP_AD_STATE_BL_LIN) {
bl = ad->bl_lin[bl >> ad->bl_bright_shift];
bl = bl << ad->bl_bright_shift;
}
mutex_unlock(&mfd->bl_lock);
}
pp_ad_input_write(ad, bl);
}
if (ad->sts & PP_AD_STS_DIRTY_CFG) {
ad->sts &= ~PP_AD_STS_DIRTY_CFG;
ad->state |= PP_AD_STATE_CFG;
pp_ad_cfg_write(ad);
if (!MDSS_AD_MODE_DATA_MATCH(ad->cfg.mode, ad->ad_data_mode)) {
ad->sts &= ~PP_AD_STS_DIRTY_DATA;
ad->state &= ~PP_AD_STATE_DATA;
pr_debug("Mode switched, data invalidated!");
}
}
if (ad->sts & PP_AD_STS_DIRTY_INIT) {
ad->sts &= ~PP_AD_STS_DIRTY_INIT;
ad->state |= PP_AD_STATE_INIT;
pp_ad_init_write(ad);
}
if ((ad->sts & PP_STS_ENABLE) && PP_AD_STATE_IS_READY(ad->state)) {
bypass = 0;
ret = 1;
ad->state |= PP_AD_STATE_RUN;
mutex_lock(&mfd->bl_lock);
mfd->mdp.update_ad_input = pp_update_ad_input;
mfd->ext_bl_ctrl = ad->cfg.bl_ctrl_mode;
mutex_unlock(&mfd->bl_lock);
} else {
if (ad->state & PP_AD_STATE_RUN) {
ret = 1;
/* Clear state and regs when going to off state*/
ad->sts = 0;
ad->sts |= PP_AD_STS_DIRTY_VSYNC;
ad->state &= !PP_AD_STATE_INIT;
ad->state &= !PP_AD_STATE_CFG;
ad->state &= !PP_AD_STATE_DATA;
ad->state &= !PP_AD_STATE_BL_LIN;
ad->bl_bright_shift = 0;
ad->ad_data = 0;
ad->ad_data_mode = 0;
ad->calc_itr = 0;
memset(&ad->bl_lin, 0, sizeof(uint32_t) *
AD_BL_LIN_LEN);
memset(&ad->bl_lin_inv, 0, sizeof(uint32_t) *
AD_BL_LIN_LEN);
memset(&ad->init, 0, sizeof(struct mdss_ad_init));
memset(&ad->cfg, 0, sizeof(struct mdss_ad_cfg));
mutex_lock(&mfd->bl_lock);
mfd->mdp.update_ad_input = NULL;
mfd->ext_bl_ctrl = 0;
mutex_unlock(&mfd->bl_lock);
}
ad->state &= ~PP_AD_STATE_RUN;
}
writel_relaxed(bypass, base);
if (PP_AD_STS_DIRTY_VSYNC & ad->sts) {
pr_debug("dirty vsync, calc_itr = %d", ad->calc_itr);
ad->sts &= ~PP_AD_STS_DIRTY_VSYNC;
if (!(PP_AD_STATE_VSYNC & ad->state) && ad->calc_itr &&
(ad->state & PP_AD_STATE_RUN)) {
ctl->add_vsync_handler(ctl, &ad->handle);
ad->state |= PP_AD_STATE_VSYNC;
} else if ((PP_AD_STATE_VSYNC & ad->state) &&
(!ad->calc_itr || !(PP_AD_STATE_RUN & ad->state))) {
ctl->remove_vsync_handler(ctl, &ad->handle);
ad->state &= ~PP_AD_STATE_VSYNC;
}
}
if (ad->sts != last_sts || ad->state != last_state) {
last_sts = ad->sts;
last_state = ad->state;
pr_debug("end: ad->sts = 0x%08x, state = 0x%08x", ad->sts,
ad->state);
}
mutex_unlock(&ad->lock);
return ret;
}
#define MDSS_PP_AD_SLEEP 10
static void pp_ad_calc_worker(struct work_struct *work)
{
struct mdss_ad_info *ad;
struct mdss_mdp_ctl *ctl;
struct msm_fb_data_type *mfd;
u32 bl, calc_done = 0;
ad = container_of(work, struct mdss_ad_info, calc_work);
mutex_lock(&ad->lock);
if (!ad->mfd || !(ad->sts & PP_STS_ENABLE)) {
mutex_unlock(&ad->lock);
return;
}
mfd = ad->mfd;
ctl = mfd_to_ctl(ad->mfd);
if (PP_AD_STATE_RUN & ad->state) {
/* Kick off calculation */
ad->calc_itr--;
writel_relaxed(1, ad->base + MDSS_MDP_REG_AD_START_CALC);
}
if (ad->state & PP_AD_STATE_RUN) {
do {
calc_done = readl_relaxed(ad->base +
MDSS_MDP_REG_AD_CALC_DONE);
if (!calc_done)
usleep(MDSS_PP_AD_SLEEP);
} while (!calc_done && (ad->state & PP_AD_STATE_RUN));
if (calc_done) {
ad->last_str = 0xFF & readl_relaxed(ad->base +
MDSS_MDP_REG_AD_STR_OUT);
if (MDSS_AD_RUNNING_AUTO_BL(ad)) {
bl = 0xFFFF & readl_relaxed(ad->base +
MDSS_MDP_REG_AD_BL_OUT);
if (ad->state & PP_AD_STATE_BL_LIN) {
bl = bl >> ad->bl_bright_shift;
bl = min_t(u32, bl,
MDSS_MAX_BL_BRIGHTNESS);
bl = ad->bl_lin_inv[bl];
bl = bl << ad->bl_bright_shift;
}
pr_debug("calc bl = %d", bl);
ad->last_str |= bl << 16;
mutex_lock(&ad->mfd->bl_lock);
mdss_fb_set_backlight(ad->mfd, bl);
mutex_unlock(&ad->mfd->bl_lock);
}
pr_debug("calc_str = %d, calc_itr %d",
ad->last_str & 0xFF,
ad->calc_itr);
} else {
ad->last_str = 0xFFFFFFFF;
}
}
complete(&ad->comp);
if (!ad->calc_itr) {
ad->state &= ~PP_AD_STATE_VSYNC;
ctl->remove_vsync_handler(ctl, &ad->handle);
}
mutex_unlock(&ad->lock);
mutex_lock(&mfd->lock);
mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_FLUSH, BIT(13 + ad->num));
mutex_unlock(&mfd->lock);
/* Trigger update notify to wake up those waiting for display updates */
mdss_fb_update_notify_update(mfd);
}
#define PP_AD_LUT_LEN 33
static void pp_ad_cfg_lut(char __iomem *offset, u32 *data)
{
int i;
u32 temp;
for (i = 0; i < PP_AD_LUT_LEN - 1; i += 2) {
temp = data[i+1] << 16;
temp |= (data[i] & 0xFFFF);
writel_relaxed(temp, offset + (i*2));
}
writel_relaxed(data[PP_AD_LUT_LEN - 1] << 16,
offset + ((PP_AD_LUT_LEN - 1) * 2));
}
int mdss_mdp_ad_addr_setup(struct mdss_data_type *mdata, u32 *ad_off)
{
u32 i;
int rc = 0;
mdata->ad_cfgs = devm_kzalloc(&mdata->pdev->dev,
sizeof(struct mdss_ad_info) * mdata->nad_cfgs,
GFP_KERNEL);
if (!mdata->ad_cfgs) {
pr_err("unable to setup assertive display:devm_kzalloc fail\n");
return -ENOMEM;
}
mdata->ad_calc_wq = create_singlethread_workqueue("ad_calc_wq");
for (i = 0; i < mdata->nad_cfgs; i++) {
mdata->ad_cfgs[i].base = mdata->mdp_base + ad_off[i];
mdata->ad_cfgs[i].num = i;
mdata->ad_cfgs[i].calc_itr = 0;
mdata->ad_cfgs[i].last_str = 0xFFFFFFFF;
mutex_init(&mdata->ad_cfgs[i].lock);
mdata->ad_cfgs[i].handle.vsync_handler = pp_ad_vsync_handler;
INIT_WORK(&mdata->ad_cfgs[i].calc_work, pp_ad_calc_worker);
}
return rc;
}
static int is_valid_calib_addr(void *addr)
{
int ret = 0;
unsigned int ptr;
ptr = (unsigned int) addr;
/* if request is outside the MDP reg-map or is not aligned 4 */
if (ptr == 0x0 || ptr > 0x5138 || ptr % 0x4)
goto end;
if (ptr >= 0x100 && ptr <= 0x5138) {
/* if ptr is in dspp range */
if (ptr >= 0x4600 && ptr <= 0x5138) {
/* if ptr is in dspp0 range*/
if (ptr >= 0x4600 && ptr <= 0x4938)
ptr -= 0x4600;
/* if ptr is in dspp1 range */
else if (ptr >= 0x4a00 && ptr <= 0x4d38)
ptr -= 0x4a00;
/* if ptr is in dspp2 range */
else if (ptr >= 0x4e00 && ptr <= 0x5138)
ptr -= 0x4e00;
/* if ptr is in pcc plane rgb coeff.range */
if (ptr >= 0x30 && ptr <= 0xe8)
ret = 1;
/* if ptr is in ARLUT red range */
else if (ptr >= 0x2b0 && ptr <= 0x2b8)
ret = 1;
/* if ptr is in PA range */
else if (ptr >= 0x238 && ptr <= 0x244)
ret = 1;
/* if ptr is in ARLUT green range */
else if (ptr >= 0x2c0 && ptr <= 0x2c8)
ret = 1;
/* if ptr is in ARLUT blue range or
gamut map table range */
else if (ptr >= 0x2d0 && ptr <= 0x338)
ret = 1;
/* if ptr is dspp0,dspp1,dspp2 op mode
register */
else if (ptr == 0)
ret = 1;
} else if (ptr >= 0x600 && ptr <= 0x608)
ret = 1;
else if (ptr >= 0x400 && ptr <= 0x408)
ret = 1;
else if ((ptr == 0x1830) || (ptr == 0x1c30) ||
(ptr == 0x1430) || (ptr == 0x1e38))
ret = 1;
else if ((ptr == 0x1e3c) || (ptr == 0x1e30))
ret = 1;
else if (ptr >= 0x3220 && ptr <= 0x3228)
ret = 1;
else if (ptr >= 0x3200 || ptr == 0x100)
ret = 1;
}
end:
return ret;
}
int mdss_mdp_calib_config(struct mdp_calib_config_data *cfg, u32 *copyback)
{
int ret = -1;
void *ptr = (void *) cfg->addr;
if (is_valid_calib_addr(ptr))
ret = 0;
else
return ret;
ptr = (void *)(((unsigned int) ptr) + (mdss_res->mdp_base));
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
if (cfg->ops & MDP_PP_OPS_READ) {
cfg->data = readl_relaxed(ptr);
*copyback = 1;
ret = 0;
} else if (cfg->ops & MDP_PP_OPS_WRITE) {
writel_relaxed(cfg->data, ptr);
ret = 0;
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
return ret;
}
int mdss_mdp_calib_mode(struct msm_fb_data_type *mfd,
struct mdss_calib_cfg *cfg)
{
if (!mdss_pp_res || !mfd)
return -EINVAL;
mutex_lock(&mdss_pp_mutex);
mfd->calib_mode = cfg->calib_mask;
mutex_unlock(&mdss_pp_mutex);
return 0;
}