/* * Copyright (c) 2012-2016, 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 "mdss_mdp_pp.h" #include #include #include #include #include #include "mdss_mdp_pp_cache_config.h" struct mdp_csc_cfg mdp_csc_8bit_convert[MDSS_MDP_MAX_CSC] = { [MDSS_MDP_CSC_YUV2RGB_601L] = { 0, { 0x0254, 0x0000, 0x0331, 0x0254, 0xff37, 0xfe60, 0x0254, 0x0409, 0x0000, }, { 0xfff0, 0xff80, 0xff80,}, { 0x0, 0x0, 0x0,}, { 0x10, 0xeb, 0x10, 0xf0, 0x10, 0xf0,}, { 0x0, 0xff, 0x0, 0xff, 0x0, 0xff,}, }, [MDSS_MDP_CSC_YUV2RGB_601FR] = { 0, { 0x0200, 0x0000, 0x02ce, 0x0200, 0xff50, 0xfe92, 0x0200, 0x038b, 0x0000, }, { 0x0000, 0xff80, 0xff80,}, { 0x0, 0x0, 0x0,}, { 0x0, 0xff, 0x0, 0xff, 0x0, 0xff,}, { 0x0, 0xff, 0x0, 0xff, 0x0, 0xff,}, }, [MDSS_MDP_CSC_YUV2RGB_709L] = { 0, { 0x0254, 0x0000, 0x0396, 0x0254, 0xff93, 0xfeef, 0x0254, 0x043e, 0x0000, }, { 0xfff0, 0xff80, 0xff80,}, { 0x0, 0x0, 0x0,}, { 0x10, 0xeb, 0x10, 0xf0, 0x10, 0xf0,}, { 0x0, 0xff, 0x0, 0xff, 0x0, 0xff,}, }, [MDSS_MDP_CSC_RGB2YUV_601L] = { 0, { 0x0083, 0x0102, 0x0032, 0xffb4, 0xff6b, 0x00e1, 0x00e1, 0xff44, 0xffdb }, { 0x0, 0x0, 0x0,}, { 0x0010, 0x0080, 0x0080,}, { 0x0, 0xff, 0x0, 0xff, 0x0, 0xff,}, { 0x0010, 0x00eb, 0x0010, 0x00f0, 0x0010, 0x00f0,}, }, [MDSS_MDP_CSC_RGB2YUV_601FR] = { 0, { 0x0099, 0x012d, 0x003a, 0xffaa, 0xff56, 0x0100, 0x0100, 0xff2a, 0xffd6 }, { 0x0, 0x0, 0x0,}, { 0x0000, 0x0080, 0x0080,}, { 0x0, 0xff, 0x0, 0xff, 0x0, 0xff,}, { 0x0, 0xff, 0x0, 0xff, 0x0, 0xff,}, }, [MDSS_MDP_CSC_RGB2YUV_709L] = { 0, { 0x005d, 0x013a, 0x0020, 0xffcc, 0xff53, 0x00e1, 0x00e1, 0xff34, 0xffeb }, { 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,}, }, [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,}, }, }; struct mdp_csc_cfg mdp_csc_10bit_convert[MDSS_MDP_MAX_CSC] = { [MDSS_MDP_CSC_YUV2RGB_601L] = { 0, { 0x0254, 0x0000, 0x0331, 0x0254, 0xff37, 0xfe60, 0x0254, 0x0409, 0x0000, }, { 0xffc0, 0xffe0, 0xffe0,}, { 0x0, 0x0, 0x0,}, { 0x40, 0x3ac, 0x40, 0x3c0, 0x40, 0x3c0,}, { 0x0, 0x3ff, 0x0, 0x3ff, 0x0, 0x3ff,}, }, [MDSS_MDP_CSC_YUV2RGB_601FR] = { 0, { 0x0200, 0x0000, 0x02ce, 0x0200, 0xff50, 0xfe92, 0x0200, 0x038b, 0x0000, }, { 0x0000, 0xffe0, 0xffe0,}, { 0x0, 0x0, 0x0,}, { 0x0, 0x3ff, 0x0, 0x3ff, 0x0, 0x3ff,}, { 0x0, 0x3ff, 0x0, 0x3ff, 0x0, 0x3ff,}, }, [MDSS_MDP_CSC_YUV2RGB_709L] = { 0, { 0x0254, 0x0000, 0x0396, 0x0254, 0xff93, 0xfeef, 0x0254, 0x043e, 0x0000, }, { 0xffc0, 0xffe0, 0xffe0,}, { 0x0, 0x0, 0x0,}, { 0x40, 0x3ac, 0x40, 0x3c0, 0x40, 0x3c0,}, { 0x0, 0x3ff, 0x0, 0x3ff, 0x0, 0x3ff,}, }, [MDSS_MDP_CSC_RGB2YUV_601L] = { 0, { 0x0083, 0x0102, 0x0032, 0xffb4, 0xff6b, 0x00e1, 0x00e1, 0xff44, 0xffdb }, { 0x0, 0x0, 0x0,}, { 0x0040, 0x0200, 0x0200,}, { 0x0, 0x3ff, 0x0, 0x3ff, 0x0, 0x3ff,}, { 0x0040, 0x03ac, 0x0040, 0x03c0, 0x0040, 0x03c0,}, }, [MDSS_MDP_CSC_RGB2YUV_601FR] = { 0, { 0x0099, 0x012d, 0x003a, 0xffaa, 0xff56, 0x0100, 0x0100, 0xff2a, 0xffd6 }, { 0x0, 0x0, 0x0,}, { 0x0000, 0x0200, 0x0200,}, { 0x0, 0x3ff, 0x0, 0x3ff, 0x0, 0x3ff,}, { 0x0, 0x3ff, 0x0, 0x3ff, 0x0, 0x3ff,}, }, [MDSS_MDP_CSC_RGB2YUV_709L] = { 0, { 0x005d, 0x013a, 0x0020, 0xffcc, 0xff53, 0x00e1, 0x00e1, 0xff34, 0xffeb }, { 0x0, 0x0, 0x0,}, { 0x0040, 0x0200, 0x0200,}, { 0x0, 0x3ff, 0x0, 0x3ff, 0x0, 0x3ff,}, { 0x0040, 0x03ac, 0x0040, 0x03c0, 0x0040, 0x03c0,}, }, [MDSS_MDP_CSC_YUV2YUV] = { 0, { 0x0200, 0x0000, 0x0000, 0x0000, 0x0200, 0x0000, 0x0000, 0x0000, 0x0200, }, { 0x0, 0x0, 0x0,}, { 0x0, 0x0, 0x0,}, { 0x0, 0x3ff, 0x0, 0x3ff, 0x0, 0x3ff,}, { 0x0, 0x3ff, 0x0, 0x3ff, 0x0, 0x3ff,}, }, [MDSS_MDP_CSC_RGB2RGB] = { 0, { 0x0200, 0x0000, 0x0000, 0x0000, 0x0200, 0x0000, 0x0000, 0x0000, 0x0200, }, { 0x0, 0x0, 0x0,}, { 0x0, 0x0, 0x0,}, { 0x0, 0x3ff, 0x0, 0x3ff, 0x0, 0x3ff,}, { 0x0, 0x3ff, 0x0, 0x3ff, 0x0, 0x3ff,}, }, }; #define CSC_MV_OFF 0x0 #define CSC_BV_OFF 0x2C #define CSC_LV_OFF 0x14 #define CSC_POST_OFF 0xC #define CSC_10BIT_LV_SHIFT 16 #define CSC_8BIT_LV_SHIFT 8 #define HIST_INTR_DSPP_MASK 0xFFF000 #define HIST_V2_INTR_BIT_MASK 0xF33000 #define HIST_V1_INTR_BIT_MASK 0X333333 #define HIST_WAIT_TIMEOUT(frame) ((75 * HZ * (frame)) / 1000) #define HIST_KICKOFF_WAIT_FRACTION 4 /* hist collect state */ enum { HIST_UNKNOWN, HIST_IDLE, 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 MDSS_MDP_PA_SIZE 0xC #define MDSS_MDP_SIX_ZONE_SIZE 0xC #define MDSS_MDP_MEM_COL_SIZE 0x3C #define MDSS_MDP_GC_SIZE 0x28 #define MDSS_MDP_PCC_SIZE 0xB8 #define MDSS_MDP_GAMUT_SIZE 0x5C #define MDSS_MDP_IGC_DSPP_SIZE 0x28 #define MDSS_MDP_IGC_SSPP_SIZE 0x88 #define MDSS_MDP_VIG_QSEED2_SHARP_SIZE 0x0C #define TOTAL_BLEND_STAGES 0x4 #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 /* Leave space for future features */ #define PP_FLAGS_RESUME_COMMIT 0x10000000 #define IS_PP_RESUME_COMMIT(x) ((x) & PP_FLAGS_RESUME_COMMIT) #define PP_FLAGS_LUT_BASED (PP_FLAGS_DIRTY_IGC | PP_FLAGS_DIRTY_GAMUT | \ PP_FLAGS_DIRTY_PGC | PP_FLAGS_DIRTY_ARGC) #define IS_PP_LUT_DIRTY(x) ((x) & PP_FLAGS_LUT_BASED) #define IS_SIX_ZONE_DIRTY(d, pa) (((d) & PP_FLAGS_DIRTY_PA) && \ ((pa) & MDP_PP_PA_SIX_ZONE_ENABLE)) #define PP_SSPP 0 #define PP_DSPP 1 #define PP_AD_BAD_HW_NUM 255 #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_IPC_RESUME 0x80 #define PP_AD_STATE_IPC_RESET 0x100 #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_DIRTY_ENABLE 0x20 #define PP_AD_STS_IS_DIRTY(sts) (((sts) & PP_AD_STS_DIRTY_INIT) ||\ ((sts) & PP_AD_STS_DIRTY_CFG)) /* Bits 0 and 1 and 5 */ #define MDSS_AD_INPUT_AMBIENT (0x23) /* 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 MDSS_AD_AUTO_TRIGGER 0x80 #define MDSS_AD_T_FILTER_CTRL_0 0 #define MDSS_AD_IPC_FRAME_COUNT 2 #define MDSS_AD_MODE_IPC_BIT 0x4 #define MDSS_AD_MODE_MAN_IPC 0x5 #define SHARP_STRENGTH_DEFAULT 32 #define SHARP_EDGE_THR_DEFAULT 112 #define SHARP_SMOOTH_THR_DEFAULT 8 #define SHARP_NOISE_THR_DEFAULT 2 static struct mdp_pp_driver_ops pp_driver_ops; static struct mdp_pp_feature_ops *pp_ops; static DEFINE_MUTEX(mdss_pp_mutex); static struct mdss_pp_res_type *mdss_pp_res; static u32 pp_hist_read(char __iomem *v_addr, struct pp_hist_col_info *hist_info); static int pp_hist_setup(u32 *op, u32 block, struct mdss_mdp_mixer *mix); static int pp_hist_disable(struct pp_hist_col_info *hist_info); static void pp_update_pcc_regs(char __iomem *addr, struct mdp_pcc_cfg_data *cfg_ptr); static void pp_update_igc_lut(struct mdp_igc_lut_data *cfg, char __iomem *addr, u32 blk_idx, u32 total_idx); static void pp_update_gc_one_lut(char __iomem *addr, struct mdp_ar_gc_lut_data *lut_data, uint8_t num_stages); static void pp_update_argc_lut(char __iomem *addr, struct mdp_pgc_lut_data *config); static void pp_update_hist_lut(char __iomem *base, struct mdp_hist_lut_data *cfg); static int pp_gm_has_invalid_lut_size(struct mdp_gamut_cfg_data *config); static void pp_gamut_config(struct mdp_gamut_cfg_data *gamut_cfg, char __iomem *base, struct pp_sts_type *pp_sts); static void pp_pa_config(unsigned long flags, char __iomem *addr, struct pp_sts_type *pp_sts, struct mdp_pa_cfg *pa_config); static void pp_pa_v2_config(unsigned long flags, char __iomem *addr, struct pp_sts_type *pp_sts, struct mdp_pa_v2_data *pa_v2_config, int mdp_location); static void pp_pcc_config(unsigned long flags, char __iomem *addr, struct pp_sts_type *pp_sts, struct mdp_pcc_cfg_data *pcc_config); static void pp_igc_config(unsigned long flags, char __iomem *addr, struct pp_sts_type *pp_sts, struct mdp_igc_lut_data *igc_config, u32 pipe_num, u32 pipe_cnt); static void pp_enhist_config(unsigned long flags, char __iomem *addr, struct pp_sts_type *pp_sts, struct mdp_hist_lut_data *enhist_cfg); static void pp_dither_config(char __iomem *addr, struct pp_sts_type *pp_sts, struct mdp_dither_cfg_data *dither_cfg); static void pp_dspp_opmode_config(struct mdss_mdp_ctl *ctl, u32 num, struct pp_sts_type *pp_sts, int mdp_rev, u32 *opmode); static void pp_sharp_config(char __iomem *addr, struct pp_sts_type *pp_sts, struct mdp_sharp_cfg *sharp_config); static void pp_update_pa_v2_vig_opmode(struct pp_sts_type *pp_sts, u32 *opmode); static int pp_copy_pa_six_zone_lut(struct mdp_pa_v2_cfg_data *pa_v2_config, u32 disp_num); static void pp_update_pa_v2_global_adj_regs(char __iomem *addr, struct mdp_pa_v2_data *pa_config); static void pp_update_pa_v2_mem_col(char __iomem *addr, struct mdp_pa_v2_data *pa_v2_config); static void pp_update_pa_v2_mem_col_regs(char __iomem *addr, struct mdp_pa_mem_col_cfg *cfg); static void pp_update_pa_v2_six_zone_regs(char __iomem *addr, struct mdp_pa_v2_data *pa_v2_config); static void pp_update_pa_v2_sts(struct pp_sts_type *pp_sts, struct mdp_pa_v2_data *pa_v2_config); static int pp_read_pa_v2_regs(char __iomem *addr, struct mdp_pa_v2_data *pa_v2_config, u32 disp_num); static void pp_read_pa_mem_col_regs(char __iomem *addr, struct mdp_pa_mem_col_cfg *mem_col_cfg); static struct msm_fb_data_type *mdss_get_mfd_from_index(int index); static int mdss_mdp_mfd_valid_ad(struct msm_fb_data_type *mfd); static int mdss_mdp_get_ad(struct msm_fb_data_type *mfd, struct mdss_ad_info **ad); static int pp_ad_invalidate_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_mdp_ad *ad_hw, struct mdss_ad_info *ad); static void pp_ad_init_write(struct mdss_mdp_ad *ad_hw, struct mdss_ad_info *ad, struct mdss_mdp_ctl *ctl); static void pp_ad_input_write(struct mdss_mdp_ad *ad_hw, struct mdss_ad_info *ad); static int pp_ad_setup_hw_nums(struct msm_fb_data_type *mfd, struct mdss_ad_info *ad); static void pp_ad_bypass_config(struct mdss_ad_info *ad, struct mdss_mdp_ctl *ctl, u32 num, u32 *opmode); static int mdss_mdp_ad_setup(struct msm_fb_data_type *mfd); static void pp_ad_cfg_lut(char __iomem *addr, u32 *data); static int pp_ad_attenuate_bl(struct mdss_ad_info *ad, u32 bl, u32 *bl_out); static int pp_ad_linearize_bl(struct mdss_ad_info *ad, u32 bl, u32 *bl_out, int inv); static int pp_ad_calc_bl(struct msm_fb_data_type *mfd, int bl_in, int *bl_out, bool *bl_out_notify); static int pp_num_to_side(struct mdss_mdp_ctl *ctl, u32 num); static int pp_update_pcc_pipe_setup(struct mdss_mdp_pipe *pipe, u32 location); static void mdss_mdp_hist_irq_set_mask(u32 irq); static void mdss_mdp_hist_irq_clear_mask(u32 irq); static void mdss_mdp_hist_intr_notify(u32 disp); static int mdss_mdp_panel_default_dither_config(struct msm_fb_data_type *mfd, u32 panel_bpp); static int mdss_mdp_limited_lut_igc_config(struct msm_fb_data_type *mfd); static inline int pp_validate_dspp_mfd_block(struct msm_fb_data_type *mfd, int block); static int pp_mfd_release_all(struct msm_fb_data_type *mfd); static int pp_mfd_ad_release_all(struct msm_fb_data_type *mfd); static int mdss_mdp_ad_ipc_reset(struct msm_fb_data_type *mfd); static u32 last_sts, last_state; static inline void mdss_mdp_pp_get_dcm_state(struct mdss_mdp_pipe *pipe, u32 *dcm_state) { if (pipe && pipe->mixer_left && pipe->mixer_left->ctl && pipe->mixer_left->ctl->mfd) *dcm_state = pipe->mixer_left->ctl->mfd->dcm_state; } inline int linear_map(int in, int *out, int in_max, int out_max) { if (in < 0 || !out || in_max <= 0 || out_max <= 0) return -EINVAL; *out = ((2 * (in * out_max) + in_max) / (2 * in_max)); pr_debug("in = %d, out = %d, in_max = %d, out_max = %d\n", in, *out, in_max, out_max); if ((in > 0) && (*out == 0)) *out = 1; return 0; } int mdss_mdp_csc_setup_data(u32 block, u32 blk_idx, struct mdp_csc_cfg *data) { int i, ret = 0; char __iomem *base, *addr; u32 val = 0, lv_shift = 0; struct mdss_data_type *mdata; struct mdss_mdp_pipe *pipe; struct mdss_mdp_cdm *cdm; struct mdss_mdp_writeback *wb; if (data == NULL) { pr_err("no csc matrix specified\n"); return -EINVAL; } mdata = mdss_mdp_get_mdata(); switch (block) { case MDSS_MDP_BLOCK_SSPP: lv_shift = CSC_8BIT_LV_SHIFT; pipe = mdss_mdp_pipe_search(mdata, BIT(blk_idx)); if (!pipe) { pr_err("invalid blk index=%d\n", blk_idx); ret = -EINVAL; break; } if (mdss_mdp_pipe_is_yuv(pipe)) { base = pipe->base + MDSS_MDP_REG_VIG_CSC_1_BASE; } else { pr_err("non ViG pipe %d for CSC is not allowed\n", blk_idx); ret = -EINVAL; } break; case MDSS_MDP_BLOCK_WB: lv_shift = CSC_8BIT_LV_SHIFT; if (blk_idx < mdata->nwb) { wb = mdata->wb + blk_idx; if (wb->base) base = wb->base + MDSS_MDP_REG_WB_CSC_BASE; else ret = -EINVAL; } else { ret = -EINVAL; } break; case MDSS_MDP_BLOCK_CDM: lv_shift = CSC_10BIT_LV_SHIFT; if (blk_idx < mdata->ncdm) { cdm = mdata->cdm_off + blk_idx; if (cdm->base) base = cdm->base + MDSS_MDP_REG_CDM_CSC_10_BASE; else ret = -EINVAL; } else { ret = -EINVAL; } break; default: ret = -EINVAL; break; } if (ret != 0) { pr_err("unsupported block id %d for csc\n", blk_idx); return ret; } addr = base + CSC_MV_OFF; for (i = 0; i < 9; i++) { if (i & 0x1) { val |= data->csc_mv[i] << 16; writel_relaxed(val, addr); addr += sizeof(u32); } else { val = data->csc_mv[i]; } } writel_relaxed(val, addr); /* COEFF_33 */ addr = base + CSC_BV_OFF; for (i = 0; i < 3; i++) { writel_relaxed(data->csc_pre_bv[i], addr); writel_relaxed(data->csc_post_bv[i], addr + CSC_POST_OFF); addr += sizeof(u32); } addr = base + CSC_LV_OFF; for (i = 0; i < 6; i += 2) { val = (data->csc_pre_lv[i] << lv_shift) | data->csc_pre_lv[i+1]; writel_relaxed(val, addr); val = (data->csc_post_lv[i] << lv_shift) | data->csc_post_lv[i+1]; writel_relaxed(val, addr + CSC_POST_OFF); addr += sizeof(u32); } return ret; } int mdss_mdp_csc_setup(u32 block, u32 blk_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\n", csc_type, block, blk_idx); if (block == MDSS_MDP_BLOCK_CDM) data = &mdp_csc_10bit_convert[csc_type]; else data = &mdp_csc_8bit_convert[csc_type]; return mdss_mdp_csc_setup_data(block, blk_idx, data); } static void pp_gamut_config(struct mdp_gamut_cfg_data *gamut_cfg, char __iomem *base, struct pp_sts_type *pp_sts) { char __iomem *addr; int i, j; if (gamut_cfg->flags & MDP_PP_OPS_WRITE) { addr = 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++) writel_relaxed((u32)gamut_cfg->r_tbl[i][j] & 0x1FFF, addr); addr += 4; } for (i = 0; i < MDP_GAMUT_TABLE_NUM; i++) { for (j = 0; j < gamut_cfg->tbl_size[i]; j++) writel_relaxed((u32)gamut_cfg->g_tbl[i][j] & 0x1FFF, addr); addr += 4; } for (i = 0; i < MDP_GAMUT_TABLE_NUM; i++) { for (j = 0; j < gamut_cfg->tbl_size[i]; j++) writel_relaxed((u32)gamut_cfg->b_tbl[i][j] & 0x1FFF, addr); addr += 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; pp_sts_set_split_bits(&pp_sts->gamut_sts, gamut_cfg->flags); } static void pp_pa_config(unsigned long flags, char __iomem *addr, 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) { writel_relaxed(pa_config->hue_adj, addr); addr += 4; writel_relaxed(pa_config->sat_adj, addr); addr += 4; writel_relaxed(pa_config->val_adj, addr); addr += 4; writel_relaxed(pa_config->cont_adj, addr); } 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_pa_v2_config(unsigned long flags, char __iomem *addr, struct pp_sts_type *pp_sts, struct mdp_pa_v2_data *pa_v2_config, int mdp_location) { if ((flags & PP_FLAGS_DIRTY_PA) && (pa_v2_config->flags & MDP_PP_OPS_WRITE)) { pp_update_pa_v2_global_adj_regs(addr, pa_v2_config); /* Update PA DSPP Regs */ if (mdp_location == PP_DSPP) { addr += 0x10; pp_update_pa_v2_six_zone_regs(addr, pa_v2_config); addr += 0xC; pp_update_pa_v2_mem_col(addr, pa_v2_config); } else if (mdp_location == PP_SSPP) { /* Update PA SSPP Regs */ addr -= MDSS_MDP_REG_VIG_PA_BASE; addr += MDSS_MDP_REG_VIG_MEM_COL_BASE; pp_update_pa_v2_mem_col(addr, pa_v2_config); } pp_update_pa_v2_sts(pp_sts, pa_v2_config); } } static void pp_update_pa_v2_global_adj_regs(char __iomem *addr, struct mdp_pa_v2_data *pa_v2_config) { if (pa_v2_config->flags & MDP_PP_PA_HUE_ENABLE) writel_relaxed(pa_v2_config->global_hue_adj, addr); addr += 4; if (pa_v2_config->flags & MDP_PP_PA_SAT_ENABLE) /* Sat Global Adjust reg includes Sat Threshold */ writel_relaxed(pa_v2_config->global_sat_adj, addr); addr += 4; if (pa_v2_config->flags & MDP_PP_PA_VAL_ENABLE) writel_relaxed(pa_v2_config->global_val_adj, addr); addr += 4; if (pa_v2_config->flags & MDP_PP_PA_CONT_ENABLE) writel_relaxed(pa_v2_config->global_cont_adj, addr); } static void pp_update_pa_v2_mem_col(char __iomem *addr, struct mdp_pa_v2_data *pa_v2_config) { /* Update skin zone memory color registers */ if (pa_v2_config->flags & MDP_PP_PA_SKIN_ENABLE) pp_update_pa_v2_mem_col_regs(addr, &pa_v2_config->skin_cfg); addr += 0x14; /* Update sky zone memory color registers */ if (pa_v2_config->flags & MDP_PP_PA_SKY_ENABLE) pp_update_pa_v2_mem_col_regs(addr, &pa_v2_config->sky_cfg); addr += 0x14; /* Update foliage zone memory color registers */ if (pa_v2_config->flags & MDP_PP_PA_FOL_ENABLE) pp_update_pa_v2_mem_col_regs(addr, &pa_v2_config->fol_cfg); } static void pp_update_pa_v2_mem_col_regs(char __iomem *addr, struct mdp_pa_mem_col_cfg *cfg) { writel_relaxed(cfg->color_adjust_p0, addr); addr += 4; writel_relaxed(cfg->color_adjust_p1, addr); addr += 4; writel_relaxed(cfg->hue_region, addr); addr += 4; writel_relaxed(cfg->sat_region, addr); addr += 4; writel_relaxed(cfg->val_region, addr); } static void pp_update_pa_v2_six_zone_regs(char __iomem *addr, struct mdp_pa_v2_data *pa_v2_config) { int i; u32 data; /* Update six zone memory color registers */ if (pa_v2_config->flags & MDP_PP_PA_SIX_ZONE_ENABLE) { addr += 4; writel_relaxed(pa_v2_config->six_zone_curve_p1[0], addr); addr -= 4; /* Index Update to trigger auto-incrementing LUT accesses */ data = (1 << 26); writel_relaxed((pa_v2_config->six_zone_curve_p0[0] & 0xFFF) | data, addr); /* Remove Index Update */ for (i = 1; i < MDP_SIX_ZONE_LUT_SIZE; i++) { addr += 4; writel_relaxed(pa_v2_config->six_zone_curve_p1[i], addr); addr -= 4; writel_relaxed(pa_v2_config->six_zone_curve_p0[i] & 0xFFF, addr); } addr += 8; writel_relaxed(pa_v2_config->six_zone_thresh, addr); } } static void pp_update_pa_v2_sts(struct pp_sts_type *pp_sts, struct mdp_pa_v2_data *pa_v2_config) { pp_sts->pa_sts = 0; /* PA STS update */ if (pa_v2_config->flags & MDP_PP_OPS_ENABLE) pp_sts->pa_sts |= PP_STS_ENABLE; else pp_sts->pa_sts &= ~PP_STS_ENABLE; /* Global HSV STS update */ if (pa_v2_config->flags & MDP_PP_PA_HUE_MASK) pp_sts->pa_sts |= PP_STS_PA_HUE_MASK; if (pa_v2_config->flags & MDP_PP_PA_SAT_MASK) pp_sts->pa_sts |= PP_STS_PA_SAT_MASK; if (pa_v2_config->flags & MDP_PP_PA_VAL_MASK) pp_sts->pa_sts |= PP_STS_PA_VAL_MASK; if (pa_v2_config->flags & MDP_PP_PA_CONT_MASK) pp_sts->pa_sts |= PP_STS_PA_CONT_MASK; if (pa_v2_config->flags & MDP_PP_PA_MEM_PROTECT_EN) pp_sts->pa_sts |= PP_STS_PA_MEM_PROTECT_EN; if (pa_v2_config->flags & MDP_PP_PA_SAT_ZERO_EXP_EN) pp_sts->pa_sts |= PP_STS_PA_SAT_ZERO_EXP_EN; /* Memory Color STS update */ if (pa_v2_config->flags & MDP_PP_PA_MEM_COL_SKIN_MASK) pp_sts->pa_sts |= PP_STS_PA_MEM_COL_SKIN_MASK; if (pa_v2_config->flags & MDP_PP_PA_MEM_COL_SKY_MASK) pp_sts->pa_sts |= PP_STS_PA_MEM_COL_SKY_MASK; if (pa_v2_config->flags & MDP_PP_PA_MEM_COL_FOL_MASK) pp_sts->pa_sts |= PP_STS_PA_MEM_COL_FOL_MASK; /* Six Zone STS update */ if (pa_v2_config->flags & MDP_PP_PA_SIX_ZONE_HUE_MASK) pp_sts->pa_sts |= PP_STS_PA_SIX_ZONE_HUE_MASK; if (pa_v2_config->flags & MDP_PP_PA_SIX_ZONE_SAT_MASK) pp_sts->pa_sts |= PP_STS_PA_SIX_ZONE_SAT_MASK; if (pa_v2_config->flags & MDP_PP_PA_SIX_ZONE_VAL_MASK) pp_sts->pa_sts |= PP_STS_PA_SIX_ZONE_VAL_MASK; pp_sts_set_split_bits(&pp_sts->pa_sts, pa_v2_config->flags); } static void pp_pcc_config(unsigned long flags, char __iomem *addr, 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(addr, 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; pp_sts_set_split_bits(&pp_sts->pcc_sts, pcc_config->ops); } } static void pp_igc_config(unsigned long flags, char __iomem *addr, struct pp_sts_type *pp_sts, struct mdp_igc_lut_data *igc_config, u32 pipe_num, u32 pipe_cnt) { u32 tbl_idx; if (igc_config->ops & MDP_PP_OPS_WRITE) pp_update_igc_lut(igc_config, addr, pipe_num, pipe_cnt); if (igc_config->ops & MDP_PP_IGC_FLAG_ROM0) { pp_sts->igc_sts |= PP_STS_ENABLE; tbl_idx = 1; } else if (igc_config->ops & MDP_PP_IGC_FLAG_ROM1) { pp_sts->igc_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; pp_sts_set_split_bits(&pp_sts->igc_sts, igc_config->ops); } static void pp_enhist_config(unsigned long flags, char __iomem *addr, 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(addr, 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 *addr, 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, addr); addr += 4; writel_relaxed(sharp_config->edge_thr, addr); addr += 4; writel_relaxed(sharp_config->smooth_thr, addr); addr += 4; writel_relaxed(sharp_config->noise_thr, addr); } 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 void pp_vig_pipe_opmode_config(struct pp_sts_type *pp_sts, u32 *opmode) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if ((mdata->mdp_rev < MDSS_MDP_HW_REV_103) && (pp_sts->pa_sts & PP_STS_ENABLE)) *opmode |= MDSS_MDP_VIG_OP_PA_EN; else if (mdata->mdp_rev >= MDSS_MDP_HW_REV_103) pp_update_pa_v2_vig_opmode(pp_sts, opmode); if (pp_sts->enhist_sts & PP_STS_ENABLE) /* Enable HistLUT and PA */ *opmode |= MDSS_MDP_VIG_OP_HIST_LUTV_EN | MDSS_MDP_VIG_OP_PA_EN; } static int pp_vig_pipe_setup(struct mdss_mdp_pipe *pipe, u32 *op) { unsigned long flags = 0; char __iomem *offset; struct mdss_data_type *mdata; u32 dcm_state = DCM_UNINIT, current_opmode, csc_reset; int ret = 0; pr_debug("pnum=%x\n", pipe->num); mdss_mdp_pp_get_dcm_state(pipe, &dcm_state); mdata = mdss_mdp_get_mdata(); if ((pipe->flags & MDP_OVERLAY_PP_CFG_EN) && (pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_CSC_CFG)) { *op |= !!(pipe->pp_cfg.csc_cfg.flags & MDP_CSC_FLAG_ENABLE) << 17; *op |= !!(pipe->pp_cfg.csc_cfg.flags & MDP_CSC_FLAG_YUV_IN) << 18; *op |= !!(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) */ mdss_mdp_csc_setup_data(MDSS_MDP_BLOCK_SSPP, pipe->num, &pipe->pp_cfg.csc_cfg); } else if (pipe->src_fmt->is_yuv) { *op |= (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 */ mdss_mdp_csc_setup(MDSS_MDP_BLOCK_SSPP, pipe->num, pp_vig_csc_pipe_val(pipe)); } /* Update CSC state only if tuning mode is enable */ if (dcm_state == DTM_ENTER) { /* Reset bit 16 to 19 for CSC_STATE in VIG_OP_MODE */ csc_reset = 0xFFF0FFFF; current_opmode = readl_relaxed(pipe->base + MDSS_MDP_REG_VIG_OP_MODE); *op |= (current_opmode & csc_reset); return 0; } /* Histogram collection enabled checked inside pp_hist_setup */ pp_hist_setup(op, MDSS_PP_SSPP_CFG | pipe->num, pipe->mixer_left); if (!(pipe->flags & MDP_OVERLAY_PP_CFG_EN)) { pr_debug("Overlay PP CFG enable not set\n"); return 0; } if ((pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_PA_CFG) && (mdata->mdp_rev < MDSS_MDP_HW_REV_103)) { flags = PP_FLAGS_DIRTY_PA; pp_pa_config(flags, pipe->base + MDSS_MDP_REG_VIG_PA_BASE, &pipe->pp_res.pp_sts, &pipe->pp_cfg.pa_cfg); } if ((pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_PA_V2_CFG) && (mdata->mdp_rev >= MDSS_MDP_HW_REV_103)) { flags = PP_FLAGS_DIRTY_PA; if (!pp_ops[PA].pp_set_config) pp_pa_v2_config(flags, pipe->base + MDSS_MDP_REG_VIG_PA_BASE, &pipe->pp_res.pp_sts, &pipe->pp_cfg.pa_v2_cfg, PP_SSPP); else pp_ops[PA].pp_set_config(pipe->base, &pipe->pp_res.pp_sts, &pipe->pp_cfg.pa_v2_cfg_data, SSPP_VIG); } if (pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_HIST_LUT_CFG) { flags = PP_FLAGS_DIRTY_ENHIST; if (!pp_ops[HIST_LUT].pp_set_config) { pp_enhist_config(flags, 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) && !(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); } } else { pp_ops[HIST_LUT].pp_set_config(pipe->base, &pipe->pp_res.pp_sts, &pipe->pp_cfg.hist_lut_cfg, SSPP_VIG); } } if (pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_PCC_CFG) { ret = pp_update_pcc_pipe_setup(pipe, SSPP_VIG); if (ret) pr_err("error in enabling the pcc ret %d pipe type %d pipe num %d\n", ret, pipe->type, pipe->num); } if (pp_driver_ops.pp_opmode_config) pp_driver_ops.pp_opmode_config(SSPP_VIG, &pipe->pp_res.pp_sts, op, 0); else pp_vig_pipe_opmode_config(&pipe->pp_res.pp_sts, op); return 0; } static void pp_update_pa_v2_vig_opmode(struct pp_sts_type *pp_sts, u32 *opmode) { if (pp_sts->pa_sts & PP_STS_ENABLE) *opmode |= MDSS_MDP_VIG_OP_PA_EN; if (pp_sts->pa_sts & PP_STS_PA_HUE_MASK) *opmode |= MDSS_MDP_VIG_OP_PA_HUE_MASK; if (pp_sts->pa_sts & PP_STS_PA_SAT_MASK) *opmode |= MDSS_MDP_VIG_OP_PA_SAT_MASK; if (pp_sts->pa_sts & PP_STS_PA_VAL_MASK) *opmode |= MDSS_MDP_VIG_OP_PA_VAL_MASK; if (pp_sts->pa_sts & PP_STS_PA_CONT_MASK) *opmode |= MDSS_MDP_VIG_OP_PA_CONT_MASK; if (pp_sts->pa_sts & PP_STS_PA_MEM_PROTECT_EN) *opmode |= MDSS_MDP_VIG_OP_PA_MEM_PROTECT_EN; if (pp_sts->pa_sts & PP_STS_PA_SAT_ZERO_EXP_EN) *opmode |= MDSS_MDP_VIG_OP_PA_SAT_ZERO_EXP_EN; if (pp_sts->pa_sts & PP_STS_PA_MEM_COL_SKIN_MASK) *opmode |= MDSS_MDP_VIG_OP_PA_MEM_COL_SKIN_MASK; if (pp_sts->pa_sts & PP_STS_PA_MEM_COL_SKY_MASK) *opmode |= MDSS_MDP_VIG_OP_PA_MEM_COL_SKY_MASK; if (pp_sts->pa_sts & PP_STS_PA_MEM_COL_FOL_MASK) *opmode |= MDSS_MDP_VIG_OP_PA_MEM_COL_FOL_MASK; } static int pp_rgb_pipe_setup(struct mdss_mdp_pipe *pipe, u32 *op) { int ret = 0; if (!pipe) { pr_err("invalid param pipe %p\n", pipe); return -EINVAL; } if (pipe->flags & MDP_OVERLAY_PP_CFG_EN && pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_PCC_CFG) { ret = pp_update_pcc_pipe_setup(pipe, SSPP_RGB); if (ret) pr_err("error in enabling the pcc ret %d pipe type %d pipe num %d\n", ret, pipe->type, pipe->num); } return 0; } static int pp_dma_pipe_setup(struct mdss_mdp_pipe *pipe, u32 *op) { int ret = 0; if (!pipe) { pr_err("invalid param pipe %p\n", pipe); return -EINVAL; } if (pipe->flags & MDP_OVERLAY_PP_CFG_EN && pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_PCC_CFG) { ret = pp_update_pcc_pipe_setup(pipe, SSPP_DMA); if (ret) pr_err("error in enabling the pcc ret %d pipe type %d pipe num %d\n", ret, pipe->type, pipe->num); } return 0; } static int mdss_mdp_scale_setup(struct mdss_mdp_pipe *pipe) { u32 scale_config = 0; int init_phasex = 0, init_phasey = 0; int phasex_step = 0, phasey_step = 0; u32 chroma_sample; u32 filter_mode; struct mdss_data_type *mdata; u32 src_w, src_h; u32 dcm_state = DCM_UNINIT; u32 chroma_shift_x = 0, chroma_shift_y = 0; pr_debug("pipe=%d, change pxl ext=%d\n", pipe->num, pipe->scale.enable_pxl_ext); mdata = mdss_mdp_get_mdata(); mdss_mdp_pp_get_dcm_state(pipe, &dcm_state); 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 || pipe->type == MDSS_MDP_PIPE_TYPE_CURSOR) { if (pipe->dst.h != pipe->src.h || pipe->dst.w != pipe->src.w) { pr_err("no scaling supported on dma/cursor pipe, num:%d\n", pipe->num); return -EINVAL; } else { return 0; } } src_w = DECIMATED_DIMENSION(pipe->src.w, pipe->horz_deci); src_h = DECIMATED_DIMENSION(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 (dcm_state != DTM_ENTER && ((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->src_fmt->is_yuv && (pipe->pp_res.pp_sts.sharp_sts & PP_STS_ENABLE)) || (chroma_sample == MDSS_MDP_CHROMA_420) || (chroma_sample == MDSS_MDP_CHROMA_H1V2) || (pipe->scale.enable_pxl_ext && (src_h != pipe->dst.h))) { 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\n", src_h, pipe->dst.h); return -EINVAL; } scale_config |= MDSS_MDP_SCALEY_EN; phasey_step = pipe->scale.phase_step_y[0]; init_phasey = pipe->scale.init_phase_y[0]; if (pipe->type == MDSS_MDP_PIPE_TYPE_VIG) { if (!pipe->vert_deci && ((chroma_sample == MDSS_MDP_CHROMA_420) || (chroma_sample == MDSS_MDP_CHROMA_H1V2))) chroma_shift_y = 1; /* 2x upsample chroma */ if (src_h <= pipe->dst.h) scale_config |= /* G/Y, A */ (filter_mode << 10) | (MDSS_MDP_SCALE_FILTER_BIL << 18); else scale_config |= /* G/Y, A */ (MDSS_MDP_SCALE_FILTER_PCMN << 10) | (MDSS_MDP_SCALE_FILTER_PCMN << 18); if ((src_h >> chroma_shift_y) <= pipe->dst.h) scale_config |= /* CrCb */ (MDSS_MDP_SCALE_FILTER_BIL << 14); else scale_config |= /* CrCb */ (MDSS_MDP_SCALE_FILTER_PCMN << 14); writel_relaxed(init_phasey, pipe->base + MDSS_MDP_REG_VIG_QSEED2_C12_INIT_PHASEY); writel_relaxed(phasey_step >> chroma_shift_y, 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_BIL << 18); else scale_config |= /* RGB, A */ (MDSS_MDP_SCALE_FILTER_PCMN << 10) | (MDSS_MDP_SCALE_FILTER_PCMN << 18); } } if ((src_w != pipe->dst.w) || (pipe->src_fmt->is_yuv && (pipe->pp_res.pp_sts.sharp_sts & PP_STS_ENABLE)) || (chroma_sample == MDSS_MDP_CHROMA_420) || (chroma_sample == MDSS_MDP_CHROMA_H2V1) || (pipe->scale.enable_pxl_ext && (src_w != pipe->dst.w))) { 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\n", src_w, pipe->dst.w); return -EINVAL; } scale_config |= MDSS_MDP_SCALEX_EN; init_phasex = pipe->scale.init_phase_x[0]; phasex_step = pipe->scale.phase_step_x[0]; if (pipe->type == MDSS_MDP_PIPE_TYPE_VIG) { if (!pipe->horz_deci && ((chroma_sample == MDSS_MDP_CHROMA_420) || (chroma_sample == MDSS_MDP_CHROMA_H2V1))) chroma_shift_x = 1; /* 2x upsample chroma */ if (src_w <= pipe->dst.w) scale_config |= /* G/Y, A */ (filter_mode << 8) | (MDSS_MDP_SCALE_FILTER_BIL << 16); else scale_config |= /* G/Y, A */ (MDSS_MDP_SCALE_FILTER_PCMN << 8) | (MDSS_MDP_SCALE_FILTER_PCMN << 16); if ((src_w >> chroma_shift_x) <= pipe->dst.w) scale_config |= /* CrCb */ (MDSS_MDP_SCALE_FILTER_BIL << 12); else scale_config |= /* CrCb */ (MDSS_MDP_SCALE_FILTER_PCMN << 12); writel_relaxed(init_phasex, pipe->base + MDSS_MDP_REG_VIG_QSEED2_C12_INIT_PHASEX); writel_relaxed(phasex_step >> chroma_shift_x, 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_BIL << 16); else scale_config |= /* RGB, A */ (MDSS_MDP_SCALE_FILTER_PCMN << 8) | (MDSS_MDP_SCALE_FILTER_PCMN << 16); } } if (pipe->scale.enable_pxl_ext) { if (pipe->type == MDSS_MDP_PIPE_TYPE_VIG) { /*program x,y initial phase and phase step*/ writel_relaxed(pipe->scale.init_phase_x[0], pipe->base + MDSS_MDP_REG_VIG_QSEED2_C03_INIT_PHASEX); writel_relaxed(pipe->scale.phase_step_x[0], pipe->base + MDSS_MDP_REG_VIG_QSEED2_C03_PHASESTEPX); writel_relaxed(pipe->scale.init_phase_x[1], pipe->base + MDSS_MDP_REG_VIG_QSEED2_C12_INIT_PHASEX); writel_relaxed(pipe->scale.phase_step_x[1], pipe->base + MDSS_MDP_REG_VIG_QSEED2_C12_PHASESTEPX); writel_relaxed(pipe->scale.init_phase_y[0], pipe->base + MDSS_MDP_REG_VIG_QSEED2_C03_INIT_PHASEY); writel_relaxed(pipe->scale.phase_step_y[0], pipe->base + MDSS_MDP_REG_VIG_QSEED2_C03_PHASESTEPY); writel_relaxed(pipe->scale.init_phase_y[1], pipe->base + MDSS_MDP_REG_VIG_QSEED2_C12_INIT_PHASEY); writel_relaxed(pipe->scale.phase_step_y[1], pipe->base + MDSS_MDP_REG_VIG_QSEED2_C12_PHASESTEPY); } else { writel_relaxed(pipe->scale.phase_step_x[0], pipe->base + MDSS_MDP_REG_SCALE_PHASE_STEP_X); writel_relaxed(pipe->scale.phase_step_y[0], pipe->base + MDSS_MDP_REG_SCALE_PHASE_STEP_Y); writel_relaxed(pipe->scale.init_phase_x[0], pipe->base + MDSS_MDP_REG_SCALE_INIT_PHASE_X); writel_relaxed(pipe->scale.init_phase_y[0], pipe->base + MDSS_MDP_REG_SCALE_INIT_PHASE_Y); } } else { if (pipe->type == MDSS_MDP_PIPE_TYPE_VIG) { /*program x,y initial phase and phase step*/ writel_relaxed(0, pipe->base + MDSS_MDP_REG_VIG_QSEED2_C03_INIT_PHASEX); writel_relaxed(init_phasex, pipe->base + MDSS_MDP_REG_VIG_QSEED2_C12_INIT_PHASEX); writel_relaxed(phasex_step, pipe->base + MDSS_MDP_REG_VIG_QSEED2_C03_PHASESTEPX); writel_relaxed(phasex_step >> chroma_shift_x, pipe->base + MDSS_MDP_REG_VIG_QSEED2_C12_PHASESTEPX); writel_relaxed(0, pipe->base + MDSS_MDP_REG_VIG_QSEED2_C03_INIT_PHASEY); writel_relaxed(init_phasey, pipe->base + MDSS_MDP_REG_VIG_QSEED2_C12_INIT_PHASEY); writel_relaxed(phasey_step, pipe->base + MDSS_MDP_REG_VIG_QSEED2_C03_PHASESTEPY); writel_relaxed(phasey_step >> chroma_shift_y, pipe->base + MDSS_MDP_REG_VIG_QSEED2_C12_PHASESTEPY); } else { 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); writel_relaxed(0, pipe->base + MDSS_MDP_REG_SCALE_INIT_PHASE_X); writel_relaxed(0, pipe->base + MDSS_MDP_REG_SCALE_INIT_PHASE_Y); } } writel_relaxed(scale_config, pipe->base + MDSS_MDP_REG_SCALE_CONFIG); 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) { pr_err("scale setup on pipe %d type %d failed ret %d\n", pipe->num, pipe->type, ret); return -EINVAL; } switch (pipe->type) { case MDSS_MDP_PIPE_TYPE_VIG: ret = pp_vig_pipe_setup(pipe, op); break; case MDSS_MDP_PIPE_TYPE_RGB: ret = pp_rgb_pipe_setup(pipe, op); break; case MDSS_MDP_PIPE_TYPE_DMA: ret = pp_dma_pipe_setup(pipe, op); break; default: pr_debug("no PP setup for pipe type %d\n", pipe->type); break; } return ret; } void mdss_mdp_pipe_pp_clear(struct mdss_mdp_pipe *pipe) { struct pp_hist_col_info *hist_info; if (!pipe) { pr_err("Invalid pipe context passed, %p\n", pipe); return; } if (mdss_mdp_pipe_is_yuv(pipe)) { hist_info = &pipe->pp_res.hist; pp_hist_disable(hist_info); } kfree(pipe->pp_res.pa_cfg_payload); pipe->pp_res.pa_cfg_payload = NULL; pipe->pp_cfg.pa_v2_cfg_data.cfg_payload = NULL; kfree(pipe->pp_res.igc_cfg_payload); pipe->pp_res.igc_cfg_payload = NULL; pipe->pp_cfg.igc_cfg.cfg_payload = NULL; kfree(pipe->pp_res.pcc_cfg_payload); pipe->pp_res.pcc_cfg_payload = NULL; pipe->pp_cfg.pcc_cfg_data.cfg_payload = NULL; kfree(pipe->pp_res.hist_lut_cfg_payload); pipe->pp_res.hist_lut_cfg_payload = NULL; pipe->pp_cfg.hist_lut_cfg.cfg_payload = NULL; memset(&pipe->pp_res.pp_sts, 0, sizeof(struct pp_sts_type)); pipe->pp_cfg.config_ops = 0; } int mdss_mdp_pipe_sspp_setup(struct mdss_mdp_pipe *pipe, u32 *op) { int ret = 0; unsigned long flags = 0; char __iomem *pipe_base; u32 pipe_num, pipe_cnt; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); u32 current_opmode, location; u32 dcm_state = DCM_UNINIT; if (pipe == NULL) return -EINVAL; mdss_mdp_pp_get_dcm_state(pipe, &dcm_state); /* Read IGC state and update the same if tuning mode is enable */ if (dcm_state == DTM_ENTER) { current_opmode = readl_relaxed(pipe->base + MDSS_MDP_REG_SSPP_SRC_OP_MODE); *op |= (current_opmode & BIT(16)); return ret; } /* * 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 = mdata->mdp_base + MDSS_MDP_REG_IGC_VIG_BASE; pipe_cnt = mdata->nvig_pipes; location = SSPP_VIG; switch (pipe->num) { case MDSS_MDP_SSPP_VIG0: pipe_num = 0; break; case MDSS_MDP_SSPP_VIG1: pipe_num = 1; break; case MDSS_MDP_SSPP_VIG2: pipe_num = 2; break; case MDSS_MDP_SSPP_VIG3: pipe_num = 3; break; default: pr_err("Invalid pipe num %d pipe type %d\n", pipe->num, pipe->type); return -EINVAL; } break; case MDSS_MDP_PIPE_TYPE_RGB: pipe_base = mdata->mdp_base + MDSS_MDP_REG_IGC_RGB_BASE; pipe_cnt = mdata->nrgb_pipes; location = SSPP_RGB; switch (pipe->num) { case MDSS_MDP_SSPP_RGB0: pipe_num = 0; break; case MDSS_MDP_SSPP_RGB1: pipe_num = 1; break; case MDSS_MDP_SSPP_RGB2: pipe_num = 2; break; case MDSS_MDP_SSPP_RGB3: pipe_num = 3; break; default: pr_err("Invalid pipe num %d pipe type %d\n", pipe->num, pipe->type); return -EINVAL; } break; case MDSS_MDP_PIPE_TYPE_DMA: pipe_base = mdata->mdp_base + MDSS_MDP_REG_IGC_DMA_BASE; pipe_num = pipe->num - MDSS_MDP_SSPP_DMA0; pipe_cnt = mdata->ndma_pipes; location = SSPP_DMA; break; case MDSS_MDP_PIPE_TYPE_CURSOR: /* cursor does not support the feature */ return 0; default: pr_err("Invalid pipe type %d\n", pipe->type); return -EINVAL; } if (pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_IGC_CFG) { flags |= PP_FLAGS_DIRTY_IGC; if (!pp_ops[IGC].pp_set_config) { pp_igc_config(flags, pipe_base, &pipe->pp_res.pp_sts, &pipe->pp_cfg.igc_cfg, pipe_num, pipe_cnt); } else { pipe->pp_cfg.igc_cfg.block = pipe_num; pipe_base = mdata->mdp_base + mdata->pp_block_off.sspp_igc_lut_off; pp_ops[IGC].pp_set_config(pipe_base, &pipe->pp_res.pp_sts, &pipe->pp_cfg.igc_cfg, location); } } if (pipe->pp_res.pp_sts.igc_sts & PP_STS_ENABLE) *op |= (1 << 16); /* IGC_LUT_EN */ return ret; } static int pp_mixer_setup(struct mdss_mdp_mixer *mixer) { u32 flags, disp_num, opmode = 0, lm_bitmask = 0; struct mdp_pgc_lut_data *pgc_config; struct pp_sts_type *pp_sts; struct mdss_mdp_ctl *ctl; char __iomem *addr; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (!mixer || !mixer->ctl || !mixer->ctl->mfd || !mdata) { pr_err("invalid parameters, mixer %p ctl %p mfd %p mdata %p\n", mixer, (mixer ? mixer->ctl : NULL), (mixer ? (mixer->ctl ? mixer->ctl->mfd : NULL) : NULL), mdata); return -EINVAL; } ctl = mixer->ctl; disp_num = ctl->mfd->index; if (disp_num < MDSS_BLOCK_DISP_NUM) flags = mdss_pp_res->pp_disp_flags[disp_num]; else flags = 0; if (mixer->num == MDSS_MDP_INTF_LAYERMIXER3) lm_bitmask = BIT(20); else if (mixer->type == MDSS_MDP_MIXER_TYPE_WRITEBACK) lm_bitmask = BIT(9) << mixer->num; else lm_bitmask = BIT(6) << mixer->num; pp_sts = &mdss_pp_res->pp_disp_sts[disp_num]; /* GC_LUT is in layer mixer */ if (flags & PP_FLAGS_DIRTY_ARGC) { if (pp_ops[GC].pp_set_config) { if (mdata->pp_block_off.lm_pgc_off == U32_MAX) { pr_err("invalid pgc offset %d\n", U32_MAX); } else { addr = mixer->base + mdata->pp_block_off.lm_pgc_off; pp_ops[GC].pp_set_config(addr, pp_sts, &mdss_pp_res->argc_disp_cfg[disp_num], LM); } } else { pgc_config = &mdss_pp_res->argc_disp_cfg[disp_num]; if (pgc_config->flags & MDP_PP_OPS_WRITE) { addr = mixer->base + MDSS_MDP_REG_LM_GC_LUT_BASE; pp_update_argc_lut(addr, 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 |= lm_bitmask; } /* update LM opmode if LM needs flush */ if ((pp_sts->argc_sts & PP_STS_ENABLE) && (ctl->flush_bits & lm_bitmask)) { if (pp_driver_ops.pp_opmode_config) { pp_driver_ops.pp_opmode_config(LM, pp_sts, &opmode, 0); } else { addr = mixer->base + MDSS_MDP_REG_LM_OP_MODE; opmode = readl_relaxed(addr); opmode |= (1 << 0); /* GC_LUT_EN */ writel_relaxed(opmode, addr); } } return 0; } static char __iomem *mdss_mdp_get_mixer_addr_off(u32 mixer_num) { struct mdss_data_type *mdata; struct mdss_mdp_mixer *mixer; mdata = mdss_mdp_get_mdata(); if (mdata->nmixers_intf <= mixer_num) { pr_err("Invalid mixer_num=%d\n", mixer_num); return ERR_PTR(-EINVAL); } mixer = mdata->mixer_intf + mixer_num; return mixer->base; } 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->ndspp <= dspp_num) { pr_debug("destination not supported dspp_num=%d\n", 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_hist_setup(u32 *op, u32 block, struct mdss_mdp_mixer *mix) { int ret = -EINVAL; char __iomem *base; u32 op_flags; struct mdss_mdp_pipe *pipe; struct pp_hist_col_info *hist_info; unsigned long flag; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); u32 intr_mask; if (!mdata) return -EPERM; intr_mask = 1; if (mix && (PP_LOCAT(block) == MDSS_PP_DSPP_CFG)) { /* HIST_EN */ op_flags = BIT(16); hist_info = &mdss_pp_res->dspp_hist[mix->num]; base = mdss_mdp_get_dspp_addr_off(PP_BLOCK(block)); if (IS_ERR(base)) { ret = -EPERM; goto error; } } else if (PP_LOCAT(block) == MDSS_PP_SSPP_CFG && (pp_driver_ops.is_sspp_hist_supp())) { pipe = mdss_mdp_pipe_get(mdata, BIT(PP_BLOCK(block))); if (IS_ERR_OR_NULL(pipe)) { pr_debug("pipe DNE (%d)\n", (u32) BIT(PP_BLOCK(block))); ret = -ENODEV; goto error; } op_flags = BIT(8); hist_info = &pipe->pp_res.hist; base = pipe->base; mdss_mdp_pipe_unmap(pipe); } else { goto error; } mutex_lock(&hist_info->hist_mutex); spin_lock_irqsave(&hist_info->hist_lock, flag); if (hist_info->col_en) { mdss_mdp_hist_irq_set_mask(intr_mask << hist_info->intr_shift); *op |= op_flags; } spin_unlock_irqrestore(&hist_info->hist_lock, flag); mutex_unlock(&hist_info->hist_mutex); ret = 0; error: return ret; } static void pp_dither_config(char __iomem *addr, struct pp_sts_type *pp_sts, struct mdp_dither_cfg_data *dither_cfg) { u32 data; int i; if (dither_cfg->flags & MDP_PP_OPS_WRITE) { data = dither_depth_map[dither_cfg->g_y_depth]; data |= dither_depth_map[dither_cfg->b_cb_depth] << 2; data |= dither_depth_map[dither_cfg->r_cr_depth] << 4; writel_relaxed(data, addr); addr += 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); writel_relaxed(data, addr); addr += 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; pp_sts_set_split_bits(&pp_sts->dither_sts, dither_cfg->flags); } static void pp_dspp_opmode_config(struct mdss_mdp_ctl *ctl, u32 num, struct pp_sts_type *pp_sts, int mdp_rev, u32 *opmode) { int side; bool pa_side_enabled = false; side = pp_num_to_side(ctl, num); if (side < 0) return; if (pp_driver_ops.pp_opmode_config) { pp_driver_ops.pp_opmode_config(DSPP, pp_sts, opmode, side); return; } if (pp_sts_is_enabled(pp_sts->pa_sts, side)) { *opmode |= MDSS_MDP_DSPP_OP_PA_EN; /* PA_EN */ pa_side_enabled = true; } if (mdp_rev >= MDSS_MDP_HW_REV_103 && pa_side_enabled) { if (pp_sts->pa_sts & PP_STS_PA_HUE_MASK) *opmode |= MDSS_MDP_DSPP_OP_PA_HUE_MASK; if (pp_sts->pa_sts & PP_STS_PA_SAT_MASK) *opmode |= MDSS_MDP_DSPP_OP_PA_SAT_MASK; if (pp_sts->pa_sts & PP_STS_PA_VAL_MASK) *opmode |= MDSS_MDP_DSPP_OP_PA_VAL_MASK; if (pp_sts->pa_sts & PP_STS_PA_CONT_MASK) *opmode |= MDSS_MDP_DSPP_OP_PA_CONT_MASK; if (pp_sts->pa_sts & PP_STS_PA_MEM_PROTECT_EN) *opmode |= MDSS_MDP_DSPP_OP_PA_MEM_PROTECT_EN; if (pp_sts->pa_sts & PP_STS_PA_SAT_ZERO_EXP_EN) *opmode |= MDSS_MDP_DSPP_OP_PA_SAT_ZERO_EXP_EN; if (pp_sts->pa_sts & PP_STS_PA_MEM_COL_SKIN_MASK) *opmode |= MDSS_MDP_DSPP_OP_PA_MEM_COL_SKIN_MASK; if (pp_sts->pa_sts & PP_STS_PA_MEM_COL_FOL_MASK) *opmode |= MDSS_MDP_DSPP_OP_PA_MEM_COL_FOL_MASK; if (pp_sts->pa_sts & PP_STS_PA_MEM_COL_SKY_MASK) *opmode |= MDSS_MDP_DSPP_OP_PA_MEM_COL_SKY_MASK; if (pp_sts->pa_sts & PP_STS_PA_SIX_ZONE_HUE_MASK) *opmode |= MDSS_MDP_DSPP_OP_PA_SIX_ZONE_HUE_MASK; if (pp_sts->pa_sts & PP_STS_PA_SIX_ZONE_SAT_MASK) *opmode |= MDSS_MDP_DSPP_OP_PA_SIX_ZONE_SAT_MASK; if (pp_sts->pa_sts & PP_STS_PA_SIX_ZONE_VAL_MASK) *opmode |= MDSS_MDP_DSPP_OP_PA_SIX_ZONE_VAL_MASK; } if (pp_sts_is_enabled(pp_sts->pcc_sts, side)) *opmode |= MDSS_MDP_DSPP_OP_PCC_EN; /* PCC_EN */ if (pp_sts_is_enabled(pp_sts->igc_sts, side)) { *opmode |= MDSS_MDP_DSPP_OP_IGC_LUT_EN | /* IGC_LUT_EN */ (pp_sts->igc_tbl_idx << 1); } if (pp_sts->enhist_sts & PP_STS_ENABLE) { *opmode |= MDSS_MDP_DSPP_OP_HIST_LUTV_EN | /* HIST_LUT_EN */ MDSS_MDP_DSPP_OP_PA_EN; /* PA_EN */ } if (pp_sts_is_enabled(pp_sts->dither_sts, side)) *opmode |= MDSS_MDP_DSPP_OP_DST_DITHER_EN; /* DITHER_EN */ if (pp_sts_is_enabled(pp_sts->gamut_sts, side)) { *opmode |= MDSS_MDP_DSPP_OP_GAMUT_EN; /* GAMUT_EN */ if (pp_sts->gamut_sts & PP_STS_GAMUT_FIRST) *opmode |= MDSS_MDP_DSPP_OP_GAMUT_PCC_ORDER; } if (pp_sts_is_enabled(pp_sts->pgc_sts, side)) *opmode |= MDSS_MDP_DSPP_OP_ARGC_LUT_EN; } static int pp_dspp_setup(u32 disp_num, struct mdss_mdp_mixer *mixer) { u32 ad_flags, flags, dspp_num, opmode = 0, ad_bypass; struct mdp_pgc_lut_data *pgc_config; struct pp_sts_type *pp_sts; char __iomem *base, *addr; int ret = 0; struct mdss_data_type *mdata; struct mdss_ad_info *ad = NULL; struct mdss_mdp_ad *ad_hw = NULL; struct mdp_pa_v2_cfg_data *pa_v2_cfg_data = NULL; struct mdss_mdp_ctl *ctl; u32 mixer_cnt; u32 mixer_id[MDSS_MDP_INTF_MAX_LAYERMIXER]; int side; 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 >= mdata->ndspp)) return -EINVAL; base = mdss_mdp_get_dspp_addr_off(dspp_num); if (IS_ERR(base)) return -EINVAL; side = pp_num_to_side(ctl, dspp_num); if (side < 0) { pr_err("invalid side information for dspp_num %d", dspp_num); return -EINVAL; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); if ((mdata->pp_block_off.dspp_gamut_off != U32_MAX) && (pp_driver_ops.gamut_clk_gate_en)) pp_driver_ops.gamut_clk_gate_en(base + mdata->pp_block_off.dspp_gamut_off); ret = pp_hist_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 && disp_num < mdata->nad_cfgs && (mixer_cnt <= mdata->nmax_concurrent_ad_hw)) { ad = &mdata->ad_cfgs[disp_num]; ad_flags = ad->reg_sts; ad_hw = &mdata->ad_off[dspp_num]; } else { ad_flags = 0; } /* nothing to update */ if ((!flags) && (!(opmode)) && (!ad_flags)) goto dspp_exit; pp_sts = &mdss_pp_res->pp_disp_sts[disp_num]; pp_sts->side_sts = side; if (flags & PP_FLAGS_DIRTY_PA) { if (!pp_ops[PA].pp_set_config) { if (mdata->mdp_rev >= MDSS_MDP_HW_REV_103) { pa_v2_cfg_data = &mdss_pp_res->pa_v2_disp_cfg[disp_num]; pp_pa_v2_config(flags, base + MDSS_MDP_REG_DSPP_PA_BASE, pp_sts, &pa_v2_cfg_data->pa_v2_data, PP_DSPP); } else pp_pa_config(flags, base + MDSS_MDP_REG_DSPP_PA_BASE, pp_sts, &mdss_pp_res->pa_disp_cfg[disp_num]); } else { pp_ops[PA].pp_set_config(base, pp_sts, &mdss_pp_res->pa_v2_disp_cfg[disp_num], DSPP); } } if (flags & PP_FLAGS_DIRTY_PCC) { if (!pp_ops[PCC].pp_set_config) pp_pcc_config(flags, base + MDSS_MDP_REG_DSPP_PCC_BASE, pp_sts, &mdss_pp_res->pcc_disp_cfg[disp_num]); else { if (mdata->pp_block_off.dspp_pcc_off == U32_MAX) { pr_err("invalid pcc off %d\n", U32_MAX); } else { addr = base + mdata->pp_block_off.dspp_pcc_off; pp_ops[PCC].pp_set_config(addr, pp_sts, &mdss_pp_res->pcc_disp_cfg[disp_num], DSPP); } } } if (flags & PP_FLAGS_DIRTY_IGC) { if (!pp_ops[IGC].pp_set_config) { pp_igc_config(flags, mdata->mdp_base + MDSS_MDP_REG_IGC_DSPP_BASE, pp_sts, &mdss_pp_res->igc_disp_cfg[disp_num], dspp_num, mdata->ndspp); } else { addr = mdata->mdp_base + MDSS_MDP_REG_IGC_DSPP_BASE; /* Pass dspp num using block */ mdss_pp_res->igc_disp_cfg[disp_num].block = dspp_num; pp_ops[IGC].pp_set_config(addr, pp_sts, &mdss_pp_res->igc_disp_cfg[disp_num], DSPP); } } if (flags & PP_FLAGS_DIRTY_ENHIST) { if (!pp_ops[HIST_LUT].pp_set_config) { 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->enhist_sts & PP_STS_ENABLE) && !(pp_sts->pa_sts & PP_STS_ENABLE)) { /* Program default value */ addr = base + MDSS_MDP_REG_DSPP_PA_BASE; writel_relaxed(0, addr); writel_relaxed(0, addr + 4); writel_relaxed(0, addr + 8); writel_relaxed(0, addr + 12); } } else { /* Pass dspp num using block */ mdss_pp_res->enhist_disp_cfg[disp_num].block = dspp_num; pp_ops[HIST_LUT].pp_set_config(base, pp_sts, &mdss_pp_res->enhist_disp_cfg[disp_num], DSPP); } } if (flags & PP_FLAGS_DIRTY_DITHER) { if (!pp_ops[DITHER].pp_set_config) { pp_dither_config(addr, pp_sts, &mdss_pp_res->dither_disp_cfg[disp_num]); } else { addr = base + MDSS_MDP_REG_DSPP_DITHER_DEPTH; pp_ops[DITHER].pp_set_config(addr, pp_sts, &mdss_pp_res->dither_disp_cfg[disp_num], 0); } } if (flags & PP_FLAGS_DIRTY_GAMUT) { if (!pp_ops[GAMUT].pp_set_config) { pp_gamut_config(&mdss_pp_res->gamut_disp_cfg[disp_num], base, pp_sts); } else { if (mdata->pp_block_off.dspp_gamut_off == U32_MAX) { pr_err("invalid gamut off %d\n", U32_MAX); } else { addr = base + mdata->pp_block_off.dspp_gamut_off; pp_ops[GAMUT].pp_set_config(addr, pp_sts, &mdss_pp_res->gamut_disp_cfg[disp_num], DSPP); } } } if (flags & PP_FLAGS_DIRTY_PGC) { pgc_config = &mdss_pp_res->pgc_disp_cfg[disp_num]; if (pp_ops[GC].pp_set_config) { if (mdata->pp_block_off.dspp_pgc_off == U32_MAX) { pr_err("invalid pgc offset %d\n", U32_MAX); } else { addr = base + mdata->pp_block_off.dspp_pgc_off; pp_ops[GC].pp_set_config(addr, pp_sts, &mdss_pp_res->pgc_disp_cfg[disp_num], DSPP); } } else { if (pgc_config->flags & MDP_PP_OPS_WRITE) { addr = base + MDSS_MDP_REG_DSPP_GC_BASE; pp_update_argc_lut(addr, 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; pp_sts_set_split_bits(&pp_sts->pgc_sts, pgc_config->flags); } } pp_dspp_opmode_config(ctl, dspp_num, pp_sts, mdata->mdp_rev, &opmode); if (ad_hw) { mutex_lock(&ad->lock); ad_flags = ad->reg_sts; if (ad_flags & PP_AD_STS_DIRTY_DATA) pp_ad_input_write(ad_hw, ad); if (ad_flags & PP_AD_STS_DIRTY_INIT) pp_ad_init_write(ad_hw, ad, ctl); if (ad_flags & PP_AD_STS_DIRTY_CFG) pp_ad_cfg_write(ad_hw, ad); if (ad->state & PP_AD_STATE_IPC_RESET) { writel_relaxed(ad->cfg.t_filter_recursion, ad_hw->base + MDSS_MDP_REG_AD_TFILT_CTRL); writel_relaxed(ad->cfg.mode | MDSS_AD_AUTO_TRIGGER, ad_hw->base + MDSS_MDP_REG_AD_MODE_SEL); } pp_ad_bypass_config(ad, ctl, ad_hw->num, &ad_bypass); writel_relaxed(ad_bypass, ad_hw->base); mutex_unlock(&ad->lock); } writel_relaxed(opmode, base + MDSS_MDP_REG_DSPP_OP_MODE); if (dspp_num == MDSS_MDP_DSPP3) ctl->flush_bits |= BIT(21); else ctl->flush_bits |= BIT(13 + dspp_num); wmb(); dspp_exit: mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); 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 */ mutex_lock(&ctl->lock); if (!mdss_mdp_ctl_is_power_on(ctl)) { ret = -EPERM; goto error; } ret = mdss_mdp_pp_setup_locked(ctl); error: mutex_unlock(&ctl->lock); return ret; } int mdss_mdp_pp_setup_locked(struct mdss_mdp_ctl *ctl) { struct mdss_data_type *mdata = ctl->mdata; int ret = 0, i; u32 flags, pa_v2_flags; u32 max_bw_needed; u32 mixer_cnt; u32 mixer_id[MDSS_MDP_INTF_MAX_LAYERMIXER]; u32 disp_num; bool valid_mixers = true; bool valid_ad_panel = true; if ((!ctl) || (!ctl->mfd) || (!mdss_pp_res) || (!mdata)) return -EINVAL; /* treat fb_num the same as block logical id*/ disp_num = ctl->mfd->index; mixer_cnt = mdss_mdp_get_ctl_mixers(disp_num, mixer_id); if (!mixer_cnt) { valid_mixers = false; ret = -EINVAL; pr_warn("Configuring post processing without mixers, err = %d\n", ret); goto exit; } if (mdata->nad_cfgs == 0) valid_mixers = false; for (i = 0; i < mixer_cnt && valid_mixers; i++) { if (mixer_id[i] >= mdata->nad_cfgs) valid_mixers = false; } valid_ad_panel = (ctl->mfd->panel_info->type != DTV_PANEL) && (((mdata->mdp_rev < MDSS_MDP_HW_REV_103) && (ctl->mfd->panel_info->type == WRITEBACK_PANEL)) || (ctl->mfd->panel_info->type != WRITEBACK_PANEL)); if (valid_mixers && (mixer_cnt <= mdata->nmax_concurrent_ad_hw) && valid_ad_panel) { ret = mdss_mdp_ad_setup(ctl->mfd); if (ret < 0) pr_warn("ad_setup(disp%d) returns %d\n", disp_num, ret); } mutex_lock(&mdss_pp_mutex); flags = mdss_pp_res->pp_disp_flags[disp_num]; if (pp_ops[PA].pp_set_config) pa_v2_flags = mdss_pp_res->pa_v2_disp_cfg[disp_num].flags; else pa_v2_flags = mdss_pp_res->pa_v2_disp_cfg[disp_num].pa_v2_data.flags; /* * If a LUT based PP feature needs to be reprogrammed during resume, * increase the register bus bandwidth to maximum frequency * in order to speed up the register reprogramming. */ max_bw_needed = (IS_PP_RESUME_COMMIT(flags) && (IS_PP_LUT_DIRTY(flags) || IS_SIX_ZONE_DIRTY(flags, pa_v2_flags))); if (mdata->pp_reg_bus_clt && max_bw_needed) { ret = mdss_update_reg_bus_vote(mdata->pp_reg_bus_clt, VOTE_INDEX_80_MHZ); if (ret) pr_err("Updated reg_bus_scale failed, ret = %d", ret); } if (ctl->mixer_left) { pp_mixer_setup(ctl->mixer_left); pp_dspp_setup(disp_num, ctl->mixer_left); } if (ctl->mixer_right) { pp_mixer_setup(ctl->mixer_right); pp_dspp_setup(disp_num, ctl->mixer_right); } if (valid_mixers && (mixer_cnt <= mdata->nmax_concurrent_ad_hw) && valid_ad_panel) { ret = mdss_mdp_ad_ipc_reset(ctl->mfd); if (ret < 0) pr_warn("ad_setup(disp%d) returns %d\n", disp_num, ret); } /* clear dirty flag */ if (disp_num < MDSS_BLOCK_DISP_NUM) { mdss_pp_res->pp_disp_flags[disp_num] = 0; if (disp_num < mdata->nad_cfgs) mdata->ad_cfgs[disp_num].reg_sts = 0; } if (mdata->pp_reg_bus_clt && max_bw_needed) { ret = mdss_update_reg_bus_vote(mdata->pp_reg_bus_clt, VOTE_INDEX_DISABLE); if (ret) pr_err("Updated reg_bus_scale failed, ret = %d", ret); } if (IS_PP_RESUME_COMMIT(flags)) mdss_pp_res->pp_disp_flags[disp_num] &= ~PP_FLAGS_RESUME_COMMIT; mutex_unlock(&mdss_pp_mutex); exit: return ret; } /* * Set dirty and write bits on features that were enabled so they will be * reconfigured */ int mdss_mdp_pp_resume(struct msm_fb_data_type *mfd) { u32 flags = 0, disp_num, ret = 0; struct pp_sts_type pp_sts; struct mdss_ad_info *ad; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdp_pa_v2_cfg_data *pa_v2_cache_cfg = NULL; if (!mfd) { pr_err("invalid input: mfd = 0x%p\n", mfd); return -EINVAL; } if (!mdss_mdp_mfd_valid_dspp(mfd)) { pr_debug("PP not supported on display num %d hw config\n", mfd->index); return -EPERM; } disp_num = mfd->index; pp_sts = mdss_pp_res->pp_disp_sts[disp_num]; if (pp_sts.pa_sts & PP_STS_ENABLE) { flags |= PP_FLAGS_DIRTY_PA; pa_v2_cache_cfg = &mdss_pp_res->pa_v2_disp_cfg[disp_num]; if (pp_ops[PA].pp_set_config) { if (!(pa_v2_cache_cfg->flags & MDP_PP_OPS_DISABLE)) pa_v2_cache_cfg->flags |= MDP_PP_OPS_WRITE; } else if (mdata->mdp_rev >= MDSS_MDP_HW_REV_103) { if (!(pa_v2_cache_cfg->pa_v2_data.flags & MDP_PP_OPS_DISABLE)) pa_v2_cache_cfg->pa_v2_data.flags |= MDP_PP_OPS_WRITE; } else { 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; mdss_pp_res->pp_disp_flags[disp_num] |= PP_FLAGS_RESUME_COMMIT; ret = mdss_mdp_get_ad(mfd, &ad); if (ret == -ENODEV || ret == -EPERM) { pr_debug("AD not supported on device, disp num %d\n", mfd->index); return 0; } else if (ret || !ad) { pr_err("Failed to get ad info: ret = %d, ad = 0x%p.\n", ret, ad); return ret; } mutex_lock(&ad->lock); if (mfd->ipc_resume) { mfd->ipc_resume = false; if (PP_AD_STATE_RUN & ad->state) { ad->ipc_frame_count = 0; ad->state |= PP_AD_STATE_IPC_RESUME; ad->cfg.mode |= MDSS_AD_MODE_IPC_BIT; pr_debug("switch mode to %d, last_ad_data = %d\n", ad->cfg.mode, ad->last_ad_data); } } if (PP_AD_STATE_CFG & ad->state) ad->sts |= PP_AD_STS_DIRTY_CFG; if (PP_AD_STATE_INIT & ad->state) ad->sts |= PP_AD_STS_DIRTY_INIT; if ((PP_AD_STATE_DATA & ad->state) && (ad->sts & PP_STS_ENABLE)) ad->sts |= PP_AD_STS_DIRTY_DATA; if (PP_AD_STATE_RUN & ad->state) ad->state &= ~PP_AD_STATE_VSYNC; mutex_unlock(&ad->lock); return 0; } static int mdss_mdp_pp_dt_parse(struct device *dev) { int ret = -EINVAL; struct device_node *node; struct mdss_data_type *mdata; u32 prop_val; mdata = mdss_mdp_get_mdata(); if (dev && mdata) { /* initialize offsets to U32_MAX */ memset(&mdata->pp_block_off, U8_MAX, sizeof(mdata->pp_block_off)); node = of_get_child_by_name(dev->of_node, "qcom,mdss-pp-offsets"); if (node) { ret = of_property_read_u32(node, "qcom,mdss-sspp-mdss-igc-lut-off", &prop_val); if (ret) { pr_err("read property %s failed ret %d\n", "qcom,mdss-sspp-mdss-igc-lut-off", ret); goto bail_out; } else { mdata->pp_block_off.sspp_igc_lut_off = prop_val; } ret = of_property_read_u32(node, "qcom,mdss-sspp-vig-pcc-off", &prop_val); if (ret) { pr_err("read property %s failed ret %d\n", "qcom,mdss-sspp-vig-pcc-off", ret); goto bail_out; } else { mdata->pp_block_off.vig_pcc_off = prop_val; } ret = of_property_read_u32(node, "qcom,mdss-sspp-rgb-pcc-off", &prop_val); if (ret) { pr_err("read property %s failed ret %d\n", "qcom,mdss-sspp-rgb-pcc-off", ret); goto bail_out; } else { mdata->pp_block_off.rgb_pcc_off = prop_val; } ret = of_property_read_u32(node, "qcom,mdss-sspp-dma-pcc-off", &prop_val); if (ret) { pr_err("read property %s failed ret %d\n", "qcom,mdss-sspp-dma-pcc-off", ret); goto bail_out; } else { mdata->pp_block_off.dma_pcc_off = prop_val; } ret = of_property_read_u32(node, "qcom,mdss-lm-pgc-off", &prop_val); if (ret) { pr_err("read property %s failed ret %d\n", "qcom,mdss-lm-pgc-off", ret); goto bail_out; } else { mdata->pp_block_off.lm_pgc_off = prop_val; } ret = of_property_read_u32(node, "qcom,mdss-dspp-gamut-off", &prop_val); if (ret) { pr_debug("Could not read/find %s prop ret %d\n", "qcom,mdss-dspp-gamut-off", ret); mdata->pp_block_off.dspp_gamut_off = U32_MAX; } else { mdata->pp_block_off.dspp_gamut_off = prop_val; } ret = of_property_read_u32(node, "qcom,mdss-dspp-pcc-off", &prop_val); if (ret) { pr_err("read property %s failed ret %d\n", "qcom,mdss-dspp-pcc-off", ret); goto bail_out; } else { mdata->pp_block_off.dspp_pcc_off = prop_val; } ret = of_property_read_u32(node, "qcom,mdss-dspp-pgc-off", &prop_val); if (ret) { pr_err("read property %s failed ret %d\n", "qcom,mdss-dspp-pgc-off", ret); goto bail_out; } else { mdata->pp_block_off.dspp_pgc_off = prop_val; } } else { pr_debug("offsets are not supported\n"); ret = 0; } } else { pr_err("invalid dev %p mdata %p\n", dev, mdata); ret = -EINVAL; } bail_out: return ret; } 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; struct pp_hist_col_info *hist; void *ret_ptr = NULL; u32 ctl_off = 0; if (!mdata) return -EPERM; mdata->pp_reg_bus_clt = mdss_reg_bus_vote_client_create("pp\0"); if (IS_ERR_OR_NULL(mdata->pp_reg_bus_clt)) pr_err("bus client register failed\n"); 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!\n", __func__); ret = -ENOMEM; } else { if (mdss_mdp_pp_dt_parse(dev)) pr_info("No PP info in device tree\n"); ret_ptr = pp_get_driver_ops(&pp_driver_ops); if (IS_ERR(ret_ptr)) { pr_err("pp_get_driver_ops failed, ret=%d\n", (int) PTR_ERR(ret_ptr)); ret = PTR_ERR(ret_ptr); goto pp_exit; } else { mdss_pp_res->pp_data_res = ret_ptr; pp_ops = pp_driver_ops.pp_ops; } hist = devm_kzalloc(dev, sizeof(struct pp_hist_col_info) * mdata->ndspp, GFP_KERNEL); if (hist == NULL) { pr_err("dspp histogram allocation failed!\n"); ret = -ENOMEM; goto pp_exit; } for (i = 0; i < mdata->ndspp; i++) { mutex_init(&hist[i].hist_mutex); spin_lock_init(&hist[i].hist_lock); hist[i].intr_shift = (i * 4) + 12; if (pp_driver_ops.get_hist_offset) { ret = pp_driver_ops.get_hist_offset( DSPP, &ctl_off); if (ret) { pr_err("get_hist_offset ret %d\n", ret); goto hist_exit; } hist[i].base = i < mdata->ndspp ? mdss_mdp_get_dspp_addr_off(i) + ctl_off : NULL; } else { hist[i].base = i < mdata->ndspp ? mdss_mdp_get_dspp_addr_off(i) + MDSS_MDP_REG_DSPP_HIST_CTL_BASE : NULL; } } if (mdata->ndspp == 4) hist[3].intr_shift = 22; mdss_pp_res->dspp_hist = hist; } } if (mdata && mdata->vig_pipes) { 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); vig[i].pp_res.hist.intr_shift = (vig[i].num * 4); if (i == 3) vig[i].pp_res.hist.intr_shift = 10; if (pp_driver_ops.get_hist_offset) { ret = pp_driver_ops.get_hist_offset( DSPP, &ctl_off); if (ret) { pr_err("get_hist_offset ret %d\n", ret); goto hist_exit; } vig[i].pp_res.hist.base = vig[i].base + ctl_off; } else { vig[i].pp_res.hist.base = vig[i].base + MDSS_MDP_REG_VIG_HIST_CTL_BASE; } } } mutex_unlock(&mdss_pp_mutex); return ret; hist_exit: devm_kfree(dev, hist); pp_exit: devm_kfree(dev, mdss_pp_res); mutex_unlock(&mdss_pp_mutex); return ret; } void mdss_mdp_pp_term(struct device *dev) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (mdss_pp_res) { mutex_lock(&mdss_pp_mutex); devm_kfree(dev, mdss_pp_res->dspp_hist); devm_kfree(dev, mdss_pp_res); mdss_pp_res = NULL; mutex_unlock(&mdss_pp_mutex); } mdss_reg_bus_vote_client_destroy(mdata->pp_reg_bus_clt); mdata->pp_reg_bus_clt = NULL; } int mdss_mdp_pp_overlay_init(struct msm_fb_data_type *mfd) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (!mfd || !mdata) { pr_err("Invalid mfd %p mdata %p\n", mfd, mdata); return -EPERM; } if (mdata->nad_cfgs) mfd->mdp.ad_calc_bl = pp_ad_calc_bl; mfd->mdp.pp_release_fnc = pp_mfd_release_all; return 0; } int mdss_mdp_pp_default_overlay_config(struct msm_fb_data_type *mfd, struct mdss_panel_data *pdata) { int ret = 0; if (!mfd || !pdata) { pr_err("Invalid parameters mfd %p pdata %p\n", mfd, pdata); return -EINVAL; } ret = mdss_mdp_panel_default_dither_config(mfd, pdata->panel_info.bpp); if (ret) pr_err("Unable to configure default dither on fb%d ret %d\n", mfd->index, ret); if (pdata->panel_info.type == DTV_PANEL) { ret = mdss_mdp_limited_lut_igc_config(mfd); if (ret) pr_err("Unable to configure DTV panel default IGC ret %d\n", ret); } return ret; } static bool pp_ad_bl_threshold_check(int al_thresh, int base, int prev_bl, int curr_bl) { int bl_thresh = 0, diff = 0; bool ret = false; pr_debug("al_thresh = %d, base = %d\n", al_thresh, base); if (base <= 0) { pr_debug("Invalid base for threshold calculation %d\n", base); return ret; } bl_thresh = (curr_bl * al_thresh) / (base * 4); diff = (curr_bl > prev_bl) ? (curr_bl - prev_bl) : (prev_bl - curr_bl); ret = (diff > bl_thresh) ? true : false; pr_debug("prev_bl =%d, curr_bl = %d, bl_thresh = %d, diff = %d, ret = %d\n", prev_bl, curr_bl, bl_thresh, diff, ret); return ret; } static int pp_ad_calc_bl(struct msm_fb_data_type *mfd, int bl_in, int *bl_out, bool *bl_out_notify) { int ret = -1; int temp = bl_in; u32 ad_bl_out = 0; struct mdss_ad_info *ad; ret = mdss_mdp_get_ad(mfd, &ad); if (ret == -ENODEV || ret == -EPERM) { pr_debug("AD not supported on device, disp num %d\n", mfd->index); return 0; } else if (ret || !ad) { pr_err("Failed to get ad info: ret = %d, ad = 0x%p.\n", ret, ad); return ret; } mutex_lock(&ad->lock); if (!mfd->ad_bl_level) mfd->ad_bl_level = bl_in; if (!(ad->state & PP_AD_STATE_RUN)) { pr_debug("AD is not running.\n"); mutex_unlock(&ad->lock); return -EPERM; } if (!ad->bl_mfd || !ad->bl_mfd->panel_info || !ad->bl_att_lut) { pr_err("Invalid ad info: bl_mfd = 0x%p, ad->bl_mfd->panel_info = 0x%p, bl_att_lut = 0x%p\n", ad->bl_mfd, (!ad->bl_mfd) ? NULL : ad->bl_mfd->panel_info, ad->bl_att_lut); mutex_unlock(&ad->lock); return -EINVAL; } ret = pp_ad_linearize_bl(ad, bl_in, &temp, MDP_PP_AD_BL_LINEAR); if (ret) { pr_err("Failed to linearize BL: %d\n", ret); mutex_unlock(&ad->lock); return ret; } ret = pp_ad_attenuate_bl(ad, temp, &temp); if (ret) { pr_err("Failed to attenuate BL: %d\n", ret); mutex_unlock(&ad->lock); return ret; } ad_bl_out = temp; ret = pp_ad_linearize_bl(ad, temp, &temp, MDP_PP_AD_BL_LINEAR_INV); if (ret) { pr_err("Failed to inverse linearize BL: %d\n", ret); mutex_unlock(&ad->lock); return ret; } *bl_out = temp; if (pp_ad_bl_threshold_check(ad->init.al_thresh, ad->init.alpha_base, ad->last_bl, ad_bl_out)) { mfd->ad_bl_level = ad_bl_out; pr_debug("backlight send to AD block: %d\n", mfd->ad_bl_level); *bl_out_notify = true; pp_ad_invalidate_input(mfd); } mutex_unlock(&ad->lock); return 0; } 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]; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); mixer_cnt = mdss_mdp_get_ctl_mixers(disp_num, mixer_id); if (!mixer_cnt || !mdata) return -EPERM; /* only read the first mixer */ for (i = 0; i < mixer_cnt; i++) { if (mixer_id[i] < mdata->nmixers_intf) break; } if (i >= mixer_cnt || mixer_id[i] >= mdata->ndspp) return -EPERM; *dspp_num = mixer_id[i]; return 0; } int mdss_mdp_pa_config(struct msm_fb_data_type *mfd, struct mdp_pa_cfg_data *config, u32 *copyback) { int ret = 0; u32 disp_num, dspp_num = 0; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); char __iomem *pa_addr; if (mdata->mdp_rev >= MDSS_MDP_HW_REV_103) return -EINVAL; ret = pp_validate_dspp_mfd_block(mfd, config->block); if (ret) { pr_err("Invalid block %d mfd index %d, ret %d\n", config->block, (mfd ? mfd->index : -1), ret); return ret; } 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("no dspp connects to disp %d\n", disp_num); goto pa_config_exit; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); pa_addr = mdss_mdp_get_dspp_addr_off(dspp_num) + MDSS_MDP_REG_DSPP_PA_BASE; config->pa_data.hue_adj = readl_relaxed(pa_addr); pa_addr += 4; config->pa_data.sat_adj = readl_relaxed(pa_addr); pa_addr += 4; config->pa_data.val_adj = readl_relaxed(pa_addr); pa_addr += 4; config->pa_data.cont_adj = readl_relaxed(pa_addr); *copyback = 1; mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); } 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; } int mdss_mdp_pa_v2_config(struct msm_fb_data_type *mfd, struct mdp_pa_v2_cfg_data *config, u32 *copyback) { int ret = 0; u32 disp_num, dspp_num = 0; char __iomem *pa_addr; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdp_pa_v2_cfg_data *pa_v2_cache = NULL; struct mdp_pp_cache_res res_cache; uint32_t flags = 0; if (mdata->mdp_rev < MDSS_MDP_HW_REV_103) return -EINVAL; ret = pp_validate_dspp_mfd_block(mfd, config->block); if (ret) { pr_err("Invalid block %d mfd index %d, ret %d\n", config->block, (mfd ? mfd->index : -1), ret); return ret; } if (pp_ops[PA].pp_set_config) flags = config->flags; else flags = config->pa_v2_data.flags; if ((flags & MDSS_PP_SPLIT_MASK) == MDSS_PP_SPLIT_MASK) { pr_warn("Can't set both split bits\n"); return -EINVAL; } mutex_lock(&mdss_pp_mutex); disp_num = config->block - MDP_LOGICAL_BLOCK_DISP_0; if (flags & MDP_PP_OPS_READ) { ret = pp_get_dspp_num(disp_num, &dspp_num); if (ret) { pr_err("no dspp connects to disp %d\n", disp_num); goto pa_config_exit; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); pa_addr = mdss_mdp_get_dspp_addr_off(dspp_num); if (IS_ERR(pa_addr)) { ret = PTR_ERR(pa_addr); goto pa_clk_off; } if (pp_ops[PA].pp_get_config) { ret = pp_ops[PA].pp_get_config(pa_addr, config, DSPP, disp_num); if (ret) pr_err("PA get config failed %d\n", ret); } else { pa_addr += MDSS_MDP_REG_DSPP_PA_BASE; ret = pp_read_pa_v2_regs(pa_addr, &config->pa_v2_data, disp_num); if (ret) goto pa_config_exit; *copyback = 1; } pa_clk_off: mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); } else { if (pp_ops[PA].pp_set_config) { pr_debug("version of PA is %d\n", config->version); res_cache.block = DSPP; res_cache.mdss_pp_res = mdss_pp_res; res_cache.pipe_res = NULL; ret = pp_pa_cache_params(config, &res_cache); if (ret) { pr_err("PA config failed version %d ret %d\n", config->version, ret); ret = -EFAULT; goto pa_config_exit; } } else { if (flags & MDP_PP_PA_SIX_ZONE_ENABLE) { ret = pp_copy_pa_six_zone_lut(config, disp_num); if (ret) { pr_err("PA copy six zone lut failed ret %d\n", ret); goto pa_config_exit; } } pa_v2_cache = &mdss_pp_res->pa_v2_disp_cfg[disp_num]; *pa_v2_cache = *config; pa_v2_cache->pa_v2_data.six_zone_curve_p0 = mdss_pp_res->six_zone_lut_curve_p0[disp_num]; pa_v2_cache->pa_v2_data.six_zone_curve_p1 = mdss_pp_res->six_zone_lut_curve_p1[disp_num]; } mdss_pp_res->pp_disp_flags[disp_num] |= PP_FLAGS_DIRTY_PA; } pa_config_exit: mutex_unlock(&mdss_pp_mutex); return ret; } static int pp_read_pa_v2_regs(char __iomem *addr, struct mdp_pa_v2_data *pa_v2_config, u32 disp_num) { int i; u32 data; if (pa_v2_config->flags & MDP_PP_PA_HUE_ENABLE) pa_v2_config->global_hue_adj = readl_relaxed(addr); addr += 4; if (pa_v2_config->flags & MDP_PP_PA_SAT_ENABLE) pa_v2_config->global_sat_adj = readl_relaxed(addr); addr += 4; if (pa_v2_config->flags & MDP_PP_PA_VAL_ENABLE) pa_v2_config->global_val_adj = readl_relaxed(addr); addr += 4; if (pa_v2_config->flags & MDP_PP_PA_CONT_ENABLE) pa_v2_config->global_cont_adj = readl_relaxed(addr); addr += 4; /* Six zone LUT and thresh data */ if (pa_v2_config->flags & MDP_PP_PA_SIX_ZONE_ENABLE) { if (pa_v2_config->six_zone_len != MDP_SIX_ZONE_LUT_SIZE) return -EINVAL; data = (3 << 25); writel_relaxed(data, addr); for (i = 0; i < MDP_SIX_ZONE_LUT_SIZE; i++) { addr += 4; mdss_pp_res->six_zone_lut_curve_p1[disp_num][i] = readl_relaxed(addr); addr -= 4; mdss_pp_res->six_zone_lut_curve_p0[disp_num][i] = readl_relaxed(addr) & 0xFFF; } if (copy_to_user(pa_v2_config->six_zone_curve_p0, &mdss_pp_res->six_zone_lut_curve_p0[disp_num][0], pa_v2_config->six_zone_len * sizeof(u32))) { return -EFAULT; } if (copy_to_user(pa_v2_config->six_zone_curve_p1, &mdss_pp_res->six_zone_lut_curve_p1[disp_num][0], pa_v2_config->six_zone_len * sizeof(u32))) { return -EFAULT; } addr += 8; pa_v2_config->six_zone_thresh = readl_relaxed(addr); addr += 4; } else { addr += 12; } /* Skin memory color config registers */ if (pa_v2_config->flags & MDP_PP_PA_SKIN_ENABLE) pp_read_pa_mem_col_regs(addr, &pa_v2_config->skin_cfg); addr += 0x14; /* Sky memory color config registers */ if (pa_v2_config->flags & MDP_PP_PA_SKY_ENABLE) pp_read_pa_mem_col_regs(addr, &pa_v2_config->sky_cfg); addr += 0x14; /* Foliage memory color config registers */ if (pa_v2_config->flags & MDP_PP_PA_FOL_ENABLE) pp_read_pa_mem_col_regs(addr, &pa_v2_config->fol_cfg); return 0; } static void pp_read_pa_mem_col_regs(char __iomem *addr, struct mdp_pa_mem_col_cfg *mem_col_cfg) { mem_col_cfg->color_adjust_p0 = readl_relaxed(addr); addr += 4; mem_col_cfg->color_adjust_p1 = readl_relaxed(addr); addr += 4; mem_col_cfg->hue_region = readl_relaxed(addr); addr += 4; mem_col_cfg->sat_region = readl_relaxed(addr); addr += 4; mem_col_cfg->val_region = readl_relaxed(addr); } static int pp_copy_pa_six_zone_lut(struct mdp_pa_v2_cfg_data *pa_v2_config, u32 disp_num) { if (pa_v2_config->pa_v2_data.six_zone_len != MDP_SIX_ZONE_LUT_SIZE) return -EINVAL; if (copy_from_user(&mdss_pp_res->six_zone_lut_curve_p0[disp_num][0], pa_v2_config->pa_v2_data.six_zone_curve_p0, pa_v2_config->pa_v2_data.six_zone_len * sizeof(u32))) { return -EFAULT; } if (copy_from_user(&mdss_pp_res->six_zone_lut_curve_p1[disp_num][0], pa_v2_config->pa_v2_data.six_zone_curve_p1, pa_v2_config->pa_v2_data.six_zone_len * sizeof(u32))) { return -EFAULT; } return 0; } static void pp_read_pcc_regs(char __iomem *addr, struct mdp_pcc_cfg_data *cfg_ptr) { cfg_ptr->r.c = readl_relaxed(addr); cfg_ptr->g.c = readl_relaxed(addr + 4); cfg_ptr->b.c = readl_relaxed(addr + 8); addr += 0x10; cfg_ptr->r.r = readl_relaxed(addr); cfg_ptr->g.r = readl_relaxed(addr + 4); cfg_ptr->b.r = readl_relaxed(addr + 8); addr += 0x10; cfg_ptr->r.g = readl_relaxed(addr); cfg_ptr->g.g = readl_relaxed(addr + 4); cfg_ptr->b.g = readl_relaxed(addr + 8); addr += 0x10; cfg_ptr->r.b = readl_relaxed(addr); cfg_ptr->g.b = readl_relaxed(addr + 4); cfg_ptr->b.b = readl_relaxed(addr + 8); addr += 0x10; cfg_ptr->r.rr = readl_relaxed(addr); cfg_ptr->g.rr = readl_relaxed(addr + 4); cfg_ptr->b.rr = readl_relaxed(addr + 8); addr += 0x10; cfg_ptr->r.rg = readl_relaxed(addr); cfg_ptr->g.rg = readl_relaxed(addr + 4); cfg_ptr->b.rg = readl_relaxed(addr + 8); addr += 0x10; cfg_ptr->r.rb = readl_relaxed(addr); cfg_ptr->g.rb = readl_relaxed(addr + 4); cfg_ptr->b.rb = readl_relaxed(addr + 8); addr += 0x10; cfg_ptr->r.gg = readl_relaxed(addr); cfg_ptr->g.gg = readl_relaxed(addr + 4); cfg_ptr->b.gg = readl_relaxed(addr + 8); addr += 0x10; cfg_ptr->r.gb = readl_relaxed(addr); cfg_ptr->g.gb = readl_relaxed(addr + 4); cfg_ptr->b.gb = readl_relaxed(addr + 8); addr += 0x10; cfg_ptr->r.bb = readl_relaxed(addr); cfg_ptr->g.bb = readl_relaxed(addr + 4); cfg_ptr->b.bb = readl_relaxed(addr + 8); addr += 0x10; cfg_ptr->r.rgb_0 = readl_relaxed(addr); cfg_ptr->g.rgb_0 = readl_relaxed(addr + 4); cfg_ptr->b.rgb_0 = readl_relaxed(addr + 8); addr += 0x10; cfg_ptr->r.rgb_1 = readl_relaxed(addr); cfg_ptr->g.rgb_1 = readl_relaxed(addr + 4); cfg_ptr->b.rgb_1 = readl_relaxed(addr + 8); } static void pp_update_pcc_regs(char __iomem *addr, struct mdp_pcc_cfg_data *cfg_ptr) { writel_relaxed(cfg_ptr->r.c, addr); writel_relaxed(cfg_ptr->g.c, addr + 4); writel_relaxed(cfg_ptr->b.c, addr + 8); addr += 0x10; writel_relaxed(cfg_ptr->r.r, addr); writel_relaxed(cfg_ptr->g.r, addr + 4); writel_relaxed(cfg_ptr->b.r, addr + 8); addr += 0x10; writel_relaxed(cfg_ptr->r.g, addr); writel_relaxed(cfg_ptr->g.g, addr + 4); writel_relaxed(cfg_ptr->b.g, addr + 8); addr += 0x10; writel_relaxed(cfg_ptr->r.b, addr); writel_relaxed(cfg_ptr->g.b, addr + 4); writel_relaxed(cfg_ptr->b.b, addr + 8); addr += 0x10; writel_relaxed(cfg_ptr->r.rr, addr); writel_relaxed(cfg_ptr->g.rr, addr + 4); writel_relaxed(cfg_ptr->b.rr, addr + 8); addr += 0x10; writel_relaxed(cfg_ptr->r.rg, addr); writel_relaxed(cfg_ptr->g.rg, addr + 4); writel_relaxed(cfg_ptr->b.rg, addr + 8); addr += 0x10; writel_relaxed(cfg_ptr->r.rb, addr); writel_relaxed(cfg_ptr->g.rb, addr + 4); writel_relaxed(cfg_ptr->b.rb, addr + 8); addr += 0x10; writel_relaxed(cfg_ptr->r.gg, addr); writel_relaxed(cfg_ptr->g.gg, addr + 4); writel_relaxed(cfg_ptr->b.gg, addr + 8); addr += 0x10; writel_relaxed(cfg_ptr->r.gb, addr); writel_relaxed(cfg_ptr->g.gb, addr + 4); writel_relaxed(cfg_ptr->b.gb, addr + 8); addr += 0x10; writel_relaxed(cfg_ptr->r.bb, addr); writel_relaxed(cfg_ptr->g.bb, addr + 4); writel_relaxed(cfg_ptr->b.bb, addr + 8); addr += 0x10; writel_relaxed(cfg_ptr->r.rgb_0, addr); writel_relaxed(cfg_ptr->g.rgb_0, addr + 4); writel_relaxed(cfg_ptr->b.rgb_0, addr + 8); addr += 0x10; writel_relaxed(cfg_ptr->r.rgb_1, addr); writel_relaxed(cfg_ptr->g.rgb_1, addr + 4); writel_relaxed(cfg_ptr->b.rgb_1, addr + 8); } int mdss_mdp_pcc_config(struct msm_fb_data_type *mfd, struct mdp_pcc_cfg_data *config, u32 *copyback) { int ret = 0; u32 disp_num, dspp_num = 0; char __iomem *addr; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdp_pp_cache_res res_cache; ret = pp_validate_dspp_mfd_block(mfd, config->block); if (ret) { pr_err("Invalid block %d mfd index %d, ret %d\n", config->block, (mfd ? mfd->index : -1), ret); return ret; } if ((config->ops & MDSS_PP_SPLIT_MASK) == MDSS_PP_SPLIT_MASK) { pr_warn("Can't set both split bits\n"); 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\n", __func__, disp_num); goto pcc_config_exit; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); if (pp_ops[PCC].pp_get_config) { addr = mdss_mdp_get_dspp_addr_off(disp_num); if (IS_ERR_OR_NULL(addr)) { pr_err("invalid dspp base_addr %p\n", addr); ret = -EINVAL; goto pcc_clk_off; } if (mdata->pp_block_off.dspp_pcc_off == U32_MAX) { pr_err("invalid pcc params off %d\n", mdata->pp_block_off.dspp_pcc_off); ret = -EINVAL; goto pcc_clk_off; } addr += mdata->pp_block_off.dspp_pcc_off; ret = pp_ops[PCC].pp_get_config(addr, config, DSPP, disp_num); if (ret) pr_err("pcc get config failed %d\n", ret); goto pcc_clk_off; } addr = mdss_mdp_get_dspp_addr_off(dspp_num) + MDSS_MDP_REG_DSPP_PCC_BASE; pp_read_pcc_regs(addr, config); *copyback = 1; pcc_clk_off: mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); } else { if (pp_ops[PCC].pp_set_config) { pr_debug("version of pcc is %d\n", config->version); res_cache.block = DSPP; res_cache.mdss_pp_res = mdss_pp_res; res_cache.pipe_res = NULL; ret = pp_pcc_cache_params(config, &res_cache); if (ret) { pr_err("pcc config failed version %d ret %d\n", config->version, ret); ret = -EFAULT; goto pcc_config_exit; } else goto pcc_set_dirty; } mdss_pp_res->pcc_disp_cfg[disp_num] = *config; pcc_set_dirty: 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_cached(struct mdp_igc_lut_data *cfg) { int i; u32 disp_num; disp_num = cfg->block - MDP_LOGICAL_BLOCK_DISP_0; for (i = 0; i < IGC_LUT_ENTRIES; i++) { cfg->c0_c1_data[i] = mdss_pp_res->igc_disp_cfg[disp_num].c0_c1_data[i]; cfg->c2_data[i] = mdss_pp_res->igc_disp_cfg[disp_num].c2_data[i]; } } static void pp_read_igc_lut(struct mdp_igc_lut_data *cfg, char __iomem *addr, u32 blk_idx, int32_t total_idx) { int i; u32 data; int32_t mask = 0, idx = total_idx; while (idx > 0) { mask = (mask << 1) + 1; idx--; } /* INDEX_UPDATE & VALUE_UPDATEN */ data = (3 << 24) | (((~(1 << blk_idx)) & mask) << 28); writel_relaxed(data, addr); for (i = 0; i < cfg->len; i++) cfg->c0_c1_data[i] = readl_relaxed(addr) & 0xFFF; addr += 0x4; writel_relaxed(data, addr); for (i = 0; i < cfg->len; i++) cfg->c0_c1_data[i] |= (readl_relaxed(addr) & 0xFFF) << 16; addr += 0x4; writel_relaxed(data, addr); for (i = 0; i < cfg->len; i++) cfg->c2_data[i] = readl_relaxed(addr) & 0xFFF; } static void pp_update_igc_lut(struct mdp_igc_lut_data *cfg, char __iomem *addr, u32 blk_idx, u32 total_idx) { int i; u32 data; int32_t mask = 0, idx = total_idx; while (idx > 0) { mask = (mask << 1) + 1; idx--; } /* INDEX_UPDATE */ data = (1 << 25) | (((~(1 << blk_idx)) & mask) << 28); writel_relaxed((cfg->c0_c1_data[0] & 0xFFF) | data, addr); /* disable index update */ data &= ~(1 << 25); for (i = 1; i < cfg->len; i++) writel_relaxed((cfg->c0_c1_data[i] & 0xFFF) | data, addr); addr += 0x4; data |= (1 << 25); writel_relaxed(((cfg->c0_c1_data[0] >> 16) & 0xFFF) | data, addr); data &= ~(1 << 25); for (i = 1; i < cfg->len; i++) writel_relaxed(((cfg->c0_c1_data[i] >> 16) & 0xFFF) | data, addr); addr += 0x4; data |= (1 << 25); writel_relaxed((cfg->c2_data[0] & 0xFFF) | data, addr); data &= ~(1 << 25); for (i = 1; i < cfg->len; i++) writel_relaxed((cfg->c2_data[i] & 0xFFF) | data, addr); } static int mdss_mdp_limited_lut_igc_config(struct msm_fb_data_type *mfd) { int ret = 0; u32 copyback = 0; u32 copy_from_kernel = 1; struct mdp_igc_lut_data config; struct mdp_pp_feature_version igc_version = { .pp_feature = IGC, }; struct mdp_igc_lut_data_v1_7 igc_data; if (!mfd) return -EINVAL; if (!mdss_mdp_mfd_valid_dspp(mfd)) { pr_debug("IGC not supported on display num %d hw configuration\n", mfd->index); return 0; } ret = mdss_mdp_pp_get_version(&igc_version); if (ret) pr_err("failed to get default IGC version, ret %d\n", ret); config.version = igc_version.version_info; config.ops = MDP_PP_OPS_WRITE | MDP_PP_OPS_ENABLE; config.block = (mfd->index) + MDP_LOGICAL_BLOCK_DISP_0; switch (config.version) { case mdp_igc_v1_7: config.cfg_payload = &igc_data; igc_data.table_fmt = mdp_igc_custom; igc_data.len = IGC_LUT_ENTRIES; igc_data.c0_c1_data = igc_limited; igc_data.c2_data = igc_limited; break; case mdp_pp_legacy: default: config.cfg_payload = NULL; config.len = IGC_LUT_ENTRIES; config.c0_c1_data = igc_limited; config.c2_data = igc_limited; break; } ret = mdss_mdp_igc_lut_config(mfd, &config, ©back, copy_from_kernel); return ret; } int mdss_mdp_igc_lut_config(struct msm_fb_data_type *mfd, struct mdp_igc_lut_data *config, u32 *copyback, u32 copy_from_kernel) { int ret = 0; u32 tbl_idx, disp_num, dspp_num = 0; struct mdp_igc_lut_data local_cfg; char __iomem *igc_addr; struct mdp_pp_cache_res res_cache; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); ret = pp_validate_dspp_mfd_block(mfd, config->block); if (ret) { pr_err("Invalid block %d mfd index %d, ret %d\n", config->block, (mfd ? mfd->index : -1), ret); return ret; } if ((config->ops & MDSS_PP_SPLIT_MASK) == MDSS_PP_SPLIT_MASK) { pr_warn("Can't set both split bits\n"); return -EINVAL; } mutex_lock(&mdss_pp_mutex); disp_num = config->block - MDP_LOGICAL_BLOCK_DISP_0; if (config->ops & MDP_PP_OPS_READ) { if (config->len != IGC_LUT_ENTRIES && !pp_ops[IGC].pp_get_config) { pr_err("invalid len for IGC table for read %d\n", config->len); return -EINVAL; } ret = pp_get_dspp_num(disp_num, &dspp_num); if (ret) { pr_err("%s, no dspp connects to disp %d\n", __func__, disp_num); goto igc_config_exit; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); 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_addr = mdata->mdp_base + 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]; if (mdata->has_no_lut_read) pp_read_igc_lut_cached(&local_cfg); else { if (pp_ops[IGC].pp_get_config) { config->block = dspp_num; pp_ops[IGC].pp_get_config(igc_addr, config, DSPP, disp_num); goto clock_off; } else { pp_read_igc_lut(&local_cfg, igc_addr, dspp_num, mdata->ndspp); } } if (copy_to_user(config->c0_c1_data, local_cfg.c0_c1_data, config->len * sizeof(u32))) { ret = -EFAULT; mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); goto igc_config_exit; } if (copy_to_user(config->c2_data, local_cfg.c2_data, config->len * sizeof(u32))) { ret = -EFAULT; mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); goto igc_config_exit; } *copyback = 1; clock_off: mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); } else { if (pp_ops[IGC].pp_set_config) { res_cache.block = DSPP; res_cache.mdss_pp_res = mdss_pp_res; res_cache.pipe_res = NULL; ret = pp_igc_lut_cache_params(config, &res_cache, copy_from_kernel); if (ret) { pr_err("igc caching failed ret %d", ret); goto igc_config_exit; } else goto igc_set_dirty; } if (config->len != IGC_LUT_ENTRIES) { pr_err("invalid len for IGC table for write %d\n", config->len); return -EINVAL; } 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]; igc_set_dirty: 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(char __iomem *addr, struct mdp_ar_gc_lut_data *lut_data, uint8_t num_stages) { int i, start_idx, idx; start_idx = ((readl_relaxed(addr) >> 16) & 0xF) + 1; for (i = start_idx; i < GC_LUT_SEGMENTS; i++) { idx = min((uint8_t)i, (uint8_t)(num_stages-1)); writel_relaxed(lut_data[idx].x_start, addr); } for (i = 0; i < start_idx; i++) { idx = min((uint8_t)i, (uint8_t)(num_stages-1)); writel_relaxed(lut_data[idx].x_start, addr); } addr += 4; start_idx = ((readl_relaxed(addr) >> 16) & 0xF) + 1; for (i = start_idx; i < GC_LUT_SEGMENTS; i++) { idx = min((uint8_t)i, (uint8_t)(num_stages-1)); writel_relaxed(lut_data[idx].slope, addr); } for (i = 0; i < start_idx; i++) { idx = min((uint8_t)i, (uint8_t)(num_stages-1)); writel_relaxed(lut_data[idx].slope, addr); } addr += 4; start_idx = ((readl_relaxed(addr) >> 16) & 0xF) + 1; for (i = start_idx; i < GC_LUT_SEGMENTS; i++) { idx = min((uint8_t)i, (uint8_t)(num_stages-1)); writel_relaxed(lut_data[idx].offset, addr); } for (i = 0; i < start_idx; i++) { idx = min((uint8_t)i, (uint8_t)(num_stages-1)); writel_relaxed(lut_data[idx].offset, addr); } } static void pp_update_argc_lut(char __iomem *addr, struct mdp_pgc_lut_data *config) { pp_update_gc_one_lut(addr, config->r_data, config->num_r_stages); addr += 0x10; pp_update_gc_one_lut(addr, config->g_data, config->num_g_stages); addr += 0x10; pp_update_gc_one_lut(addr, config->b_data, config->num_b_stages); } static void pp_read_gc_one_lut(char __iomem *addr, struct mdp_ar_gc_lut_data *gc_data) { int i, start_idx, data; data = readl_relaxed(addr); start_idx = (data >> 16) & 0xF; gc_data[start_idx].x_start = data & 0xFFF; for (i = start_idx + 1; i < GC_LUT_SEGMENTS; i++) { data = readl_relaxed(addr); gc_data[i].x_start = data & 0xFFF; } for (i = 0; i < start_idx; i++) { data = readl_relaxed(addr); gc_data[i].x_start = data & 0xFFF; } addr += 4; data = readl_relaxed(addr); start_idx = (data >> 16) & 0xF; gc_data[start_idx].slope = data & 0x7FFF; for (i = start_idx + 1; i < GC_LUT_SEGMENTS; i++) { data = readl_relaxed(addr); gc_data[i].slope = data & 0x7FFF; } for (i = 0; i < start_idx; i++) { data = readl_relaxed(addr); gc_data[i].slope = data & 0x7FFF; } addr += 4; data = readl_relaxed(addr); start_idx = (data >> 16) & 0xF; gc_data[start_idx].offset = data & 0x7FFF; for (i = start_idx + 1; i < GC_LUT_SEGMENTS; i++) { data = readl_relaxed(addr); gc_data[i].offset = data & 0x7FFF; } for (i = 0; i < start_idx; i++) { data = readl_relaxed(addr); gc_data[i].offset = data & 0x7FFF; } } static int pp_read_argc_lut(struct mdp_pgc_lut_data *config, char __iomem *addr) { int ret = 0; pp_read_gc_one_lut(addr, config->r_data); addr += 0x10; pp_read_gc_one_lut(addr, config->g_data); addr += 0x10; pp_read_gc_one_lut(addr, config->b_data); return ret; } static int pp_read_argc_lut_cached(struct mdp_pgc_lut_data *config) { int i; u32 disp_num; struct mdp_pgc_lut_data *pgc_ptr; disp_num = PP_BLOCK(config->block) - MDP_LOGICAL_BLOCK_DISP_0; switch (PP_LOCAT(config->block)) { case MDSS_PP_LM_CFG: pgc_ptr = &mdss_pp_res->argc_disp_cfg[disp_num]; break; case MDSS_PP_DSPP_CFG: pgc_ptr = &mdss_pp_res->pgc_disp_cfg[disp_num]; break; default: return -EINVAL; } for (i = 0; i < GC_LUT_SEGMENTS; i++) { config->r_data[i].x_start = pgc_ptr->r_data[i].x_start; config->r_data[i].slope = pgc_ptr->r_data[i].slope; config->r_data[i].offset = pgc_ptr->r_data[i].offset; config->g_data[i].x_start = pgc_ptr->g_data[i].x_start; config->g_data[i].slope = pgc_ptr->g_data[i].slope; config->g_data[i].offset = pgc_ptr->g_data[i].offset; config->b_data[i].x_start = pgc_ptr->b_data[i].x_start; config->b_data[i].slope = pgc_ptr->b_data[i].slope; config->b_data[i].offset = pgc_ptr->b_data[i].offset; } return 0; } /* Note: Assumes that its inputs have been checked by calling function */ static void pp_update_hist_lut(char __iomem *addr, struct mdp_hist_lut_data *cfg) { int i; for (i = 0; i < ENHIST_LUT_ENTRIES; i++) writel_relaxed(cfg->data[i], addr); /* swap */ if (PP_LOCAT(cfg->block) == MDSS_PP_DSPP_CFG) writel_relaxed(1, addr + 4); else writel_relaxed(1, addr + 16); } int mdss_mdp_argc_config(struct msm_fb_data_type *mfd, struct mdp_pgc_lut_data *config, u32 *copyback) { int ret = 0; u32 disp_num, num = 0, is_lm = 0; struct mdp_pgc_lut_data local_cfg; struct mdp_pgc_lut_data *pgc_ptr; u32 tbl_size, r_size, g_size, b_size; char __iomem *argc_addr = 0; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdss_mdp_ctl *ctl = NULL; u32 dirty_flag = 0; if ((PP_BLOCK(config->block) < MDP_LOGICAL_BLOCK_DISP_0) || (PP_BLOCK(config->block) >= MDP_BLOCK_MAX)) { pr_err("invalid block value %d\n", PP_BLOCK(config->block)); return -EINVAL; } if ((config->flags & MDSS_PP_SPLIT_MASK) == MDSS_PP_SPLIT_MASK) { pr_warn("Can't set both split bits\n"); return -EINVAL; } if ((PP_BLOCK(config->block) - MDP_LOGICAL_BLOCK_DISP_0) != mfd->index) { pr_err("PP block %d does not match corresponding mfd index %d\n", config->block, mfd->index); return -EINVAL; } disp_num = PP_BLOCK(config->block) - MDP_LOGICAL_BLOCK_DISP_0; ctl = mfd_to_ctl(mfd); num = (ctl && ctl->mixer_left) ? ctl->mixer_left->num : -1; if (num < 0) { pr_err("invalid mfd index %d config\n", mfd->index); return -EPERM; } switch (PP_LOCAT(config->block)) { case MDSS_PP_LM_CFG: /* * LM GC LUT should be disabled before being rewritten. Skip * GC LUT config if it is already enabled. */ if ((mdss_pp_res->pp_disp_sts[disp_num].argc_sts & PP_STS_ENABLE) && !(config->flags & MDP_PP_OPS_DISABLE)) { pr_err("LM GC already enabled disp %d, skipping config\n", mfd->index); return -EPERM; } argc_addr = mdss_mdp_get_mixer_addr_off(num) + MDSS_MDP_REG_LM_GC_LUT_BASE; pgc_ptr = &mdss_pp_res->argc_disp_cfg[disp_num]; dirty_flag = PP_FLAGS_DIRTY_ARGC; break; case MDSS_PP_DSPP_CFG: if (!mdss_mdp_mfd_valid_dspp(mfd)) { pr_err("invalid mfd index %d for dspp config\n", mfd->index); return -EPERM; } argc_addr = mdss_mdp_get_dspp_addr_off(num) + MDSS_MDP_REG_DSPP_GC_BASE; pgc_ptr = &mdss_pp_res->pgc_disp_cfg[disp_num]; dirty_flag = PP_FLAGS_DIRTY_PGC; break; default: goto argc_config_exit; break; } mutex_lock(&mdss_pp_mutex); tbl_size = GC_LUT_SEGMENTS * sizeof(struct mdp_ar_gc_lut_data); if (config->flags & MDP_PP_OPS_READ) { mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); if (pp_ops[GC].pp_get_config) { char __iomem *temp_addr = NULL; u32 off = 0; is_lm = (PP_LOCAT(config->block) == MDSS_PP_LM_CFG); off = (is_lm) ? mdata->pp_block_off.lm_pgc_off : mdata->pp_block_off.dspp_pgc_off; if (off == U32_MAX) { pr_err("invalid offset for loc %d off %d\n", PP_LOCAT(config->block), U32_MAX); ret = -EINVAL; goto clock_off; } temp_addr = (is_lm) ? mdss_mdp_get_mixer_addr_off(num) : mdss_mdp_get_dspp_addr_off(num); if (IS_ERR_OR_NULL(temp_addr)) { pr_err("invalid addr is_lm %d\n", is_lm); ret = -EINVAL; goto clock_off; } temp_addr += off; ret = pp_ops[GC].pp_get_config(temp_addr, config, ((is_lm) ? LM : DSPP), disp_num); if (ret) pr_err("gc get config failed %d\n", ret); goto clock_off; } 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]; if (mdata->has_no_lut_read) pp_read_argc_lut_cached(&local_cfg); else pp_read_argc_lut(&local_cfg, argc_addr); 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); 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); 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); ret = -EFAULT; goto argc_config_exit; } *copyback = 1; clock_off: mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); } else { if (pp_ops[GC].pp_set_config) { pr_debug("version of gc is %d\n", config->version); is_lm = (PP_LOCAT(config->block) == MDSS_PP_LM_CFG); ret = pp_pgc_lut_cache_params(config, mdss_pp_res, ((is_lm) ? LM : DSPP)); if (ret) { pr_err("pgc cache params failed, ret %d\n", ret); goto argc_config_exit; } } else { r_size = config->num_r_stages * sizeof(struct mdp_ar_gc_lut_data); g_size = config->num_g_stages * sizeof(struct mdp_ar_gc_lut_data); b_size = config->num_b_stages * sizeof(struct mdp_ar_gc_lut_data); if (r_size > tbl_size || g_size > tbl_size || b_size > tbl_size || r_size == 0 || g_size == 0 || b_size == 0) { ret = -EINVAL; pr_warn("%s, number of rgb stages invalid\n", __func__); goto argc_config_exit; } if (copy_from_user(&mdss_pp_res->gc_lut_r[disp_num][0], config->r_data, r_size)) { ret = -EFAULT; goto argc_config_exit; } if (copy_from_user(&mdss_pp_res->gc_lut_g[disp_num][0], config->g_data, g_size)) { ret = -EFAULT; goto argc_config_exit; } if (copy_from_user(&mdss_pp_res->gc_lut_b[disp_num][0], config->b_data, b_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]; } mdss_pp_res->pp_disp_flags[disp_num] |= dirty_flag; } argc_config_exit: mutex_unlock(&mdss_pp_mutex); return ret; } int mdss_mdp_hist_lut_config(struct msm_fb_data_type *mfd, struct mdp_hist_lut_data *config, u32 *copyback) { int i, ret = 0; u32 disp_num, dspp_num = 0; char __iomem *hist_addr = NULL, *base_addr = NULL; struct mdp_pp_cache_res res_cache; ret = pp_validate_dspp_mfd_block(mfd, PP_BLOCK(config->block)); if (ret) { pr_err("Invalid block %d mfd index %d, ret %d\n", PP_BLOCK(config->block), (mfd ? mfd->index : -1), ret); return ret; } 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\n", __func__, disp_num); goto enhist_config_exit; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); base_addr = mdss_mdp_get_dspp_addr_off(dspp_num); if (IS_ERR_OR_NULL(base_addr)) { pr_err("invalid base addr %p\n", base_addr); ret = -EINVAL; goto hist_lut_clk_off; } hist_addr = base_addr + MDSS_MDP_REG_DSPP_HIST_LUT_BASE; if (pp_ops[HIST_LUT].pp_get_config) { ret = pp_ops[HIST_LUT].pp_get_config(base_addr, config, DSPP, disp_num); if (ret) pr_err("hist_lut get config failed %d\n", ret); goto hist_lut_clk_off; } for (i = 0; i < ENHIST_LUT_ENTRIES; i++) mdss_pp_res->enhist_lut[disp_num][i] = readl_relaxed(hist_addr); 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); ret = -EFAULT; goto enhist_config_exit; } *copyback = 1; hist_lut_clk_off: mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); } else { if (pp_ops[HIST_LUT].pp_set_config) { res_cache.block = DSPP; res_cache.mdss_pp_res = mdss_pp_res; res_cache.pipe_res = NULL; ret = pp_hist_lut_cache_params(config, &res_cache); if (ret) { pr_err("hist_lut config failed version %d ret %d\n", config->version, ret); ret = -EFAULT; goto enhist_config_exit; } else { goto enhist_set_dirty; } } 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]; enhist_set_dirty: mdss_pp_res->pp_disp_flags[disp_num] |= PP_FLAGS_DIRTY_ENHIST; } enhist_config_exit: mutex_unlock(&mdss_pp_mutex); return ret; } static int mdss_mdp_panel_default_dither_config(struct msm_fb_data_type *mfd, u32 panel_bpp) { int ret = 0; struct mdp_dither_cfg_data dither; struct mdp_pp_feature_version dither_version = { .pp_feature = DITHER, }; struct mdp_dither_data_v1_7 dither_data; if (!mdss_mdp_mfd_valid_dspp(mfd)) { pr_debug("dither config not supported on display num %d\n", mfd->index); return 0; } dither.block = mfd->index + MDP_LOGICAL_BLOCK_DISP_0; dither.flags = MDP_PP_OPS_DISABLE; ret = mdss_mdp_pp_get_version(&dither_version); if (ret) { pr_err("failed to get default dither version, ret %d\n", ret); return ret; } dither.version = dither_version.version_info; switch (panel_bpp) { case 18: dither.flags = MDP_PP_OPS_ENABLE | MDP_PP_OPS_WRITE; switch (dither.version) { case mdp_dither_v1_7: dither_data.g_y_depth = 2; dither_data.r_cr_depth = 2; dither_data.b_cb_depth = 2; dither.cfg_payload = &dither_data; break; case mdp_pp_legacy: default: dither.g_y_depth = 2; dither.r_cr_depth = 2; dither.b_cb_depth = 2; dither.cfg_payload = NULL; break; } break; default: dither.cfg_payload = NULL; break; } ret = mdss_mdp_dither_config(mfd, &dither, NULL, true); if (ret) pr_err("dither config failed, ret %d\n", ret); return ret; } int mdss_mdp_dither_config(struct msm_fb_data_type *mfd, struct mdp_dither_cfg_data *config, u32 *copyback, int copy_from_kernel) { u32 disp_num; int ret = 0; ret = pp_validate_dspp_mfd_block(mfd, config->block); if (ret) { pr_err("Invalid block %d mfd index %d, ret %d\n", config->block, (mfd ? mfd->index : -1), ret); return ret; } if (config->flags & MDP_PP_OPS_READ) { pr_err("Dither read is not supported\n"); return -EOPNOTSUPP; } if ((config->flags & MDSS_PP_SPLIT_MASK) == MDSS_PP_SPLIT_MASK) { pr_warn("Can't set both split bits\n"); return -EINVAL; } mutex_lock(&mdss_pp_mutex); disp_num = config->block - MDP_LOGICAL_BLOCK_DISP_0; if (pp_ops[DITHER].pp_set_config) { pr_debug("version of dither is %d\n", config->version); ret = pp_dither_cache_params(config, mdss_pp_res, copy_from_kernel); if (ret) { pr_err("dither config failed version %d ret %d\n", config->version, ret); goto dither_config_exit; } else { goto dither_set_dirty; } } mdss_pp_res->dither_disp_cfg[disp_num] = *config; dither_set_dirty: mdss_pp_res->pp_disp_flags[disp_num] |= PP_FLAGS_DIRTY_DITHER; dither_config_exit: mutex_unlock(&mdss_pp_mutex); return ret; } static int pp_gm_has_invalid_lut_size(struct mdp_gamut_cfg_data *config) { if (config->tbl_size[0] != GAMUT_T0_SIZE) return -EINVAL; if (config->tbl_size[1] != GAMUT_T1_SIZE) return -EINVAL; if (config->tbl_size[2] != GAMUT_T2_SIZE) return -EINVAL; if (config->tbl_size[3] != GAMUT_T3_SIZE) return -EINVAL; if (config->tbl_size[4] != GAMUT_T4_SIZE) return -EINVAL; if (config->tbl_size[5] != GAMUT_T5_SIZE) return -EINVAL; if (config->tbl_size[6] != GAMUT_T6_SIZE) return -EINVAL; if (config->tbl_size[7] != GAMUT_T7_SIZE) return -EINVAL; return 0; } int mdss_mdp_gamut_config(struct msm_fb_data_type *mfd, struct mdp_gamut_cfg_data *config, u32 *copyback) { int i, j, ret = 0; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); u32 disp_num, dspp_num = 0; uint16_t *tbl_off; struct mdp_gamut_cfg_data local_cfg; uint16_t *r_tbl[MDP_GAMUT_TABLE_NUM]; uint16_t *g_tbl[MDP_GAMUT_TABLE_NUM]; uint16_t *b_tbl[MDP_GAMUT_TABLE_NUM]; char __iomem *addr; u32 data = (3 << 20); ret = pp_validate_dspp_mfd_block(mfd, config->block); if (ret) { pr_err("Invalid block %d mfd index %d, ret %d\n", config->block, (mfd ? mfd->index : -1), ret); return ret; } if ((config->flags & MDSS_PP_SPLIT_MASK) == MDSS_PP_SPLIT_MASK) { pr_warn("Can't set both split bits\n"); 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\n", __func__, disp_num); goto gamut_config_exit; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); if (pp_ops[GAMUT].pp_get_config) { addr = mdss_mdp_get_dspp_addr_off(disp_num); if (IS_ERR_OR_NULL(addr)) { pr_err("invalid dspp base addr %p\n", addr); ret = -EINVAL; goto gamut_clk_off; } if (mdata->pp_block_off.dspp_gamut_off == U32_MAX) { pr_err("invalid gamut parmas off %d\n", mdata->pp_block_off.dspp_gamut_off); ret = -EINVAL; goto gamut_clk_off; } addr += mdata->pp_block_off.dspp_gamut_off; ret = pp_ops[GAMUT].pp_get_config(addr, config, DSPP, disp_num); if (ret) pr_err("gamut get config failed %d\n", ret); goto gamut_clk_off; } if (pp_gm_has_invalid_lut_size(config)) { pr_err("invalid lut size for gamut\n"); ret = -EINVAL; goto gamut_clk_off; } addr = mdss_mdp_get_dspp_addr_off(dspp_num) + MDSS_MDP_REG_DSPP_GAMUT_BASE; for (i = 0; i < MDP_GAMUT_TABLE_NUM; i++) { r_tbl[i] = kzalloc( sizeof(uint16_t) * config->tbl_size[i], GFP_KERNEL); if (!r_tbl[i]) { pr_err("%s: alloc failed\n", __func__); ret = -ENOMEM; goto gamut_clk_off; } /* Reset gamut LUT index to 0 */ writel_relaxed(data, addr); for (j = 0; j < config->tbl_size[i]; j++) r_tbl[i][j] = readl_relaxed(addr) & 0x1FFF; addr += 4; ret = copy_to_user(config->r_tbl[i], r_tbl[i], sizeof(uint16_t) * config->tbl_size[i]); kfree(r_tbl[i]); if (ret) { pr_err("%s: copy tbl to usr failed\n", __func__); ret = -EFAULT; goto gamut_clk_off; } } for (i = 0; i < MDP_GAMUT_TABLE_NUM; i++) { g_tbl[i] = kzalloc( sizeof(uint16_t) * config->tbl_size[i], GFP_KERNEL); if (!g_tbl[i]) { pr_err("%s: alloc failed\n", __func__); ret = -ENOMEM; goto gamut_clk_off; } /* Reset gamut LUT index to 0 */ writel_relaxed(data, addr); for (j = 0; j < config->tbl_size[i]; j++) g_tbl[i][j] = readl_relaxed(addr) & 0x1FFF; addr += 4; ret = copy_to_user(config->g_tbl[i], g_tbl[i], sizeof(uint16_t) * config->tbl_size[i]); kfree(g_tbl[i]); if (ret) { pr_err("%s: copy tbl to usr failed\n", __func__); ret = -EFAULT; goto gamut_clk_off; } } for (i = 0; i < MDP_GAMUT_TABLE_NUM; i++) { b_tbl[i] = kzalloc( sizeof(uint16_t) * config->tbl_size[i], GFP_KERNEL); if (!b_tbl[i]) { pr_err("%s: alloc failed\n", __func__); ret = -ENOMEM; goto gamut_clk_off; } /* Reset gamut LUT index to 0 */ writel_relaxed(data, addr); for (j = 0; j < config->tbl_size[i]; j++) b_tbl[i][j] = readl_relaxed(addr) & 0x1FFF; addr += 4; ret = copy_to_user(config->b_tbl[i], b_tbl[i], sizeof(uint16_t) * config->tbl_size[i]); kfree(b_tbl[i]); if (ret) { pr_err("%s: copy tbl to usr failed\n", __func__); ret = -EFAULT; goto gamut_clk_off; } } *copyback = 1; gamut_clk_off: mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); } else { if (pp_ops[GAMUT].pp_set_config) { pr_debug("version of gamut is %d\n", config->version); ret = pp_gamut_cache_params(config, mdss_pp_res); if (ret) { pr_err("gamut config failed version %d ret %d\n", config->version, ret); ret = -EFAULT; goto gamut_config_exit; } else { goto gamut_set_dirty; } } 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; gamut_set_dirty: mdss_pp_res->pp_disp_flags[disp_num] |= PP_FLAGS_DIRTY_GAMUT; } gamut_config_exit: mutex_unlock(&mdss_pp_mutex); return ret; } static u32 pp_hist_read(char __iomem *v_addr, struct pp_hist_col_info *hist_info) { int i, i_start; u32 sum = 0; u32 data; data = readl_relaxed(v_addr); i_start = data >> 24; hist_info->data[i_start] = data & 0xFFFFFF; sum += hist_info->data[i_start]; for (i = i_start + 1; i < HIST_V_SIZE; i++) { hist_info->data[i] = readl_relaxed(v_addr) & 0xFFFFFF; sum += hist_info->data[i]; } for (i = 0; i < i_start; i++) { hist_info->data[i] = readl_relaxed(v_addr) & 0xFFFFFF; sum += hist_info->data[i]; } hist_info->hist_cnt_read++; return sum; } /* Assumes that relevant clocks are enabled */ static int pp_hist_enable(struct pp_hist_col_info *hist_info, struct mdp_histogram_start_req *req, struct mdss_mdp_ctl *ctl) { unsigned long flag; int ret = 0; mutex_lock(&hist_info->hist_mutex); /* check if it is idle */ spin_lock_irqsave(&hist_info->hist_lock, flag); if (hist_info->col_en) { spin_unlock_irqrestore(&hist_info->hist_lock, flag); pr_err("%s Hist collection has already been enabled %p\n", __func__, hist_info->base); ret = -EBUSY; goto exit; } hist_info->col_state = HIST_IDLE; hist_info->col_en = true; hist_info->frame_cnt = req->frame_cnt; hist_info->hist_cnt_read = 0; hist_info->hist_cnt_sent = 0; hist_info->hist_cnt_time = 0; if (ctl && ctl->mfd) { hist_info->ctl = ctl; hist_info->disp_num = ctl->mfd->index + MDP_LOGICAL_BLOCK_DISP_0; } /* if hist v2, make sure HW is unlocked */ writel_relaxed(0, hist_info->base); spin_unlock_irqrestore(&hist_info->hist_lock, flag); exit: mutex_unlock(&hist_info->hist_mutex); return ret; } #define MDSS_MAX_HIST_BIN_SIZE 16777215 int mdss_mdp_hist_start(struct mdp_histogram_start_req *req) { 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]; u32 frame_size, intr_mask = 0; struct mdss_mdp_pipe *pipe; struct mdss_mdp_ctl *ctl; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); bool sspp_hist_supp = false; if (!mdss_is_ready()) return -EPROBE_DEFER; if (mdata->mdp_rev < MDSS_MDP_HW_REV_103) { pr_err("Unsupported mdp rev %d\n", mdata->mdp_rev); return -EOPNOTSUPP; } if (pp_driver_ops.is_sspp_hist_supp) sspp_hist_supp = pp_driver_ops.is_sspp_hist_supp(); if (!sspp_hist_supp && (PP_LOCAT(req->block) == MDSS_PP_SSPP_CFG)) { pr_warn("No histogram on SSPP\n"); ret = -EINVAL; goto hist_exit; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); 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\n", i); goto hist_stop_clk; } 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->num > MDSS_MDP_SSPP_VIG2) && (pipe->num != MDSS_MDP_SSPP_VIG3)) { ret = -EINVAL; pr_warn("Invalid Hist pipe (%d)\n", i); mdss_mdp_pipe_unmap(pipe); goto hist_stop_clk; } hist_info = &pipe->pp_res.hist; ret = pp_hist_enable(hist_info, req, NULL); intr_mask = 1 << hist_info->intr_shift; mdss_mdp_hist_intr_req(&mdata->hist_intr, intr_mask, true); mdss_mdp_pipe_unmap(pipe); } } else if (PP_LOCAT(req->block) == MDSS_PP_DSPP_CFG) { if ((PP_BLOCK(req->block) < MDP_LOGICAL_BLOCK_DISP_0) || (PP_BLOCK(req->block) >= MDP_BLOCK_MAX)) goto hist_stop_clk; 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\n", __func__, disp_num); ret = -EPERM; goto hist_stop_clk; } if (mixer_cnt > mdata->nmixers_intf) { pr_err("%s, Too many dspp connects to disp %d\n", __func__, mixer_cnt); ret = -EPERM; goto hist_stop_clk; } ctl = mdata->mixer_intf[mixer_id[0]].ctl; frame_size = (ctl->width * ctl->height); if (!frame_size || ((MDSS_MAX_HIST_BIN_SIZE / frame_size) < req->frame_cnt)) { pr_err("%s, too many frames for given display size, %d\n", __func__, req->frame_cnt); ret = -EINVAL; goto hist_stop_clk; } for (i = 0; i < mixer_cnt; i++) { dspp_num = mixer_id[i]; if (dspp_num >= mdata->ndspp) { ret = -EINVAL; pr_warn("Invalid dspp num %d\n", dspp_num); goto hist_stop_clk; } hist_info = &mdss_pp_res->dspp_hist[dspp_num]; ret = pp_hist_enable(hist_info, req, ctl); if (ret) { pr_err("failed to enable histogram dspp_num %d ret %d\n", dspp_num, ret); goto hist_stop_clk; } intr_mask |= 1 << hist_info->intr_shift; mdss_pp_res->pp_disp_flags[disp_num] |= PP_FLAGS_DIRTY_HIST_COL; } mdss_mdp_hist_intr_req(&mdata->hist_intr, intr_mask, true); } hist_stop_clk: mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); hist_exit: return ret; } static int pp_hist_disable(struct pp_hist_col_info *hist_info) { int ret = 0; unsigned long flag; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); u32 intr_mask = 1; mutex_lock(&hist_info->hist_mutex); spin_lock_irqsave(&hist_info->hist_lock, flag); if (hist_info->col_en == false) { spin_unlock_irqrestore(&hist_info->hist_lock, flag); pr_debug("Histogram already disabled (%p)\n", hist_info->base); ret = -EINVAL; goto exit; } hist_info->col_en = false; hist_info->col_state = HIST_UNKNOWN; hist_info->disp_num = 0; hist_info->ctl = NULL; /* make sure HW is unlocked */ writel_relaxed(0, hist_info->base); spin_unlock_irqrestore(&hist_info->hist_lock, flag); mdss_mdp_hist_intr_req(&mdata->hist_intr, intr_mask << hist_info->intr_shift, false); ret = 0; exit: mutex_unlock(&hist_info->hist_mutex); return ret; } int mdss_mdp_hist_stop(u32 block) { int i, ret = 0; u32 disp_num; struct pp_hist_col_info *hist_info; struct mdss_mdp_pipe *pipe; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (!mdata) return -EPERM; if (mdata->mdp_rev < MDSS_MDP_HW_REV_103) { pr_err("Unsupported mdp rev %d\n", mdata->mdp_rev); return -EOPNOTSUPP; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); if (PP_LOCAT(block) == MDSS_PP_SSPP_CFG) { i = MDSS_PP_ARG_MASK & block; if (!i) { pr_warn("Must pass pipe arguments, %d\n", 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)) { pr_warn("Invalid Hist pipe (%d)\n", i); continue; } else if ((pipe->num > MDSS_MDP_SSPP_VIG2) && (pipe->num != MDSS_MDP_SSPP_VIG3)) { mdss_mdp_pipe_unmap(pipe); pr_warn("Invalid Hist pipe (%d) pipe->num (%d)\n", i, pipe->num); continue; } hist_info = &pipe->pp_res.hist; ret = pp_hist_disable(hist_info); mdss_mdp_pipe_unmap(pipe); if (ret) goto hist_stop_clk; } } else if (PP_LOCAT(block) == MDSS_PP_DSPP_CFG) { if ((PP_BLOCK(block) < MDP_LOGICAL_BLOCK_DISP_0) || (PP_BLOCK(block) >= MDP_BLOCK_MAX)) goto hist_stop_clk; disp_num = PP_BLOCK(block); for (i = 0; i < mdata->ndspp; i++) { hist_info = &mdss_pp_res->dspp_hist[i]; if (disp_num != hist_info->disp_num) continue; ret = pp_hist_disable(hist_info); if (ret) goto hist_stop_clk; mdss_pp_res->pp_disp_flags[i] |= PP_FLAGS_DIRTY_HIST_COL; } } hist_stop_clk: mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); return ret; } /** * mdss_mdp_hist_intr_req() - Request changes the histogram interupts * @intr: structure containting state of interrupt register * @bits: the bits on interrupt register that should be changed * @en: true if bits should be set, false if bits should be cleared * * Adds or removes the bits from the interrupt request. * * Does not store reference count for each bit. I.e. a bit with multiple * enable requests can be disabled with a single disable request. * * Return: 0 if uneventful, errno on invalid input */ int mdss_mdp_hist_intr_req(struct mdss_intr *intr, u32 bits, bool en) { unsigned long flag; int ret = 0; if (!intr) { pr_err("NULL addr passed, %p\n", intr); return -EINVAL; } spin_lock_irqsave(&intr->lock, flag); if (en) intr->req |= bits; else intr->req &= ~bits; spin_unlock_irqrestore(&intr->lock, flag); mdss_mdp_hist_intr_setup(intr, MDSS_IRQ_REQ); return ret; } #define MDSS_INTR_STATE_ACTIVE 1 #define MDSS_INTR_STATE_NULL 0 #define MDSS_INTR_STATE_SUSPEND -1 /** * mdss_mdp_hist_intr_setup() - Manage intr and clk depending on requests. * @intr: structure containting state of intr reg * @state: MDSS_IRQ_SUSPEND if suspend is needed, * MDSS_IRQ_RESUME if resume is needed, * MDSS_IRQ_REQ if neither (i.e. requesting an interrupt) * * This function acts as a gatekeeper for the interrupt, making sure that the * MDP clocks are enabled while the interrupts are enabled to prevent * unclocked accesses. * * To reduce code repetition, 4 state transitions have been encoded here. Each * transition updates the interrupt's state structure (mdss_intr) to reflect * the which bits have been requested (intr->req), are currently enabled * (intr->curr), as well as defines which interrupt bits need to be enabled or * disabled ('en' and 'dis' respectively). The 4th state is not explicity * coded in the if/else chain, but is for MDSS_IRQ_REQ's when the interrupt * is in suspend, in which case, the only change required (intr->req being * updated) has already occured in the calling function. * * To control the clock, which can't be requested while holding the spinlock, * the inital state is compared with the exit state to detect when the * interrupt needs a clock. * * The clock requests surrounding the majority of this function serve to * enable the register writes to change the interrupt register, as well as to * prevent a race condition that could keep the clocks on (due to mdp_clk_cnt * never being decremented below 0) when a enable/disable occurs but the * disable requests the clocks disabled before the enable is able to request * the clocks enabled. * * Return: 0 if uneventful, errno on repeated action or invalid input */ int mdss_mdp_hist_intr_setup(struct mdss_intr *intr, int type) { unsigned long flag; int ret = 0, req_clk = 0; u32 en = 0, dis = 0; u32 diff, init_curr; int init_state; if (!intr) { WARN(1, "NULL intr pointer\n"); return -EINVAL; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); spin_lock_irqsave(&intr->lock, flag); init_state = intr->state; init_curr = intr->curr; if (type == MDSS_IRQ_RESUME) { /* resume intrs */ if (intr->state == MDSS_INTR_STATE_ACTIVE) { ret = -EPERM; goto exit; } en = intr->req; dis = 0; intr->curr = intr->req; intr->state = intr->curr ? MDSS_INTR_STATE_ACTIVE : MDSS_INTR_STATE_NULL; } else if (type == MDSS_IRQ_SUSPEND) { /* suspend intrs */ if (intr->state == MDSS_INTR_STATE_SUSPEND) { ret = -EPERM; goto exit; } en = 0; dis = intr->curr; intr->curr = 0; intr->state = MDSS_INTR_STATE_SUSPEND; } else if (intr->state != MDSS_IRQ_SUSPEND && type == MDSS_IRQ_REQ) { /* Not resuming/suspending or in suspend state */ diff = intr->req ^ intr->curr; en = diff & ~intr->curr; dis = diff & ~intr->req; intr->curr = intr->req; intr->state = intr->curr ? MDSS_INTR_STATE_ACTIVE : MDSS_INTR_STATE_NULL; } if (en) mdss_mdp_hist_irq_enable(en); if (dis) mdss_mdp_hist_irq_disable(dis); if ((init_state != MDSS_INTR_STATE_ACTIVE) && (intr->state == MDSS_INTR_STATE_ACTIVE)) req_clk = 1; else if ((init_state == MDSS_INTR_STATE_ACTIVE) && (intr->state != MDSS_INTR_STATE_ACTIVE)) req_clk = -1; exit: spin_unlock_irqrestore(&intr->lock, flag); if (req_clk < 0) mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); else if (req_clk > 0) mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); return ret; } static int pp_hist_collect(struct mdp_histogram_data *hist, struct pp_hist_col_info *hist_info, char __iomem *ctl_base, u32 expect_sum, u32 block) { int ret = 0; u32 sum; char __iomem *v_base = NULL; unsigned long flag; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (!mdata) return -EPERM; mutex_lock(&hist_info->hist_mutex); spin_lock_irqsave(&hist_info->hist_lock, flag); if ((hist_info->col_en == 0) || (hist_info->col_state != HIST_READY)) { pr_err("invalid params for histogram hist_info->col_en %d hist_info->col_state %d", hist_info->col_en, hist_info->col_state); ret = -ENODATA; spin_unlock_irqrestore(&hist_info->hist_lock, flag); goto hist_collect_exit; } spin_unlock_irqrestore(&hist_info->hist_lock, flag); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); if (pp_ops[HIST].pp_get_config) { sum = pp_ops[HIST].pp_get_config(ctl_base, hist_info, block, 0); } else { if (block == DSPP) v_base = ctl_base + MDSS_MDP_REG_DSPP_HIST_DATA_BASE; else if (block == SSPP_VIG) v_base = ctl_base + MDSS_MDP_REG_VIG_HIST_CTL_BASE; sum = pp_hist_read(v_base, hist_info); } writel_relaxed(0, hist_info->base); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); if (sum < 0) { pr_err("failed to get the hist data, sum = %d\n", sum); ret = sum; } else if (expect_sum && sum != expect_sum) { pr_err("hist error: bin sum incorrect! (%d/%d)\n", sum, expect_sum); ret = -EINVAL; } hist_collect_exit: mutex_unlock(&hist_info->hist_mutex); return ret; } int mdss_mdp_hist_collect(struct mdp_histogram_data *hist) { int i, j, off, ret = 0, temp_ret = 0; struct pp_hist_col_info *hist_info; struct pp_hist_col_info *hists[MDSS_MDP_INTF_MAX_LAYERMIXER]; 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; u32 exp_sum = 0; struct mdss_mdp_pipe *pipe; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); unsigned long flag; if (mdata->mdp_rev < MDSS_MDP_HW_REV_103) { pr_err("Unsupported mdp rev %d\n", mdata->mdp_rev); return -EOPNOTSUPP; } if (PP_LOCAT(hist->block) == MDSS_PP_DSPP_CFG) { 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\n", __func__, disp_num); ret = -EPERM; goto hist_collect_exit; } if (hist_cnt > mdata->nmixers_intf) { pr_err("%s, Too many dspp connects to disp %d\n", __func__, hist_cnt); ret = -EPERM; goto hist_collect_exit; } for (i = 0; i < hist_cnt; i++) { dspp_num = mixer_id[i]; if (dspp_num >= mdata->ndspp) { ret = -EINVAL; pr_warn("Invalid dspp num %d\n", dspp_num); goto hist_collect_exit; } hists[i] = &mdss_pp_res->dspp_hist[dspp_num]; } for (i = 0; i < hist_cnt; i++) { dspp_num = mixer_id[i]; ctl_base = mdss_mdp_get_dspp_addr_off(dspp_num); exp_sum = (mdata->mixer_intf[dspp_num].width * mdata->mixer_intf[dspp_num].height); if (ret) temp_ret = ret; ret = pp_hist_collect(hist, hists[i], ctl_base, exp_sum, DSPP); if (ret) pr_err("hist error: dspp[%d] collect %d\n", dspp_num, ret); } /* state of dspp histogram blocks attached to logical display * should be changed atomically to idle. This will ensure that * histogram interrupt will see consistent states for all dspp's * attached to logical display. * */ for (i = 0; i < hist_cnt; i++) { if (!i) spin_lock_irqsave(&hists[i]->hist_lock, flag); else spin_lock(&hists[i]->hist_lock); } for (i = 0; i < hist_cnt; i++) hists[i]->col_state = HIST_IDLE; for (i = hist_cnt - 1; i >= 0; i--) { if (!i) spin_unlock_irqrestore(&hists[i]->hist_lock, flag); else spin_unlock(&hists[i]->hist_lock); } if (ret || temp_ret) { ret = ret ? ret : temp_ret; goto hist_collect_exit; } if (hist->bin_cnt != HIST_V_SIZE) { pr_err("User not expecting size %d output\n", HIST_V_SIZE); ret = -EINVAL; goto hist_collect_exit; } if (hist_cnt > 1) { for (i = 1; i < hist_cnt; i++) { mutex_lock(&hists[i]->hist_mutex); for (j = 0; j < HIST_V_SIZE; j++) hists[0]->data[j] += hists[i]->data[j]; mutex_unlock(&hists[i]->hist_mutex); } } hist_data_addr = hists[0]->data; for (i = 0; i < hist_cnt; i++) hists[i]->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\n", 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\n", 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)) { pr_warn("Invalid Hist pipe (%d)\n", i); continue; } else if ((pipe->num > MDSS_MDP_SSPP_VIG2) && (pipe->num != MDSS_MDP_SSPP_VIG3)) { mdss_mdp_pipe_unmap(pipe); pr_warn("Invalid Hist pipe (%d)\n", i); continue; } 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)) { pr_warn("Invalid Hist pipe (%d)\n", i); continue; } else if ((pipe->num > MDSS_MDP_SSPP_VIG2) && (pipe->num != MDSS_MDP_SSPP_VIG3)) { mdss_mdp_pipe_unmap(pipe); pr_warn("Invalid Hist pipe (%d)\n", i); continue; } hist_info = &pipe->pp_res.hist; ctl_base = pipe->base; if (ret) temp_ret = ret; ret = pp_hist_collect(hist, hist_info, ctl_base, exp_sum, SSPP_VIG); if (ret) pr_debug("hist error: pipe[%d] collect: %d\n", pipe->num, ret); 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)) { pr_warn("Invalid Hist pipe (%d)\n", i); continue; } else if ((pipe->num > MDSS_MDP_SSPP_VIG2) && (pipe->num != MDSS_MDP_SSPP_VIG3)) { mdss_mdp_pipe_unmap(pipe); pr_warn("Invalid Hist pipe (%d)\n", i); continue; } hist_info = &pipe->pp_res.hist; mdss_mdp_pipe_unmap(pipe); } if (ret || temp_ret) { ret = ret ? ret : temp_ret; goto hist_collect_exit; } if (pipe_cnt != 0 && (hist->bin_cnt != (HIST_V_SIZE * pipe_cnt))) { pr_err("User not expecting size %d output\n", pipe_cnt * HIST_V_SIZE); ret = -EINVAL; goto hist_collect_exit; } if (pipe_cnt > 1) { hist_concat = kzalloc(HIST_V_SIZE * pipe_cnt * sizeof(u32), GFP_KERNEL); if (!hist_concat) { ret = -ENOMEM; goto hist_collect_exit; } 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)); if (IS_ERR_OR_NULL(pipe)) { pr_warn("Invalid Hist pipe (%d)\n", i); continue; } 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\n", 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; } static inline struct pp_hist_col_info *get_hist_info_from_isr(u32 *isr) { u32 blk_idx; struct pp_hist_col_info *hist_info = NULL; struct mdss_mdp_pipe *pipe; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (*isr & HIST_INTR_DSPP_MASK) { if (*isr & (MDSS_MDP_HIST_INTR_DSPP_0_DONE | MDSS_MDP_HIST_INTR_DSPP_0_RESET_DONE)) { blk_idx = 0; *isr &= ~(MDSS_MDP_HIST_INTR_DSPP_0_DONE | MDSS_MDP_HIST_INTR_DSPP_0_RESET_DONE); } else if (*isr & (MDSS_MDP_HIST_INTR_DSPP_1_DONE | MDSS_MDP_HIST_INTR_DSPP_1_RESET_DONE)) { blk_idx = 1; *isr &= ~(MDSS_MDP_HIST_INTR_DSPP_1_DONE | MDSS_MDP_HIST_INTR_DSPP_1_RESET_DONE); } else if (*isr & (MDSS_MDP_HIST_INTR_DSPP_2_DONE | MDSS_MDP_HIST_INTR_DSPP_2_RESET_DONE)) { blk_idx = 2; *isr &= ~(MDSS_MDP_HIST_INTR_DSPP_2_DONE | MDSS_MDP_HIST_INTR_DSPP_2_RESET_DONE); } else { blk_idx = 3; *isr &= ~(MDSS_MDP_HIST_INTR_DSPP_3_DONE | MDSS_MDP_HIST_INTR_DSPP_3_RESET_DONE); } hist_info = &mdss_pp_res->dspp_hist[blk_idx]; } else { if (*isr & (MDSS_MDP_HIST_INTR_VIG_0_DONE | MDSS_MDP_HIST_INTR_VIG_0_RESET_DONE)) { blk_idx = MDSS_MDP_SSPP_VIG0; *isr &= ~(MDSS_MDP_HIST_INTR_VIG_0_DONE | MDSS_MDP_HIST_INTR_VIG_0_RESET_DONE); } else if (*isr & (MDSS_MDP_HIST_INTR_VIG_1_DONE | MDSS_MDP_HIST_INTR_VIG_1_RESET_DONE)) { blk_idx = MDSS_MDP_SSPP_VIG1; *isr &= ~(MDSS_MDP_HIST_INTR_VIG_1_DONE | MDSS_MDP_HIST_INTR_VIG_1_RESET_DONE); } else if (*isr & (MDSS_MDP_HIST_INTR_VIG_2_DONE | MDSS_MDP_HIST_INTR_VIG_2_RESET_DONE)) { blk_idx = MDSS_MDP_SSPP_VIG2; *isr &= ~(MDSS_MDP_HIST_INTR_VIG_2_DONE | MDSS_MDP_HIST_INTR_VIG_2_RESET_DONE); } else { blk_idx = MDSS_MDP_SSPP_VIG3; *isr &= ~(MDSS_MDP_HIST_INTR_VIG_3_DONE | MDSS_MDP_HIST_INTR_VIG_3_RESET_DONE); } pipe = mdss_mdp_pipe_search(mdata, BIT(blk_idx)); if (IS_ERR_OR_NULL(pipe)) { pr_debug("pipe DNE, %d\n", blk_idx); return NULL; } hist_info = &pipe->pp_res.hist; } return hist_info; } /** * mdss_mdp_hist_intr_done - Handle histogram interrupts. * @isr: incoming histogram interrupts as bit mask * * This function takes the histogram interrupts received by the * MDP interrupt handler, and handles each of the interrupts by * progressing the histogram state if necessary and then clearing * the interrupt. */ void mdss_mdp_hist_intr_done(u32 isr) { u32 isr_blk, is_hist_done, isr_tmp; struct pp_hist_col_info *hist_info = NULL; u32 isr_mask = HIST_V2_INTR_BIT_MASK; u32 intr_mask = 1, disp_num = 0; if (pp_driver_ops.get_hist_isr_info) pp_driver_ops.get_hist_isr_info(&isr_mask); isr &= isr_mask; while (isr != 0) { isr_tmp = isr; hist_info = get_hist_info_from_isr(&isr); if (NULL == hist_info) { pr_err("hist interrupt gave incorrect blk_idx\n"); continue; } isr_blk = (isr_tmp >> hist_info->intr_shift) & 0x3; is_hist_done = isr_blk & 0x1; spin_lock(&hist_info->hist_lock); if (hist_info && is_hist_done && hist_info->col_en && hist_info->col_state == HIST_IDLE) { hist_info->col_state = HIST_READY; disp_num = hist_info->disp_num; /* Clear the interrupt until next commit */ mdss_mdp_hist_irq_clear_mask(intr_mask << hist_info->intr_shift); writel_relaxed(1, hist_info->base); spin_unlock(&hist_info->hist_lock); mdss_mdp_hist_intr_notify(disp_num); } else { spin_unlock(&hist_info->hist_lock); } }; } static struct msm_fb_data_type *mdss_get_mfd_from_index(int index) { struct msm_fb_data_type *out = NULL; struct mdss_mdp_ctl *ctl; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); int i; for (i = 0; i < mdata->nctl; i++) { ctl = mdata->ctl_off + i; if ((mdss_mdp_ctl_is_power_on(ctl)) && (ctl->mfd) && (ctl->mfd->index == index)) out = ctl->mfd; } return out; } static int pp_num_to_side(struct mdss_mdp_ctl *ctl, u32 num) { u32 mixer_id[MDSS_MDP_INTF_MAX_LAYERMIXER]; u32 mixer_num; if (!ctl || !ctl->mfd) return -EINVAL; mixer_num = mdss_mdp_get_ctl_mixers(ctl->mfd->index, mixer_id); if (mixer_num < 2) return MDSS_SIDE_NONE; else if (mixer_id[1] == num) return MDSS_SIDE_RIGHT; else if (mixer_id[0] == num) return MDSS_SIDE_LEFT; else pr_err("invalid, not on any side\n"); return -EINVAL; } static int mdss_mdp_get_ad(struct msm_fb_data_type *mfd, struct mdss_ad_info **ret_ad) { int ret = 0; struct mdss_data_type *mdata; struct mdss_mdp_ctl *ctl = NULL; *ret_ad = NULL; if (!mfd) { pr_err("invalid parameter mfd %p\n", mfd); return -EINVAL; } mdata = mfd_to_mdata(mfd); if (mdata->nad_cfgs == 0) { pr_debug("Assertive Display not supported by device\n"); return -ENODEV; } if (!mdss_mdp_mfd_valid_ad(mfd)) { pr_debug("AD not supported on display num %d hw config\n", mfd->index); return -EPERM; } if (mfd->panel_info->type == DTV_PANEL) { pr_debug("AD not supported on external display\n"); return -EPERM; } ctl = mfd_to_ctl(mfd); if ((ctl) && (ctl->mixer_left)) *ret_ad = &mdata->ad_cfgs[ctl->mixer_left->num]; else ret = -EPERM; return ret; } /* must call this function from within ad->lock */ static int pp_ad_invalidate_input(struct msm_fb_data_type *mfd) { int ret; struct mdss_ad_info *ad; if (!mfd) { pr_err("Invalid mfd\n"); return -EINVAL; } ret = mdss_mdp_get_ad(mfd, &ad); if (ret == -ENODEV || ret == -EPERM) { pr_debug("AD not supported on device, disp num %d\n", mfd->index); return 0; } else if (ret || !ad) { pr_err("Failed to get ad info: ret = %d, ad = 0x%p.\n", ret, ad); return ret; } pr_debug("AD backlight level changed (%d), trigger update to AD\n", mfd->ad_bl_level); if (ad->cfg.mode == MDSS_AD_MODE_AUTO_BL) { pr_err("AD auto backlight no longer supported.\n"); return -EINVAL; } if (ad->state & PP_AD_STATE_RUN) { ad->calc_itr = ad->cfg.stab_itr; ad->sts |= PP_AD_STS_DIRTY_VSYNC; ad->sts |= PP_AD_STS_DIRTY_DATA; } return 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 msm_fb_data_type *bl_mfd; int lin_ret = -1, inv_ret = -1, att_ret = -1, ret = 0; u32 last_ops; struct mdss_overlay_private *mdp5_data; ret = mdss_mdp_get_ad(mfd, &ad); if (ret == -ENODEV || ret == -EPERM) { pr_err("AD not supported on device, disp num %d\n", mfd->index); return ret; } else if (ret || !ad) { pr_err("Failed to get ad info: ret = %d, ad = 0x%p.\n", ret, ad); return ret; } if (mfd->panel_info->type == WRITEBACK_PANEL) { bl_mfd = mdss_get_mfd_from_index(0); if (!bl_mfd) return ret; } else { bl_mfd = mfd; } if ((init_cfg->ops & MDSS_PP_SPLIT_MASK) == MDSS_PP_SPLIT_MASK) { pr_warn("Can't set both split bits\n"); 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, init_cfg->params.init.bl_lin_len * sizeof(uint32_t)); inv_ret = copy_from_user(&ad->bl_lin_inv, init_cfg->params.init.bl_lin_inv, init_cfg->params.init.bl_lin_len * sizeof(uint32_t)); if (lin_ret || inv_ret) ret = -ENOMEM; } else { ret = -EINVAL; } if (ret) { ad->state &= ~PP_AD_STATE_BL_LIN; goto ad_config_exit; } else ad->state |= PP_AD_STATE_BL_LIN; if ((init_cfg->params.init.bl_att_len == AD_BL_ATT_LUT_LEN) && (init_cfg->params.init.bl_att_lut)) { att_ret = copy_from_user(&ad->bl_att_lut, init_cfg->params.init.bl_att_lut, init_cfg->params.init.bl_att_len * sizeof(uint32_t)); if (att_ret) ret = -ENOMEM; } else { ret = -EINVAL; } if (ret) { ad->state &= ~PP_AD_STATE_BL_LIN; goto ad_config_exit; } 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)); if (ad->state & PP_AD_STATE_IPC_RESUME) ad->cfg.mode |= MDSS_AD_MODE_IPC_BIT; ad->cfg.backlight_scale = MDSS_MDP_AD_BL_SCALE; ad->sts |= PP_AD_STS_DIRTY_CFG; mdp5_data = mfd_to_mdp5_data(mfd); if (mdp5_data) mdp5_data->ad_events = 0; } last_ops = ad->ops & MDSS_PP_SPLIT_MASK; ad->ops = init_cfg->ops & MDSS_PP_SPLIT_MASK; /* * if there is a change in the split mode config, the init values * need to be re-written to hardware (if they have already been * written or if there is data pending to be written). Check for * pending data (DIRTY_INIT) is not checked here since it will not * affect the outcome of this conditional (i.e. if init hasn't * already been written (*_STATE_INIT is set), this conditional will * only evaluate to true (and set the DIRTY bit) if the DIRTY bit has * already been set). */ if ((last_ops ^ ad->ops) && (ad->state & PP_AD_STATE_INIT)) ad->sts |= PP_AD_STS_DIRTY_INIT; 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; ad->bl_mfd = NULL; } else if (!ret && (init_cfg->ops & MDP_PP_OPS_ENABLE)) { ad->sts |= PP_STS_ENABLE; ad->mfd = mfd; ad->bl_mfd = bl_mfd; } ad_config_exit: mutex_unlock(&ad->lock); 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; u32 bl; struct mdss_overlay_private *mdp5_data; ret = mdss_mdp_get_ad(mfd, &ad); if (ret == -ENODEV || ret == -EPERM) { pr_err("AD not supported on device, disp num %d\n", mfd->index); return ret; } else if (ret || !ad) { pr_err("Failed to get ad info: ret = %d, ad = 0x%p.\n", ret, ad); return ret; } 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.\n"); 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)) { pr_err("Invalid mode %x\n", ad->cfg.mode); ret = -EINVAL; goto error; } if (input->in.amb_light > MDSS_MDP_MAX_AD_AL) { pr_warn("invalid input ambient light\n"); ret = -EINVAL; goto error; } ad->ad_data_mode = MDSS_AD_INPUT_AMBIENT; pr_debug("ambient = %d\n", input->in.amb_light); 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; mdp5_data = mfd_to_mdp5_data(mfd); if (mdp5_data) mdp5_data->ad_events = 0; 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)) { pr_err("Invalid mode %x\n", ad->cfg.mode); ret = -EINVAL; goto error; } if (input->in.strength > MDSS_MDP_MAX_AD_STR) { pr_warn("invalid input strength\n"); ret = -EINVAL; goto error; } ad->ad_data_mode = MDSS_AD_INPUT_STRENGTH; pr_debug("strength = %d\n", input->in.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!\n"); break; } mutex_unlock(&ad->lock); mutex_lock(&mfd->bl_lock); MDSS_BRIGHT_TO_BL(bl, bl, mfd->panel_info->bl_max, mfd->panel_info->brightness_max); mdss_fb_set_backlight(mfd, bl); mutex_unlock(&mfd->bl_lock); mutex_lock(&ad->lock); mfd->calib_mode_bl = bl; } else { pr_warn("should be in calib mode\n"); } break; default: pr_warn("invalid default %d\n", input->mode); ret = -EINVAL; goto error; } error: mutex_unlock(&ad->lock); return ret; } static void pp_ad_input_write(struct mdss_mdp_ad *ad_hw, struct mdss_ad_info *ad) { char __iomem *base; base = ad_hw->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: ad->last_ad_data = ad->ad_data; ad->last_ad_data_valid = true; writel_relaxed(ad->bl_data, 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(ad->bl_data, 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(ad->bl_data, base + MDSS_MDP_REG_AD_BL); writel_relaxed(ad->ad_data, base + MDSS_MDP_REG_AD_STR_MAN); break; case MDSS_AD_MODE_MAN_IPC: if (!ad->last_ad_data_valid) { ad->last_ad_data = ad->ad_data; ad->last_ad_data_valid = true; } writel_relaxed(ad->bl_data, base + MDSS_MDP_REG_AD_BL); writel_relaxed(ad->last_ad_data, base + MDSS_MDP_REG_AD_AL); writel_relaxed(ad->last_str, base + MDSS_MDP_REG_AD_STR_MAN); break; default: pr_warn("Invalid mode! %d\n", ad->cfg.mode); break; } } #define MDSS_AD_MERGED_WIDTH 4 static void pp_ad_init_write(struct mdss_mdp_ad *ad_hw, struct mdss_ad_info *ad, struct mdss_mdp_ctl *ctl) { struct mdss_data_type *mdata = ctl->mdata; u32 temp; u32 frame_start, frame_end, procs_start, procs_end, tile_ctrl; u32 num; int side; char __iomem *base; bool is_calc, is_dual_pipe, split_mode; u32 mixer_id[MDSS_MDP_INTF_MAX_LAYERMIXER]; u32 mixer_num; mixer_num = mdss_mdp_get_ctl_mixers(ctl->mfd->index, mixer_id); if (mixer_num > 1) is_dual_pipe = true; else is_dual_pipe = false; base = ad_hw->base; is_calc = ad->calc_hw_num == ad_hw->num; split_mode = !!(ad->ops & MDSS_PP_SPLIT_MASK); 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); if (split_mode) temp = mdata->mixer_intf[ad_hw->num].width << 16; else 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); if (mdata->mdp_rev >= MDSS_MDP_HW_REV_103) { if (is_dual_pipe && !split_mode) { num = ad_hw->num; side = pp_num_to_side(ctl, num); tile_ctrl = 0x5; if ((ad->calc_hw_num + 1) == num) tile_ctrl |= 0x10; if (side <= MDSS_SIDE_NONE) { WARN(1, "error finding sides, %d\n", side); frame_start = 0; procs_start = frame_start; frame_end = 0; procs_end = frame_end; } else if (side == MDSS_SIDE_LEFT) { frame_start = 0; procs_start = 0; frame_end = mdata->mixer_intf[num].width + MDSS_AD_MERGED_WIDTH; procs_end = mdata->mixer_intf[num].width; } else { procs_start = ad->init.frame_w - (mdata->mixer_intf[num].width); procs_end = ad->init.frame_w; frame_start = procs_start - MDSS_AD_MERGED_WIDTH; frame_end = procs_end; } procs_end -= 1; frame_end -= 1; } else { frame_start = 0x0; frame_end = 0xFFFF; procs_start = 0x0; procs_end = 0xFFFF; tile_ctrl = 0x0; } writel_relaxed(frame_start, base + MDSS_MDP_REG_AD_FRAME_START); writel_relaxed(frame_end, base + MDSS_MDP_REG_AD_FRAME_END); writel_relaxed(procs_start, base + MDSS_MDP_REG_AD_PROCS_START); writel_relaxed(procs_end, base + MDSS_MDP_REG_AD_PROCS_END); writel_relaxed(tile_ctrl, base + MDSS_MDP_REG_AD_TILE_CTRL); } } #define MDSS_PP_AD_DEF_CALIB 0x6E static void pp_ad_cfg_write(struct mdss_mdp_ad *ad_hw, struct mdss_ad_info *ad) { char __iomem *base; u32 temp, temp_calib = MDSS_PP_AD_DEF_CALIB; base = ad_hw->base; 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: memcpy(ad->last_calib, ad->cfg.calib, sizeof(ad->last_calib)); ad->last_calib_valid = true; 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 | MDSS_AD_AUTO_TRIGGER, base + MDSS_MDP_REG_AD_MODE_SEL); pr_debug("stab_itr = %d\n", ad->cfg.stab_itr); break; case MDSS_AD_MODE_MAN_IPC: if (!ad->last_calib_valid) { memcpy(ad->last_calib, ad->cfg.calib, sizeof(ad->last_calib)); ad->last_calib_valid = true; } writel_relaxed(MDSS_AD_T_FILTER_CTRL_0, base + MDSS_MDP_REG_AD_TFILT_CTRL); 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->last_calib[3] << 16; temp |= ad->last_calib[2] & 0xFFFF; writel_relaxed(temp, base + MDSS_MDP_REG_AD_CALIB_CD); temp_calib = ad->last_calib[0] & 0xFFFF; temp = ad->last_calib[1] << 16; temp |= temp_calib; writel_relaxed(temp, base + MDSS_MDP_REG_AD_CALIB_AB); writel_relaxed(ad->cfg.backlight_scale, base + MDSS_MDP_REG_AD_BL_MAX); writel_relaxed(ad->cfg.mode | MDSS_AD_AUTO_TRIGGER, base + MDSS_MDP_REG_AD_MODE_SEL); pr_debug("stab_itr = %d\n", 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 void pp_ad_bypass_config(struct mdss_ad_info *ad, struct mdss_mdp_ctl *ctl, u32 num, u32 *opmode) { int side = pp_num_to_side(ctl, num); if (pp_sts_is_enabled(ad->reg_sts | (ad->ops & MDSS_PP_SPLIT_MASK), side)) { *opmode = 0; } else { *opmode = MDSS_PP_AD_BYPASS_DEF; } } static int pp_ad_setup_hw_nums(struct msm_fb_data_type *mfd, struct mdss_ad_info *ad) { u32 mixer_id[MDSS_MDP_INTF_MAX_LAYERMIXER]; u32 mixer_num; mixer_num = mdss_mdp_get_ctl_mixers(mfd->index, mixer_id); if (!mixer_num) return -EINVAL; /* default to left mixer */ ad->calc_hw_num = mixer_id[0]; if ((mixer_num > 1) && (ad->ops & MDSS_PP_SPLIT_RIGHT_ONLY)) ad->calc_hw_num = mixer_id[1]; return 0; } static int mdss_mdp_ad_ipc_reset(struct msm_fb_data_type *mfd) { int ret = 0; struct mdss_ad_info *ad; if (!mfd) { pr_err("mfd = 0x%p\n", mfd); return -EINVAL; } ret = mdss_mdp_get_ad(mfd, &ad); if (ret == -ENODEV || ret == -EPERM) { pr_debug("AD not supported on device, disp num %d\n", mfd->index); return 0; } else if (ret || !ad) { pr_err("Failed to get ad info: ret = %d, ad = 0x%p.\n", ret, ad); return ret; } mutex_lock(&ad->lock); if (ad->state & PP_AD_STATE_RUN && ad->state & PP_AD_STATE_IPC_RESET) ad->state &= ~PP_AD_STATE_IPC_RESET; mutex_unlock(&ad->lock); return 0; } 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, *sctl; struct msm_fb_data_type *bl_mfd; struct mdss_data_type *mdata; u32 bypass = MDSS_PP_AD_BYPASS_DEF, bl; u32 width; if (!mfd) { pr_err("mfd = 0x%p\n", mfd); return -EINVAL; } ctl = mfd_to_ctl(mfd); if (!ctl) { pr_err("ctl = 0x%p\n", ctl); return -EINVAL; } sctl = mdss_mdp_get_split_ctl(ctl); ret = mdss_mdp_get_ad(mfd, &ad); if (ret == -ENODEV || ret == -EPERM) { pr_debug("AD not supported on device, disp num %d\n", mfd->index); return 0; } else if (ret || !ad) { pr_err("Failed to get ad info: ret = %d, ad = 0x%p.\n", ret, ad); return ret; } if (mfd->panel_info->type == WRITEBACK_PANEL) { bl_mfd = mdss_get_mfd_from_index(0); if (!bl_mfd) { ret = -EINVAL; pr_warn("failed to get primary FB bl handle, err = %d\n", ret); goto exit; } } else { bl_mfd = mfd; } mdata = mdss_mdp_get_mdata(); mutex_lock(&ad->lock); if (ad->state & PP_AD_STATE_RUN && ad->state & PP_AD_STATE_IPC_RESUME) { if (ad->ipc_frame_count == MDSS_AD_IPC_FRAME_COUNT) { ad->state &= ~PP_AD_STATE_IPC_RESUME; ad->state |= PP_AD_STATE_IPC_RESET; ad->cfg.mode &= ~MDSS_AD_MODE_IPC_BIT; if (ad->last_ad_data != ad->ad_data) ad->sts |= PP_AD_STS_DIRTY_DATA; if (memcmp(ad->last_calib, ad->cfg.calib, sizeof(ad->last_calib))) ad->sts |= PP_AD_STS_DIRTY_CFG; pr_debug("switch mode to %d, last_ad_data = %d\n", ad->cfg.mode, ad->last_ad_data); } else { ad->ipc_frame_count++; } } 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\n", ad->sts, ad->state); } if (ad->sts & PP_AD_STS_DIRTY_DATA) { ad->sts &= ~PP_AD_STS_DIRTY_DATA; ad->state |= PP_AD_STATE_DATA; pr_debug("dirty data, last_bl = %d\n", ad->last_bl); bl = bl_mfd->ad_bl_level; if (ad->last_bl != bl) { ad->last_bl = bl; linear_map(bl, &ad->bl_data, bl_mfd->panel_info->bl_max, MDSS_MDP_AD_BL_SCALE); } if (!(ad->state & PP_AD_STATE_IPC_RESUME)) { ad->calc_itr = ad->cfg.stab_itr; ad->sts |= PP_AD_STS_DIRTY_VSYNC; } ad->reg_sts |= PP_AD_STS_DIRTY_DATA; } if (ad->sts & PP_AD_STS_DIRTY_CFG) { ad->sts &= ~PP_AD_STS_DIRTY_CFG; ad->state |= PP_AD_STATE_CFG; ad->reg_sts |= PP_AD_STS_DIRTY_CFG; } if (ad->sts & PP_AD_STS_DIRTY_INIT) { ad->sts &= ~PP_AD_STS_DIRTY_INIT; if (pp_ad_setup_hw_nums(mfd, ad)) { pr_warn("failed to setup ad master\n"); ad->calc_hw_num = PP_AD_BAD_HW_NUM; } else { ad->state |= PP_AD_STATE_INIT; ad->reg_sts |= PP_AD_STS_DIRTY_INIT; } } width = ctl->width; if (sctl) width += sctl->width; /* update ad screen size if it has changed since last configuration */ if ((ad->init.frame_w != width) || (ad->init.frame_h != ctl->height)) { pr_debug("changing from %dx%d to %dx%d\n", ad->init.frame_w, ad->init.frame_h, width, ctl->height); ad->init.frame_w = width; ad->init.frame_h = ctl->height; ad->reg_sts |= PP_AD_STS_DIRTY_INIT; } if ((ad->sts & PP_STS_ENABLE) && PP_AD_STATE_IS_READY(ad->state)) { bypass = 0; ad->reg_sts |= PP_AD_STS_DIRTY_ENABLE; ad->state |= PP_AD_STATE_RUN; if (bl_mfd != mfd) bl_mfd->ext_ad_ctrl = mfd->index; bl_mfd->ext_bl_ctrl = ad->cfg.bl_ctrl_mode; } else { if (ad->state & PP_AD_STATE_RUN) { ad->reg_sts = PP_AD_STS_DIRTY_ENABLE; /* 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->state &= ~PP_AD_STATE_IPC_RESUME; ad->state &= ~PP_AD_STATE_IPC_RESET; ad->ad_data = 0; ad->ad_data_mode = 0; ad->last_bl = 0; ad->last_ad_data = 0; ad->last_calib_valid = false; ad->last_ad_data_valid = false; ad->ipc_frame_count = 0; ad->calc_itr = 0; ad->calc_hw_num = PP_AD_BAD_HW_NUM; memset(&ad->last_calib, 0, sizeof(ad->last_calib)); 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->bl_att_lut, 0, sizeof(uint32_t) * AD_BL_ATT_LUT_LEN); memset(&ad->init, 0, sizeof(struct mdss_ad_init)); memset(&ad->cfg, 0, sizeof(struct mdss_ad_cfg)); bl_mfd->ext_bl_ctrl = 0; bl_mfd->ext_ad_ctrl = -1; } ad->state &= ~PP_AD_STATE_RUN; } if (!bypass) ad->reg_sts |= PP_STS_ENABLE; else ad->reg_sts &= ~PP_STS_ENABLE; if (PP_AD_STS_DIRTY_VSYNC & ad->sts) { pr_debug("dirty vsync, calc_itr = %d\n", 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->ops.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->ops.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\n", ad->sts, ad->state); } mutex_unlock(&ad->lock); exit: 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 mdss_overlay_private *mdp5_data; struct mdss_data_type *mdata; char __iomem *base; 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; } mdp5_data = mfd_to_mdp5_data(ad->mfd); if (!mdp5_data) { pr_err("mdp5_data = 0x%p\n", mdp5_data); mutex_unlock(&ad->lock); return; } ctl = mfd_to_ctl(ad->mfd); mdata = mfd_to_mdata(ad->mfd); if (!ctl || !mdata || ad->calc_hw_num >= mdata->nad_cfgs) { pr_err("ctl = 0x%p, mdata = 0x%p, ad->calc_hw_num = %d, mdata->nad_cfg = %d\n", ctl, mdata, ad->calc_hw_num, (!mdata ? 0 : mdata->nad_cfgs)); mutex_unlock(&ad->lock); return; } base = mdata->ad_off[ad->calc_hw_num].base; if ((ad->cfg.mode == MDSS_AD_MODE_AUTO_STR) && (ad->last_bl == 0)) { mutex_unlock(&ad->lock); return; } if ((PP_AD_STATE_RUN & ad->state) && ad->calc_itr > 0) ad->calc_itr--; mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); ad->last_str = 0xFF & readl_relaxed(base + MDSS_MDP_REG_AD_STR_OUT); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); if (mdata->ad_debugen) pr_debug("itr number %d str %d\n", ad->calc_itr, ad->last_str); mdp5_data->ad_events++; sysfs_notify_dirent(mdp5_data->ad_event_sd); if (!ad->calc_itr) { ad->state &= ~PP_AD_STATE_VSYNC; ctl->ops.remove_vsync_handler(ctl, &ad->handle); } mutex_unlock(&ad->lock); } #define PP_AD_LUT_LEN 33 static void pp_ad_cfg_lut(char __iomem *addr, 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, addr + (i*2)); } writel_relaxed(data[PP_AD_LUT_LEN - 1] << 16, addr + ((PP_AD_LUT_LEN - 1) * 2)); } /* must call this function from within ad->lock */ static int pp_ad_attenuate_bl(struct mdss_ad_info *ad, u32 bl, u32 *bl_out) { u32 shift = 0, ratio_temp = 0; u32 n, lut_interval, bl_att; if (bl < 0 || ad->init.alpha < 0) { pr_err("Invalid input: backlight = %d, alpha = %d\n", bl, ad->init.alpha); return -EINVAL; } if (ad->init.alpha == 0) { pr_debug("alpha = %d, hence no attenuation needed\n", ad->init.alpha); return 0; } pr_debug("bl_in = %d\n", bl); /* map panel backlight range to AD backlight range */ linear_map(bl, &bl, ad->bl_mfd->panel_info->bl_max, MDSS_MDP_AD_BL_SCALE); pr_debug("Before attenuation = %d\n", bl); ratio_temp = MDSS_MDP_AD_BL_SCALE / (AD_BL_ATT_LUT_LEN - 1); while (ratio_temp > 0) { ratio_temp = ratio_temp >> 1; shift++; } n = bl >> shift; if (n >= (AD_BL_ATT_LUT_LEN - 1)) { pr_err("Invalid index for BL attenuation: %d.\n", n); return -EINVAL; } lut_interval = (MDSS_MDP_AD_BL_SCALE + 1) / (AD_BL_ATT_LUT_LEN - 1); bl_att = ((ad->bl_att_lut[n + 1] - ad->bl_att_lut[n]) * (bl - lut_interval * n) + (ad->bl_att_lut[n] * lut_interval)) / lut_interval; pr_debug("n = %u, bl_att_lut[%u] = %u, bl_att_lut[%u] = %u, bl_att = %u\n", n, n, ad->bl_att_lut[n], n + 1, ad->bl_att_lut[n + 1], bl_att); *bl_out = (ad->init.alpha * bl_att + (ad->init.alpha_base - ad->init.alpha) * bl) / ad->init.alpha_base; pr_debug("After attenuation = %d\n", *bl_out); /* map AD backlight range back to panel backlight range */ linear_map(*bl_out, bl_out, MDSS_MDP_AD_BL_SCALE, ad->bl_mfd->panel_info->bl_max); pr_debug("bl_out = %d\n", *bl_out); return 0; } /* must call this function from within ad->lock */ static int pp_ad_linearize_bl(struct mdss_ad_info *ad, u32 bl, u32 *bl_out, int inv) { u32 n, bl_lut_max_index = AD_BL_LIN_LEN - 1; uint32_t *bl_lut = NULL; int ret = -EINVAL; if (bl < 0 || bl > ad->bl_mfd->panel_info->bl_max) { pr_err("Invalid backlight input: bl = %d, bl_max = %d\n", bl, ad->bl_mfd->panel_info->bl_max); return -EINVAL; } pr_debug("bl_in = %d, inv = %d\n", bl, inv); if (inv == MDP_PP_AD_BL_LINEAR_INV) { bl_lut = ad->bl_lin; } else if (inv == MDP_PP_AD_BL_LINEAR) { bl_lut = ad->bl_lin_inv; } else { pr_err("invalid inv param: inv = %d\n", inv); return -EINVAL; } /* map panel backlight range to AD backlight range */ linear_map(bl, &bl, ad->bl_mfd->panel_info->bl_max, MDSS_MDP_AD_BL_SCALE); pr_debug("Before linearization = %d\n", bl); n = bl * bl_lut_max_index / MDSS_MDP_AD_BL_SCALE; pr_debug("n = %u\n", n); if (n > bl_lut_max_index) { pr_err("Invalid index for BL linearization: %d.\n", n); return ret; } else if (n == bl_lut_max_index) { *bl_out = bl_lut[n]; } else if (bl == n * MDSS_MDP_AD_BL_SCALE / bl_lut_max_index) { *bl_out = bl_lut[n]; } else if (bl == (n + 1) * MDSS_MDP_AD_BL_SCALE / bl_lut_max_index) { *bl_out = bl_lut[n + 1]; } else { /* linear piece-wise interpolation */ *bl_out = ((bl_lut[n + 1] - bl_lut[n]) * (bl - n * MDSS_MDP_AD_BL_SCALE / bl_lut_max_index) + bl_lut[n] * MDSS_MDP_AD_BL_SCALE / bl_lut_max_index) * bl_lut_max_index / MDSS_MDP_AD_BL_SCALE; } pr_debug("After linearization = %d\n", *bl_out); /* map AD backlight range back to panel backlight range */ linear_map(*bl_out, bl_out, MDSS_MDP_AD_BL_SCALE, ad->bl_mfd->panel_info->bl_max); pr_debug("bl_out = %d\n", *bl_out); return 0; } int mdss_mdp_ad_addr_setup(struct mdss_data_type *mdata, u32 *ad_offsets) { u32 i; int rc = 0; mdata->ad_off = devm_kzalloc(&mdata->pdev->dev, sizeof(struct mdss_mdp_ad) * mdata->nad_cfgs, GFP_KERNEL); if (!mdata->ad_off) { pr_err("unable to setup assertive display hw:devm_kzalloc fail\n"); return -ENOMEM; } 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"); devm_kfree(&mdata->pdev->dev, mdata->ad_off); return -ENOMEM; } mdata->ad_calc_wq = create_singlethread_workqueue("ad_calc_wq"); for (i = 0; i < mdata->nad_cfgs; i++) { mdata->ad_off[i].base = mdata->mdss_io.base + ad_offsets[i]; mdata->ad_off[i].num = i; mdata->ad_cfgs[i].num = i; mdata->ad_cfgs[i].ops = 0; mdata->ad_cfgs[i].reg_sts = 0; mdata->ad_cfgs[i].calc_itr = 0; mdata->ad_cfgs[i].last_str = 0xFFFFFFFF; mdata->ad_cfgs[i].last_bl = 0; mdata->ad_cfgs[i].last_ad_data = 0; memset(mdata->ad_cfgs[i].last_calib, 0, sizeof(mdata->ad_cfgs[i].last_calib)); mdata->ad_cfgs[i].last_calib_valid = false; mdata->ad_cfgs[i].last_ad_data_valid = false; mutex_init(&mdata->ad_cfgs[i].lock); mdata->ad_cfgs[i].handle.vsync_handler = pp_ad_vsync_handler; mdata->ad_cfgs[i].handle.cmd_post_flush = true; INIT_WORK(&mdata->ad_cfgs[i].calc_work, pp_ad_calc_worker); } return rc; } static int is_valid_calib_ctrl_addr(char __iomem *ptr) { char __iomem *base; int ret = 0, counter = 0; int stage = 0; struct mdss_mdp_ctl *ctl; /* Controller */ for (counter = 0; counter < mdss_res->nctl; counter++) { ctl = mdss_res->ctl_off + counter; base = ctl->base; if (ptr == base + MDSS_MDP_REG_CTL_TOP) { ret = MDP_PP_OPS_READ; break; } else if (ptr == base + MDSS_MDP_REG_CTL_FLUSH) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; } for (stage = 0; stage < (mdss_res->nmixers_intf + mdss_res->nmixers_wb); stage++) if (ptr == base + MDSS_MDP_REG_CTL_LAYER(stage)) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; goto End; } } End: return ret; } static int is_valid_calib_dspp_addr(char __iomem *ptr) { char __iomem *base; int ret = 0, counter = 0; struct mdss_mdp_mixer *mixer; for (counter = 0; counter < mdss_res->nmixers_intf; counter++) { mixer = mdss_res->mixer_intf + counter; base = mixer->dspp_base; if (ptr == base) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; /* PA range */ } else if ((ptr >= base + MDSS_MDP_REG_DSPP_PA_BASE) && (ptr <= base + MDSS_MDP_REG_DSPP_PA_BASE + MDSS_MDP_PA_SIZE)) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; /* PCC range */ } else if ((ptr >= base + MDSS_MDP_REG_DSPP_PCC_BASE) && (ptr <= base + MDSS_MDP_REG_DSPP_PCC_BASE + MDSS_MDP_PCC_SIZE)) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; /* Gamut range */ } else if ((ptr >= base + MDSS_MDP_REG_DSPP_GAMUT_BASE) && (ptr <= base + MDSS_MDP_REG_DSPP_GAMUT_BASE + MDSS_MDP_GAMUT_SIZE)) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; /* GC range */ } else if ((ptr >= base + MDSS_MDP_REG_DSPP_GC_BASE) && (ptr <= base + MDSS_MDP_REG_DSPP_GC_BASE + MDSS_MDP_GC_SIZE)) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; /* Dither enable/disable */ } else if ((ptr == base + MDSS_MDP_REG_DSPP_DITHER_DEPTH)) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; /* Six zone and mem color */ } else if (mdss_res->mdp_rev >= MDSS_MDP_HW_REV_103 && (ptr >= base + MDSS_MDP_REG_DSPP_SIX_ZONE_BASE) && (ptr <= base + MDSS_MDP_REG_DSPP_SIX_ZONE_BASE + MDSS_MDP_SIX_ZONE_SIZE + MDSS_MDP_MEM_COL_SIZE)) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; } } return ret; } static int is_valid_calib_vig_addr(char __iomem *ptr) { char __iomem *base; int ret = 0, counter = 0; struct mdss_mdp_pipe *pipe; for (counter = 0; counter < mdss_res->nvig_pipes; counter++) { pipe = mdss_res->vig_pipes + counter; base = pipe->base; if (ptr == base + MDSS_MDP_REG_VIG_OP_MODE) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; } else if (ptr == base + MDSS_MDP_REG_SSPP_SRC_FORMAT) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; } else if (ptr == base + MDSS_MDP_REG_SSPP_SRC_CONSTANT_COLOR) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; } else if (ptr == base + MDSS_MDP_REG_SSPP_SRC_UNPACK_PATTERN) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; } else if (ptr == base + MDSS_MDP_REG_SSPP_SRC_OP_MODE) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; /* QSEED2 range */ } else if ((ptr >= base + MDSS_MDP_REG_VIG_QSEED2_SHARP) && (ptr <= base + MDSS_MDP_REG_VIG_QSEED2_SHARP + MDSS_MDP_VIG_QSEED2_SHARP_SIZE)) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; /* PA range */ } else if ((ptr >= base + MDSS_MDP_REG_VIG_PA_BASE) && (ptr <= base + MDSS_MDP_REG_VIG_PA_BASE + MDSS_MDP_PA_SIZE)) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; /* Mem color range */ } else if (mdss_res->mdp_rev >= MDSS_MDP_HW_REV_103 && (ptr >= base + MDSS_MDP_REG_VIG_MEM_COL_BASE) && (ptr <= base + MDSS_MDP_REG_VIG_MEM_COL_BASE + MDSS_MDP_MEM_COL_SIZE)) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; } } return ret; } static int is_valid_calib_rgb_addr(char __iomem *ptr) { char __iomem *base; int ret = 0, counter = 0; struct mdss_mdp_pipe *pipe; for (counter = 0; counter < mdss_res->nrgb_pipes; counter++) { pipe = mdss_res->rgb_pipes + counter; base = pipe->base; if (ptr == base + MDSS_MDP_REG_SSPP_SRC_FORMAT) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; } else if (ptr == base + MDSS_MDP_REG_SSPP_SRC_CONSTANT_COLOR) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; } else if (ptr == base + MDSS_MDP_REG_SSPP_SRC_UNPACK_PATTERN) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; } else if (ptr == base + MDSS_MDP_REG_SSPP_SRC_OP_MODE) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; } } return ret; } static int is_valid_calib_dma_addr(char __iomem *ptr) { char __iomem *base; int ret = 0, counter = 0; struct mdss_mdp_pipe *pipe; for (counter = 0; counter < mdss_res->ndma_pipes; counter++) { pipe = mdss_res->dma_pipes + counter; base = pipe->base; if (ptr == base + MDSS_MDP_REG_SSPP_SRC_FORMAT) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; } else if (ptr == base + MDSS_MDP_REG_SSPP_SRC_CONSTANT_COLOR) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; } else if (ptr == base + MDSS_MDP_REG_SSPP_SRC_UNPACK_PATTERN) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; } else if (ptr == base + MDSS_MDP_REG_SSPP_SRC_OP_MODE) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; } } return ret; } static int is_valid_calib_mixer_addr(char __iomem *ptr) { char __iomem *base; int ret = 0, counter = 0; int stage = 0; struct mdss_mdp_mixer *mixer; for (counter = 0; counter < (mdss_res->nmixers_intf + mdss_res->nmixers_wb); counter++) { mixer = mdss_res->mixer_intf + counter; base = mixer->base; if (ptr == base + MDSS_MDP_REG_LM_OP_MODE) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; /* GC range */ } else if ((ptr >= base + MDSS_MDP_REG_LM_GC_LUT_BASE) && (ptr <= base + MDSS_MDP_REG_LM_GC_LUT_BASE + MDSS_MDP_GC_SIZE)) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; break; } for (stage = 0; stage < TOTAL_BLEND_STAGES; stage++) if (ptr == base + MDSS_MDP_REG_LM_BLEND_OFFSET(stage) + MDSS_MDP_REG_LM_BLEND_OP) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; goto End; } else if (ptr == base + MDSS_MDP_REG_LM_BLEND_OFFSET(stage) + MDSS_MDP_REG_LM_BLEND_FG_ALPHA) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; goto End; } else if (ptr == base + MDSS_MDP_REG_LM_BLEND_OFFSET(stage) + MDSS_MDP_REG_LM_BLEND_BG_ALPHA) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; goto End; } } End: return ret; } static int is_valid_calib_addr(void *addr, u32 operation) { int ret = 0; char __iomem *ptr = addr; char __iomem *mixer_base = mdss_res->mixer_intf->base; char __iomem *rgb_base = mdss_res->rgb_pipes->base; char __iomem *dma_base = mdss_res->dma_pipes->base; char __iomem *vig_base = mdss_res->vig_pipes->base; char __iomem *ctl_base = mdss_res->ctl_off->base; char __iomem *dspp_base = mdss_res->mixer_intf->dspp_base; if ((uintptr_t) addr % 4) { ret = 0; } else if (ptr == mdss_res->mdss_io.base + MDSS_REG_HW_VERSION) { ret = MDP_PP_OPS_READ; } else if (ptr == (mdss_res->mdp_base + MDSS_MDP_REG_HW_VERSION) || ptr == (mdss_res->mdp_base + MDSS_MDP_REG_DISP_INTF_SEL)) { ret = MDP_PP_OPS_READ; /* IGC DSPP range */ } else if (ptr >= (mdss_res->mdp_base + MDSS_MDP_REG_IGC_DSPP_BASE) && ptr <= (mdss_res->mdp_base + MDSS_MDP_REG_IGC_DSPP_BASE + MDSS_MDP_IGC_DSPP_SIZE)) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; /* IGC SSPP range */ } else if (ptr >= (mdss_res->mdp_base + MDSS_MDP_REG_IGC_VIG_BASE) && ptr <= (mdss_res->mdp_base + MDSS_MDP_REG_IGC_VIG_BASE + MDSS_MDP_IGC_SSPP_SIZE)) { ret = MDP_PP_OPS_READ | MDP_PP_OPS_WRITE; } else { if (ptr >= dspp_base) { ret = is_valid_calib_dspp_addr(ptr); if (ret) goto valid_addr; } if (ptr >= ctl_base) { ret = is_valid_calib_ctrl_addr(ptr); if (ret) goto valid_addr; } if (ptr >= vig_base) { ret = is_valid_calib_vig_addr(ptr); if (ret) goto valid_addr; } if (ptr >= rgb_base) { ret = is_valid_calib_rgb_addr(ptr); if (ret) goto valid_addr; } if (ptr >= dma_base) { ret = is_valid_calib_dma_addr(ptr); if (ret) goto valid_addr; } if (ptr >= mixer_base) ret = is_valid_calib_mixer_addr(ptr); } valid_addr: return ret & operation; } int mdss_mdp_calib_config(struct mdp_calib_config_data *cfg, u32 *copyback) { int ret = -1; void *ptr; /* Calib addrs are always offsets from the MDSS base */ ptr = (void *)((unsigned long) cfg->addr) + ((uintptr_t) mdss_res->mdss_io.base); if (is_valid_calib_addr(ptr, cfg->ops)) ret = 0; else return ret; mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); 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); 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_lock(&mfd->bl_lock); mfd->calib_mode_bl = mfd->bl_level; mutex_unlock(&mfd->bl_lock); mutex_unlock(&mdss_pp_mutex); return 0; } int mdss_mdp_calib_config_buffer(struct mdp_calib_config_buffer *cfg, u32 *copyback) { int ret = -1, counter; uint32_t *buff = NULL, *buff_org = NULL; void *ptr; int i = 0; if (!cfg) { pr_err("Invalid buffer pointer\n"); return ret; } if (cfg->size == 0 || cfg->size > PAGE_SIZE) { pr_err("Invalid buffer size %d\n", cfg->size); return ret; } counter = cfg->size / (sizeof(uint32_t) * 2); buff_org = buff = kzalloc(cfg->size, GFP_KERNEL); if (buff == NULL) { pr_err("Config buffer allocation failed\n"); return ret; } if (copy_from_user(buff, cfg->buffer, cfg->size)) { kfree(buff); pr_err("config buffer copy failed\n"); return ret; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); for (i = 0; i < counter; i++) { ptr = (void *) (((unsigned int) *buff) + mdss_res->mdss_io.base); if (!is_valid_calib_addr(ptr, cfg->ops)) { ret = -1; pr_err("Address validation failed or access not permitted\n"); break; } buff++; if (cfg->ops & MDP_PP_OPS_READ) *buff = readl_relaxed(ptr); else if (cfg->ops & MDP_PP_OPS_WRITE) writel_relaxed(*buff, ptr); buff++; } if (ret & MDP_PP_OPS_READ) { ret = copy_to_user(cfg->buffer, buff_org, cfg->size); *copyback = 1; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); kfree(buff_org); return ret; } static int sspp_cache_location(u32 pipe_type, enum pp_config_block *block) { int ret = 0; if (!block) { pr_err("invalid params %p\n", block); return -EINVAL; } switch (pipe_type) { case MDSS_MDP_PIPE_TYPE_VIG: *block = SSPP_VIG; break; case MDSS_MDP_PIPE_TYPE_RGB: *block = SSPP_RGB; break; case MDSS_MDP_PIPE_TYPE_DMA: *block = SSPP_DMA; break; default: pr_err("invalid pipe type %d\n", pipe_type); ret = -EINVAL; break; } return ret; } int mdss_mdp_pp_sspp_config(struct mdss_mdp_pipe *pipe) { struct mdp_histogram_start_req hist; struct mdp_pp_cache_res cache_res; u32 len = 0; int ret = 0; if (!pipe) { pr_err("invalid params, pipe %p\n", pipe); return -EINVAL; } cache_res.mdss_pp_res = NULL; cache_res.pipe_res = pipe; ret = sspp_cache_location(pipe->type, &cache_res.block); if (ret) { pr_err("invalid cache res block for igc ret %d\n", ret); goto exit_fail; } if ((pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_IGC_CFG)) { len = pipe->pp_cfg.igc_cfg.len; if (pp_ops[IGC].pp_set_config) { ret = pp_igc_lut_cache_params(&pipe->pp_cfg.igc_cfg, &cache_res, false); if (ret) { pr_err("failed to cache igc params ret %d\n", ret); goto exit_fail; } } else if (len == IGC_LUT_ENTRIES) { ret = copy_from_user(pipe->pp_res.igc_c0_c1, pipe->pp_cfg.igc_cfg.c0_c1_data, sizeof(uint32_t) * len); if (ret) { pr_err("failed to copy the igc c0_c1 data\n"); ret = -EFAULT; goto exit_fail; } ret = copy_from_user(pipe->pp_res.igc_c2, pipe->pp_cfg.igc_cfg.c2_data, sizeof(uint32_t) * len); if (ret) { ret = -EFAULT; pr_err("failed to copy the igc c2 data\n"); goto exit_fail; } pipe->pp_cfg.igc_cfg.c0_c1_data = pipe->pp_res.igc_c0_c1; pipe->pp_cfg.igc_cfg.c2_data = pipe->pp_res.igc_c2; } else pr_warn("invalid length of IGC len %d\n", len); } if (pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_HIST_CFG) { if (pipe->pp_cfg.hist_cfg.ops & MDP_PP_OPS_ENABLE) { hist.block = pipe->pp_cfg.hist_cfg.block; hist.frame_cnt = pipe->pp_cfg.hist_cfg.frame_cnt; hist.bit_mask = pipe->pp_cfg.hist_cfg.bit_mask; hist.num_bins = pipe->pp_cfg.hist_cfg.num_bins; mdss_mdp_hist_start(&hist); } else if (pipe->pp_cfg.hist_cfg.ops & MDP_PP_OPS_DISABLE) { mdss_mdp_hist_stop(pipe->pp_cfg.hist_cfg.block); } } if (pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_HIST_LUT_CFG) { if (!pp_ops[HIST_LUT].pp_set_config) { len = pipe->pp_cfg.hist_lut_cfg.len; if (len != ENHIST_LUT_ENTRIES) { ret = -EINVAL; pr_err("Invalid hist lut len: %d\n", len); goto exit_fail; } ret = copy_from_user(pipe->pp_res.hist_lut, pipe->pp_cfg.hist_lut_cfg.data, sizeof(uint32_t) * len); if (ret) { ret = -EFAULT; pr_err("failed to copy the hist lut\n"); goto exit_fail; } pipe->pp_cfg.hist_lut_cfg.data = pipe->pp_res.hist_lut; } else { ret = pp_hist_lut_cache_params( &pipe->pp_cfg.hist_lut_cfg, &cache_res); if (ret) { pr_err("Failed to cache Hist LUT params on pipe %d, ret %d\n", pipe->num, ret); goto exit_fail; } } } if ((pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_PA_V2_CFG) && (pp_ops[PA].pp_set_config)) { ret = pp_pa_cache_params(&pipe->pp_cfg.pa_v2_cfg_data, &cache_res); if (ret) { pr_err("Failed to cache PA params on pipe %d, ret %d\n", pipe->num, ret); goto exit_fail; } } if (pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_PCC_CFG && pp_ops[PCC].pp_set_config) { ret = pp_pcc_cache_params(&pipe->pp_cfg.pcc_cfg_data, &cache_res); if (ret) { pr_err("failed to cache the pcc params ret %d\n", ret); goto exit_fail; } } exit_fail: if (ret) { pr_err("VIG PP setup failed on pipe %d type %d ret %d\n", pipe->num, pipe->type, ret); pipe->pp_cfg.config_ops = 0; } return ret; } static int pp_update_pcc_pipe_setup(struct mdss_mdp_pipe *pipe, u32 location) { int ret = 0; struct mdss_data_type *mdata = NULL; char __iomem *pipe_base = NULL; if (!pipe) { pr_err("invalid param pipe %p\n", pipe); return -EINVAL; } mdata = mdss_mdp_get_mdata(); pipe_base = pipe->base; switch (location) { case SSPP_VIG: if (mdata->pp_block_off.vig_pcc_off == U32_MAX) { pr_err("invalid offset for vig pcc %d\n", U32_MAX); ret = -EINVAL; goto exit_sspp_setup; } pipe_base += mdata->pp_block_off.vig_pcc_off; break; case SSPP_RGB: if (mdata->pp_block_off.rgb_pcc_off == U32_MAX) { pr_err("invalid offset for rgb pcc %d\n", U32_MAX); ret = -EINVAL; goto exit_sspp_setup; } pipe_base += mdata->pp_block_off.rgb_pcc_off; break; case SSPP_DMA: if (mdata->pp_block_off.dma_pcc_off == U32_MAX) { pr_err("invalid offset for dma pcc %d\n", U32_MAX); ret = -EINVAL; goto exit_sspp_setup; } pipe_base += mdata->pp_block_off.dma_pcc_off; break; default: pr_err("invalid location for PCC %d\n", location); ret = -EINVAL; goto exit_sspp_setup; } pp_ops[PCC].pp_set_config(pipe_base, &pipe->pp_res.pp_sts, &pipe->pp_cfg.pcc_cfg_data, location); exit_sspp_setup: return ret; } int mdss_mdp_pp_get_version(struct mdp_pp_feature_version *version) { int ret = 0; u32 ver_info = mdp_pp_legacy; if (!version) { pr_err("invalid param version %p\n", version); ret = -EINVAL; goto exit_version; } if (version->pp_feature >= PP_FEATURE_MAX) { pr_err("invalid feature passed %d\n", version->pp_feature); ret = -EINVAL; goto exit_version; } if (pp_ops[version->pp_feature].pp_get_version) ret = pp_ops[version->pp_feature].pp_get_version(&ver_info); if (ret) pr_err("failed to query version for feature %d ret %d\n", version->pp_feature, ret); else version->version_info = ver_info; exit_version: return ret; } static void mdss_mdp_hist_irq_set_mask(u32 irq) { u32 mask; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); spin_lock(&mdata->hist_intr.lock); mask = readl_relaxed(mdata->mdp_base + MDSS_MDP_REG_HIST_INTR_EN); mask |= irq; pr_debug("interrupt mask being set %x irq updated %x\n", mask, irq); writel_relaxed(mask, mdata->mdp_base + MDSS_MDP_REG_HIST_INTR_EN); spin_unlock(&mdata->hist_intr.lock); } static void mdss_mdp_hist_irq_clear_mask(u32 irq) { u32 mask; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); spin_lock(&mdata->hist_intr.lock); mask = readl_relaxed(mdata->mdp_base + MDSS_MDP_REG_HIST_INTR_EN); mask = mask & ~irq; pr_debug("interrupt mask being cleared %x irq cleared %x\n", mask, irq); writel_relaxed(mask, mdata->mdp_base + MDSS_MDP_REG_HIST_INTR_EN); spin_unlock(&mdata->hist_intr.lock); } static void mdss_mdp_hist_intr_notify(u32 disp) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct pp_hist_col_info *hist_info = NULL; int i = 0, disp_count = 0, hist_count = 0; struct mdss_mdp_ctl *ctl = NULL; struct mdss_overlay_private *mdp5_data = NULL; for (i = 0; i < mdata->ndspp; i++) { hist_info = &mdss_pp_res->dspp_hist[i]; spin_lock(&hist_info->hist_lock); if (hist_info->disp_num == disp) { disp_count++; ctl = hist_info->ctl; if (hist_info->col_state == HIST_READY) hist_count++; } spin_unlock(&hist_info->hist_lock); } if (disp_count != hist_count || !ctl) return; mdp5_data = mfd_to_mdp5_data(ctl->mfd); if (!mdp5_data) { pr_err("mdp5_data is NULL\n"); return; } mdp5_data->hist_events++; sysfs_notify_dirent(mdp5_data->hist_event_sd); } int mdss_mdp_copy_layer_pp_info(struct mdp_input_layer *layer) { struct mdp_overlay_pp_params *pp_info = NULL; int ret = 0; uint32_t ops; if (!layer) { pr_err("invalid layer pointer passed %p\n", layer); return -EFAULT; } pp_info = kmalloc(sizeof(struct mdp_overlay_pp_params), GFP_KERNEL); if (!pp_info) return -ENOMEM; ret = copy_from_user(pp_info, layer->pp_info, sizeof(struct mdp_overlay_pp_params)); if (ret) { pr_err("layer list copy from user failed, pp_info = %p\n", layer->pp_info); ret = -EFAULT; goto exit_pp_info; } ops = pp_info->config_ops; if (ops & MDP_OVERLAY_PP_IGC_CFG) { ret = pp_copy_layer_igc_payload(pp_info); if (ret) { pr_err("Failed to copy IGC payload, ret = %d\n", ret); goto exit_pp_info; } } if (ops & MDP_OVERLAY_PP_HIST_LUT_CFG) { ret = pp_copy_layer_hist_lut_payload(pp_info); if (ret) { pr_err("Failed to copy Hist LUT payload, ret = %d\n", ret); goto exit_igc; } } if (ops & MDP_OVERLAY_PP_PA_V2_CFG) { ret = pp_copy_layer_pa_payload(pp_info); if (ret) { pr_err("Failed to copy PA payload, ret = %d\n", ret); goto exit_hist_lut; } } if (ops & MDP_OVERLAY_PP_PCC_CFG) { ret = pp_copy_layer_pcc_payload(pp_info); if (ret) { pr_err("Failed to copy PCC payload, ret = %d\n", ret); goto exit_pa; } } layer->pp_info = pp_info; return ret; exit_pa: kfree(pp_info->pa_v2_cfg_data.cfg_payload); exit_hist_lut: kfree(pp_info->hist_lut_cfg.cfg_payload); exit_igc: kfree(pp_info->igc_cfg.cfg_payload); exit_pp_info: kfree(pp_info); return ret; } void mdss_mdp_free_layer_pp_info(struct mdp_input_layer *layer) { struct mdp_overlay_pp_params *pp_info = (layer) ? (struct mdp_overlay_pp_params *) layer->pp_info : NULL; if (!pp_info) return; kfree(pp_info->igc_cfg.cfg_payload); kfree(pp_info->hist_lut_cfg.cfg_payload); kfree(pp_info->pa_v2_cfg_data.cfg_payload); kfree(pp_info->pcc_cfg_data.cfg_payload); kfree(pp_info); layer->pp_info = NULL; } int mdss_mdp_mfd_valid_dspp(struct msm_fb_data_type *mfd) { struct mdss_mdp_ctl *ctl = NULL; int valid_dspp = false; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); ctl = mfd_to_ctl(mfd); valid_dspp = (ctl) && (ctl->mixer_left) && (ctl->mixer_left->num < mdata->ndspp); if ((ctl) && (ctl->mixer_right)) valid_dspp &= (ctl->mixer_right->num < mdata->ndspp); return valid_dspp; } static int mdss_mdp_mfd_valid_ad(struct msm_fb_data_type *mfd) { struct mdss_mdp_ctl *ctl = NULL; int valid_ad = false; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); ctl = mfd_to_ctl(mfd); valid_ad = (ctl) && (ctl->mixer_left) && (ctl->mixer_left->num < mdata->nad_cfgs); if ((ctl) && (ctl->mixer_right)) valid_ad &= (ctl->mixer_right->num < mdata->nad_cfgs); return valid_ad; } static int pp_mfd_release_all(struct msm_fb_data_type *mfd) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); int ret = 0; if (mdata->nad_cfgs) { ret = pp_mfd_ad_release_all(mfd); if (ret) pr_err("ad release all failed on disp %d, ret %d\n", mfd->index, ret); } if (mdss_mdp_mfd_valid_dspp(mfd)) mdss_mdp_hist_stop(mfd->index + MDP_LOGICAL_BLOCK_DISP_0); memset(&mdss_pp_res->pp_disp_sts[mfd->index], 0, sizeof(mdss_pp_res->pp_disp_sts[mfd->index])); mfd->bl_scale = 1024; return ret; } static int pp_mfd_ad_release_all(struct msm_fb_data_type *mfd) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdss_mdp_ctl *ctl = NULL; struct mdss_ad_info *ad = NULL; int ret = 0; if (!mdata || !mfd) { pr_err("invalid params mdata %p mfd %p\n", mdata, mfd); return -EINVAL; } if (!mdata->ad_calc_wq) return 0; ret = mdss_mdp_get_ad(mfd, &ad); if (ret == -ENODEV || ret == -EPERM) { pr_debug("AD not supported on device, disp num %d\n", mfd->index); return 0; } else if (ret) { pr_err("failed to get ad_info ret %d\n", ret); return ret; } if (!ad->mfd) return 0; mutex_lock(&ad->lock); ad->sts &= ~PP_STS_ENABLE; ad->mfd = NULL; ad->bl_mfd = NULL; ad->state = 0; cancel_work_sync(&ad->calc_work); mutex_unlock(&ad->lock); ctl = mfd_to_ctl(mfd); if (ctl && ctl->ops.remove_vsync_handler) ctl->ops.remove_vsync_handler(ctl, &ad->handle); return ret; } static inline int pp_validate_dspp_mfd_block(struct msm_fb_data_type *mfd, int block) { if (!mfd) return -EINVAL; if (!mdss_mdp_mfd_valid_dspp(mfd)) { pr_err("invalid display num %d for PP config\n", mfd->index); return -EPERM; } if ((block < MDP_LOGICAL_BLOCK_DISP_0) || (block >= MDP_BLOCK_MAX)) { pr_err("invalid block %d\n", block); return -EINVAL; } if ((block - MDP_LOGICAL_BLOCK_DISP_0) != mfd->index) { pr_err("PP block %d does not match corresponding mfd index %d\n", block, mfd->index); return -EINVAL; } return 0; }