/* 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 #include #include #include #include #include #include #include #include "mdss_fb.h" #include "mdss_mdp.h" #include "mdss_mdp_trace.h" #include "mdss_debug.h" #define MDSS_MDP_WB_OUTPUT_BPP 3 static inline u64 fudge_factor(u64 val, u32 numer, u32 denom) { u64 result = (val * (u64)numer); do_div(result, denom); return result; } static inline u64 apply_fudge_factor(u64 val, struct mult_factor *factor) { return fudge_factor(val, factor->numer, factor->denom); } static inline u64 apply_inverse_fudge_factor(u64 val, struct mult_factor *factor) { return fudge_factor(val, factor->denom, factor->numer); } static DEFINE_MUTEX(mdss_mdp_ctl_lock); static u32 mdss_mdp_get_vbp_factor_max(struct mdss_mdp_ctl *ctl); static void __mdss_mdp_reset_mixercfg(struct mdss_mdp_ctl *ctl) { u32 off; int i, nmixers; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (!ctl || !mdata) return; nmixers = mdata->nmixers_intf + mdata->nmixers_wb; for (i = 0; i < nmixers; i++) { off = MDSS_MDP_REG_CTL_LAYER(i); mdss_mdp_ctl_write(ctl, off, 0); off += MDSS_MDP_REG_CTL_LAYER_EXTN(i); mdss_mdp_ctl_write(ctl, off, 0); } } static inline int __mdss_mdp_ctl_get_mixer_off(struct mdss_mdp_mixer *mixer) { if (mixer->type == MDSS_MDP_MIXER_TYPE_INTF) { if (mixer->num == MDSS_MDP_INTF_LAYERMIXER3) return MDSS_MDP_CTL_X_LAYER_5; else return MDSS_MDP_REG_CTL_LAYER(mixer->num); } else { return MDSS_MDP_REG_CTL_LAYER(mixer->num + MDSS_MDP_INTF_LAYERMIXER3); } } static inline int __mdss_mdp_ctl_get_mixer_extn_off( struct mdss_mdp_mixer *mixer) { if (mixer->type == MDSS_MDP_MIXER_TYPE_INTF) { if (mixer->num == MDSS_MDP_INTF_LAYERMIXER3) return MDSS_MDP_REG_CTL_LAYER_EXTN(5); else return MDSS_MDP_REG_CTL_LAYER_EXTN(mixer->num); } else { return MDSS_MDP_REG_CTL_LAYER_EXTN(mixer->num + MDSS_MDP_INTF_LAYERMIXER3); } } u32 mdss_mdp_get_mixercfg(struct mdss_mdp_mixer *mixer, bool extn) { u32 mixer_off; if (!mixer || !mixer->ctl) return 0; if (extn) mixer_off = __mdss_mdp_ctl_get_mixer_extn_off(mixer); else mixer_off = __mdss_mdp_ctl_get_mixer_off(mixer); return mdss_mdp_ctl_read(mixer->ctl, mixer_off); } static inline u32 mdss_mdp_get_pclk_rate(struct mdss_mdp_ctl *ctl) { struct mdss_panel_info *pinfo = &ctl->panel_data->panel_info; return (ctl->intf_type == MDSS_INTF_DSI) ? pinfo->mipi.dsi_pclk_rate : pinfo->clk_rate; } static inline u32 mdss_mdp_clk_fudge_factor(struct mdss_mdp_mixer *mixer, u32 rate) { struct mdss_panel_info *pinfo = &mixer->ctl->panel_data->panel_info; rate = apply_fudge_factor(rate, &mdss_res->clk_factor); /* * If the panel is video mode and its back porch period is * small, the workaround of increasing mdp clk is needed to * avoid underrun. */ if (mixer->ctl->is_video_mode && pinfo && (pinfo->lcdc.v_back_porch < MDP_MIN_VBP)) rate = apply_fudge_factor(rate, &mdss_res->clk_factor); return rate; } struct mdss_mdp_prefill_params { u32 smp_bytes; u32 xres; u32 src_w; u32 dst_w; u32 src_h; u32 dst_h; u32 dst_y; u32 bpp; u32 pnum; bool is_yuv; bool is_caf; bool is_fbc; bool is_bwc; bool is_tile; bool is_hflip; bool is_cmd; bool is_ubwc; bool is_nv12; }; static inline bool mdss_mdp_perf_is_caf(struct mdss_mdp_pipe *pipe) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); /* * CAF mode filter is enabled when format is yuv and * upscaling. Post processing had the decision to use CAF * under these conditions. */ return ((mdata->mdp_rev >= MDSS_MDP_HW_REV_102) && pipe->src_fmt->is_yuv && ((pipe->src.h >> pipe->vert_deci) <= pipe->dst.h)); } static inline u32 mdss_mdp_calc_y_scaler_bytes(struct mdss_mdp_prefill_params *params, struct mdss_prefill_data *prefill) { u32 y_scaler_bytes = 0, y_scaler_lines = 0; if (params->is_yuv) { if (params->src_h != params->dst_h) { y_scaler_lines = (params->is_caf) ? prefill->y_scaler_lines_caf : prefill->y_scaler_lines_bilinear; /* * y is src_width, u is src_width/2 and v is * src_width/2, so the total is scaler_lines * * src_w * 2 */ y_scaler_bytes = y_scaler_lines * params->src_w * 2; } } else { if (params->src_h != params->dst_h) { y_scaler_lines = prefill->y_scaler_lines_bilinear; y_scaler_bytes = y_scaler_lines * params->src_w * params->bpp; } } return y_scaler_bytes; } static inline u32 mdss_mdp_align_latency_buf_bytes( u32 latency_buf_bytes, u32 percentage, u32 smp_bytes) { u32 aligned_bytes; aligned_bytes = ((smp_bytes - latency_buf_bytes) * percentage) / 100; pr_debug("percentage=%d, extra_bytes(per)=%d smp_bytes=%d latency=%d\n", percentage, aligned_bytes, smp_bytes, latency_buf_bytes); return latency_buf_bytes + aligned_bytes; } /** * @ mdss_mdp_calc_latency_buf_bytes() - * Get the number of bytes for the * latency lines. * @is_yuv - true if format is yuv * @is_bwc - true if BWC is enabled * @is_tile - true if it is Tile format * @src_w - source rectangle width * @bpp - Bytes per pixel of source rectangle * @use_latency_buf_percentage - use an extra percentage for * the latency bytes calculation. * @smp_bytes - size of the smp for alignment * @is_ubwc - true if UBWC is enabled * @is_nv12 - true if NV12 format is used * @is_hflip - true if HFLIP is enabled * * Return: * The amount of bytes to consider for the latency lines, where: * If use_latency_buf_percentate is TRUE: * Function will return the amount of bytes for the * latency lines plus a percentage of the * additional bytes allocated to align with the * SMP size. Percentage is determined by * "latency_buff_per", which can be modified * through debugfs. * If use_latency_buf_percentage is FALSE: * Function will return only the the amount of bytes * for the latency lines without any * extra bytes. */ u32 mdss_mdp_calc_latency_buf_bytes(bool is_yuv, bool is_bwc, bool is_tile, u32 src_w, u32 bpp, bool use_latency_buf_percentage, u32 smp_bytes, bool is_ubwc, bool is_nv12, bool is_hflip) { u32 latency_lines = 0, latency_buf_bytes; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (is_hflip && !mdata->hflip_buffer_reused) latency_lines = 1; if (is_yuv) { if (is_ubwc) { if (is_nv12) latency_lines += 8; else latency_lines += 4; latency_buf_bytes = src_w * bpp * latency_lines; } else if (is_bwc) { latency_lines += 4; latency_buf_bytes = src_w * bpp * latency_lines; } else { if (!mdata->hflip_buffer_reused) latency_lines += 1; else latency_lines = 2; /* multiply * 2 for the two YUV planes */ latency_buf_bytes = mdss_mdp_align_latency_buf_bytes( src_w * bpp * latency_lines, use_latency_buf_percentage ? mdata->latency_buff_per : 0, smp_bytes) * 2; } } else { if (is_ubwc) { latency_lines += 4; latency_buf_bytes = src_w * bpp * latency_lines; } else if (is_tile) { latency_lines += 8; latency_buf_bytes = src_w * bpp * latency_lines; } else if (is_bwc) { latency_lines += 4; latency_buf_bytes = src_w * bpp * latency_lines; } else { if (!mdata->hflip_buffer_reused) latency_lines += 1; else latency_lines = 2; latency_buf_bytes = mdss_mdp_align_latency_buf_bytes( src_w * bpp * latency_lines, use_latency_buf_percentage ? mdata->latency_buff_per : 0, smp_bytes); } } return latency_buf_bytes; } static inline u32 mdss_mdp_calc_scaling_w_h(u32 val, u32 src_h, u32 dst_h, u32 src_w, u32 dst_w) { if (dst_h) val = mult_frac(val, src_h, dst_h); if (dst_w) val = mult_frac(val, src_w, dst_w); return val; } static u32 mdss_mdp_perf_calc_pipe_prefill_video(struct mdss_mdp_prefill_params *params) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdss_prefill_data *prefill = &mdata->prefill_data; u32 prefill_bytes; u32 latency_buf_bytes; u32 y_buf_bytes = 0; u32 y_scaler_bytes = 0; u32 pp_bytes = 0, pp_lines = 0; u32 post_scaler_bytes; u32 fbc_bytes = 0; prefill_bytes = prefill->ot_bytes; latency_buf_bytes = mdss_mdp_calc_latency_buf_bytes(params->is_yuv, params->is_bwc, params->is_tile, params->src_w, params->bpp, true, params->smp_bytes, params->is_ubwc, params->is_nv12, params->is_hflip); prefill_bytes += latency_buf_bytes; pr_debug("latency_buf_bytes bw_calc=%d actual=%d\n", latency_buf_bytes, params->smp_bytes); if (params->is_yuv) y_buf_bytes = prefill->y_buf_bytes; y_scaler_bytes = mdss_mdp_calc_y_scaler_bytes(params, prefill); prefill_bytes += y_buf_bytes + y_scaler_bytes; if (mdata->apply_post_scale_bytes || (params->src_h != params->dst_h) || (params->src_w != params->dst_w)) { post_scaler_bytes = prefill->post_scaler_pixels * params->bpp; post_scaler_bytes = mdss_mdp_calc_scaling_w_h(post_scaler_bytes, params->src_h, params->dst_h, params->src_w, params->dst_w); prefill_bytes += post_scaler_bytes; } if (params->xres) pp_lines = DIV_ROUND_UP(prefill->pp_pixels, params->xres); if (params->xres && params->dst_h && (params->dst_y <= pp_lines)) pp_bytes = ((params->src_w * params->bpp * prefill->pp_pixels / params->xres) * params->src_h) / params->dst_h; prefill_bytes += pp_bytes; if (params->is_fbc) { fbc_bytes = prefill->fbc_lines * params->bpp; fbc_bytes = mdss_mdp_calc_scaling_w_h(fbc_bytes, params->src_h, params->dst_h, params->src_w, params->dst_w); } prefill_bytes += fbc_bytes; trace_mdp_perf_prefill_calc(params->pnum, latency_buf_bytes, prefill->ot_bytes, y_buf_bytes, y_scaler_bytes, pp_lines, pp_bytes, post_scaler_bytes, fbc_bytes, prefill_bytes); pr_debug("ot=%d y_buf=%d pp_lines=%d pp=%d post_sc=%d fbc_bytes=%d\n", prefill->ot_bytes, y_buf_bytes, pp_lines, pp_bytes, post_scaler_bytes, fbc_bytes); return prefill_bytes; } static u32 mdss_mdp_perf_calc_pipe_prefill_cmd(struct mdss_mdp_prefill_params *params) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdss_prefill_data *prefill = &mdata->prefill_data; u32 prefill_bytes; u32 ot_bytes = 0; u32 latency_lines, latency_buf_bytes; u32 y_buf_bytes = 0; u32 y_scaler_bytes; u32 fbc_cmd_lines = 0, fbc_cmd_bytes = 0; u32 post_scaler_bytes = 0; /* y_scaler_bytes are same for the first or non first line */ y_scaler_bytes = mdss_mdp_calc_y_scaler_bytes(params, prefill); prefill_bytes = y_scaler_bytes; /* 1st line if fbc is not enabled and 2nd line if fbc is enabled */ if (((params->dst_y == 0) && !params->is_fbc) || ((params->dst_y <= 1) && params->is_fbc)) { if (params->is_ubwc) { if (params->is_nv12) latency_lines = 8; else latency_lines = 4; } else if (params->is_bwc || params->is_tile) { latency_lines = 4; } else if (params->is_hflip) { latency_lines = 1; } else { latency_lines = 0; } latency_buf_bytes = params->src_w * params->bpp * latency_lines; prefill_bytes += latency_buf_bytes; fbc_cmd_lines++; if (params->is_fbc) fbc_cmd_lines++; fbc_cmd_bytes = params->bpp * params->dst_w * fbc_cmd_lines; fbc_cmd_bytes = mdss_mdp_calc_scaling_w_h(fbc_cmd_bytes, params->src_h, params->dst_h, params->src_w, params->dst_w); prefill_bytes += fbc_cmd_bytes; } else { ot_bytes = prefill->ot_bytes; prefill_bytes += ot_bytes; latency_buf_bytes = mdss_mdp_calc_latency_buf_bytes( params->is_yuv, params->is_bwc, params->is_tile, params->src_w, params->bpp, true, params->smp_bytes, params->is_ubwc, params->is_nv12, params->is_hflip); prefill_bytes += latency_buf_bytes; if (params->is_yuv) y_buf_bytes = prefill->y_buf_bytes; prefill_bytes += y_buf_bytes; if (mdata->apply_post_scale_bytes || (params->src_h != params->dst_h) || (params->src_w != params->dst_w)) { post_scaler_bytes = prefill->post_scaler_pixels * params->bpp; post_scaler_bytes = mdss_mdp_calc_scaling_w_h( post_scaler_bytes, params->src_h, params->dst_h, params->src_w, params->dst_w); prefill_bytes += post_scaler_bytes; } } pr_debug("ot=%d bwc=%d smp=%d y_buf=%d fbc=%d\n", ot_bytes, params->is_bwc, latency_buf_bytes, y_buf_bytes, fbc_cmd_bytes); return prefill_bytes; } u32 mdss_mdp_perf_calc_pipe_prefill_single(struct mdss_mdp_prefill_params *params) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdss_prefill_data *prefill = &mdata->prefill_data; u32 prefill_bytes; u32 latency_lines, latency_buf_bytes; u32 y_scaler_bytes; u32 fbc_cmd_lines = 0, fbc_cmd_bytes = 0; if (params->is_ubwc) { if (params->is_nv12) latency_lines = 8; else latency_lines = 4; } else if (params->is_bwc || params->is_tile) /* can start processing after receiving 4 lines */ latency_lines = 4; else if (params->is_hflip) /* need oneline before reading backwards */ latency_lines = 1; else latency_lines = 0; latency_buf_bytes = params->src_w * params->bpp * latency_lines; prefill_bytes = latency_buf_bytes; y_scaler_bytes = mdss_mdp_calc_y_scaler_bytes(params, prefill); prefill_bytes += y_scaler_bytes; if (params->is_cmd) fbc_cmd_lines++; if (params->is_fbc) fbc_cmd_lines++; if (fbc_cmd_lines) { fbc_cmd_bytes = params->bpp * params->dst_w * fbc_cmd_lines; fbc_cmd_bytes = mdss_mdp_calc_scaling_w_h(fbc_cmd_bytes, params->src_h, params->dst_h, params->src_w, params->dst_w); prefill_bytes += fbc_cmd_bytes; } return prefill_bytes; } u32 mdss_mdp_perf_calc_smp_size(struct mdss_mdp_pipe *pipe, bool calc_smp_size) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); u32 smp_bytes; if (pipe->type == PIPE_TYPE_CURSOR) return 0; /* Get allocated or fixed smp bytes */ smp_bytes = mdss_mdp_smp_get_size(pipe); /* * We need to calculate the SMP size for scenarios where * allocation have not happened yet (i.e. during prepare IOCTL). */ if (calc_smp_size && !mdata->has_pixel_ram) { u32 calc_smp_total; calc_smp_total = mdss_mdp_smp_calc_num_blocks(pipe); calc_smp_total *= mdata->smp_mb_size; /* * If the pipe has fixed SMPs, then we must consider * the max smp size. */ if (calc_smp_total > smp_bytes) smp_bytes = calc_smp_total; } pr_debug("SMP size (bytes) %d for pnum=%d calc=%d\n", smp_bytes, pipe->num, calc_smp_size); BUG_ON(smp_bytes == 0); return smp_bytes; } static void mdss_mdp_get_bw_vote_mode(void *data, u32 mdp_rev, struct mdss_mdp_perf_params *perf, enum perf_calc_vote_mode calc_mode, u32 flags) { if (!data) goto exit; switch (mdp_rev) { case MDSS_MDP_HW_REV_105: case MDSS_MDP_HW_REV_109: if (calc_mode == PERF_CALC_VOTE_MODE_PER_PIPE) { struct mdss_mdp_mixer *mixer = (struct mdss_mdp_mixer *)data; if ((flags & PERF_CALC_PIPE_SINGLE_LAYER) && !mixer->rotator_mode && (mixer->type == MDSS_MDP_MIXER_TYPE_INTF)) set_bit(MDSS_MDP_BW_MODE_SINGLE_LAYER, perf->bw_vote_mode); } else if (calc_mode == PERF_CALC_VOTE_MODE_CTL) { struct mdss_mdp_ctl *ctl = (struct mdss_mdp_ctl *)data; if (ctl->is_video_mode && (ctl->mfd->split_mode == MDP_SPLIT_MODE_NONE)) set_bit(MDSS_MDP_BW_MODE_SINGLE_IF, perf->bw_vote_mode); } break; default: break; }; pr_debug("mode=0x%lx\n", *(perf->bw_vote_mode)); exit: return; } static u32 get_pipe_mdp_clk_rate(struct mdss_mdp_pipe *pipe, struct mdss_rect src, struct mdss_rect dst, u32 fps, u32 v_total, u32 flags) { struct mdss_mdp_mixer *mixer; u32 rate, src_h; mixer = pipe->mixer_left; src_h = src.h >> pipe->vert_deci; if (mixer->rotator_mode) { rate = pipe->src.w * pipe->src.h * fps; rate /= 4; /* block mode fetch at 4 pix/clk */ } else { rate = dst.w; if (src_h > dst.h) rate = (rate * src_h) / dst.h; rate *= v_total * fps; /* pipes decoding BWC content have different clk requirement */ if (pipe->bwc_mode && !pipe->src_fmt->is_yuv && pipe->src_fmt->bpp == 4) { u32 bwc_rate = mult_frac((src.w * src_h * fps), v_total, dst.h << 1); pr_debug("src: w:%d h:%d fps:%d vtotal:%d dst h:%d\n", src.w, src_h, fps, v_total, dst.h); pr_debug("pipe%d: bwc_rate=%d normal_rate=%d\n", pipe->num, bwc_rate, rate); rate = max(bwc_rate, rate); } } if (flags & PERF_CALC_PIPE_APPLY_CLK_FUDGE) rate = mdss_mdp_clk_fudge_factor(mixer, rate); return rate; } static inline bool validate_comp_ratio(struct mult_factor *factor) { return factor->numer && factor->denom; } u32 apply_comp_ratio_factor(u32 quota, struct mdss_mdp_format_params *fmt, struct mult_factor *factor) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (!mdata || !test_bit(MDSS_QOS_OVERHEAD_FACTOR, mdata->mdss_qos_map)) return quota; /* apply compression ratio, only for compressed formats */ if (mdss_mdp_is_ubwc_format(fmt) && validate_comp_ratio(factor)) quota = apply_inverse_fudge_factor(quota , factor); return quota; } static u32 mdss_mdp_get_rotator_fps(struct mdss_mdp_pipe *pipe) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); u32 fps = DEFAULT_FRAME_RATE; if (pipe->frame_rate) fps = pipe->frame_rate; if (mdata->traffic_shaper_en) fps = DEFAULT_ROTATOR_FRAME_RATE; if (pipe->src.w >= 3840 || pipe->src.h >= 3840) fps = ROTATOR_LOW_FRAME_RATE; pr_debug("rotator fps:%d\n", fps); return fps; } u64 mdss_mdp_perf_calc_simplified_prefill(struct mdss_mdp_pipe *pipe, u32 v_total, u32 fps, struct mdss_mdp_ctl *ctl) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct simplified_prefill_factors *pfactors = &mdata->prefill_data.prefill_factors; u64 prefill_per_pipe = 0; u32 prefill_lines = pfactors->xtra_ff_factor; /* do not calculate prefill for command mode */ if (!ctl->is_video_mode) goto exit; prefill_per_pipe = pipe->src.w * pipe->src_fmt->bpp; /* format factors */ if (mdss_mdp_is_tile_format(pipe->src_fmt)) { if (mdss_mdp_is_nv12_format(pipe->src_fmt)) prefill_lines += pfactors->fmt_mt_nv12_factor; else prefill_lines += pfactors->fmt_mt_factor; } else { prefill_lines += pfactors->fmt_linear_factor; } /* scaling factors */ if (pipe->src.h > pipe->dst.h) { prefill_lines += pfactors->scale_factor; prefill_per_pipe = fudge_factor(prefill_per_pipe, DECIMATED_DIMENSION(pipe->src.h, pipe->vert_deci), pipe->dst.h); } prefill_per_pipe *= prefill_lines * mdss_mdp_get_vbp_factor_max(ctl); pr_debug("pipe src: %dx%d bpp:%d\n", pipe->src.w, pipe->src.h, pipe->src_fmt->bpp); pr_debug("ff_factor:%d mt_nv12:%d mt:%d\n", pfactors->xtra_ff_factor, (mdss_mdp_is_tile_format(pipe->src_fmt) && mdss_mdp_is_nv12_format(pipe->src_fmt)) ? pfactors->fmt_mt_nv12_factor : 0, mdss_mdp_is_tile_format(pipe->src_fmt) ? pfactors->fmt_mt_factor : 0); pr_debug("pipe prefill:%llu lines:%d\n", prefill_per_pipe, prefill_lines); exit: return prefill_per_pipe; } /** * mdss_mdp_perf_calc_pipe() - calculate performance numbers required by pipe * @pipe: Source pipe struct containing updated pipe params * @perf: Structure containing values that should be updated for * performance tuning * @flags: flags to determine how to perform some of the * calculations, supported flags: * * PERF_CALC_PIPE_APPLY_CLK_FUDGE: * Determine if mdp clock fudge is applicable. * PERF_CALC_PIPE_SINGLE_LAYER: * Indicate if the calculation is for a single pipe staged * in the layer mixer * PERF_CALC_PIPE_CALC_SMP_SIZE: * Indicate if the smp size needs to be calculated, this is * for the cases where SMP haven't been allocated yet, so we need * to estimate here the smp size (i.e. PREPARE IOCTL). * * Function calculates the minimum required performance calculations in order * to avoid MDP underflow. The calculations are based on the way MDP * fetches (bandwidth requirement) and processes data through MDP pipeline * (MDP clock requirement) based on frame size and scaling requirements. */ int mdss_mdp_perf_calc_pipe(struct mdss_mdp_pipe *pipe, struct mdss_mdp_perf_params *perf, struct mdss_rect *roi, u32 flags) { struct mdss_mdp_mixer *mixer; int fps = DEFAULT_FRAME_RATE; u32 quota, v_total = 0, src_h, xres = 0, h_total = 0; struct mdss_rect src, dst; bool is_fbc = false; struct mdss_mdp_prefill_params prefill_params; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); bool calc_smp_size = false; if (!pipe || !perf || !pipe->mixer_left) return -EINVAL; mixer = pipe->mixer_left; dst = pipe->dst; src = pipe->src; if (mixer->rotator_mode) { fps = mdss_mdp_get_rotator_fps(pipe); } else if (mixer->type == MDSS_MDP_MIXER_TYPE_INTF) { struct mdss_panel_info *pinfo; if (!mixer->ctl) return -EINVAL; pinfo = &mixer->ctl->panel_data->panel_info; if (pinfo->type == MIPI_VIDEO_PANEL) { fps = pinfo->panel_max_fps; v_total = pinfo->panel_max_vtotal; } else { fps = mdss_panel_get_framerate(pinfo); v_total = mdss_panel_get_vtotal(pinfo); } xres = get_panel_width(mixer->ctl); is_fbc = pinfo->fbc.enabled; h_total = mdss_panel_get_htotal(pinfo, false); if (is_pingpong_split(mixer->ctl->mfd)) h_total += mdss_panel_get_htotal( &mixer->ctl->panel_data->next->panel_info, false); } else { v_total = mixer->height; xres = mixer->width; h_total = mixer->width; } mixer->ctl->frame_rate = fps; if (roi && !mixer->ctl->is_video_mode && !pipe->src_split_req) mdss_mdp_crop_rect(&src, &dst, roi); pr_debug("v_total=%d, xres=%d fps=%d\n", v_total, xres, fps); /* * when doing vertical decimation lines will be skipped, hence there is * no need to account for these lines in MDP clock or request bus * bandwidth to fetch them. */ src_h = DECIMATED_DIMENSION(src.h, pipe->vert_deci); quota = fps * src.w * src_h; pr_debug("src(w,h)(%d,%d) dst(w,h)(%d,%d) dst_y=%d bpp=%d yuv=%d\n", pipe->src.w, src_h, pipe->dst.w, pipe->dst.h, pipe->dst.y, pipe->src_fmt->bpp, pipe->src_fmt->is_yuv); if (pipe->src_fmt->chroma_sample == MDSS_MDP_CHROMA_420) /* * with decimation, chroma is not downsampled, this means we * need to allocate bw for extra lines that will be fetched */ if (pipe->vert_deci) quota *= 2; else quota = (quota * 3) / 2; else quota *= pipe->src_fmt->bpp; if (mixer->rotator_mode) { if (test_bit(MDSS_QOS_OVERHEAD_FACTOR, mdata->mdss_qos_map)) { /* rotator read */ quota = apply_comp_ratio_factor(quota, pipe->src_fmt, &pipe->comp_ratio); /* * rotator write: here we are using src_fmt since * current implementation only supports calculate * bandwidth based in the source parameters. * The correct fine-tuned calculation should use * destination format and destination rectangles to * calculate the bandwidth, but leaving this * calculation as per current support. */ quota += apply_comp_ratio_factor(quota, pipe->src_fmt, &pipe->comp_ratio); } else { quota *= 2; /* bus read + write */ } } else { quota = mult_frac(quota, v_total, dst.h); if (!mixer->ctl->is_video_mode) quota = mult_frac(quota, h_total, xres); if (test_bit(MDSS_QOS_OVERHEAD_FACTOR, mdata->mdss_qos_map)) quota = apply_comp_ratio_factor(quota, pipe->src_fmt, &pipe->comp_ratio); } perf->bw_overlap = quota; perf->mdp_clk_rate = get_pipe_mdp_clk_rate(pipe, src, dst, fps, v_total, flags); if (mixer->ctl->intf_num == MDSS_MDP_NO_INTF || mdata->disable_prefill || mixer->ctl->disable_prefill || (pipe->flags & MDP_SOLID_FILL)) { perf->prefill_bytes = 0; perf->bw_prefill = 0; goto exit; } if (test_bit(MDSS_QOS_SIMPLIFIED_PREFILL, mdata->mdss_qos_map)) { perf->bw_prefill = mdss_mdp_perf_calc_simplified_prefill(pipe, v_total, fps, mixer->ctl); goto exit; } calc_smp_size = (flags & PERF_CALC_PIPE_CALC_SMP_SIZE) ? true : false; prefill_params.smp_bytes = mdss_mdp_perf_calc_smp_size(pipe, calc_smp_size); prefill_params.xres = xres; prefill_params.src_w = src.w; prefill_params.src_h = src_h; prefill_params.dst_w = dst.w; prefill_params.dst_h = dst.h; prefill_params.dst_y = dst.y; prefill_params.bpp = pipe->src_fmt->bpp; prefill_params.is_yuv = pipe->src_fmt->is_yuv; prefill_params.is_caf = mdss_mdp_perf_is_caf(pipe); prefill_params.is_fbc = is_fbc; prefill_params.is_bwc = pipe->bwc_mode; prefill_params.is_tile = mdss_mdp_is_tile_format(pipe->src_fmt); prefill_params.is_hflip = pipe->flags & MDP_FLIP_LR; prefill_params.is_cmd = !mixer->ctl->is_video_mode; prefill_params.pnum = pipe->num; prefill_params.is_bwc = mdss_mdp_is_ubwc_format(pipe->src_fmt); prefill_params.is_nv12 = mdss_mdp_is_nv12_format(pipe->src_fmt); mdss_mdp_get_bw_vote_mode(mixer, mdata->mdp_rev, perf, PERF_CALC_VOTE_MODE_PER_PIPE, flags); if (flags & PERF_CALC_PIPE_SINGLE_LAYER) perf->prefill_bytes = mdss_mdp_perf_calc_pipe_prefill_single(&prefill_params); else if (!prefill_params.is_cmd) perf->prefill_bytes = mdss_mdp_perf_calc_pipe_prefill_video(&prefill_params); else perf->prefill_bytes = mdss_mdp_perf_calc_pipe_prefill_cmd(&prefill_params); exit: pr_debug("mixer=%d pnum=%d clk_rate=%u bw_overlap=%llu bw_prefill=%llu (%d) %s\n", mixer->num, pipe->num, perf->mdp_clk_rate, perf->bw_overlap, perf->bw_prefill, perf->prefill_bytes, mdata->disable_prefill ? "prefill is disabled" : ""); return 0; } static inline int mdss_mdp_perf_is_overlap(u32 y00, u32 y01, u32 y10, u32 y11) { return (y10 < y00 && y11 >= y01) || (y10 >= y00 && y10 < y01); } static inline int cmpu32(const void *a, const void *b) { return (*(u32 *)a < *(u32 *)b) ? -1 : 0; } static void mdss_mdp_perf_calc_mixer(struct mdss_mdp_mixer *mixer, struct mdss_mdp_perf_params *perf, struct mdss_mdp_pipe **pipe_list, int num_pipes, u32 flags) { struct mdss_mdp_pipe *pipe; struct mdss_panel_info *pinfo = NULL; int fps = DEFAULT_FRAME_RATE; u32 v_total = 0, bpp = MDSS_MDP_WB_OUTPUT_BPP; int i; u32 max_clk_rate = 0; u64 bw_overlap_max = 0; u64 bw_overlap[MAX_PIPES_PER_LM] = { 0 }; u64 bw_overlap_async = 0; u32 v_region[MAX_PIPES_PER_LM * 2] = { 0 }; u32 prefill_val = 0; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); bool apply_fudge = true; struct mdss_mdp_format_params *fmt; BUG_ON(num_pipes > MAX_PIPES_PER_LM); memset(perf, 0, sizeof(*perf)); if (!mixer->rotator_mode) { if (mixer->type == MDSS_MDP_MIXER_TYPE_INTF) { pinfo = &mixer->ctl->panel_data->panel_info; if (pinfo->type == MIPI_VIDEO_PANEL) { fps = pinfo->panel_max_fps; v_total = pinfo->panel_max_vtotal; } else { fps = mdss_panel_get_framerate(pinfo); v_total = mdss_panel_get_vtotal(pinfo); } if (pinfo->type == WRITEBACK_PANEL) { fmt = mdss_mdp_get_format_params( mixer->ctl->dst_format); if (fmt) bpp = fmt->bpp; pinfo = NULL; } } else { v_total = mixer->height; } perf->mdp_clk_rate = mixer->width * v_total * fps; perf->mdp_clk_rate = mdss_mdp_clk_fudge_factor(mixer, perf->mdp_clk_rate); if (!pinfo) { /* perf for bus writeback */ perf->bw_writeback = fps * mixer->width * mixer->height * bpp; if (test_bit(MDSS_QOS_OVERHEAD_FACTOR, mdata->mdss_qos_map)) perf->bw_writeback = apply_comp_ratio_factor( perf->bw_writeback, fmt, &mixer->ctl->dst_comp_ratio); } else if (pinfo->type == MIPI_CMD_PANEL) { u32 dsi_transfer_rate = mixer->width * v_total; /* adjust transfer time from micro seconds */ dsi_transfer_rate = mult_frac(dsi_transfer_rate, 1000000, pinfo->mdp_transfer_time_us); if (dsi_transfer_rate > perf->mdp_clk_rate) perf->mdp_clk_rate = dsi_transfer_rate; } if (is_dsc_compression(pinfo) && mixer->ctl->opmode & MDSS_MDP_CTL_OP_PACK_3D_ENABLE) perf->mdp_clk_rate *= 2; } /* * In case of border color, we still need enough mdp clock * to avoid under-run. Clock requirement for border color is * based on mixer width. */ if (num_pipes == 0) goto exit; memset(bw_overlap, 0, sizeof(u64) * MAX_PIPES_PER_LM); memset(v_region, 0, sizeof(u32) * MAX_PIPES_PER_LM * 2); /* * Apply this logic only for 8x26 to reduce clock rate * for single video playback use case */ if (IS_MDSS_MAJOR_MINOR_SAME(mdata->mdp_rev, MDSS_MDP_HW_REV_101) && mixer->type == MDSS_MDP_MIXER_TYPE_INTF) { u32 npipes = 0; for (i = 0; i < num_pipes; i++) { pipe = pipe_list[i]; if (pipe) { if (npipes) { apply_fudge = true; break; } npipes++; apply_fudge = !(pipe->src_fmt->is_yuv) || !(pipe->flags & MDP_SOURCE_ROTATED_90); } } } if (apply_fudge) flags |= PERF_CALC_PIPE_APPLY_CLK_FUDGE; if (num_pipes == 1) flags |= PERF_CALC_PIPE_SINGLE_LAYER; for (i = 0; i < num_pipes; i++) { struct mdss_mdp_perf_params tmp; memset(&tmp, 0, sizeof(tmp)); pipe = pipe_list[i]; if (pipe == NULL) continue; /* * if is pipe used across two LMs in source split configuration * then it is staged on both LMs. In such cases skip BW calc * for such pipe on right LM to prevent adding BW twice. */ if (pipe->src_split_req && mixer->is_right_mixer) continue; if (mdss_mdp_perf_calc_pipe(pipe, &tmp, &mixer->roi, flags)) continue; if (!mdss_mdp_is_nrt_ctl_path(mixer->ctl)) perf->max_per_pipe_ib = max(perf->max_per_pipe_ib, tmp.bw_overlap); bitmap_or(perf->bw_vote_mode, perf->bw_vote_mode, tmp.bw_vote_mode, MDSS_MDP_BW_MODE_MAX); /* * for async layers, the overlap calculation is skipped * and the bandwidth is added at the end, accounting for * worst case, that async layer might overlap with * all the other layers. */ if (pipe->async_update) { bw_overlap[i] = 0; v_region[2*i] = 0; v_region[2*i + 1] = 0; bw_overlap_async += tmp.bw_overlap; } else { bw_overlap[i] = tmp.bw_overlap; v_region[2*i] = pipe->dst.y; v_region[2*i + 1] = pipe->dst.y + pipe->dst.h; } if (tmp.mdp_clk_rate > max_clk_rate) max_clk_rate = tmp.mdp_clk_rate; if (test_bit(MDSS_QOS_SIMPLIFIED_PREFILL, mdata->mdss_qos_map)) prefill_val += tmp.bw_prefill; else prefill_val += tmp.prefill_bytes; } /* * Sort the v_region array so the total display area can be * divided into individual regions. Check how many pipes fetch * data for each region and sum them up, then the worst case * of all regions is ib request. */ sort(v_region, num_pipes * 2, sizeof(u32), cmpu32, NULL); for (i = 1; i < num_pipes * 2; i++) { int j; u64 bw_max_region = 0; u32 y0, y1; pr_debug("v_region[%d]%d\n", i, v_region[i]); if (v_region[i] == v_region[i-1]) continue; y0 = v_region[i-1]; y1 = v_region[i]; for (j = 0; j < num_pipes; j++) { if (!bw_overlap[j]) continue; pipe = pipe_list[j]; if (mdss_mdp_perf_is_overlap(y0, y1, pipe->dst.y, (pipe->dst.y + pipe->dst.h))) bw_max_region += bw_overlap[j]; pr_debug("v[%d](%d,%d)pipe[%d](%d,%d)bw(%llu %llu)\n", i, y0, y1, j, pipe->dst.y, pipe->dst.y + pipe->dst.h, bw_overlap[j], bw_max_region); } bw_overlap_max = max(bw_overlap_max, bw_max_region); } perf->bw_overlap += bw_overlap_max + bw_overlap_async; if (test_bit(MDSS_QOS_SIMPLIFIED_PREFILL, mdata->mdss_qos_map)) perf->bw_prefill += prefill_val; else perf->prefill_bytes += prefill_val; if (max_clk_rate > perf->mdp_clk_rate) perf->mdp_clk_rate = max_clk_rate; exit: pr_debug("final mixer=%d video=%d clk_rate=%u bw=%llu prefill=%d mode=0x%lx\n", mixer->num, mixer->ctl->is_video_mode, perf->mdp_clk_rate, perf->bw_overlap, prefill_val, *(perf->bw_vote_mode)); } static bool is_mdp_prefetch_needed(struct mdss_panel_info *pinfo) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); bool enable_prefetch = false; if (mdata->mdp_rev >= MDSS_MDP_HW_REV_105) { if ((pinfo->lcdc.v_back_porch + pinfo->lcdc.v_pulse_width + pinfo->lcdc.v_front_porch) < mdata->min_prefill_lines) pr_warn_once("low vbp+vfp may lead to perf issues in some cases\n"); enable_prefetch = true; if ((pinfo->lcdc.v_back_porch + pinfo->lcdc.v_pulse_width) >= MDSS_MDP_MAX_PREFILL_FETCH) enable_prefetch = false; } else { if ((pinfo->lcdc.v_back_porch + pinfo->lcdc.v_pulse_width) < mdata->min_prefill_lines) pr_warn_once("low vbp may lead to display performance issues"); } return enable_prefetch; } /** * mdss_mdp_get_prefetch_lines: - Number of fetch lines in vertical front porch * @pinfo: Pointer to the panel information. * * Returns the number of fetch lines in vertical front porch at which mdp * can start fetching the next frame. * * In some cases, vertical front porch is too high. In such cases limit * the mdp fetch lines as the last (25 - vbp - vpw) lines of vertical * front porch. */ int mdss_mdp_get_prefetch_lines(struct mdss_panel_info *pinfo) { int prefetch_avail = 0; int v_total, vfp_start; u32 prefetch_needed; if (!is_mdp_prefetch_needed(pinfo)) return 0; v_total = mdss_panel_get_vtotal(pinfo); vfp_start = (pinfo->lcdc.v_back_porch + pinfo->lcdc.v_pulse_width + pinfo->yres); prefetch_avail = v_total - vfp_start; prefetch_needed = MDSS_MDP_MAX_PREFILL_FETCH - pinfo->lcdc.v_back_porch - pinfo->lcdc.v_pulse_width; if (prefetch_avail > prefetch_needed) prefetch_avail = prefetch_needed; return prefetch_avail; } static u32 mdss_mdp_get_vbp_factor(struct mdss_mdp_ctl *ctl) { u32 fps, v_total, vbp, vbp_fac; struct mdss_panel_info *pinfo; if (!ctl || !ctl->panel_data) return 0; pinfo = &ctl->panel_data->panel_info; fps = mdss_panel_get_framerate(pinfo); v_total = mdss_panel_get_vtotal(pinfo); vbp = pinfo->lcdc.v_back_porch + pinfo->lcdc.v_pulse_width; vbp += pinfo->prg_fet; vbp_fac = (vbp) ? fps * v_total / vbp : 0; pr_debug("vbp_fac=%d vbp=%d v_total=%d\n", vbp_fac, vbp, v_total); return vbp_fac; } static u32 mdss_mdp_get_vbp_factor_max(struct mdss_mdp_ctl *ctl) { u32 vbp_max = 0; int i; struct mdss_data_type *mdata; if (!ctl || !ctl->mdata) return 0; mdata = ctl->mdata; for (i = 0; i < mdata->nctl; i++) { struct mdss_mdp_ctl *ctl = mdata->ctl_off + i; u32 vbp_fac; /* skip command mode interfaces */ if (test_bit(MDSS_QOS_SIMPLIFIED_PREFILL, mdata->mdss_qos_map) && !ctl->is_video_mode) continue; if (mdss_mdp_ctl_is_power_on(ctl)) { vbp_fac = mdss_mdp_get_vbp_factor(ctl); vbp_max = max(vbp_max, vbp_fac); } } return vbp_max; } static bool mdss_mdp_video_mode_intf_connected(struct mdss_mdp_ctl *ctl) { int i; struct mdss_data_type *mdata; if (!ctl || !ctl->mdata) return 0; mdata = ctl->mdata; for (i = 0; i < mdata->nctl; i++) { struct mdss_mdp_ctl *ctl = mdata->ctl_off + i; if (ctl->is_video_mode && mdss_mdp_ctl_is_power_on(ctl)) { pr_debug("video interface connected ctl:%d\n", ctl->num); return true; } } return false; } static void __mdss_mdp_perf_calc_ctl_helper(struct mdss_mdp_ctl *ctl, struct mdss_mdp_perf_params *perf, struct mdss_mdp_pipe **left_plist, int left_cnt, struct mdss_mdp_pipe **right_plist, int right_cnt, u32 flags) { struct mdss_mdp_perf_params tmp; struct mdss_data_type *mdata = ctl->mdata; memset(perf, 0, sizeof(*perf)); if (ctl->mixer_left) { mdss_mdp_perf_calc_mixer(ctl->mixer_left, &tmp, left_plist, left_cnt, flags); bitmap_or(perf->bw_vote_mode, perf->bw_vote_mode, tmp.bw_vote_mode, MDSS_MDP_BW_MODE_MAX); perf->max_per_pipe_ib = tmp.max_per_pipe_ib; perf->bw_overlap += tmp.bw_overlap; perf->mdp_clk_rate = tmp.mdp_clk_rate; perf->bw_writeback += tmp.bw_writeback; if (test_bit(MDSS_QOS_SIMPLIFIED_PREFILL, mdata->mdss_qos_map)) perf->bw_prefill += tmp.bw_prefill; else perf->prefill_bytes += tmp.prefill_bytes; } if (ctl->mixer_right) { mdss_mdp_perf_calc_mixer(ctl->mixer_right, &tmp, right_plist, right_cnt, flags); bitmap_or(perf->bw_vote_mode, perf->bw_vote_mode, tmp.bw_vote_mode, MDSS_MDP_BW_MODE_MAX); perf->max_per_pipe_ib = max(perf->max_per_pipe_ib, tmp.max_per_pipe_ib); perf->bw_overlap += tmp.bw_overlap; perf->bw_writeback += tmp.bw_writeback; if (tmp.mdp_clk_rate > perf->mdp_clk_rate) perf->mdp_clk_rate = tmp.mdp_clk_rate; if (test_bit(MDSS_QOS_SIMPLIFIED_PREFILL, mdata->mdss_qos_map)) perf->bw_prefill += tmp.bw_prefill; else perf->prefill_bytes += tmp.prefill_bytes; if (ctl->intf_type) { u32 clk_rate = mdss_mdp_get_pclk_rate(ctl); /* minimum clock rate due to inefficiency in 3dmux */ clk_rate = mult_frac(clk_rate >> 1, 9, 8); if (clk_rate > perf->mdp_clk_rate) perf->mdp_clk_rate = clk_rate; } } /* request minimum bandwidth to have bus clock on when display is on */ if (perf->bw_overlap == 0) perf->bw_overlap = SZ_16M; if (!test_bit(MDSS_QOS_SIMPLIFIED_PREFILL, mdata->mdss_qos_map) && (ctl->intf_type != MDSS_MDP_NO_INTF)) { u32 vbp_fac = mdss_mdp_get_vbp_factor_max(ctl); perf->bw_prefill = perf->prefill_bytes; /* * Prefill bandwidth equals the amount of data (number * of prefill_bytes) divided by the the amount time * available (blanking period). It is equivalent that * prefill bytes times a factor in unit Hz, which is * the reciprocal of time. */ perf->bw_prefill *= vbp_fac; } perf->bw_ctl = max(perf->bw_prefill, perf->bw_overlap); pr_debug("ctl=%d prefill bw=%llu overlap bw=%llu mode=0x%lx writeback:%llu\n", ctl->num, perf->bw_prefill, perf->bw_overlap, *(perf->bw_vote_mode), perf->bw_writeback); } static u32 mdss_check_for_flip(struct mdss_mdp_ctl *ctl) { u32 i, panel_orientation; struct mdss_mdp_pipe *pipe; u32 flags = 0; panel_orientation = ctl->mfd->panel_orientation; if (panel_orientation & MDP_FLIP_LR) flags |= MDSS_MAX_BW_LIMIT_HFLIP; if (panel_orientation & MDP_FLIP_UD) flags |= MDSS_MAX_BW_LIMIT_VFLIP; for (i = 0; i < MAX_PIPES_PER_LM; i++) { if ((flags & MDSS_MAX_BW_LIMIT_HFLIP) && (flags & MDSS_MAX_BW_LIMIT_VFLIP)) return flags; if (ctl->mixer_left && ctl->mixer_left->stage_pipe[i]) { pipe = ctl->mixer_left->stage_pipe[i]; if (pipe->flags & MDP_FLIP_LR) flags |= MDSS_MAX_BW_LIMIT_HFLIP; if (pipe->flags & MDP_FLIP_UD) flags |= MDSS_MAX_BW_LIMIT_VFLIP; } if (ctl->mixer_right && ctl->mixer_right->stage_pipe[i]) { pipe = ctl->mixer_right->stage_pipe[i]; if (pipe->flags & MDP_FLIP_LR) flags |= MDSS_MAX_BW_LIMIT_HFLIP; if (pipe->flags & MDP_FLIP_UD) flags |= MDSS_MAX_BW_LIMIT_VFLIP; } } return flags; } static int mdss_mdp_set_threshold_max_bandwidth(struct mdss_mdp_ctl *ctl) { u32 mode, threshold = 0, max = INT_MAX; u32 i = 0; struct mdss_max_bw_settings *max_bw_settings = ctl->mdata->max_bw_settings; if (!ctl->mdata->max_bw_settings_cnt && !ctl->mdata->max_bw_settings) return 0; mode = ctl->mdata->bw_mode_bitmap; if (!((mode & MDSS_MAX_BW_LIMIT_HFLIP) && (mode & MDSS_MAX_BW_LIMIT_VFLIP))) mode |= mdss_check_for_flip(ctl); pr_debug("final mode = %d, bw_mode_bitmap = %d\n", mode, ctl->mdata->bw_mode_bitmap); /* Return minimum bandwidth limit */ for (i = 0; i < ctl->mdata->max_bw_settings_cnt; i++) { if (max_bw_settings[i].mdss_max_bw_mode & mode) { threshold = max_bw_settings[i].mdss_max_bw_val; if (threshold < max) max = threshold; } } return max; } int mdss_mdp_perf_bw_check(struct mdss_mdp_ctl *ctl, struct mdss_mdp_pipe **left_plist, int left_cnt, struct mdss_mdp_pipe **right_plist, int right_cnt) { struct mdss_data_type *mdata = ctl->mdata; struct mdss_mdp_perf_params perf; u32 bw, threshold, i, mode_switch, max_bw; u64 bw_sum_of_intfs = 0; bool is_video_mode; /* we only need bandwidth check on real-time clients (interfaces) */ if (ctl->intf_type == MDSS_MDP_NO_INTF) return 0; __mdss_mdp_perf_calc_ctl_helper(ctl, &perf, left_plist, left_cnt, right_plist, right_cnt, PERF_CALC_PIPE_CALC_SMP_SIZE); ctl->bw_pending = perf.bw_ctl; for (i = 0; i < mdata->nctl; i++) { struct mdss_mdp_ctl *temp = mdata->ctl_off + i; if (temp->power_state == MDSS_PANEL_POWER_ON && (temp->intf_type != MDSS_MDP_NO_INTF)) bw_sum_of_intfs += temp->bw_pending; } /* convert bandwidth to kb */ bw = DIV_ROUND_UP_ULL(bw_sum_of_intfs, 1000); pr_debug("calculated bandwidth=%uk\n", bw); /* mfd validation happens in func */ mode_switch = mdss_fb_get_mode_switch(ctl->mfd); if (mode_switch) is_video_mode = (mode_switch == MIPI_VIDEO_PANEL); else is_video_mode = ctl->is_video_mode; threshold = (is_video_mode || mdss_mdp_video_mode_intf_connected(ctl)) ? mdata->max_bw_low : mdata->max_bw_high; max_bw = mdss_mdp_set_threshold_max_bandwidth(ctl); if (max_bw && (max_bw < threshold)) threshold = max_bw; pr_debug("final threshold bw limit = %d\n", threshold); if (bw > threshold) { ctl->bw_pending = 0; pr_debug("exceeds bandwidth: %ukb > %ukb\n", bw, threshold); return -E2BIG; } return 0; } static u32 mdss_mdp_get_max_pipe_bw(struct mdss_mdp_pipe *pipe) { struct mdss_mdp_ctl *ctl = pipe->mixer_left->ctl; struct mdss_max_bw_settings *max_per_pipe_bw_settings; u32 flags = 0, threshold = 0, panel_orientation; u32 i, max = INT_MAX; if (!ctl->mdata->mdss_per_pipe_bw_cnt && !ctl->mdata->max_per_pipe_bw_settings) return 0; panel_orientation = ctl->mfd->panel_orientation; max_per_pipe_bw_settings = ctl->mdata->max_per_pipe_bw_settings; /* Check for panel orienatation */ panel_orientation = ctl->mfd->panel_orientation; if (panel_orientation & MDP_FLIP_LR) flags |= MDSS_MAX_BW_LIMIT_HFLIP; if (panel_orientation & MDP_FLIP_UD) flags |= MDSS_MAX_BW_LIMIT_VFLIP; /* check for Hflip/Vflip in pipe */ if (pipe->flags & MDP_FLIP_LR) flags |= MDSS_MAX_BW_LIMIT_HFLIP; if (pipe->flags & MDP_FLIP_UD) flags |= MDSS_MAX_BW_LIMIT_VFLIP; flags |= ctl->mdata->bw_mode_bitmap; for (i = 0; i < ctl->mdata->mdss_per_pipe_bw_cnt; i++) { if (max_per_pipe_bw_settings[i].mdss_max_bw_mode & flags) { threshold = max_per_pipe_bw_settings[i].mdss_max_bw_val; if (threshold < max) max = threshold; } } return max; } int mdss_mdp_perf_bw_check_pipe(struct mdss_mdp_perf_params *perf, struct mdss_mdp_pipe *pipe) { struct mdss_data_type *mdata = pipe->mixer_left->ctl->mdata; struct mdss_mdp_ctl *ctl = pipe->mixer_left->ctl; u32 vbp_fac, threshold; u64 prefill_bw, pipe_bw, max_pipe_bw; /* we only need bandwidth check on real-time clients (interfaces) */ if (ctl->intf_type == MDSS_MDP_NO_INTF) return 0; if (test_bit(MDSS_QOS_SIMPLIFIED_PREFILL, mdata->mdss_qos_map)) { prefill_bw = perf->bw_prefill; } else { vbp_fac = mdss_mdp_get_vbp_factor_max(ctl); prefill_bw = perf->prefill_bytes * vbp_fac; } pipe_bw = max(prefill_bw, perf->bw_overlap); pr_debug("prefill=%llu, vbp_fac=%u, overlap=%llu\n", prefill_bw, vbp_fac, perf->bw_overlap); /* convert bandwidth to kb */ pipe_bw = DIV_ROUND_UP_ULL(pipe_bw, 1000); threshold = mdata->max_bw_per_pipe; max_pipe_bw = mdss_mdp_get_max_pipe_bw(pipe); if (max_pipe_bw && (max_pipe_bw < threshold)) threshold = max_pipe_bw; pr_debug("bw=%llu threshold=%u\n", pipe_bw, threshold); if (threshold && pipe_bw > threshold) { pr_debug("pipe exceeds bandwidth: %llukb > %ukb\n", pipe_bw, threshold); return -E2BIG; } return 0; } static void mdss_mdp_perf_calc_ctl(struct mdss_mdp_ctl *ctl, struct mdss_mdp_perf_params *perf) { struct mdss_mdp_pipe *left_plist[MAX_PIPES_PER_LM]; struct mdss_mdp_pipe *right_plist[MAX_PIPES_PER_LM]; int i, left_cnt = 0, right_cnt = 0; for (i = 0; i < MAX_PIPES_PER_LM; i++) { if (ctl->mixer_left && ctl->mixer_left->stage_pipe[i]) { left_plist[left_cnt] = ctl->mixer_left->stage_pipe[i]; left_cnt++; } if (ctl->mixer_right && ctl->mixer_right->stage_pipe[i]) { right_plist[right_cnt] = ctl->mixer_right->stage_pipe[i]; right_cnt++; } } __mdss_mdp_perf_calc_ctl_helper(ctl, perf, left_plist, left_cnt, right_plist, right_cnt, 0); if (ctl->is_video_mode || ((ctl->intf_type != MDSS_MDP_NO_INTF) && mdss_mdp_video_mode_intf_connected(ctl))) { perf->bw_ctl = max(apply_fudge_factor(perf->bw_overlap, &mdss_res->ib_factor_overlap), apply_fudge_factor(perf->bw_prefill, &mdss_res->ib_factor)); perf->bw_writeback = apply_fudge_factor(perf->bw_writeback, &mdss_res->ib_factor); } pr_debug("ctl=%d clk_rate=%u\n", ctl->num, perf->mdp_clk_rate); pr_debug("bw_overlap=%llu bw_prefill=%llu prefill_bytes=%d\n", perf->bw_overlap, perf->bw_prefill, perf->prefill_bytes); } static void set_status(u32 *value, bool status, u32 bit_num) { if (status) *value |= BIT(bit_num); else *value &= ~BIT(bit_num); } /** * @ mdss_mdp_ctl_perf_set_transaction_status() - * Set the status of the on-going operations * for the command mode panels. * @ctl - pointer to a ctl * * This function is called to set the status bit in the perf_transaction_status * according to the operation that it is on-going for the command mode * panels, where: * * PERF_SW_COMMIT_STATE: * 1 - If SW operation has been commited and bw * has been requested (HW transaction have not started yet). * 0 - If there is no SW operation pending * PERF_HW_MDP_STATE: * 1 - If HW transaction is on-going * 0 - If there is no HW transaction on going (ping-pong interrupt * has finished) * Only if both states are zero there are no pending operations and * BW could be released. * State can be queried calling "mdss_mdp_ctl_perf_get_transaction_status" */ void mdss_mdp_ctl_perf_set_transaction_status(struct mdss_mdp_ctl *ctl, enum mdss_mdp_perf_state_type component, bool new_status) { u32 previous_transaction; bool previous_status; unsigned long flags; if (!ctl || !ctl->panel_data || (ctl->panel_data->panel_info.type != MIPI_CMD_PANEL)) return; spin_lock_irqsave(&ctl->spin_lock, flags); previous_transaction = ctl->perf_transaction_status; previous_status = previous_transaction & BIT(component) ? PERF_STATUS_BUSY : PERF_STATUS_DONE; /* * If we set "done" state when previous state was not "busy", * we want to print a warning since maybe there is a state * that we are not considering */ WARN((PERF_STATUS_DONE == new_status) && (PERF_STATUS_BUSY != previous_status), "unexpected previous state for component: %d\n", component); set_status(&ctl->perf_transaction_status, new_status, (u32)component); pr_debug("ctl:%d component:%d previous:%d status:%d\n", ctl->num, component, previous_transaction, ctl->perf_transaction_status); pr_debug("ctl:%d new_status:%d prev_status:%d\n", ctl->num, new_status, previous_status); spin_unlock_irqrestore(&ctl->spin_lock, flags); } /** * @ mdss_mdp_ctl_perf_get_transaction_status() - * Get the status of the on-going operations * for the command mode panels. * @ctl - pointer to a ctl * * Return: * The status of the transactions for the command mode panels, * note that the bandwidth can be released only if all transaction * status bits are zero. */ u32 mdss_mdp_ctl_perf_get_transaction_status(struct mdss_mdp_ctl *ctl) { unsigned long flags; u32 transaction_status; if (!ctl) return PERF_STATUS_BUSY; /* * If Rotator mode and bandwidth has been released; return STATUS_DONE * so the bandwidth is re-calculated. */ if (ctl->mixer_left && ctl->mixer_left->rotator_mode && !ctl->perf_release_ctl_bw) return PERF_STATUS_DONE; /* * If Video Mode or not valid data to determine the status, return busy * status, so the bandwidth cannot be freed by the caller */ if (!ctl || !ctl->panel_data || (ctl->panel_data->panel_info.type != MIPI_CMD_PANEL)) { return PERF_STATUS_BUSY; } spin_lock_irqsave(&ctl->spin_lock, flags); transaction_status = ctl->perf_transaction_status; spin_unlock_irqrestore(&ctl->spin_lock, flags); pr_debug("ctl:%d status:%d\n", ctl->num, transaction_status); return transaction_status; } /** * @ mdss_mdp_ctl_perf_update_traffic_shaper_bw - * Apply BW fudge factor to rotator * if mdp clock increased during * rotation session. * @ctl - pointer to the controller * @mdp_clk - new mdp clock * * If mdp clock increased and traffic shaper is enabled, we need to * account for the additional bandwidth that will be requested by * the rotator when running at a higher clock, so we apply a fudge * factor proportional to the mdp clock increment. */ static void mdss_mdp_ctl_perf_update_traffic_shaper_bw(struct mdss_mdp_ctl *ctl, u32 mdp_clk) { if ((mdp_clk > 0) && (mdp_clk > ctl->traffic_shaper_mdp_clk)) { ctl->cur_perf.bw_ctl = fudge_factor(ctl->cur_perf.bw_ctl, mdp_clk, ctl->traffic_shaper_mdp_clk); pr_debug("traffic shaper bw:%llu, clk: %d, mdp_clk:%d\n", ctl->cur_perf.bw_ctl, ctl->traffic_shaper_mdp_clk, mdp_clk); } } static u64 mdss_mdp_ctl_calc_client_vote(struct mdss_data_type *mdata, struct mdss_mdp_perf_params *perf, bool nrt_client, u32 mdp_clk) { u64 bw_sum_of_intfs = 0; int i; struct mdss_mdp_ctl *ctl; struct mdss_mdp_mixer *mixer; struct mdss_mdp_perf_params perf_temp; bitmap_zero(perf_temp.bw_vote_mode, MDSS_MDP_BW_MODE_MAX); for (i = 0; i < mdata->nctl; i++) { ctl = mdata->ctl_off + i; mixer = ctl->mixer_left; if (mdss_mdp_ctl_is_power_on(ctl) && /* RealTime clients */ ((!nrt_client && ctl->mixer_left && !ctl->mixer_left->rotator_mode) || /* Non-RealTime clients */ (nrt_client && mdss_mdp_is_nrt_ctl_path(ctl)))) { /* Skip rotation layers as bw calc by rot driver */ if (ctl->mixer_left && ctl->mixer_left->rotator_mode) continue; /* * If traffic shaper is enabled we must check * if additional bandwidth is required. */ if (ctl->traffic_shaper_enabled) mdss_mdp_ctl_perf_update_traffic_shaper_bw (ctl, mdp_clk); mdss_mdp_get_bw_vote_mode(ctl, mdata->mdp_rev, &perf_temp, PERF_CALC_VOTE_MODE_CTL, 0); bitmap_or(perf_temp.bw_vote_mode, perf_temp.bw_vote_mode, ctl->cur_perf.bw_vote_mode, MDSS_MDP_BW_MODE_MAX); if (nrt_client && ctl->mixer_left && !ctl->mixer_left->rotator_mode) { bw_sum_of_intfs += ctl->cur_perf.bw_writeback; continue; } perf->max_per_pipe_ib = max(perf->max_per_pipe_ib, ctl->cur_perf.max_per_pipe_ib); bw_sum_of_intfs += ctl->cur_perf.bw_ctl; pr_debug("ctl_num=%d bw=%llu mode=0x%lx\n", ctl->num, ctl->cur_perf.bw_ctl, *(ctl->cur_perf.bw_vote_mode)); } } return bw_sum_of_intfs; } static void mdss_mdp_ctl_update_client_vote(struct mdss_data_type *mdata, struct mdss_mdp_perf_params *perf, bool nrt_client, u64 bw_vote) { u64 bus_ab_quota, bus_ib_quota; bus_ab_quota = max(bw_vote, mdata->perf_tune.min_bus_vote); if (test_bit(MDSS_QOS_PER_PIPE_IB, mdata->mdss_qos_map)) { if (!nrt_client) bus_ib_quota = perf->max_per_pipe_ib; else bus_ib_quota = 0; } else { bus_ib_quota = bw_vote; } if (test_bit(MDSS_MDP_BW_MODE_SINGLE_LAYER, perf->bw_vote_mode) && (bus_ib_quota >= PERF_SINGLE_PIPE_BW_FLOOR)) { struct mult_factor ib_factor_vscaling; ib_factor_vscaling.numer = 2; ib_factor_vscaling.denom = 1; bus_ib_quota = apply_fudge_factor(bus_ib_quota, &ib_factor_vscaling); } if (test_bit(MDSS_QOS_PER_PIPE_IB, mdata->mdss_qos_map) && !nrt_client) bus_ib_quota = apply_fudge_factor(bus_ib_quota, &mdata->per_pipe_ib_factor); bus_ab_quota = apply_fudge_factor(bus_ab_quota, &mdss_res->ab_factor); ATRACE_INT("bus_quota", bus_ib_quota); mdss_bus_scale_set_quota(nrt_client ? MDSS_MDP_NRT : MDSS_MDP_RT, bus_ab_quota, bus_ib_quota); pr_debug("client:%s ab=%llu ib=%llu\n", nrt_client ? "nrt" : "rt", bus_ab_quota, bus_ib_quota); } static void mdss_mdp_ctl_perf_update_bus(struct mdss_data_type *mdata, struct mdss_mdp_ctl *ctl, u32 mdp_clk) { u64 bw_sum_of_rt_intfs = 0, bw_sum_of_nrt_intfs = 0; struct mdss_mdp_perf_params perf = {0}; ATRACE_BEGIN(__func__); /* * non-real time client * 1. rotator path * 2. writeback output path */ if (mdss_mdp_is_nrt_ctl_path(ctl)) { bitmap_zero(perf.bw_vote_mode, MDSS_MDP_BW_MODE_MAX); bw_sum_of_nrt_intfs = mdss_mdp_ctl_calc_client_vote(mdata, &perf, true, mdp_clk); mdss_mdp_ctl_update_client_vote(mdata, &perf, true, bw_sum_of_nrt_intfs); } /* * real time client * 1. any realtime interface - primary or secondary interface * 2. writeback input path */ if (!mdss_mdp_is_nrt_ctl_path(ctl) || (ctl->intf_num == MDSS_MDP_NO_INTF)) { bitmap_zero(perf.bw_vote_mode, MDSS_MDP_BW_MODE_MAX); bw_sum_of_rt_intfs = mdss_mdp_ctl_calc_client_vote(mdata, &perf, false, mdp_clk); mdss_mdp_ctl_update_client_vote(mdata, &perf, false, bw_sum_of_rt_intfs); } ATRACE_END(__func__); } /** * @mdss_mdp_ctl_perf_release_bw() - request zero bandwidth * @ctl - pointer to a ctl * * Function checks a state variable for the ctl, if all pending commit * requests are done, meaning no more bandwidth is needed, release * bandwidth request. */ void mdss_mdp_ctl_perf_release_bw(struct mdss_mdp_ctl *ctl) { int transaction_status; struct mdss_data_type *mdata; int i; /* only do this for command panel */ if (!ctl || !ctl->mdata || !ctl->panel_data || (ctl->panel_data->panel_info.type != MIPI_CMD_PANEL)) return; mutex_lock(&mdss_mdp_ctl_lock); mdata = ctl->mdata; /* * If video interface present, cmd panel bandwidth cannot be * released. */ for (i = 0; i < mdata->nctl; i++) { struct mdss_mdp_ctl *ctl_local = mdata->ctl_off + i; if (mdss_mdp_ctl_is_power_on(ctl_local) && ctl_local->is_video_mode) goto exit; } transaction_status = mdss_mdp_ctl_perf_get_transaction_status(ctl); pr_debug("transaction_status=0x%x\n", transaction_status); /*Release the bandwidth only if there are no transactions pending*/ if (!transaction_status && mdata->enable_bw_release) { /* * for splitdisplay if release_bw is called using secondary * then find the main ctl and release BW for main ctl because * BW is always calculated/stored using main ctl. */ struct mdss_mdp_ctl *ctl_local = mdss_mdp_get_main_ctl(ctl) ? : ctl; trace_mdp_cmd_release_bw(ctl_local->num); ctl_local->cur_perf.bw_ctl = 0; ctl_local->new_perf.bw_ctl = 0; pr_debug("Release BW ctl=%d\n", ctl_local->num); mdss_mdp_ctl_perf_update_bus(mdata, ctl, 0); } exit: mutex_unlock(&mdss_mdp_ctl_lock); } static int mdss_mdp_select_clk_lvl(struct mdss_data_type *mdata, u32 clk_rate) { int i; for (i = 0; i < mdata->nclk_lvl; i++) { if (clk_rate > mdata->clock_levels[i]) { continue; } else { clk_rate = mdata->clock_levels[i]; break; } } return clk_rate; } static void mdss_mdp_perf_release_ctl_bw(struct mdss_mdp_ctl *ctl, struct mdss_mdp_perf_params *perf) { /* Set to zero controller bandwidth. */ memset(perf, 0, sizeof(*perf)); ctl->perf_release_ctl_bw = false; } u32 mdss_mdp_get_mdp_clk_rate(struct mdss_data_type *mdata) { u32 clk_rate = 0; uint i; struct clk *clk = mdss_mdp_get_clk(MDSS_CLK_MDP_CORE); for (i = 0; i < mdata->nctl; i++) { struct mdss_mdp_ctl *ctl; ctl = mdata->ctl_off + i; if (mdss_mdp_ctl_is_power_on(ctl)) { clk_rate = max(ctl->cur_perf.mdp_clk_rate, clk_rate); clk_rate = clk_round_rate(clk, clk_rate); } } clk_rate = mdss_mdp_select_clk_lvl(mdata, clk_rate); pr_debug("clk:%u nctl:%d\n", clk_rate, mdata->nctl); return clk_rate; } static bool is_traffic_shaper_enabled(struct mdss_data_type *mdata) { uint i; for (i = 0; i < mdata->nctl; i++) { struct mdss_mdp_ctl *ctl; ctl = mdata->ctl_off + i; if (mdss_mdp_ctl_is_power_on(ctl)) if (ctl->traffic_shaper_enabled) return true; } return false; } static void mdss_mdp_ctl_perf_update(struct mdss_mdp_ctl *ctl, int params_changed, bool stop_req) { struct mdss_mdp_perf_params *new, *old; int update_bus = 0, update_clk = 0; struct mdss_data_type *mdata; bool is_bw_released; u32 clk_rate = 0; if (!ctl || !ctl->mdata) return; ATRACE_BEGIN(__func__); mutex_lock(&mdss_mdp_ctl_lock); mdata = ctl->mdata; old = &ctl->cur_perf; new = &ctl->new_perf; /* * We could have released the bandwidth if there were no transactions * pending, so we want to re-calculate the bandwidth in this situation. */ is_bw_released = !mdss_mdp_ctl_perf_get_transaction_status(ctl); if (mdss_mdp_ctl_is_power_on(ctl)) { /* Skip perf update if ctl is used for rotation */ if (ctl->mixer_left && ctl->mixer_left->rotator_mode) goto end; if (ctl->perf_release_ctl_bw && mdata->enable_rotator_bw_release) mdss_mdp_perf_release_ctl_bw(ctl, new); else if (is_bw_released || params_changed) mdss_mdp_perf_calc_ctl(ctl, new); /* * three cases for bus bandwidth update. * 1. new bandwidth vote or writeback output vote * are higher than current vote for update request. * 2. new bandwidth vote or writeback output vote are * lower than current vote at end of commit or stop. * 3. end of writeback/rotator session - last chance to * non-realtime remove vote. */ if ((params_changed && ((new->bw_ctl > old->bw_ctl) || (new->bw_writeback > old->bw_writeback))) || (!params_changed && ((new->bw_ctl < old->bw_ctl) || (new->bw_writeback < old->bw_writeback))) || (stop_req && mdss_mdp_is_nrt_ctl_path(ctl))) { pr_debug("c=%d p=%d new_bw=%llu,old_bw=%llu\n", ctl->num, params_changed, new->bw_ctl, old->bw_ctl); if (stop_req) { old->bw_writeback = 0; old->bw_ctl = 0; old->max_per_pipe_ib = 0; } else { old->bw_ctl = new->bw_ctl; old->max_per_pipe_ib = new->max_per_pipe_ib; old->bw_writeback = new->bw_writeback; } bitmap_copy(old->bw_vote_mode, new->bw_vote_mode, MDSS_MDP_BW_MODE_MAX); update_bus = 1; } /* * If traffic shaper is enabled, we do not decrease the clock, * otherwise we would increase traffic shaper latency. Clock * would be decreased after traffic shaper is done. */ if ((params_changed && (new->mdp_clk_rate > old->mdp_clk_rate)) || (!params_changed && (new->mdp_clk_rate < old->mdp_clk_rate) && (false == is_traffic_shaper_enabled(mdata)))) { old->mdp_clk_rate = new->mdp_clk_rate; update_clk = 1; } } else { memset(old, 0, sizeof(*old)); memset(new, 0, sizeof(*new)); update_bus = 1; update_clk = 1; } /* * Calculate mdp clock before bandwidth calculation. If traffic shaper * is enabled and clock increased, the bandwidth calculation can * use the new clock for the rotator bw calculation. */ if (update_clk) clk_rate = mdss_mdp_get_mdp_clk_rate(mdata); if (update_bus) mdss_mdp_ctl_perf_update_bus(mdata, ctl, clk_rate); /* * Update the clock after bandwidth vote to ensure * bandwidth is available before clock rate is increased. */ if (update_clk) { ATRACE_INT("mdp_clk", clk_rate); mdss_mdp_set_clk_rate(clk_rate); pr_debug("update clk rate = %d HZ\n", clk_rate); } end: mutex_unlock(&mdss_mdp_ctl_lock); ATRACE_END(__func__); } struct mdss_mdp_ctl *mdss_mdp_ctl_alloc(struct mdss_data_type *mdata, u32 off) { struct mdss_mdp_ctl *ctl = NULL; u32 cnum; u32 nctl = mdata->nctl; mutex_lock(&mdss_mdp_ctl_lock); if (mdata->wfd_mode == MDSS_MDP_WFD_SHARED) nctl++; for (cnum = off; cnum < nctl; cnum++) { ctl = mdata->ctl_off + cnum; if (ctl->ref_cnt == 0) { ctl->ref_cnt++; ctl->mdata = mdata; mutex_init(&ctl->lock); mutex_init(&ctl->offlock); mutex_init(&ctl->flush_lock); mutex_init(&ctl->rsrc_lock); spin_lock_init(&ctl->spin_lock); BLOCKING_INIT_NOTIFIER_HEAD(&ctl->notifier_head); pr_debug("alloc ctl_num=%d\n", ctl->num); break; } ctl = NULL; } mutex_unlock(&mdss_mdp_ctl_lock); return ctl; } int mdss_mdp_ctl_free(struct mdss_mdp_ctl *ctl) { if (!ctl) return -ENODEV; pr_debug("free ctl_num=%d ref_cnt=%d\n", ctl->num, ctl->ref_cnt); if (!ctl->ref_cnt) { pr_err("called with ref_cnt=0\n"); return -EINVAL; } if (ctl->mixer_left && ctl->mixer_left->ref_cnt) mdss_mdp_mixer_free(ctl->mixer_left); if (ctl->mixer_right && ctl->mixer_right->ref_cnt) mdss_mdp_mixer_free(ctl->mixer_right); if (ctl->wb) mdss_mdp_wb_free(ctl->wb); mutex_lock(&mdss_mdp_ctl_lock); ctl->ref_cnt--; ctl->intf_num = MDSS_MDP_NO_INTF; ctl->intf_type = MDSS_MDP_NO_INTF; ctl->is_secure = false; ctl->power_state = MDSS_PANEL_POWER_OFF; ctl->mixer_left = NULL; ctl->mixer_right = NULL; ctl->wb = NULL; ctl->cdm = NULL; memset(&ctl->ops, 0, sizeof(ctl->ops)); mutex_unlock(&mdss_mdp_ctl_lock); return 0; } /** * mdss_mdp_mixer_alloc() - allocate mdp mixer. * @ctl: mdp controller. * @type: specifying type of mixer requested. interface or writeback. * @mux: specifies if mixer allocation is for split_fb cases. * @rotator: specifies if the mixer requested for rotator operations. * * This function is called to request allocation of mdp mixer * during mdp controller path setup. * * Return: mdp mixer structure that is allocated. * NULL if mixer allocation fails. */ struct mdss_mdp_mixer *mdss_mdp_mixer_alloc( struct mdss_mdp_ctl *ctl, u32 type, int mux, int rotator) { struct mdss_mdp_mixer *mixer = NULL, *alt_mixer = NULL; u32 nmixers_intf; u32 nmixers_wb; u32 i; u32 nmixers; struct mdss_mdp_mixer *mixer_pool = NULL; if (!ctl || !ctl->mdata) return NULL; mutex_lock(&mdss_mdp_ctl_lock); nmixers_intf = ctl->mdata->nmixers_intf; nmixers_wb = ctl->mdata->nmixers_wb; switch (type) { case MDSS_MDP_MIXER_TYPE_INTF: mixer_pool = ctl->mdata->mixer_intf; nmixers = nmixers_intf; /* * try to reserve first layer mixer for write back if * assertive display needs to be supported through wfd */ if (ctl->mdata->has_wb_ad && ctl->intf_num && ((ctl->panel_data->panel_info.type != MIPI_CMD_PANEL) || !mux)) { alt_mixer = mixer_pool; mixer_pool++; nmixers--; } else if ((ctl->panel_data->panel_info.type == WRITEBACK_PANEL) && (ctl->mdata->ndspp < nmixers)) { mixer_pool += ctl->mdata->ndspp; nmixers -= ctl->mdata->ndspp; } break; case MDSS_MDP_MIXER_TYPE_WRITEBACK: mixer_pool = ctl->mdata->mixer_wb; nmixers = nmixers_wb; break; default: nmixers = 0; pr_err("invalid pipe type %d\n", type); break; } /*Allocate virtual wb mixer if no dedicated wfd wb blk is present*/ if ((ctl->mdata->wfd_mode == MDSS_MDP_WFD_SHARED) && (type == MDSS_MDP_MIXER_TYPE_WRITEBACK)) nmixers += 1; for (i = 0; i < nmixers; i++) { mixer = mixer_pool + i; if (mixer->ref_cnt == 0) break; mixer = NULL; } if (!mixer && alt_mixer && (alt_mixer->ref_cnt == 0)) mixer = alt_mixer; if (mixer) { mixer->ref_cnt++; mixer->params_changed++; mixer->ctl = ctl; mixer->next_pipe_map = 0; mixer->pipe_mapped = 0; pr_debug("alloc mixer num %d for ctl=%d\n", mixer->num, ctl->num); } mutex_unlock(&mdss_mdp_ctl_lock); return mixer; } struct mdss_mdp_mixer *mdss_mdp_mixer_assign(u32 id, bool wb) { struct mdss_mdp_mixer *mixer = NULL; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); mutex_lock(&mdss_mdp_ctl_lock); if (wb && id < mdata->nmixers_wb) mixer = mdata->mixer_wb + id; else if (!wb && id < mdata->nmixers_intf) mixer = mdata->mixer_intf + id; if (mixer && mixer->ref_cnt == 0) { mixer->ref_cnt++; mixer->params_changed++; } else { pr_err("mixer is in use already = %d\n", id); mixer = NULL; } mutex_unlock(&mdss_mdp_ctl_lock); return mixer; } int mdss_mdp_mixer_free(struct mdss_mdp_mixer *mixer) { if (!mixer) return -ENODEV; pr_debug("free mixer_num=%d ref_cnt=%d\n", mixer->num, mixer->ref_cnt); if (!mixer->ref_cnt) { pr_err("called with ref_cnt=0\n"); return -EINVAL; } mutex_lock(&mdss_mdp_ctl_lock); mixer->ref_cnt--; mixer->is_right_mixer = false; mutex_unlock(&mdss_mdp_ctl_lock); return 0; } struct mdss_mdp_mixer *mdss_mdp_block_mixer_alloc(void) { struct mdss_mdp_ctl *ctl = NULL; struct mdss_mdp_mixer *mixer = NULL; struct mdss_mdp_writeback *wb = NULL; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); u32 offset = mdss_mdp_get_wb_ctl_support(mdata, true); int ret = 0; ctl = mdss_mdp_ctl_alloc(mdss_res, offset); if (!ctl) { pr_debug("unable to allocate wb ctl\n"); return NULL; } mixer = mdss_mdp_mixer_alloc(ctl, MDSS_MDP_MIXER_TYPE_WRITEBACK, false, true); if (!mixer) { pr_debug("unable to allocate wb mixer\n"); goto error; } mixer->rotator_mode = 1; switch (mixer->num) { case MDSS_MDP_WB_LAYERMIXER0: ctl->opmode = MDSS_MDP_CTL_OP_ROT0_MODE; break; case MDSS_MDP_WB_LAYERMIXER1: ctl->opmode = MDSS_MDP_CTL_OP_ROT1_MODE; break; default: pr_err("invalid layer mixer=%d\n", mixer->num); goto error; } wb = mdss_mdp_wb_alloc(MDSS_MDP_WB_ROTATOR, ctl->num); if (!wb) { pr_err("Unable to allocate writeback block\n"); goto error; } ctl->mixer_left = mixer; ctl->ops.start_fnc = mdss_mdp_writeback_start; ctl->power_state = MDSS_PANEL_POWER_ON; ctl->wb_type = MDSS_MDP_WB_CTL_TYPE_BLOCK; mixer->ctl = ctl; ctl->wb = wb; if (ctl->ops.start_fnc) ret = ctl->ops.start_fnc(ctl); if (!ret) return mixer; error: if (wb) mdss_mdp_wb_free(wb); if (mixer) mdss_mdp_mixer_free(mixer); if (ctl) mdss_mdp_ctl_free(ctl); return NULL; } int mdss_mdp_block_mixer_destroy(struct mdss_mdp_mixer *mixer) { struct mdss_mdp_ctl *ctl; if (!mixer || !mixer->ctl) { pr_err("invalid ctl handle\n"); return -ENODEV; } ctl = mixer->ctl; mixer->rotator_mode = 0; pr_debug("destroy ctl=%d mixer=%d\n", ctl->num, mixer->num); if (ctl->ops.stop_fnc) ctl->ops.stop_fnc(ctl, MDSS_PANEL_POWER_OFF); mdss_mdp_ctl_free(ctl); mdss_mdp_ctl_perf_update(ctl, 0, true); return 0; } int mdss_mdp_ctl_cmd_autorefresh_enable(struct mdss_mdp_ctl *ctl, int frame_cnt) { int ret = 0; if (ctl->panel_data->panel_info.type == MIPI_CMD_PANEL) { ret = mdss_mdp_cmd_set_autorefresh_mode(ctl, frame_cnt); } else { pr_err("Mode not supported for this panel\n"); ret = -EINVAL; } return ret; } int mdss_mdp_ctl_splash_finish(struct mdss_mdp_ctl *ctl, bool handoff) { switch (ctl->panel_data->panel_info.type) { case MIPI_VIDEO_PANEL: case EDP_PANEL: return mdss_mdp_video_reconfigure_splash_done(ctl, handoff); case MIPI_CMD_PANEL: return mdss_mdp_cmd_reconfigure_splash_done(ctl, handoff); default: return 0; } } static inline int mdss_mdp_set_split_ctl(struct mdss_mdp_ctl *ctl, struct mdss_mdp_ctl *split_ctl) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdss_panel_info *pinfo; if (!ctl || !split_ctl || !mdata) return -ENODEV; /* setup split ctl mixer as right mixer of original ctl so that * original ctl can work the same way as dual pipe solution */ ctl->mixer_right = split_ctl->mixer_left; pinfo = &ctl->panel_data->panel_info; /* add x offset from left ctl's border */ split_ctl->border_x_off += (pinfo->lcdc.border_left + pinfo->lcdc.border_right); return 0; } static inline void __dsc_enable(struct mdss_mdp_mixer *mixer) { mdss_mdp_pingpong_write(mixer->pingpong_base, MDSS_MDP_REG_PP_DSC_MODE, 1); } static inline void __dsc_disable(struct mdss_mdp_mixer *mixer) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); char __iomem *offset = mdata->mdp_base; mdss_mdp_pingpong_write(mixer->pingpong_base, MDSS_MDP_REG_PP_DSC_MODE, 0); if (mixer->num == MDSS_MDP_INTF_LAYERMIXER0) { offset += MDSS_MDP_DSC_0_OFFSET; } else if (mixer->num == MDSS_MDP_INTF_LAYERMIXER1) { offset += MDSS_MDP_DSC_1_OFFSET; } else { pr_err("invalid mixer numer=%d\n", mixer->num); return; } writel_relaxed(0, offset + MDSS_MDP_REG_DSC_COMMON_MODE); } static void __dsc_config(struct mdss_mdp_mixer *mixer, struct dsc_desc *dsc, u32 mode, bool ich_reset_override) { u32 data; int bpp, lsb; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); char __iomem *offset = mdata->mdp_base; u32 initial_lines = dsc->initial_lines; bool is_cmd_mode = !(mode & BIT(2)); data = mdss_mdp_pingpong_read(mixer->pingpong_base, MDSS_MDP_REG_PP_DCE_DATA_OUT_SWAP); data |= BIT(18); /* endian flip */ mdss_mdp_pingpong_write(mixer->pingpong_base, MDSS_MDP_REG_PP_DCE_DATA_OUT_SWAP, data); if (mixer->num == MDSS_MDP_INTF_LAYERMIXER0) { offset += MDSS_MDP_DSC_0_OFFSET; } else if (mixer->num == MDSS_MDP_INTF_LAYERMIXER1) { offset += MDSS_MDP_DSC_1_OFFSET; } else { pr_err("invalid mixer numer=%d\n", mixer->num); return; } writel_relaxed(mode, offset + MDSS_MDP_REG_DSC_COMMON_MODE); data = 0; if (ich_reset_override) data = 3 << 28; if (is_cmd_mode) initial_lines += 1; data |= (initial_lines << 20); data |= ((dsc->slice_last_group_size - 1) << 18); /* bpp is 6.4 format, 4 LSBs bits are for fractional part */ lsb = dsc->bpp % 4; bpp = dsc->bpp / 4; bpp *= 4; /* either 8 or 12 */ bpp <<= 4; bpp |= lsb; data |= (bpp << 8); data |= (dsc->block_pred_enable << 7); data |= (dsc->line_buf_depth << 3); data |= (dsc->enable_422 << 2); data |= (dsc->convert_rgb << 1); data |= dsc->input_10_bits; pr_debug("%d %d %d %d %d %d %d %d %d, data=%x\n", ich_reset_override, initial_lines , dsc->slice_last_group_size, dsc->bpp, dsc->block_pred_enable, dsc->line_buf_depth, dsc->enable_422, dsc->convert_rgb, dsc->input_10_bits, data); writel_relaxed(data, offset + MDSS_MDP_REG_DSC_ENC); data = dsc->pic_width << 16; data |= dsc->pic_height; writel_relaxed(data, offset + MDSS_MDP_REG_DSC_PICTURE); data = dsc->slice_width << 16; data |= dsc->slice_height; writel_relaxed(data, offset + MDSS_MDP_REG_DSC_SLICE); data = dsc->chunk_size << 16; writel_relaxed(data, offset + MDSS_MDP_REG_DSC_CHUNK_SIZE); pr_debug("mix%d pic_w=%d pic_h=%d, slice_w=%d slice_h=%d, chunk=%d\n", mixer->num, dsc->pic_width, dsc->pic_height, dsc->slice_width, dsc->slice_height, dsc->chunk_size); MDSS_XLOG(mixer->num, dsc->pic_width, dsc->pic_height, dsc->slice_width, dsc->slice_height, dsc->chunk_size); data = dsc->initial_dec_delay << 16; data |= dsc->initial_xmit_delay; writel_relaxed(data, offset + MDSS_MDP_REG_DSC_DELAY); data = dsc->initial_scale_value; writel_relaxed(data, offset + MDSS_MDP_REG_DSC_SCALE_INITIAL); data = dsc->scale_decrement_interval; writel_relaxed(data, offset + MDSS_MDP_REG_DSC_SCALE_DEC_INTERVAL); data = dsc->scale_increment_interval; writel_relaxed(data, offset + MDSS_MDP_REG_DSC_SCALE_INC_INTERVAL); data = dsc->first_line_bpg_offset; writel_relaxed(data, offset + MDSS_MDP_REG_DSC_FIRST_LINE_BPG_OFFSET); data = dsc->nfl_bpg_offset << 16; data |= dsc->slice_bpg_offset; writel_relaxed(data, offset + MDSS_MDP_REG_DSC_BPG_OFFSET); data = dsc->initial_offset << 16; data |= dsc->final_offset; writel_relaxed(data, offset + MDSS_MDP_REG_DSC_DSC_OFFSET); data = dsc->det_thresh_flatness << 10; data |= dsc->max_qp_flatness << 5; data |= dsc->min_qp_flatness; writel_relaxed(data, offset + MDSS_MDP_REG_DSC_FLATNESS); writel_relaxed(0x983, offset + MDSS_MDP_REG_DSC_FLATNESS); data = dsc->rc_model_size; /* rate_buffer_size */ writel_relaxed(data, offset + MDSS_MDP_REG_DSC_RC_MODEL_SIZE); data = dsc->tgt_offset_lo << 18; data |= dsc->tgt_offset_hi << 14; data |= dsc->quant_incr_limit1 << 9; data |= dsc->quant_incr_limit0 << 4; data |= dsc->edge_factor; writel_relaxed(data, offset + MDSS_MDP_REG_DSC_RC); } static void __dsc_config_thresh(struct mdss_mdp_mixer *mixer, struct dsc_desc *dsc) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); char __iomem *offset, *off; u32 *lp; char *cp; int i; offset = mdata->mdp_base; if (mixer->num == MDSS_MDP_INTF_LAYERMIXER0) { offset += MDSS_MDP_DSC_0_OFFSET; } else if (mixer->num == MDSS_MDP_INTF_LAYERMIXER1) { offset += MDSS_MDP_DSC_1_OFFSET; } else { pr_err("invalid mixer numer=%d\n", mixer->num); return; } lp = dsc->buf_thresh; off = offset + MDSS_MDP_REG_DSC_RC_BUF_THRESH; for (i = 0; i < 14; i++) { writel_relaxed(*lp++, off); off += 4; } cp = dsc->range_min_qp; off = offset + MDSS_MDP_REG_DSC_RANGE_MIN_QP; for (i = 0; i < 15; i++) { writel_relaxed(*cp++, off); off += 4; } cp = dsc->range_max_qp; off = offset + MDSS_MDP_REG_DSC_RANGE_MAX_QP; for (i = 0; i < 15; i++) { writel_relaxed(*cp++, off); off += 4; } cp = dsc->range_bpg_offset; off = offset + MDSS_MDP_REG_DSC_RANGE_BPG_OFFSET; for (i = 0; i < 15; i++) { writel_relaxed(*cp++, off); off += 4; } } static bool __is_dsc_merge_enabled(u32 common_mode) { return common_mode & BIT(1); } static bool __dsc_is_3d_mux_enabled(struct mdss_mdp_ctl *ctl, struct mdss_panel_info *pinfo) { return ctl && is_dual_lm_single_display(ctl->mfd) && pinfo && (pinfo->dsc_enc_total == 1); } /* must be called from master ctl */ static u32 __dsc_get_common_mode(struct mdss_mdp_ctl *ctl, bool mux_3d) { u32 common_mode = 0; if (ctl->is_video_mode) common_mode = BIT(2); if (mdss_mdp_is_both_lm_valid(ctl)) common_mode |= BIT(0); if (is_dual_lm_single_display(ctl->mfd)) { if (mux_3d) common_mode &= ~BIT(0); else if (mdss_mdp_is_both_lm_valid(ctl)) /* dsc_merge */ common_mode |= BIT(1); } return common_mode; } static void __dsc_get_pic_dim(struct mdss_mdp_mixer *mixer_l, struct mdss_mdp_mixer *mixer_r, u32 *pic_w, u32 *pic_h) { bool valid_l = mixer_l && mixer_l->valid_roi; bool valid_r = mixer_r && mixer_r->valid_roi; *pic_w = 0; *pic_h = 0; if (valid_l) { *pic_w = mixer_l->roi.w; *pic_h = mixer_l->roi.h; } if (valid_r) { *pic_w += mixer_r->roi.w; *pic_h = mixer_r->roi.h; } } static bool __is_ich_reset_override_needed(bool pu_en, struct dsc_desc *dsc) { /* * As per the DSC spec, ICH_RESET can be either end of the slice line * or at the end of the slice. HW internally generates ich_reset at * end of the slice line if DSC_MERGE is used or encoder has two * soft slices. However, if encoder has only 1 soft slice and DSC_MERGE * is not used then it will generate ich_reset at the end of slice. * * Now as per the spec, during one PPS session, position where * ich_reset is generated should not change. Now if full-screen frame * has more than 1 soft slice then HW will automatically generate * ich_reset at the end of slice_line. But for the same panel, if * partial frame is enabled and only 1 encoder is used with 1 slice, * then HW will generate ich_reset at end of the slice. This is a * mismatch. Prevent this by overriding HW's decision. */ return pu_en && dsc && (dsc->full_frame_slices > 1) && (dsc->slice_width == dsc->pic_width); } static void __dsc_setup_dual_lm_single_display(struct mdss_mdp_ctl *ctl, struct mdss_panel_info *pinfo) { u32 pic_width = 0, pic_height = 0; u32 intf_ip_w, enc_ip_w, common_mode, this_frame_slices; bool valid_l, valid_r; bool enable_right_dsc; bool mux_3d, ich_reset_override; struct dsc_desc *dsc; struct mdss_mdp_mixer *mixer_l, *mixer_r; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (!pinfo || !ctl || !ctl->is_master || !is_dual_lm_single_display(ctl->mfd)) return; dsc = &pinfo->dsc; mixer_l = ctl->mixer_left; mixer_r = ctl->mixer_right; mux_3d = __dsc_is_3d_mux_enabled(ctl, pinfo); common_mode = __dsc_get_common_mode(ctl, mux_3d); __dsc_get_pic_dim(mixer_l, mixer_r, &pic_width, &pic_height); valid_l = mixer_l->valid_roi; valid_r = mixer_r->valid_roi; if (mdss_mdp_is_lm_swap_needed(mdata, ctl)) { valid_l = true; valid_r = false; } this_frame_slices = pic_width / dsc->slice_width; /* enable or disable pp_split + DSC_Merge based on partial update */ if ((pinfo->partial_update_enabled) && !mux_3d && (dsc->full_frame_slices == 4) && (mdss_has_quirk(mdata, MDSS_QUIRK_DSC_2SLICE_PU_THRPUT))) { if (valid_l && valid_r) { /* left + right */ pr_debug("full line (4 slices) or middle 2 slice partial update\n"); writel_relaxed(0x0, mdata->mdp_base + mdata->ppb[0].ctl_off); writel_relaxed(0x0, mdata->mdp_base + MDSS_MDP_REG_DCE_SEL); } else if (valid_l || valid_r) { /* left-only or right-only */ if (this_frame_slices == 2) { pr_debug("2 slice parital update, use merge\n"); /* tandem + merge */ common_mode = BIT(1) | BIT(0); valid_r = true; valid_l = true; writel_relaxed(0x2 << 4, mdata->mdp_base + mdata->ppb[0].ctl_off); writel_relaxed(BIT(0), mdata->mdp_base + MDSS_MDP_REG_DCE_SEL); } else { pr_debug("only one slice partial update\n"); writel_relaxed(0x0, mdata->mdp_base + mdata->ppb[0].ctl_off); writel_relaxed(0x0, mdata->mdp_base + MDSS_MDP_REG_DCE_SEL); } } } else { writel_relaxed(0x0, mdata->mdp_base + MDSS_MDP_REG_DCE_SEL); } mdss_panel_dsc_update_pic_dim(dsc, pic_width, pic_height); intf_ip_w = this_frame_slices * dsc->slice_width; mdss_panel_dsc_pclk_param_calc(dsc, intf_ip_w); enc_ip_w = intf_ip_w; /* if dsc_merge, both encoders work on same number of slices */ if (__is_dsc_merge_enabled(common_mode)) enc_ip_w /= 2; mdss_panel_dsc_initial_line_calc(dsc, enc_ip_w); /* * __is_ich_reset_override_needed should be called only after * updating pic dimension, mdss_panel_dsc_update_pic_dim. */ ich_reset_override = __is_ich_reset_override_needed( pinfo->partial_update_enabled, dsc); if (valid_l) { __dsc_config(mixer_l, dsc, common_mode, ich_reset_override); __dsc_config_thresh(mixer_l, dsc); __dsc_enable(mixer_l); } else { __dsc_disable(mixer_l); } enable_right_dsc = valid_r; if (mux_3d && valid_l) enable_right_dsc = false; if (enable_right_dsc) { __dsc_config(mixer_r, dsc, common_mode, ich_reset_override); __dsc_config_thresh(mixer_r, dsc); __dsc_enable(mixer_r); } else { __dsc_disable(mixer_r); } pr_debug("mix%d: valid_l=%d mix%d: valid_r=%d mode=%d, pic_dim:%dx%d mux_3d=%d intf_ip_w=%d enc_ip_w=%d ich_ovrd=%d\n", mixer_l->num, valid_l, mixer_r->num, valid_r, common_mode, pic_width, pic_height, mux_3d, intf_ip_w, enc_ip_w, ich_reset_override); MDSS_XLOG(mixer_l->num, valid_l, mixer_r->num, valid_r, common_mode, pic_width, pic_height, mux_3d, intf_ip_w, enc_ip_w, ich_reset_override); } static void __dsc_setup_dual_lm_dual_display( struct mdss_mdp_ctl *ctl, struct mdss_panel_info *pinfo, struct mdss_mdp_ctl *sctl, struct mdss_panel_info *spinfo) { u32 pic_width = 0, pic_height = 0; u32 intf_ip_w, enc_ip_w, common_mode, this_frame_slices; bool valid_l, valid_r; bool ich_reset_override; struct dsc_desc *dsc_l, *dsc_r; struct mdss_mdp_mixer *mixer_l, *mixer_r; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (!pinfo || !ctl || !sctl || !spinfo || !ctl->is_master || !ctl->mfd || (ctl->mfd->split_mode != MDP_DUAL_LM_DUAL_DISPLAY)) return; dsc_l = &pinfo->dsc; dsc_r = &spinfo->dsc; mixer_l = ctl->mixer_left; mixer_r = ctl->mixer_right; common_mode = __dsc_get_common_mode(ctl, false); /* * In this topology, both DSC use same pic dimension. So no need to * maintain two separate local copies. */ __dsc_get_pic_dim(mixer_l, mixer_r, &pic_width, &pic_height); valid_l = mixer_l->valid_roi; valid_r = mixer_r->valid_roi; if (mdss_mdp_is_lm_swap_needed(mdata, ctl)) { valid_l = true; valid_r = false; } /* * Since both DSC use same pic dimension, set same pic dimension * to both DSC structures. */ mdss_panel_dsc_update_pic_dim(dsc_l, pic_width, pic_height); mdss_panel_dsc_update_pic_dim(dsc_r, pic_width, pic_height); this_frame_slices = pic_width / dsc_l->slice_width; intf_ip_w = this_frame_slices * dsc_l->slice_width; if (valid_l && valid_r) intf_ip_w /= 2; /* * In this topology when both interfaces are active, they have same * load so intf_ip_w will be same. */ mdss_panel_dsc_pclk_param_calc(dsc_l, intf_ip_w); mdss_panel_dsc_pclk_param_calc(dsc_r, intf_ip_w); /* * In this topology, since there is no dsc_merge, uncompressed input * to encoder and interface is same. */ enc_ip_w = intf_ip_w; mdss_panel_dsc_initial_line_calc(dsc_l, enc_ip_w); mdss_panel_dsc_initial_line_calc(dsc_r, enc_ip_w); /* * __is_ich_reset_override_needed should be called only after * updating pic dimension, mdss_panel_dsc_update_pic_dim. */ ich_reset_override = __is_ich_reset_override_needed( pinfo->partial_update_enabled, dsc_l); if (valid_l) { __dsc_config(mixer_l, dsc_l, common_mode, ich_reset_override); __dsc_config_thresh(mixer_l, dsc_l); __dsc_enable(mixer_l); } else { __dsc_disable(mixer_l); } if (valid_r) { __dsc_config(mixer_r, dsc_r, common_mode, ich_reset_override); __dsc_config_thresh(mixer_r, dsc_r); __dsc_enable(mixer_r); } else { __dsc_disable(mixer_r); } pr_debug("mix%d: valid_l=%d mix%d: valid_r=%d mode=%d, pic_dim:%dx%d intf_ip_w=%d enc_ip_w=%d ich_ovrd=%d\n", mixer_l->num, valid_l, mixer_r->num, valid_r, common_mode, pic_width, pic_height, intf_ip_w, enc_ip_w, ich_reset_override); MDSS_XLOG(mixer_l->num, valid_l, mixer_r->num, valid_r, common_mode, pic_width, pic_height, intf_ip_w, enc_ip_w, ich_reset_override); } static void __dsc_setup_single_lm_single_display(struct mdss_mdp_ctl *ctl, struct mdss_panel_info *pinfo) { u32 pic_width = 0, pic_height = 0; u32 intf_ip_w, enc_ip_w, common_mode, this_frame_slices; bool valid; bool ich_reset_override; struct dsc_desc *dsc; struct mdss_mdp_mixer *mixer; if (!pinfo || !ctl || !ctl->is_master) return; dsc = &pinfo->dsc; mixer = ctl->mixer_left; valid = mixer->valid_roi; common_mode = __dsc_get_common_mode(ctl, false); __dsc_get_pic_dim(mixer, NULL, &pic_width, &pic_height); mdss_panel_dsc_update_pic_dim(dsc, pic_width, pic_height); this_frame_slices = pic_width / dsc->slice_width; intf_ip_w = this_frame_slices * dsc->slice_width; mdss_panel_dsc_pclk_param_calc(dsc, intf_ip_w); enc_ip_w = intf_ip_w; mdss_panel_dsc_initial_line_calc(dsc, enc_ip_w); /* * __is_ich_reset_override_needed should be called only after * updating pic dimension, mdss_panel_dsc_update_pic_dim. */ ich_reset_override = __is_ich_reset_override_needed( pinfo->partial_update_enabled, dsc); if (valid) { __dsc_config(mixer, dsc, common_mode, ich_reset_override); __dsc_config_thresh(mixer, dsc); __dsc_enable(mixer); } else { __dsc_disable(mixer); } pr_debug("mix%d: valid=%d mode=%d, pic_dim:%dx%d intf_ip_w=%d enc_ip_w=%d ich_ovrd=%d\n", mixer->num, valid, common_mode, pic_width, pic_height, intf_ip_w, enc_ip_w, ich_reset_override); MDSS_XLOG(mixer->num, valid, common_mode, pic_width, pic_height, intf_ip_w, enc_ip_w, ich_reset_override); } void mdss_mdp_ctl_dsc_setup(struct mdss_mdp_ctl *ctl, struct mdss_panel_info *pinfo) { struct mdss_mdp_ctl *sctl; struct mdss_panel_info *spinfo; if (!is_dsc_compression(pinfo)) return; if (!ctl->is_master) { pr_debug("skip slave ctl because master will program for both\n"); return; } switch (ctl->mfd->split_mode) { case MDP_DUAL_LM_SINGLE_DISPLAY: __dsc_setup_dual_lm_single_display(ctl, pinfo); break; case MDP_DUAL_LM_DUAL_DISPLAY: sctl = mdss_mdp_get_split_ctl(ctl); spinfo = &sctl->panel_data->panel_info; __dsc_setup_dual_lm_dual_display(ctl, pinfo, sctl, spinfo); break; default: /* pp_split is not supported yet */ __dsc_setup_single_lm_single_display(ctl, pinfo); break; } } static int mdss_mdp_ctl_fbc_enable(int enable, struct mdss_mdp_mixer *mixer, struct mdss_panel_info *pdata) { struct fbc_panel_info *fbc; u32 mode = 0, budget_ctl = 0, lossy_mode = 0, width; if (!pdata) { pr_err("Invalid pdata\n"); return -EINVAL; } fbc = &pdata->fbc; if (mixer->num == MDSS_MDP_INTF_LAYERMIXER0 || mixer->num == MDSS_MDP_INTF_LAYERMIXER1) { pr_debug("Mixer supports FBC.\n"); } else { pr_debug("Mixer doesn't support FBC.\n"); return -EINVAL; } if (enable) { if (fbc->enc_mode && pdata->bpp) { /* width is the compressed width */ width = mult_frac(pdata->xres, fbc->target_bpp, pdata->bpp); } else { /* width is the source width */ width = pdata->xres; } mode = ((width) << 16) | ((fbc->slice_height) << 11) | ((fbc->pred_mode) << 10) | ((fbc->enc_mode) << 9) | ((fbc->comp_mode) << 8) | ((fbc->qerr_enable) << 7) | ((fbc->cd_bias) << 4) | ((fbc->pat_enable) << 3) | ((fbc->vlc_enable) << 2) | ((fbc->bflc_enable) << 1) | enable; budget_ctl = ((fbc->line_x_budget) << 12) | ((fbc->block_x_budget) << 8) | fbc->block_budget; lossy_mode = ((fbc->max_pred_err) << 28) | ((fbc->lossless_mode_thd) << 16) | ((fbc->lossy_mode_thd) << 8) | ((fbc->lossy_rgb_thd) << 4) | fbc->lossy_mode_idx; } mdss_mdp_pingpong_write(mixer->pingpong_base, MDSS_MDP_REG_PP_FBC_MODE, mode); mdss_mdp_pingpong_write(mixer->pingpong_base, MDSS_MDP_REG_PP_FBC_BUDGET_CTL, budget_ctl); mdss_mdp_pingpong_write(mixer->pingpong_base, MDSS_MDP_REG_PP_FBC_LOSSY_MODE, lossy_mode); return 0; } int mdss_mdp_ctl_setup(struct mdss_mdp_ctl *ctl) { struct mdss_mdp_ctl *split_ctl; u32 width, height; int split_fb; u32 max_mixer_width; struct mdss_panel_info *pinfo; if (!ctl || !ctl->panel_data) { pr_err("invalid ctl handle\n"); return -ENODEV; } pinfo = &ctl->panel_data->panel_info; if (pinfo->type == WRITEBACK_PANEL) { pr_err("writeback panel, ignore\n"); return 0; } split_ctl = mdss_mdp_get_split_ctl(ctl); width = get_panel_width(ctl); height = get_panel_yres(pinfo); max_mixer_width = ctl->mdata->max_mixer_width; split_fb = ((is_dual_lm_single_display(ctl->mfd)) && (ctl->mfd->split_fb_left <= max_mixer_width) && (ctl->mfd->split_fb_right <= max_mixer_width)) ? 1 : 0; pr_debug("max=%d xres=%d left=%d right=%d\n", max_mixer_width, width, ctl->mfd->split_fb_left, ctl->mfd->split_fb_right); if ((split_ctl && (width > max_mixer_width)) || (width > (2 * max_mixer_width))) { pr_err("Unsupported panel resolution: %dx%d\n", width, height); return -ENOTSUPP; } ctl->width = width; ctl->height = height; ctl->roi = (struct mdss_rect) {0, 0, width, height}; if (!ctl->mixer_left) { ctl->mixer_left = mdss_mdp_mixer_alloc(ctl, MDSS_MDP_MIXER_TYPE_INTF, ((width > max_mixer_width) || split_fb), 0); if (!ctl->mixer_left) { pr_err("unable to allocate layer mixer\n"); return -ENOMEM; } else if (split_fb && ctl->mixer_left->num >= 1 && (ctl->panel_data->panel_info.type == MIPI_CMD_PANEL)) { pr_err("use only DSPP0 and DSPP1 with cmd split\n"); return -EPERM; } } if (split_fb) { width = ctl->mfd->split_fb_left; width += (pinfo->lcdc.border_left + pinfo->lcdc.border_right); } else if (width > max_mixer_width) { width /= 2; } ctl->mixer_left->width = width; ctl->mixer_left->height = height; ctl->mixer_left->roi = (struct mdss_rect) {0, 0, width, height}; ctl->mixer_left->valid_roi = true; ctl->mixer_left->roi_changed = true; if (ctl->mfd->split_mode == MDP_DUAL_LM_DUAL_DISPLAY) { pr_debug("dual display detected\n"); return 0; } if (split_fb) width = ctl->mfd->split_fb_right; if (width < ctl->width) { if (ctl->mixer_right == NULL) { ctl->mixer_right = mdss_mdp_mixer_alloc(ctl, MDSS_MDP_MIXER_TYPE_INTF, true, 0); if (!ctl->mixer_right) { pr_err("unable to allocate right mixer\n"); if (ctl->mixer_left) mdss_mdp_mixer_free(ctl->mixer_left); return -ENOMEM; } } ctl->mixer_right->is_right_mixer = true; ctl->mixer_right->width = width; ctl->mixer_right->height = height; ctl->mixer_right->roi = (struct mdss_rect) {0, 0, width, height}; ctl->mixer_right->valid_roi = true; ctl->mixer_right->roi_changed = true; } else if (ctl->mixer_right) { ctl->mixer_right->valid_roi = false; ctl->mixer_right->roi_changed = false; mdss_mdp_mixer_free(ctl->mixer_right); ctl->mixer_right = NULL; } if (ctl->mixer_right) { if (!is_dsc_compression(pinfo) || (pinfo->dsc_enc_total == 1)) ctl->opmode |= MDSS_MDP_CTL_OP_PACK_3D_ENABLE | MDSS_MDP_CTL_OP_PACK_3D_H_ROW_INT; } else { ctl->opmode &= ~(MDSS_MDP_CTL_OP_PACK_3D_ENABLE | MDSS_MDP_CTL_OP_PACK_3D_H_ROW_INT); } return 0; } /** * mdss_mdp_ctl_reconfig() - re-configure ctl for new mode * @ctl: mdp controller. * @pdata: panel data * * This function is called when we are trying to dynamically change * the DSI mode. We need to change various mdp_ctl properties to * the new mode of operation. */ int mdss_mdp_ctl_reconfig(struct mdss_mdp_ctl *ctl, struct mdss_panel_data *pdata) { void *tmp; int ret = 0; /* * Switch first to prevent deleting important data in the case * where panel type is not supported in reconfig */ if ((pdata->panel_info.type != MIPI_VIDEO_PANEL) && (pdata->panel_info.type != MIPI_CMD_PANEL)) { pr_err("unsupported panel type (%d)\n", pdata->panel_info.type); return -EINVAL; } /* if only changing resolution there is no need for intf reconfig */ if (!ctl->is_video_mode == (pdata->panel_info.type == MIPI_CMD_PANEL)) goto skip_intf_reconfig; /* * Intentionally not clearing stop function, as stop will * be called after panel is instructed mode switch is happening */ tmp = ctl->ops.stop_fnc; memset(&ctl->ops, 0, sizeof(ctl->ops)); ctl->ops.stop_fnc = tmp; switch (pdata->panel_info.type) { case MIPI_VIDEO_PANEL: ctl->is_video_mode = true; ctl->intf_type = MDSS_INTF_DSI; ctl->opmode = MDSS_MDP_CTL_OP_VIDEO_MODE; ctl->ops.start_fnc = mdss_mdp_video_start; break; case MIPI_CMD_PANEL: ctl->is_video_mode = false; ctl->intf_type = MDSS_INTF_DSI; ctl->opmode = MDSS_MDP_CTL_OP_CMD_MODE; ctl->ops.start_fnc = mdss_mdp_cmd_start; break; } ctl->is_secure = false; ctl->split_flush_en = false; ctl->perf_release_ctl_bw = false; ctl->play_cnt = 0; ctl->opmode |= (ctl->intf_num << 4); skip_intf_reconfig: ctl->width = get_panel_xres(&pdata->panel_info); ctl->height = get_panel_yres(&pdata->panel_info); if (ctl->mixer_left) { ctl->mixer_left->width = ctl->width; ctl->mixer_left->height = ctl->height; } ctl->border_x_off = pdata->panel_info.lcdc.border_left; ctl->border_y_off = pdata->panel_info.lcdc.border_top; return ret; } struct mdss_mdp_ctl *mdss_mdp_ctl_init(struct mdss_panel_data *pdata, struct msm_fb_data_type *mfd) { int ret = 0, offset; struct mdss_mdp_ctl *ctl; struct mdss_data_type *mdata = mfd_to_mdata(mfd); struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); struct mdss_panel_info *pinfo; if (pdata->panel_info.type == WRITEBACK_PANEL) offset = mdss_mdp_get_wb_ctl_support(mdata, false); else offset = MDSS_MDP_CTL0; if (is_pingpong_split(mfd) && !mdata->has_pingpong_split) { pr_err("Error: pp_split cannot be enabled on fb%d if HW doesn't support it\n", mfd->index); return ERR_PTR(-EINVAL); } ctl = mdss_mdp_ctl_alloc(mdata, offset); if (!ctl) { pr_err("unable to allocate ctl\n"); return ERR_PTR(-ENOMEM); } pinfo = &pdata->panel_info; ctl->mfd = mfd; ctl->panel_data = pdata; ctl->is_video_mode = false; ctl->perf_release_ctl_bw = false; ctl->border_x_off = pinfo->lcdc.border_left; ctl->border_y_off = pinfo->lcdc.border_top; ctl->disable_prefill = false; switch (pdata->panel_info.type) { case EDP_PANEL: ctl->is_video_mode = true; ctl->intf_num = MDSS_MDP_INTF0; ctl->intf_type = MDSS_INTF_EDP; ctl->opmode = MDSS_MDP_CTL_OP_VIDEO_MODE; ctl->ops.start_fnc = mdss_mdp_video_start; break; case MIPI_VIDEO_PANEL: ctl->is_video_mode = true; if (pdata->panel_info.pdest == DISPLAY_1) ctl->intf_num = mdp5_data->mixer_swap ? MDSS_MDP_INTF2 : MDSS_MDP_INTF1; else ctl->intf_num = mdp5_data->mixer_swap ? MDSS_MDP_INTF1 : MDSS_MDP_INTF2; ctl->intf_type = MDSS_INTF_DSI; ctl->opmode = MDSS_MDP_CTL_OP_VIDEO_MODE; ctl->ops.start_fnc = mdss_mdp_video_start; break; case MIPI_CMD_PANEL: if (pdata->panel_info.pdest == DISPLAY_1) ctl->intf_num = mdp5_data->mixer_swap ? MDSS_MDP_INTF2 : MDSS_MDP_INTF1; else ctl->intf_num = mdp5_data->mixer_swap ? MDSS_MDP_INTF1 : MDSS_MDP_INTF2; ctl->intf_type = MDSS_INTF_DSI; ctl->opmode = MDSS_MDP_CTL_OP_CMD_MODE; ctl->ops.start_fnc = mdss_mdp_cmd_start; break; case DTV_PANEL: ctl->is_video_mode = true; ctl->intf_num = MDSS_MDP_INTF3; ctl->intf_type = MDSS_INTF_HDMI; ctl->opmode = MDSS_MDP_CTL_OP_VIDEO_MODE; ctl->ops.start_fnc = mdss_mdp_video_start; break; case WRITEBACK_PANEL: ctl->intf_num = MDSS_MDP_NO_INTF; ctl->ops.start_fnc = mdss_mdp_writeback_start; break; default: pr_err("unsupported panel type (%d)\n", pdata->panel_info.type); ret = -EINVAL; goto ctl_init_fail; } ctl->opmode |= (ctl->intf_num << 4); if (ctl->intf_num == MDSS_MDP_NO_INTF) { ctl->dst_format = pdata->panel_info.out_format; } else { switch (pdata->panel_info.bpp) { case 18: if (ctl->intf_type == MDSS_INTF_DSI) ctl->dst_format = MDSS_MDP_PANEL_FORMAT_RGB666 | MDSS_MDP_PANEL_FORMAT_PACK_ALIGN_MSB; else ctl->dst_format = MDSS_MDP_PANEL_FORMAT_RGB666; break; case 24: default: ctl->dst_format = MDSS_MDP_PANEL_FORMAT_RGB888; break; } } return ctl; ctl_init_fail: mdss_mdp_ctl_free(ctl); return ERR_PTR(ret); } int mdss_mdp_ctl_split_display_setup(struct mdss_mdp_ctl *ctl, struct mdss_panel_data *pdata) { struct mdss_mdp_ctl *sctl; struct mdss_mdp_mixer *mixer; if (!ctl || !pdata) return -ENODEV; if (pdata->panel_info.xres > ctl->mdata->max_mixer_width) { pr_err("Unsupported second panel resolution: %dx%d\n", pdata->panel_info.xres, pdata->panel_info.yres); return -ENOTSUPP; } if (ctl->mixer_right) { pr_err("right mixer already setup for ctl=%d\n", ctl->num); return -EPERM; } sctl = mdss_mdp_ctl_init(pdata, ctl->mfd); if (!sctl) { pr_err("unable to setup split display\n"); return -ENODEV; } sctl->width = get_panel_xres(&pdata->panel_info); sctl->height = get_panel_yres(&pdata->panel_info); sctl->roi = (struct mdss_rect){0, 0, sctl->width, sctl->height}; if (!ctl->mixer_left) { ctl->mixer_left = mdss_mdp_mixer_alloc(ctl, MDSS_MDP_MIXER_TYPE_INTF, false, 0); if (!ctl->mixer_left) { pr_err("unable to allocate layer mixer\n"); mdss_mdp_ctl_destroy(sctl); return -ENOMEM; } } mixer = mdss_mdp_mixer_alloc(sctl, MDSS_MDP_MIXER_TYPE_INTF, false, 0); if (!mixer) { pr_err("unable to allocate layer mixer\n"); mdss_mdp_ctl_destroy(sctl); return -ENOMEM; } mixer->is_right_mixer = true; mixer->width = sctl->width; mixer->height = sctl->height; mixer->roi = (struct mdss_rect) {0, 0, mixer->width, mixer->height}; mixer->valid_roi = true; mixer->roi_changed = true; sctl->mixer_left = mixer; return mdss_mdp_set_split_ctl(ctl, sctl); } static void mdss_mdp_ctl_split_display_enable(int enable, struct mdss_mdp_ctl *main_ctl, struct mdss_mdp_ctl *slave_ctl) { u32 upper = 0, lower = 0; pr_debug("split main ctl=%d intf=%d\n", main_ctl->num, main_ctl->intf_num); if (slave_ctl) pr_debug("split slave ctl=%d intf=%d\n", slave_ctl->num, slave_ctl->intf_num); if (enable) { if (main_ctl->opmode & MDSS_MDP_CTL_OP_CMD_MODE) { /* interface controlling sw trigger (cmd mode) */ lower |= BIT(1); if (main_ctl->intf_num == MDSS_MDP_INTF2) lower |= BIT(4); else lower |= BIT(8); /* * Enable SMART_PANEL_FREE_RUN if ping pong split * is enabled. */ if (is_pingpong_split(main_ctl->mfd)) lower |= BIT(2); upper = lower; } else { /* interface controlling sw trigger (video mode) */ if (main_ctl->intf_num == MDSS_MDP_INTF2) { lower |= BIT(4); upper |= BIT(8); } else { lower |= BIT(8); upper |= BIT(4); } } } writel_relaxed(upper, main_ctl->mdata->mdp_base + MDSS_MDP_REG_SPLIT_DISPLAY_UPPER_PIPE_CTRL); writel_relaxed(lower, main_ctl->mdata->mdp_base + MDSS_MDP_REG_SPLIT_DISPLAY_LOWER_PIPE_CTRL); writel_relaxed(enable, main_ctl->mdata->mdp_base + MDSS_MDP_REG_SPLIT_DISPLAY_EN); if ((main_ctl->mdata->mdp_rev >= MDSS_MDP_HW_REV_103) && main_ctl->is_video_mode) { struct mdss_overlay_private *mdp5_data; bool mixer_swap = false; if (main_ctl->mfd) { mdp5_data = mfd_to_mdp5_data(main_ctl->mfd); mixer_swap = mdp5_data->mixer_swap; } main_ctl->split_flush_en = !mixer_swap; if (main_ctl->split_flush_en) writel_relaxed(enable ? 0x1 : 0x0, main_ctl->mdata->mdp_base + MMSS_MDP_MDP_SSPP_SPARE_0); } } static void mdss_mdp_ctl_pp_split_display_enable(bool enable, struct mdss_mdp_ctl *ctl) { u32 cfg = 0, cntl = 0; if (ctl->mdata->nppb == 0) { pr_err("No PPB to enable PP split\n"); BUG(); } mdss_mdp_ctl_split_display_enable(enable, ctl, NULL); if (enable) { cfg = ctl->slave_intf_num << 20; /* Set slave intf */ cfg |= BIT(16); /* Set horizontal split */ cntl = BIT(5); /* enable dst split */ } writel_relaxed(cfg, ctl->mdata->mdp_base + ctl->mdata->ppb[0].cfg_off); writel_relaxed(cntl, ctl->mdata->mdp_base + ctl->mdata->ppb[0].ctl_off); } int mdss_mdp_ctl_destroy(struct mdss_mdp_ctl *ctl) { struct mdss_mdp_ctl *sctl; int rc; rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_CLOSE, NULL, CTL_INTF_EVENT_FLAG_DEFAULT); WARN(rc, "unable to close panel for intf=%d\n", ctl->intf_num); sctl = mdss_mdp_get_split_ctl(ctl); if (sctl) { pr_debug("destroying split display ctl=%d\n", sctl->num); mdss_mdp_ctl_free(sctl); } mdss_mdp_ctl_free(ctl); return 0; } int mdss_mdp_ctl_intf_event(struct mdss_mdp_ctl *ctl, int event, void *arg, u32 flags) { struct mdss_panel_data *pdata; int rc = 0; if (!ctl || !ctl->panel_data) return -ENODEV; pdata = ctl->panel_data; if (flags & CTL_INTF_EVENT_FLAG_SLAVE_INTF) { pdata = pdata->next; if (!pdata) { pr_err("Error: event=%d flags=0x%x, ctl%d slave intf is not present\n", event, flags, ctl->num); return -EINVAL; } } pr_debug("sending ctl=%d event=%d flag=0x%x\n", ctl->num, event, flags); do { if (pdata->event_handler) rc = pdata->event_handler(pdata, event, arg); pdata = pdata->next; } while (rc == 0 && pdata && pdata->active && !(flags & CTL_INTF_EVENT_FLAG_SKIP_BROADCAST)); return rc; } static void mdss_mdp_ctl_restore_sub(struct mdss_mdp_ctl *ctl) { u32 temp; int ret = 0; temp = readl_relaxed(ctl->mdata->mdp_base + MDSS_MDP_REG_DISP_INTF_SEL); temp |= (ctl->intf_type << ((ctl->intf_num - MDSS_MDP_INTF0) * 8)); writel_relaxed(temp, ctl->mdata->mdp_base + MDSS_MDP_REG_DISP_INTF_SEL); if (ctl->mfd && ctl->panel_data) { ctl->mfd->ipc_resume = true; mdss_mdp_pp_resume(ctl->mfd); if (is_dsc_compression(&ctl->panel_data->panel_info)) { mdss_mdp_ctl_dsc_setup(ctl, &ctl->panel_data->panel_info); } else if (ctl->panel_data->panel_info.compression_mode == COMPRESSION_FBC) { ret = mdss_mdp_ctl_fbc_enable(1, ctl->mixer_left, &ctl->panel_data->panel_info); if (ret) pr_err("Failed to restore FBC mode\n"); } } } /* * mdss_mdp_ctl_restore() - restore mdp ctl path * @locked - boolean to signal that clock lock is already acquired * * This function is called whenever MDP comes out of a power collapse as * a result of a screen update. It restores the MDP controller's software * state to the hardware registers. * Function does not enable the clocks, so caller must make sure * clocks are enabled before calling. * The locked boolean in the parametrs signals that synchronization * with mdp clocks access is not required downstream. * Only call this function setting this value to true if the clocks access * synchronization is guaranteed by the caller. */ void mdss_mdp_ctl_restore(bool locked) { struct mdss_mdp_ctl *ctl = NULL; struct mdss_mdp_ctl *sctl; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); u32 cnum; for (cnum = MDSS_MDP_CTL0; cnum < mdata->nctl; cnum++) { ctl = mdata->ctl_off + cnum; if (!mdss_mdp_ctl_is_power_on(ctl)) continue; pr_debug("restoring ctl%d, intf_type=%d\n", cnum, ctl->intf_type); ctl->play_cnt = 0; sctl = mdss_mdp_get_split_ctl(ctl); mdss_mdp_ctl_restore_sub(ctl); if (sctl) { mdss_mdp_ctl_restore_sub(sctl); mdss_mdp_ctl_split_display_enable(1, ctl, sctl); } if (ctl->ops.restore_fnc) ctl->ops.restore_fnc(ctl, locked); } } static int mdss_mdp_ctl_start_sub(struct mdss_mdp_ctl *ctl, bool handoff) { struct mdss_mdp_mixer *mixer; u32 outsize, temp; int ret = 0; int i, nmixers; pr_debug("ctl_num=%d\n", ctl->num); /* * Need start_fnc in 2 cases: * (1) handoff * (2) continuous splash finished. */ if (handoff || !ctl->panel_data->panel_info.cont_splash_enabled) { if (ctl->ops.start_fnc) ret = ctl->ops.start_fnc(ctl); else pr_warn("no start function for ctl=%d type=%d\n", ctl->num, ctl->panel_data->panel_info.type); if (ret) { pr_err("unable to start intf\n"); return ret; } } if (!ctl->panel_data->panel_info.cont_splash_enabled) { nmixers = MDSS_MDP_INTF_MAX_LAYERMIXER + MDSS_MDP_WB_MAX_LAYERMIXER; for (i = 0; i < nmixers; i++) mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_LAYER(i), 0); } temp = readl_relaxed(ctl->mdata->mdp_base + MDSS_MDP_REG_DISP_INTF_SEL); temp |= (ctl->intf_type << ((ctl->intf_num - MDSS_MDP_INTF0) * 8)); if (is_pingpong_split(ctl->mfd)) temp |= (ctl->intf_type << (ctl->intf_num * 8)); writel_relaxed(temp, ctl->mdata->mdp_base + MDSS_MDP_REG_DISP_INTF_SEL); mixer = ctl->mixer_left; if (mixer) { struct mdss_panel_info *pinfo = &ctl->panel_data->panel_info; mixer->params_changed++; outsize = (mixer->height << 16) | mixer->width; mdp_mixer_write(mixer, MDSS_MDP_REG_LM_OUT_SIZE, outsize); if (is_dsc_compression(pinfo)) { mdss_mdp_ctl_dsc_setup(ctl, pinfo); } else if (pinfo->compression_mode == COMPRESSION_FBC) { ret = mdss_mdp_ctl_fbc_enable(1, ctl->mixer_left, pinfo); } } return ret; } int mdss_mdp_ctl_start(struct mdss_mdp_ctl *ctl, bool handoff) { struct mdss_mdp_ctl *sctl; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); int ret = 0; pr_debug("ctl_num=%d, power_state=%d\n", ctl->num, ctl->power_state); if (mdss_mdp_ctl_is_power_on_interactive(ctl) && !(ctl->pending_mode_switch)) { pr_debug("%d: panel already on!\n", __LINE__); return 0; } ret = mdss_mdp_ctl_setup(ctl); if (ret) return ret; sctl = mdss_mdp_get_split_ctl(ctl); mutex_lock(&ctl->lock); if (mdss_mdp_ctl_is_power_off(ctl)) memset(&ctl->cur_perf, 0, sizeof(ctl->cur_perf)); /* * keep power_on false during handoff to avoid unexpected * operations to overlay. */ if (!handoff || ctl->pending_mode_switch) ctl->power_state = MDSS_PANEL_POWER_ON; mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); ret = mdss_mdp_ctl_start_sub(ctl, handoff); if (ret == 0) { if (sctl && ctl->mfd && ctl->mfd->split_mode == MDP_DUAL_LM_DUAL_DISPLAY) { /*split display available */ ret = mdss_mdp_ctl_start_sub(sctl, handoff); if (!ret) mdss_mdp_ctl_split_display_enable(1, ctl, sctl); } else if (ctl->mixer_right) { struct mdss_mdp_mixer *mixer = ctl->mixer_right; u32 out; mixer->params_changed++; out = (mixer->height << 16) | mixer->width; mdp_mixer_write(mixer, MDSS_MDP_REG_LM_OUT_SIZE, out); mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_PACK_3D, 0); } else if (is_pingpong_split(ctl->mfd)) { ctl->slave_intf_num = (ctl->intf_num + 1); mdss_mdp_ctl_pp_split_display_enable(true, ctl); } } mdss_mdp_hist_intr_setup(&mdata->hist_intr, MDSS_IRQ_RESUME); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); mutex_unlock(&ctl->lock); return ret; } int mdss_mdp_ctl_stop(struct mdss_mdp_ctl *ctl, int power_state) { struct mdss_mdp_ctl *sctl; int ret = 0; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); pr_debug("ctl_num=%d, power_state=%d\n", ctl->num, ctl->power_state); if (!mdss_mdp_ctl_is_power_on(ctl)) { pr_debug("%s %d already off!\n", __func__, __LINE__); return 0; } sctl = mdss_mdp_get_split_ctl(ctl); mutex_lock(&ctl->lock); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); mdss_mdp_hist_intr_setup(&mdata->hist_intr, MDSS_IRQ_SUSPEND); if (ctl->ops.stop_fnc) { ret = ctl->ops.stop_fnc(ctl, power_state); mdss_mdp_ctl_fbc_enable(0, ctl->mixer_left, &ctl->panel_data->panel_info); } else { pr_warn("no stop func for ctl=%d\n", ctl->num); } if (sctl && sctl->ops.stop_fnc) { ret = sctl->ops.stop_fnc(sctl, power_state); mdss_mdp_ctl_fbc_enable(0, sctl->mixer_left, &sctl->panel_data->panel_info); } if (ret) { pr_warn("error powering off intf ctl=%d\n", ctl->num); goto end; } if (mdss_panel_is_power_on(power_state)) { pr_debug("panel is not off, leaving ctl power on\n"); goto end; } if (sctl) mdss_mdp_ctl_split_display_enable(0, ctl, sctl); mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_TOP, 0); if (sctl) { mdss_mdp_ctl_write(sctl, MDSS_MDP_REG_CTL_TOP, 0); __mdss_mdp_reset_mixercfg(sctl); } __mdss_mdp_reset_mixercfg(ctl); ctl->play_cnt = 0; end: if (!ret) { ctl->power_state = power_state; if (!ctl->pending_mode_switch) mdss_mdp_ctl_perf_update(ctl, 0, true); } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); mutex_unlock(&ctl->lock); return ret; } /* * mdss_mdp_pipe_reset() - Halts all the pipes during ctl reset. * @mixer: Mixer from which to reset all pipes. * This function called during control path reset and will halt * all the pipes staged on the mixer. */ static void mdss_mdp_pipe_reset(struct mdss_mdp_mixer *mixer, bool is_recovery) { unsigned long pipe_map = mixer->pipe_mapped; u32 bit = 0; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); bool sw_rst_avail = mdss_mdp_pipe_is_sw_reset_available(mdata); pr_debug("pipe_map=0x%lx\n", pipe_map); for_each_set_bit_from(bit, &pipe_map, MAX_PIPES_PER_LM) { struct mdss_mdp_pipe *pipe; pipe = mdss_mdp_pipe_search(mdata, 1 << bit); if (pipe) { mdss_mdp_pipe_fetch_halt(pipe, is_recovery); if (sw_rst_avail) mdss_mdp_pipe_clk_force_off(pipe); } } } static u32 mdss_mdp_poll_ctl_reset_status(struct mdss_mdp_ctl *ctl, u32 cnt) { u32 status; /* * it takes around 30us to have mdp finish resetting its ctl path * poll every 50us so that reset should be completed at 1st poll */ do { udelay(50); status = mdss_mdp_ctl_read(ctl, MDSS_MDP_REG_CTL_SW_RESET); status &= 0x01; pr_debug("status=%x, count=%d\n", status, cnt); cnt--; } while (cnt > 0 && status); return status; } /* * mdss_mdp_check_ctl_reset_status() - checks ctl reset status * @ctl: mdp controller * * This function checks the ctl reset status before every frame update. * If the reset bit is set, it keeps polling the status till the hw * reset is complete. And does a panic if hw fails to complet the reset * with in the max poll interval. */ void mdss_mdp_check_ctl_reset_status(struct mdss_mdp_ctl *ctl) { u32 status; if (!ctl) return; status = mdss_mdp_ctl_read(ctl, MDSS_MDP_REG_CTL_SW_RESET); status &= 0x01; if (!status) return; pr_debug("hw ctl reset is set for ctl:%d\n", ctl->num); status = mdss_mdp_poll_ctl_reset_status(ctl, 5); if (status) { pr_err("hw recovery is not complete for ctl:%d\n", ctl->num); MDSS_XLOG_TOUT_HANDLER("mdp", "vbif", "vbif_nrt", "dbg_bus", "vbif_dbg_bus", "panic"); } } /* * mdss_mdp_ctl_reset() - reset mdp ctl path. * @ctl: mdp controller. * this function called when underflow happen, * it will reset mdp ctl path and poll for its completion * * Note: called within atomic context. */ int mdss_mdp_ctl_reset(struct mdss_mdp_ctl *ctl, bool is_recovery) { u32 status; struct mdss_mdp_mixer *mixer; if (!ctl) { pr_err("ctl not initialized\n"); return -EINVAL; } mixer = ctl->mixer_left; mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_SW_RESET, 1); status = mdss_mdp_poll_ctl_reset_status(ctl, 20); if (status) pr_err("sw ctl:%d reset timedout\n", ctl->num); if (mixer) { mdss_mdp_pipe_reset(mixer, is_recovery); if (is_dual_lm_single_display(ctl->mfd)) mdss_mdp_pipe_reset(ctl->mixer_right, is_recovery); } return (status) ? -EAGAIN : 0; } /* * mdss_mdp_mixer_update_pipe_map() - keep track of pipe configuration in mixer * @master_ctl: mdp controller. * * This function keeps track of the current mixer configuration in the hardware. * It's callers responsibility to call with master control. */ void mdss_mdp_mixer_update_pipe_map(struct mdss_mdp_ctl *master_ctl, int mixer_mux) { struct mdss_mdp_mixer *mixer = mdss_mdp_mixer_get(master_ctl, mixer_mux); if (!mixer) return; pr_debug("mixer%d pipe_mapped=0x%x next_pipes=0x%x\n", mixer->num, mixer->pipe_mapped, mixer->next_pipe_map); mixer->pipe_mapped = mixer->next_pipe_map; } static void mdss_mdp_set_mixer_roi(struct mdss_mdp_mixer *mixer, struct mdss_rect *roi) { mixer->valid_roi = (roi->w && roi->h); mixer->roi_changed = false; if (!mdss_rect_cmp(roi, &mixer->roi)) { mixer->roi = *roi; mixer->params_changed++; mixer->roi_changed = true; } pr_debug("mixer%d ROI %s: [%d, %d, %d, %d]\n", mixer->num, mixer->roi_changed ? "changed" : "not changed", mixer->roi.x, mixer->roi.y, mixer->roi.w, mixer->roi.h); MDSS_XLOG(mixer->num, mixer->roi_changed, mixer->valid_roi, mixer->roi.x, mixer->roi.y, mixer->roi.w, mixer->roi.h); } /* only call from master ctl */ void mdss_mdp_set_roi(struct mdss_mdp_ctl *ctl, struct mdss_rect *l_roi, struct mdss_rect *r_roi) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); enum mdss_mdp_pu_type previous_frame_pu_type, current_frame_pu_type; /* Reset ROI when we have (1) invalid ROI (2) feature disabled */ if ((!l_roi->w && l_roi->h) || (l_roi->w && !l_roi->h) || (!r_roi->w && r_roi->h) || (r_roi->w && !r_roi->h) || (!l_roi->w && !l_roi->h && !r_roi->w && !r_roi->h) || !ctl->panel_data->panel_info.partial_update_enabled) { *l_roi = (struct mdss_rect) {0, 0, ctl->mixer_left->width, ctl->mixer_left->height}; if (ctl->mixer_right) { *r_roi = (struct mdss_rect) {0, 0, ctl->mixer_right->width, ctl->mixer_right->height}; } } previous_frame_pu_type = mdss_mdp_get_pu_type(ctl); mdss_mdp_set_mixer_roi(ctl->mixer_left, l_roi); ctl->roi = ctl->mixer_left->roi; if (ctl->mfd->split_mode == MDP_DUAL_LM_DUAL_DISPLAY) { struct mdss_mdp_ctl *sctl = mdss_mdp_get_split_ctl(ctl); mdss_mdp_set_mixer_roi(sctl->mixer_left, r_roi); sctl->roi = sctl->mixer_left->roi; } else if (is_dual_lm_single_display(ctl->mfd)) { mdss_mdp_set_mixer_roi(ctl->mixer_right, r_roi); /* in this case, CTL_ROI is a union of left+right ROIs. */ ctl->roi.w += ctl->mixer_right->roi.w; /* right_only, update roi.x as per CTL ROI guidelines */ if (!ctl->mixer_left->valid_roi) { ctl->roi = ctl->mixer_right->roi; ctl->roi.x = left_lm_w_from_mfd(ctl->mfd) + ctl->mixer_right->roi.x; } } current_frame_pu_type = mdss_mdp_get_pu_type(ctl); /* * Force HW programming whenever partial update type changes * between two consecutive frames to avoid incorrect HW programming. */ if (is_split_lm(ctl->mfd) && mdata->has_src_split && (previous_frame_pu_type != current_frame_pu_type)) { ctl->mixer_left->roi_changed = true; ctl->mixer_right->roi_changed = true; } } u32 mdss_mdp_get_mixer_mask(u32 pipe_num, u32 stage) { u32 mask = 0; if ((pipe_num == MDSS_MDP_SSPP_VIG3 || pipe_num == MDSS_MDP_SSPP_RGB3)) { /* Add 2 to account for Cursor & Border bits */ mask = stage << ((3 * pipe_num) + 2); } else { mask = stage << (3 * pipe_num); } return mask; } u32 mdss_mdp_get_mixer_extn_mask(u32 pipe_num, u32 stage) { u32 mask = 0; /* * The ctl layer extension bits are ordered * VIG0-3, RGB0-3, DMA0-1 */ if (pipe_num < MDSS_MDP_SSPP_RGB0) { mask = BIT(pipe_num << 1); } else if (pipe_num >= MDSS_MDP_SSPP_RGB0 && pipe_num < MDSS_MDP_SSPP_DMA0) { mask = BIT((pipe_num + 1) << 1); } else if (pipe_num >= MDSS_MDP_SSPP_DMA0 && pipe_num < MDSS_MDP_SSPP_VIG3) { mask = BIT((pipe_num + 2) << 1); } else if (pipe_num >= MDSS_MDP_SSPP_CURSOR0 && pipe_num <= MDSS_MDP_SSPP_CURSOR1) { mask = stage << (20 + (6 * (pipe_num - MDSS_MDP_SSPP_CURSOR0))); } else if (pipe_num == MDSS_MDP_SSPP_VIG3) { mask = BIT(6); } else if (pipe_num == MDSS_MDP_SSPP_RGB3) { mask = BIT(14); } return mask; } static void mdss_mdp_mixer_setup(struct mdss_mdp_ctl *master_ctl, int mixer_mux, bool lm_swap) { int i; int stage, screen_state, outsize; u32 off, blend_op, blend_stage; u32 mixercfg = 0, mixer_op_mode = 0, bg_alpha_enable = 0, mixercfg_extn = 0; u32 fg_alpha = 0, bg_alpha = 0; struct mdss_mdp_pipe *pipe; struct mdss_mdp_ctl *ctl, *ctl_hw; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdss_mdp_mixer *mixer_hw = mdss_mdp_mixer_get(master_ctl, mixer_mux); struct mdss_mdp_mixer *mixer; if (!mixer_hw) return; ctl = mixer_hw->ctl; if (!ctl) return; ctl_hw = ctl; mixer_hw->params_changed = 0; /* check if mixer setup for rotator is needed */ if (mixer_hw->rotator_mode) { __mdss_mdp_reset_mixercfg(ctl_hw); return; } if (lm_swap) { if (mixer_mux == MDSS_MDP_MIXER_MUX_RIGHT) mixer = mdss_mdp_mixer_get(master_ctl, MDSS_MDP_MIXER_MUX_LEFT); else mixer = mdss_mdp_mixer_get(master_ctl, MDSS_MDP_MIXER_MUX_RIGHT); ctl_hw = mixer->ctl; } else { mixer = mixer_hw; } /* * if lm_swap was used on MDP_DUAL_LM_DUAL_DISPLAY then we need to * reset mixercfg every frame because there might be a stale value * in mixerfcfg register. */ if ((ctl->mfd->split_mode == MDP_DUAL_LM_DUAL_DISPLAY) && is_dsc_compression(&ctl->panel_data->panel_info) && ctl->panel_data->panel_info.partial_update_enabled && mdss_has_quirk(mdata, MDSS_QUIRK_DSC_RIGHT_ONLY_PU)) __mdss_mdp_reset_mixercfg(ctl_hw); if (!mixer->valid_roi) { /* * resetting mixer config is specifically needed when split * mode is MDP_DUAL_LM_SINGLE_DISPLAY but update is only on * one side. */ off = __mdss_mdp_ctl_get_mixer_off(mixer_hw); mdss_mdp_ctl_write(ctl_hw, off, 0); /* Program ctl layer extension bits */ off = __mdss_mdp_ctl_get_mixer_extn_off(mixer_hw); mdss_mdp_ctl_write(ctl_hw, off, 0); MDSS_XLOG(mixer->num, mixer_hw->num, XLOG_FUNC_EXIT); return; } trace_mdp_mixer_update(mixer_hw->num); pr_debug("setup mixer=%d hw=%d\n", mixer->num, mixer_hw->num); screen_state = ctl->force_screen_state; outsize = (mixer->roi.h << 16) | mixer->roi.w; mdp_mixer_write(mixer_hw, MDSS_MDP_REG_LM_OUT_SIZE, outsize); if (screen_state == MDSS_SCREEN_FORCE_BLANK) { mixercfg = MDSS_MDP_LM_BORDER_COLOR; goto update_mixer; } pipe = mixer->stage_pipe[MDSS_MDP_STAGE_BASE * MAX_PIPES_PER_STAGE]; if (pipe == NULL) { mixercfg = MDSS_MDP_LM_BORDER_COLOR; } else { if (pipe->type == MDSS_MDP_PIPE_TYPE_CURSOR) mixercfg_extn |= mdss_mdp_get_mixer_extn_mask( pipe->num, 1); else mixercfg |= mdss_mdp_get_mixer_mask(pipe->num, 1); if (pipe->src_fmt->alpha_enable) bg_alpha_enable = 1; } i = MDSS_MDP_STAGE_0 * MAX_PIPES_PER_STAGE; for (; i < MAX_PIPES_PER_LM; i++) { pipe = mixer->stage_pipe[i]; if (pipe == NULL) continue; stage = i / MAX_PIPES_PER_STAGE; if (stage != pipe->mixer_stage) { pr_err("pipe%d mixer:%d mixer:%d stage mismatch. pipe->mixer_stage=%d, mixer->stage_pipe=%d. skip staging it\n", pipe->num, mixer->num, mixer->num, pipe->mixer_stage, stage); mixer->stage_pipe[i] = NULL; continue; } /* * pipe which is staged on both LMs will be tracked through * left mixer only. */ if (!pipe->src_split_req || !mixer->is_right_mixer) mixer->next_pipe_map |= pipe->ndx; blend_stage = stage - MDSS_MDP_STAGE_0; off = MDSS_MDP_REG_LM_BLEND_OFFSET(blend_stage); /* * Account for additional blending stages * from MDP v1.5 onwards */ if (blend_stage > 3) off += MDSS_MDP_REG_LM_BLEND_STAGE4; blend_op = (MDSS_MDP_BLEND_FG_ALPHA_FG_CONST | MDSS_MDP_BLEND_BG_ALPHA_BG_CONST); fg_alpha = pipe->alpha; bg_alpha = 0xFF - pipe->alpha; /* keep fg alpha */ mixer_op_mode |= 1 << (blend_stage + 1); switch (pipe->blend_op) { case BLEND_OP_OPAQUE: blend_op = (MDSS_MDP_BLEND_FG_ALPHA_FG_CONST | MDSS_MDP_BLEND_BG_ALPHA_BG_CONST); pr_debug("pnum=%d stg=%d op=OPAQUE\n", pipe->num, stage); break; case BLEND_OP_PREMULTIPLIED: if (pipe->src_fmt->alpha_enable) { blend_op = (MDSS_MDP_BLEND_FG_ALPHA_FG_CONST | MDSS_MDP_BLEND_BG_ALPHA_FG_PIXEL); if (fg_alpha != 0xff) { bg_alpha = fg_alpha; blend_op |= MDSS_MDP_BLEND_BG_MOD_ALPHA | MDSS_MDP_BLEND_BG_INV_MOD_ALPHA; } else { blend_op |= MDSS_MDP_BLEND_BG_INV_ALPHA; } } pr_debug("pnum=%d stg=%d op=PREMULTIPLIED\n", pipe->num, stage); break; case BLEND_OP_COVERAGE: if (pipe->src_fmt->alpha_enable) { blend_op = (MDSS_MDP_BLEND_FG_ALPHA_FG_PIXEL | MDSS_MDP_BLEND_BG_ALPHA_FG_PIXEL); if (fg_alpha != 0xff) { bg_alpha = fg_alpha; blend_op |= MDSS_MDP_BLEND_FG_MOD_ALPHA | MDSS_MDP_BLEND_FG_INV_MOD_ALPHA | MDSS_MDP_BLEND_BG_MOD_ALPHA | MDSS_MDP_BLEND_BG_INV_MOD_ALPHA; } else { blend_op |= MDSS_MDP_BLEND_BG_INV_ALPHA; } } pr_debug("pnum=%d stg=%d op=COVERAGE\n", pipe->num, stage); break; default: blend_op = (MDSS_MDP_BLEND_FG_ALPHA_FG_CONST | MDSS_MDP_BLEND_BG_ALPHA_BG_CONST); pr_debug("pnum=%d stg=%d op=NONE\n", pipe->num, stage); break; } if (!pipe->src_fmt->alpha_enable && bg_alpha_enable) mixer_op_mode = 0; if ((stage < MDSS_MDP_STAGE_6) && (pipe->type != MDSS_MDP_PIPE_TYPE_CURSOR)) mixercfg |= mdss_mdp_get_mixer_mask(pipe->num, stage); else mixercfg_extn |= mdss_mdp_get_mixer_extn_mask( pipe->num, stage); trace_mdp_sspp_change(pipe); pr_debug("stg=%d op=%x fg_alpha=%x bg_alpha=%x\n", stage, blend_op, fg_alpha, bg_alpha); mdp_mixer_write(mixer_hw, off + MDSS_MDP_REG_LM_OP_MODE, blend_op); mdp_mixer_write(mixer_hw, off + MDSS_MDP_REG_LM_BLEND_FG_ALPHA, fg_alpha); mdp_mixer_write(mixer_hw, off + MDSS_MDP_REG_LM_BLEND_BG_ALPHA, bg_alpha); } if (mixer->cursor_enabled) mixercfg |= MDSS_MDP_LM_CURSOR_OUT; update_mixer: if (mixer_hw->num == MDSS_MDP_INTF_LAYERMIXER3) ctl_hw->flush_bits |= BIT(20); else if (mixer_hw->type == MDSS_MDP_MIXER_TYPE_WRITEBACK) ctl_hw->flush_bits |= BIT(9) << mixer_hw->num; else ctl_hw->flush_bits |= BIT(6) << mixer_hw->num; /* Read GC enable/disable status on LM */ mixer_op_mode |= (mdp_mixer_read(mixer_hw, MDSS_MDP_REG_LM_OP_MODE) & BIT(0)); if (mixer->src_split_req && mixer_mux == MDSS_MDP_MIXER_MUX_RIGHT) mixer_op_mode |= BIT(31); mdp_mixer_write(mixer_hw, MDSS_MDP_REG_LM_OP_MODE, mixer_op_mode); mdp_mixer_write(mixer_hw, MDSS_MDP_REG_LM_BORDER_COLOR_0, (mdata->bcolor0 & 0xFFF) | ((mdata->bcolor1 & 0xFFF) << 16)); mdp_mixer_write(mixer_hw, MDSS_MDP_REG_LM_BORDER_COLOR_1, mdata->bcolor2 & 0xFFF); off = __mdss_mdp_ctl_get_mixer_off(mixer_hw); mdss_mdp_ctl_write(ctl_hw, off, mixercfg); /* Program ctl layer extension bits */ off = __mdss_mdp_ctl_get_mixer_extn_off(mixer_hw); mdss_mdp_ctl_write(ctl_hw, off, mixercfg_extn); pr_debug("mixer=%d hw=%d cfg=0%08x cfg_extn=0x%08x op_mode=0x%08x w=%d h=%d bc0=0x%x bc1=0x%x\n", mixer->num, mixer_hw->num, mixercfg, mixercfg_extn, mixer_op_mode, mixer->roi.w, mixer->roi.h, (mdata->bcolor0 & 0xFFF) | ((mdata->bcolor1 & 0xFFF) << 16), mdata->bcolor2 & 0xFFF); MDSS_XLOG(mixer->num, mixer_hw->num, mixercfg, mixercfg_extn, mixer_op_mode, mixer->roi.h, mixer->roi.w); } int mdss_mdp_mixer_addr_setup(struct mdss_data_type *mdata, u32 *mixer_offsets, u32 *dspp_offsets, u32 *pingpong_offsets, u32 type, u32 len) { struct mdss_mdp_mixer *head; u32 i; int rc = 0; u32 size = len; if ((type == MDSS_MDP_MIXER_TYPE_WRITEBACK) && (mdata->wfd_mode == MDSS_MDP_WFD_SHARED)) size++; head = devm_kzalloc(&mdata->pdev->dev, sizeof(struct mdss_mdp_mixer) * size, GFP_KERNEL); if (!head) { pr_err("unable to setup mixer type=%d :kzalloc fail\n", type); return -ENOMEM; } for (i = 0; i < len; i++) { head[i].type = type; head[i].base = mdata->mdss_io.base + mixer_offsets[i]; head[i].ref_cnt = 0; head[i].num = i; if (type == MDSS_MDP_MIXER_TYPE_INTF && dspp_offsets && pingpong_offsets) { if (mdata->ndspp > i) head[i].dspp_base = mdata->mdss_io.base + dspp_offsets[i]; head[i].pingpong_base = mdata->mdss_io.base + pingpong_offsets[i]; } } /* * Duplicate the last writeback mixer for concurrent line and block mode * operations */ if ((type == MDSS_MDP_MIXER_TYPE_WRITEBACK) && (mdata->wfd_mode == MDSS_MDP_WFD_SHARED)) head[len] = head[len - 1]; switch (type) { case MDSS_MDP_MIXER_TYPE_INTF: mdata->mixer_intf = head; break; case MDSS_MDP_MIXER_TYPE_WRITEBACK: mdata->mixer_wb = head; break; default: pr_err("Invalid mixer type=%d\n", type); rc = -EINVAL; break; } return rc; } int mdss_mdp_ctl_addr_setup(struct mdss_data_type *mdata, u32 *ctl_offsets, u32 len) { struct mdss_mdp_ctl *head; struct mutex *shared_lock = NULL; u32 i; u32 size = len; if (mdata->wfd_mode == MDSS_MDP_WFD_SHARED) { size++; shared_lock = devm_kzalloc(&mdata->pdev->dev, sizeof(struct mutex), GFP_KERNEL); if (!shared_lock) { pr_err("unable to allocate mem for mutex\n"); return -ENOMEM; } mutex_init(shared_lock); } head = devm_kzalloc(&mdata->pdev->dev, sizeof(struct mdss_mdp_ctl) * size, GFP_KERNEL); if (!head) { pr_err("unable to setup ctl and wb: kzalloc fail\n"); return -ENOMEM; } for (i = 0; i < len; i++) { head[i].num = i; head[i].base = (mdata->mdss_io.base) + ctl_offsets[i]; head[i].ref_cnt = 0; } if (mdata->wfd_mode == MDSS_MDP_WFD_SHARED) { head[len - 1].shared_lock = shared_lock; /* * Allocate a virtual ctl to be able to perform simultaneous * line mode and block mode operations on the same * writeback block */ head[len] = head[len - 1]; head[len].num = head[len - 1].num; } mdata->ctl_off = head; return 0; } int mdss_mdp_wb_addr_setup(struct mdss_data_type *mdata, u32 num_block_wb, u32 num_intf_wb) { struct mdss_mdp_writeback *wb; u32 total, i; total = num_block_wb + num_intf_wb; wb = devm_kzalloc(&mdata->pdev->dev, sizeof(struct mdss_mdp_writeback) * total, GFP_KERNEL); if (!wb) { pr_err("unable to setup wb: kzalloc fail\n"); return -ENOMEM; } for (i = 0; i < total; i++) { wb[i].num = i; if (i < num_block_wb) { wb[i].caps = MDSS_MDP_WB_ROTATOR | MDSS_MDP_WB_WFD; if (mdss_mdp_is_ubwc_supported(mdata)) wb[i].caps |= MDSS_MDP_WB_UBWC; } else { wb[i].caps = MDSS_MDP_WB_WFD | MDSS_MDP_WB_INTF; } } mdata->wb = wb; mdata->nwb = total; mutex_init(&mdata->wb_lock); return 0; } struct mdss_mdp_mixer *mdss_mdp_mixer_get(struct mdss_mdp_ctl *ctl, int mux) { struct mdss_mdp_mixer *mixer = NULL; if (!ctl) { pr_err("ctl not initialized\n"); return NULL; } switch (mux) { case MDSS_MDP_MIXER_MUX_DEFAULT: case MDSS_MDP_MIXER_MUX_LEFT: mixer = ctl->mixer_left; break; case MDSS_MDP_MIXER_MUX_RIGHT: mixer = ctl->mixer_right; break; } return mixer; } struct mdss_mdp_pipe *mdss_mdp_get_staged_pipe(struct mdss_mdp_ctl *ctl, int mux, int stage, bool is_right_blend) { struct mdss_mdp_pipe *pipe = NULL; struct mdss_mdp_mixer *mixer; int index = (stage * MAX_PIPES_PER_STAGE) + (int)is_right_blend; if (!ctl) return NULL; BUG_ON(index > MAX_PIPES_PER_LM); mixer = mdss_mdp_mixer_get(ctl, mux); if (mixer && (index < MAX_PIPES_PER_LM)) pipe = mixer->stage_pipe[index]; pr_debug("%pS index=%d pipe%d\n", __builtin_return_address(0), index, pipe ? pipe->num : -1); return pipe; } int mdss_mdp_get_pipe_flush_bits(struct mdss_mdp_pipe *pipe) { u32 flush_bits; if (pipe->type == MDSS_MDP_PIPE_TYPE_DMA) flush_bits |= BIT(pipe->num) << 5; else if (pipe->num == MDSS_MDP_SSPP_VIG3 || pipe->num == MDSS_MDP_SSPP_RGB3) flush_bits |= BIT(pipe->num) << 10; else if (pipe->type == MDSS_MDP_PIPE_TYPE_CURSOR) flush_bits |= BIT(22 + pipe->num - MDSS_MDP_SSPP_CURSOR0); else /* RGB/VIG 0-2 pipes */ flush_bits |= BIT(pipe->num); return flush_bits; } int mdss_mdp_async_ctl_flush(struct msm_fb_data_type *mfd, u32 flush_bits) { struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); struct mdss_mdp_ctl *ctl = mdp5_data->ctl; struct mdss_mdp_ctl *sctl = mdss_mdp_get_split_ctl(ctl); int ret = 0; mutex_lock(&ctl->flush_lock); mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_FLUSH, flush_bits); if ((!ctl->split_flush_en) && sctl) mdss_mdp_ctl_write(sctl, MDSS_MDP_REG_CTL_FLUSH, flush_bits); mutex_unlock(&ctl->flush_lock); return ret; } int mdss_mdp_mixer_pipe_update(struct mdss_mdp_pipe *pipe, struct mdss_mdp_mixer *mixer, int params_changed) { struct mdss_mdp_ctl *ctl; int i, j, k; if (!pipe) return -EINVAL; if (!mixer) return -EINVAL; ctl = mixer->ctl; if (!ctl) return -EINVAL; if (pipe->mixer_stage >= MDSS_MDP_MAX_STAGE) { pr_err("invalid mixer stage\n"); return -EINVAL; } pr_debug("pnum=%x mixer=%d stage=%d\n", pipe->num, mixer->num, pipe->mixer_stage); mutex_lock(&ctl->flush_lock); if (params_changed) { mixer->params_changed++; for (i = MDSS_MDP_STAGE_UNUSED; i < MDSS_MDP_MAX_STAGE; i++) { j = i * MAX_PIPES_PER_STAGE; /* * 1. If pipe is on the right side of the blending * stage, on either left LM or right LM but it is not * crossing LM boundry then right_blend ndx is used. * 2. If pipe is on the right side of the blending * stage on left LM and it is crossing LM boundry * then for left LM it is placed into right_blend * index but for right LM it still placed into * left_blend index. */ if (pipe->is_right_blend && (!pipe->src_split_req || (pipe->src_split_req && !mixer->is_right_mixer))) j++; /* First clear all blend containers for current stage */ for (k = 0; k < MAX_PIPES_PER_STAGE; k++) { u32 ndx = (i * MAX_PIPES_PER_STAGE) + k; if (mixer->stage_pipe[ndx] == pipe) mixer->stage_pipe[ndx] = NULL; } /* then stage actual pipe on specific blend container */ if (i == pipe->mixer_stage) mixer->stage_pipe[j] = pipe; } } ctl->flush_bits |= mdss_mdp_get_pipe_flush_bits(pipe); mutex_unlock(&ctl->flush_lock); return 0; } /** * mdss_mdp_mixer_unstage_all() - Unstage all pipes from mixer * @mixer: Mixer from which to unstage all pipes * * Unstage any pipes that are currently attached to mixer. * * NOTE: this will not update the pipe structure, and thus a full * deinitialization or reconfiguration of all pipes is expected after this call. */ void mdss_mdp_mixer_unstage_all(struct mdss_mdp_mixer *mixer) { struct mdss_mdp_pipe *tmp; int i; if (!mixer) return; for (i = 0; i < MAX_PIPES_PER_LM; i++) { tmp = mixer->stage_pipe[i]; if (tmp) { mixer->stage_pipe[i] = NULL; mixer->params_changed++; tmp->params_changed++; } } } int mdss_mdp_mixer_pipe_unstage(struct mdss_mdp_pipe *pipe, struct mdss_mdp_mixer *mixer) { int index; u8 right_blend_index; if (!pipe) return -EINVAL; if (!mixer) return -EINVAL; right_blend_index = pipe->is_right_blend && !(pipe->src_split_req && mixer->is_right_mixer); index = (pipe->mixer_stage * MAX_PIPES_PER_STAGE) + right_blend_index; if (index < MAX_PIPES_PER_LM && pipe == mixer->stage_pipe[index]) { pr_debug("unstage p%d from %s side of stage=%d lm=%d ndx=%d\n", pipe->num, pipe->is_right_blend ? "right" : "left", pipe->mixer_stage, mixer->num, index); mixer->params_changed++; mixer->stage_pipe[index] = NULL; } return 0; } int mdss_mdp_ctl_update_fps(struct mdss_mdp_ctl *ctl) { struct mdss_panel_info *pinfo; struct mdss_overlay_private *mdp5_data; int ret = 0; int new_fps; if (!ctl->panel_data || !ctl->mfd) return -ENODEV; pinfo = &ctl->panel_data->panel_info; if (!pinfo->dynamic_fps || !ctl->ops.config_fps_fnc) return 0; if (!pinfo->default_fps) { /* we haven't got any call to update the fps */ return 0; } mdp5_data = mfd_to_mdp5_data(ctl->mfd); if (!mdp5_data) return -ENODEV; /* * Panel info is already updated with the new fps info, * so we need to lock the data to make sure the panel info * is not updated while we reconfigure the HW. */ mutex_lock(&mdp5_data->dfps_lock); if ((pinfo->dfps_update == DFPS_IMMEDIATE_PORCH_UPDATE_MODE_VFP) || (pinfo->dfps_update == DFPS_IMMEDIATE_PORCH_UPDATE_MODE_HFP) || pinfo->dfps_update == DFPS_IMMEDIATE_CLK_UPDATE_MODE) { new_fps = mdss_panel_get_framerate(pinfo); } else { new_fps = pinfo->new_fps; } pr_debug("fps new:%d old:%d\n", new_fps, pinfo->current_fps); if (new_fps == pinfo->current_fps) { pr_debug("FPS is already %d\n", new_fps); ret = 0; goto exit; } ret = ctl->ops.config_fps_fnc(ctl, new_fps); if (!ret) pr_debug("fps set to %d\n", new_fps); else pr_err("Failed to configure %d fps rc=%d\n", new_fps, ret); exit: mutex_unlock(&mdp5_data->dfps_lock); return ret; } int mdss_mdp_display_wakeup_time(struct mdss_mdp_ctl *ctl, ktime_t *wakeup_time) { struct mdss_panel_info *pinfo; u32 clk_rate, clk_period; u32 current_line, total_line; u32 time_of_line, time_to_vsync, adjust_line_ns; ktime_t current_time = ktime_get(); if (!ctl->ops.read_line_cnt_fnc) return -ENOSYS; pinfo = &ctl->panel_data->panel_info; if (!pinfo) return -ENODEV; clk_rate = mdss_mdp_get_pclk_rate(ctl); clk_rate /= 1000; /* in kHz */ if (!clk_rate) return -EINVAL; /* * calculate clk_period as pico second to maintain good * accuracy with high pclk rate and this number is in 17 bit * range. */ clk_period = 1000000000 / clk_rate; if (!clk_period) return -EINVAL; time_of_line = (pinfo->lcdc.h_back_porch + pinfo->lcdc.h_front_porch + pinfo->lcdc.h_pulse_width + pinfo->xres) * clk_period; time_of_line /= 1000; /* in nano second */ if (!time_of_line) return -EINVAL; current_line = ctl->ops.read_line_cnt_fnc(ctl); total_line = pinfo->lcdc.v_back_porch + pinfo->lcdc.v_front_porch + pinfo->lcdc.v_pulse_width + pinfo->yres; if (current_line > total_line) return -EINVAL; time_to_vsync = time_of_line * (total_line - current_line); if (pinfo->adjust_timer_delay_ms) { adjust_line_ns = pinfo->adjust_timer_delay_ms * 1000000; /* convert to ns */ /* Ignore large values of adjust_line_ns\ */ if (time_to_vsync > adjust_line_ns) time_to_vsync -= adjust_line_ns; } if (!time_to_vsync) return -EINVAL; *wakeup_time = ktime_add_ns(current_time, time_to_vsync); pr_debug("clk_rate=%dkHz clk_period=%d cur_line=%d tot_line=%d\n", clk_rate, clk_period, current_line, total_line); pr_debug("time_to_vsync=%d current_time=%d wakeup_time=%d\n", time_to_vsync, (int)ktime_to_ms(current_time), (int)ktime_to_ms(*wakeup_time)); return 0; } int mdss_mdp_display_wait4comp(struct mdss_mdp_ctl *ctl) { int ret; u32 reg_data, flush_data; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (!ctl) { pr_err("invalid ctl\n"); return -ENODEV; } ret = mutex_lock_interruptible(&ctl->lock); if (ret) return ret; if (!mdss_mdp_ctl_is_power_on(ctl)) { mutex_unlock(&ctl->lock); return 0; } ATRACE_BEGIN("wait_fnc"); if (ctl->ops.wait_fnc) ret = ctl->ops.wait_fnc(ctl, NULL); ATRACE_END("wait_fnc"); trace_mdp_commit(ctl); mdss_mdp_ctl_perf_update(ctl, 0, false); mdata->bw_limit_pending = false; if (IS_MDSS_MAJOR_MINOR_SAME(mdata->mdp_rev, MDSS_MDP_HW_REV_103)) { mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); reg_data = mdss_mdp_ctl_read(ctl, MDSS_MDP_REG_CTL_FLUSH); flush_data = readl_relaxed(mdata->mdp_base + AHB_CLK_OFFSET); if ((flush_data & BIT(28)) && !(ctl->flush_reg_data & reg_data)) { flush_data &= ~(BIT(28)); writel_relaxed(flush_data, mdata->mdp_base + AHB_CLK_OFFSET); ctl->flush_reg_data = 0; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); } mutex_unlock(&ctl->lock); return ret; } int mdss_mdp_display_wait4pingpong(struct mdss_mdp_ctl *ctl, bool use_lock) { struct mdss_mdp_ctl *sctl = NULL; int ret; bool recovery_needed = false; if (use_lock) { ret = mutex_lock_interruptible(&ctl->lock); if (ret) return ret; } if (!mdss_mdp_ctl_is_power_on(ctl) || !ctl->ops.wait_pingpong) { if (use_lock) mutex_unlock(&ctl->lock); return 0; } ATRACE_BEGIN("wait_pingpong"); ret = ctl->ops.wait_pingpong(ctl, NULL); ATRACE_END("wait_pingpong"); if (ret) recovery_needed = true; sctl = mdss_mdp_get_split_ctl(ctl); if (sctl && sctl->ops.wait_pingpong) { ATRACE_BEGIN("wait_pingpong sctl"); ret = sctl->ops.wait_pingpong(sctl, NULL); ATRACE_END("wait_pingpong sctl"); if (ret) recovery_needed = true; } ctl->mdata->bw_limit_pending = false; if (recovery_needed) { mdss_mdp_ctl_reset(ctl, true); if (sctl) mdss_mdp_ctl_reset(sctl, true); mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_DSI_RESET_WRITE_PTR, NULL, CTL_INTF_EVENT_FLAG_DEFAULT); pr_debug("pingpong timeout recovery finished\n"); } if (use_lock) mutex_unlock(&ctl->lock); return ret; } static void mdss_mdp_force_border_color(struct mdss_mdp_ctl *ctl) { struct mdss_mdp_ctl *sctl = mdss_mdp_get_split_ctl(ctl); struct mdss_data_type *mdata = mdss_mdp_get_mdata(); bool lm_swap = mdss_mdp_is_lm_swap_needed(mdata, ctl); ctl->force_screen_state = MDSS_SCREEN_FORCE_BLANK; if (sctl) sctl->force_screen_state = MDSS_SCREEN_FORCE_BLANK; mdss_mdp_mixer_setup(ctl, MDSS_MDP_MIXER_MUX_LEFT, lm_swap); mdss_mdp_mixer_setup(ctl, MDSS_MDP_MIXER_MUX_RIGHT, lm_swap); ctl->force_screen_state = MDSS_SCREEN_DEFAULT; if (sctl) sctl->force_screen_state = MDSS_SCREEN_DEFAULT; /* * Update the params changed for mixer for the next frame to * configure the mixer setup properly. */ if (ctl->mixer_left) ctl->mixer_left->params_changed++; if (ctl->mixer_right) ctl->mixer_right->params_changed++; } int mdss_mdp_display_commit(struct mdss_mdp_ctl *ctl, void *arg, struct mdss_mdp_commit_cb *commit_cb) { struct mdss_mdp_ctl *sctl = NULL; int ret = 0; bool is_bw_released, split_lm_valid; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); u32 ctl_flush_bits = 0, sctl_flush_bits = 0; if (!ctl) { pr_err("display function not set\n"); return -ENODEV; } mutex_lock(&ctl->lock); pr_debug("commit ctl=%d play_cnt=%d\n", ctl->num, ctl->play_cnt); if (!mdss_mdp_ctl_is_power_on(ctl)) { mutex_unlock(&ctl->lock); return 0; } split_lm_valid = mdss_mdp_is_both_lm_valid(ctl); sctl = mdss_mdp_get_split_ctl(ctl); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); mutex_lock(&ctl->flush_lock); /* * We could have released the bandwidth if there were no transactions * pending, so we want to re-calculate the bandwidth in this situation */ is_bw_released = !mdss_mdp_ctl_perf_get_transaction_status(ctl); if (is_bw_released) { if (sctl) is_bw_released = !mdss_mdp_ctl_perf_get_transaction_status(sctl); } /* * left update on any topology or * any update on MDP_DUAL_LM_SINGLE_DISPLAY topology. */ if (ctl->mixer_left->valid_roi || (is_dual_lm_single_display(ctl->mfd) && ctl->mixer_right->valid_roi)) mdss_mdp_ctl_perf_set_transaction_status(ctl, PERF_SW_COMMIT_STATE, PERF_STATUS_BUSY); /* right update on MDP_DUAL_LM_DUAL_DISPLAY */ if (sctl && sctl->mixer_left->valid_roi) mdss_mdp_ctl_perf_set_transaction_status(sctl, PERF_SW_COMMIT_STATE, PERF_STATUS_BUSY); if (ctl->mixer_right) ctl->mixer_right->src_split_req = mdata->has_src_split && split_lm_valid; if (is_bw_released || ctl->force_screen_state || (ctl->mixer_left->params_changed) || (ctl->mixer_right && ctl->mixer_right->params_changed)) { bool lm_swap = mdss_mdp_is_lm_swap_needed(mdata, ctl); ATRACE_BEGIN("prepare_fnc"); if (ctl->ops.prepare_fnc) ret = ctl->ops.prepare_fnc(ctl, arg); ATRACE_END("prepare_fnc"); if (ret) { pr_err("error preparing display\n"); mutex_unlock(&ctl->flush_lock); goto done; } ATRACE_BEGIN("mixer_programming"); mdss_mdp_ctl_perf_update(ctl, 1, false); mdss_mdp_mixer_setup(ctl, MDSS_MDP_MIXER_MUX_LEFT, lm_swap); mdss_mdp_mixer_setup(ctl, MDSS_MDP_MIXER_MUX_RIGHT, lm_swap); mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_TOP, ctl->opmode); ctl->flush_bits |= BIT(17); /* CTL */ if (sctl) { mdss_mdp_ctl_write(sctl, MDSS_MDP_REG_CTL_TOP, sctl->opmode); sctl->flush_bits |= BIT(17); sctl_flush_bits = sctl->flush_bits; } ATRACE_END("mixer_programming"); } /* * With partial frame update, enable split display bit only * when validity of ROI's on both the DSI's are identical. */ if (sctl) mdss_mdp_ctl_split_display_enable(split_lm_valid, ctl, sctl); ATRACE_BEGIN("postproc_programming"); if (ctl->mfd && ctl->mfd->dcm_state != DTM_ENTER) /* postprocessing setup, including dspp */ mdss_mdp_pp_setup_locked(ctl); if (sctl) { if (ctl->split_flush_en) { ctl->flush_bits |= sctl->flush_bits; sctl->flush_bits = 0; sctl_flush_bits = 0; } else { sctl_flush_bits = sctl->flush_bits; } } ctl_flush_bits = ctl->flush_bits; ATRACE_END("postproc_programming"); mutex_unlock(&ctl->flush_lock); ATRACE_BEGIN("frame_ready"); mdss_mdp_ctl_notify(ctl, MDP_NOTIFY_FRAME_CFG_DONE); if (commit_cb) commit_cb->commit_cb_fnc( MDP_COMMIT_STAGE_SETUP_DONE, commit_cb->data); ret = mdss_mdp_ctl_notify(ctl, MDP_NOTIFY_FRAME_READY); /* * When wait for fence timed out, driver ignores the fences * for signalling. Hardware needs to access only on the buffers * that are valid and driver needs to ensure it. This function * would set the mixer state to border when there is timeout. */ if (ret == NOTIFY_BAD) { mdss_mdp_force_border_color(ctl); ctl_flush_bits |= (ctl->flush_bits | BIT(17)); if (sctl && (!ctl->split_flush_en)) sctl_flush_bits |= (sctl->flush_bits | BIT(17)); ret = 0; } ATRACE_END("frame_ready"); if (ctl->ops.wait_pingpong && !mdata->serialize_wait4pp) mdss_mdp_display_wait4pingpong(ctl, false); /* * if serialize_wait4pp is false then roi_bkup used in wait4pingpong * will be of previous frame as expected. */ ctl->roi_bkup.w = ctl->roi.w; ctl->roi_bkup.h = ctl->roi.h; /* * update roi of panel_info which will be * used by dsi to set col_page addr of panel. */ if (ctl->panel_data && ctl->panel_data->panel_info.partial_update_enabled) { if (is_pingpong_split(ctl->mfd)) { bool pp_split = false; struct mdss_rect l_roi, r_roi, temp = {0}; u32 opmode = mdss_mdp_ctl_read(ctl, MDSS_MDP_REG_CTL_TOP) & ~0xF0; /* clear OUT_SEL */ /* * with pp split enabled, it is a requirement that both * panels share equal load, so split-point is center. */ u32 left_panel_w = left_lm_w_from_mfd(ctl->mfd) / 2; mdss_rect_split(&ctl->roi, &l_roi, &r_roi, left_panel_w); /* * If update is only on left panel then we still send * zeroed out right panel ROIs to DSI driver. Based on * zeroed ROI, DSI driver identifies which panel is not * transmitting. */ ctl->panel_data->panel_info.roi = l_roi; ctl->panel_data->next->panel_info.roi = r_roi; /* based on the roi, update ctl topology */ if (!mdss_rect_cmp(&temp, &l_roi) && !mdss_rect_cmp(&temp, &r_roi)) { /* left + right */ opmode |= (ctl->intf_num << 4); pp_split = true; } else if (mdss_rect_cmp(&temp, &l_roi)) { /* right only */ opmode |= (ctl->slave_intf_num << 4); pp_split = false; } else { /* left only */ opmode |= (ctl->intf_num << 4); pp_split = false; } mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_TOP, opmode); mdss_mdp_ctl_pp_split_display_enable(pp_split, ctl); } else { /* * if single lm update on 3D mux topology, clear it. */ if ((is_dual_lm_single_display(ctl->mfd)) && (ctl->opmode & MDSS_MDP_CTL_OP_PACK_3D_ENABLE) && (!mdss_mdp_is_both_lm_valid(ctl))) { u32 opmode = mdss_mdp_ctl_read(ctl, MDSS_MDP_REG_CTL_TOP); opmode &= ~(0xF << 19); /* clear 3D Mux */ mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_TOP, opmode); } ctl->panel_data->panel_info.roi = ctl->roi; if (sctl && sctl->panel_data) sctl->panel_data->panel_info.roi = sctl->roi; } } if (commit_cb) commit_cb->commit_cb_fnc(MDP_COMMIT_STAGE_READY_FOR_KICKOFF, commit_cb->data); if (mdss_has_quirk(mdata, MDSS_QUIRK_BWCPANIC) && !bitmap_empty(mdata->bwc_enable_map, MAX_DRV_SUP_PIPES)) mdss_mdp_bwcpanic_ctrl(mdata, true); ATRACE_BEGIN("flush_kickoff"); mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_FLUSH, ctl_flush_bits); if (sctl && sctl_flush_bits) { mdss_mdp_ctl_write(sctl, MDSS_MDP_REG_CTL_FLUSH, sctl_flush_bits); sctl->flush_bits = 0; } MDSS_XLOG(ctl->intf_num, ctl_flush_bits, sctl_flush_bits, split_lm_valid); wmb(); ctl->flush_reg_data = ctl_flush_bits; ctl->flush_bits = 0; mdss_mdp_mixer_update_pipe_map(ctl, MDSS_MDP_MIXER_MUX_LEFT); mdss_mdp_mixer_update_pipe_map(ctl, MDSS_MDP_MIXER_MUX_RIGHT); /* right-only kickoff */ if (!ctl->mixer_left->valid_roi && sctl && sctl->mixer_left->valid_roi) { /* * Seperate kickoff on DSI1 is needed only when we have * ONLY right half updating on a dual DSI panel */ if (sctl->ops.display_fnc) ret = sctl->ops.display_fnc(sctl, arg); } else { if (ctl->ops.display_fnc) ret = ctl->ops.display_fnc(ctl, arg); /* DSI0 kickoff */ } if (ret) pr_warn("ctl %d error displaying frame\n", ctl->num); ctl->play_cnt++; ATRACE_END("flush_kickoff"); done: mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); mutex_unlock(&ctl->lock); return ret; } void mdss_mdp_ctl_notifier_register(struct mdss_mdp_ctl *ctl, struct notifier_block *notifier) { struct mdss_mdp_ctl *sctl; blocking_notifier_chain_register(&ctl->notifier_head, notifier); sctl = mdss_mdp_get_split_ctl(ctl); if (sctl) blocking_notifier_chain_register(&sctl->notifier_head, notifier); } void mdss_mdp_ctl_notifier_unregister(struct mdss_mdp_ctl *ctl, struct notifier_block *notifier) { struct mdss_mdp_ctl *sctl; blocking_notifier_chain_unregister(&ctl->notifier_head, notifier); sctl = mdss_mdp_get_split_ctl(ctl); if (sctl) blocking_notifier_chain_unregister(&sctl->notifier_head, notifier); } int mdss_mdp_ctl_notify(struct mdss_mdp_ctl *ctl, int event) { return blocking_notifier_call_chain(&ctl->notifier_head, event, ctl); } int mdss_mdp_get_ctl_mixers(u32 fb_num, u32 *mixer_id) { int i; struct mdss_mdp_ctl *ctl; struct mdss_data_type *mdata; u32 mixer_cnt = 0; mutex_lock(&mdss_mdp_ctl_lock); mdata = mdss_mdp_get_mdata(); 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 == fb_num)) { if (ctl->mixer_left) { mixer_id[mixer_cnt] = ctl->mixer_left->num; mixer_cnt++; } if (mixer_cnt && ctl->mixer_right) { mixer_id[mixer_cnt] = ctl->mixer_right->num; mixer_cnt++; } if (mixer_cnt) break; } } mutex_unlock(&mdss_mdp_ctl_lock); return mixer_cnt; } /** * @mdss_mdp_ctl_mixer_switch() - return ctl mixer of @return_type * @ctl: Pointer to ctl structure to be switched. * @return_type: wb_type of the ctl to be switched to. * * Virtual mixer switch should be performed only when there is no * dedicated wfd block and writeback block is shared. */ struct mdss_mdp_ctl *mdss_mdp_ctl_mixer_switch(struct mdss_mdp_ctl *ctl, u32 return_type) { int i; struct mdss_data_type *mdata = ctl->mdata; if (ctl->wb_type == return_type) { mdata->mixer_switched = false; return ctl; } for (i = 0; i <= mdata->nctl; i++) { if (mdata->ctl_off[i].wb_type == return_type) { pr_debug("switching mixer from ctl=%d to ctl=%d\n", ctl->num, mdata->ctl_off[i].num); mdata->mixer_switched = true; return mdata->ctl_off + i; } } pr_err("unable to switch mixer to type=%d\n", return_type); return NULL; } static int __mdss_mdp_mixer_handoff_helper(struct mdss_mdp_mixer *mixer, struct mdss_mdp_pipe *pipe) { int rc = 0; struct mdss_data_type *mdata = mdss_mdp_get_mdata(); u32 right_blend = 0; if (!mixer) { rc = -EINVAL; goto error; } /* * It is possible to have more the one pipe staged on a single * layer mixer at same staging level. */ if (mixer->stage_pipe[MDSS_MDP_STAGE_UNUSED] != NULL) { if (mdata->mdp_rev < MDSS_MDP_HW_REV_103) { pr_err("More than one pipe staged on mixer num %d\n", mixer->num); rc = -EINVAL; goto error; } else if (mixer->stage_pipe[MDSS_MDP_STAGE_UNUSED + 1] != NULL) { pr_err("More than two pipe staged on mixer num %d\n", mixer->num); rc = -EINVAL; goto error; } else { right_blend = 1; } } pr_debug("Staging pipe num %d on mixer num %d\n", pipe->num, mixer->num); mixer->stage_pipe[MDSS_MDP_STAGE_UNUSED + right_blend] = pipe; pipe->mixer_left = mixer; pipe->mixer_stage = MDSS_MDP_STAGE_UNUSED; error: return rc; } /** * mdss_mdp_mixer_handoff() - Stages a given pipe on the appropriate mixer * @ctl: pointer to the control structure associated with the overlay device. * @num: the mixer number on which the pipe needs to be staged. * @pipe: pointer to the pipe to be staged. * * Function stages a given pipe on either the left mixer or the right mixer * for the control structre based on the mixer number. If the input mixer * number does not match either of the mixers then an error is returned. * This function is called during overlay handoff when certain pipes are * already staged by the bootloader. */ int mdss_mdp_mixer_handoff(struct mdss_mdp_ctl *ctl, u32 num, struct mdss_mdp_pipe *pipe) { int rc = 0; struct mdss_mdp_mixer *mx_left = ctl->mixer_left; struct mdss_mdp_mixer *mx_right = ctl->mixer_right; /* * For performance calculations, stage the handed off pipe * as MDSS_MDP_STAGE_UNUSED */ if (mx_left && (mx_left->num == num)) { rc = __mdss_mdp_mixer_handoff_helper(mx_left, pipe); } else if (mx_right && (mx_right->num == num)) { rc = __mdss_mdp_mixer_handoff_helper(mx_right, pipe); } else { pr_err("pipe num %d staged on unallocated mixer num %d\n", pipe->num, num); rc = -EINVAL; } return rc; } struct mdss_mdp_writeback *mdss_mdp_wb_alloc(u32 caps, u32 reg_index) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdss_mdp_writeback *wb = NULL; int i; bool wb_virtual_on; wb_virtual_on = (mdata->nctl == mdata->nwb_offsets); if (wb_virtual_on && reg_index >= mdata->nwb_offsets) return NULL; mutex_lock(&mdata->wb_lock); for (i = 0; i < mdata->nwb; i++) { wb = mdata->wb + i; if ((wb->caps & caps) && (atomic_read(&wb->kref.refcount) == 0)) { kref_init(&wb->kref); break; } wb = NULL; } mutex_unlock(&mdata->wb_lock); if (wb) { wb->base = mdata->mdss_io.base; if (wb_virtual_on) wb->base += mdata->wb_offsets[reg_index]; else wb->base += mdata->wb_offsets[i]; } return wb; } bool mdss_mdp_is_wb_mdp_intf(u32 num, u32 reg_index) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdss_mdp_writeback *wb = NULL; bool wb_virtual_on; wb_virtual_on = (mdata->nctl == mdata->nwb_offsets); if (num >= mdata->nwb || (wb_virtual_on && reg_index >= mdata->nwb_offsets)) return false; wb = mdata->wb + num; if (!wb) return false; return (wb->caps & MDSS_MDP_WB_INTF) ? true : false; } struct mdss_mdp_writeback *mdss_mdp_wb_assign(u32 num, u32 reg_index) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); struct mdss_mdp_writeback *wb = NULL; bool wb_virtual_on; wb_virtual_on = (mdata->nctl == mdata->nwb_offsets); if (num >= mdata->nwb) return NULL; if (wb_virtual_on && reg_index >= mdata->nwb_offsets) return NULL; mutex_lock(&mdata->wb_lock); wb = mdata->wb + num; if (atomic_read(&wb->kref.refcount) == 0) kref_init(&wb->kref); else wb = NULL; mutex_unlock(&mdata->wb_lock); if (!wb) return NULL; wb->base = mdata->mdss_io.base; if (wb_virtual_on) wb->base += mdata->wb_offsets[reg_index]; else wb->base += mdata->wb_offsets[num]; return wb; } static void mdss_mdp_wb_release(struct kref *kref) { struct mdss_mdp_writeback *wb = container_of(kref, struct mdss_mdp_writeback, kref); if (!wb) return; wb->base = NULL; } void mdss_mdp_wb_free(struct mdss_mdp_writeback *wb) { struct mdss_data_type *mdata = mdss_mdp_get_mdata(); if (kref_put_mutex(&wb->kref, mdss_mdp_wb_release, &mdata->wb_lock)) mutex_unlock(&mdata->wb_lock); }