/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #define pr_fmt(fmt) "%s: " fmt, __func__ #include #include #include #include "mdss_fb.h" #include "mdss_mdp.h" /* truncate at 1k */ #define MDSS_MDP_BUS_FACTOR_SHIFT 10 /* 1.5 bus fudge factor */ #define MDSS_MDP_BUS_FUDGE_FACTOR_IB(val) (((val) / 2) * 3) #define MDSS_MDP_BUS_FUDGE_FACTOR_AB(val) (val << 1) /* 1.25 clock fudge factor */ #define MDSS_MDP_CLK_FUDGE_FACTOR(val) (((val) * 5) / 4) enum { MDSS_MDP_PERF_UPDATE_SKIP, MDSS_MDP_PERF_UPDATE_EARLY, MDSS_MDP_PERF_UPDATE_LATE, }; #define MDSS_MDP_PERF_UPDATE_CLK BIT(0) #define MDSS_MDP_PERF_UPDATE_BUS BIT(1) #define MDSS_MDP_PERF_UPDATE_ALL -1 static DEFINE_MUTEX(mdss_mdp_ctl_lock); static int mdss_mdp_mixer_free(struct mdss_mdp_mixer *mixer); static inline void mdp_mixer_write(struct mdss_mdp_mixer *mixer, u32 reg, u32 val) { writel_relaxed(val, mixer->base + reg); } 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 int mdss_mdp_ctl_perf_commit(struct mdss_data_type *mdata, u32 flags) { struct mdss_mdp_ctl *ctl; int cnum; unsigned long clk_rate = 0; u64 bus_ab_quota = 0, bus_ib_quota = 0; if (!flags) { pr_err("nothing to update\n"); return -EINVAL; } mutex_lock(&mdss_mdp_ctl_lock); for (cnum = 0; cnum < mdata->nctl; cnum++) { ctl = mdata->ctl_off + cnum; if (ctl->power_on) { bus_ab_quota += ctl->bus_ab_quota; bus_ib_quota += ctl->bus_ib_quota; if (ctl->clk_rate > clk_rate) clk_rate = ctl->clk_rate; } } if (flags & MDSS_MDP_PERF_UPDATE_BUS) { bus_ab_quota = bus_ib_quota << MDSS_MDP_BUS_FACTOR_SHIFT; bus_ab_quota = MDSS_MDP_BUS_FUDGE_FACTOR_AB(bus_ab_quota); bus_ib_quota = MDSS_MDP_BUS_FUDGE_FACTOR_IB(bus_ib_quota); bus_ib_quota <<= MDSS_MDP_BUS_FACTOR_SHIFT; mdss_mdp_bus_scale_set_quota(bus_ab_quota, bus_ib_quota); } if (flags & MDSS_MDP_PERF_UPDATE_CLK) { clk_rate = MDSS_MDP_CLK_FUDGE_FACTOR(clk_rate); pr_debug("update clk rate = %lu\n", clk_rate); mdss_mdp_set_clk_rate(clk_rate); } mutex_unlock(&mdss_mdp_ctl_lock); return 0; } /** * 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 * * 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_mdp_mixer *mixer; int fps = DEFAULT_FRAME_RATE; u32 quota, rate, v_total, src_h; if (!pipe || !perf || !pipe->mixer) return -EINVAL; mixer = pipe->mixer; if (mixer->rotator_mode) { v_total = pipe->flags & MDP_ROT_90 ? pipe->dst.w : pipe->dst.h; } else if (mixer->type == MDSS_MDP_MIXER_TYPE_INTF) { struct mdss_panel_info *pinfo; pinfo = &mixer->ctl->panel_data->panel_info; fps = mdss_panel_get_framerate(pinfo); v_total = mdss_panel_get_vtotal(pinfo); } else { v_total = mixer->height; } /* * 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 = pipe->src.h >> pipe->vert_deci; quota = fps * pipe->src.w * src_h; if (pipe->src_fmt->chroma_sample == MDSS_MDP_CHROMA_420) quota = (quota * 3) / 2; else quota *= pipe->src_fmt->bpp; rate = pipe->dst.w; if (src_h > pipe->dst.h) rate = (rate * src_h) / pipe->dst.h; rate *= v_total * fps; if (mixer->rotator_mode) { rate /= 4; /* block mode fetch at 4 pix/clk */ quota *= 2; /* bus read + write */ perf->ib_quota = quota; } else { perf->ib_quota = (quota / pipe->dst.h) * v_total; } perf->ab_quota = quota; perf->mdp_clk_rate = rate; pr_debug("mixer=%d pnum=%d clk_rate=%u bus ab=%u ib=%u\n", mixer->num, pipe->num, rate, perf->ab_quota, perf->ib_quota); return 0; } static void mdss_mdp_perf_mixer_update(struct mdss_mdp_mixer *mixer, u32 *bus_ab_quota, u32 *bus_ib_quota, u32 *clk_rate) { struct mdss_mdp_pipe *pipe; int fps = DEFAULT_FRAME_RATE; u32 v_total; int i; u32 max_clk_rate = 0, ab_total = 0, ib_total = 0; *bus_ab_quota = 0; *bus_ib_quota = 0; *clk_rate = 0; if (!mixer->rotator_mode) { int is_writeback = false; if (mixer->type == MDSS_MDP_MIXER_TYPE_INTF) { struct mdss_panel_info *pinfo; pinfo = &mixer->ctl->panel_data->panel_info; fps = mdss_panel_get_framerate(pinfo); v_total = mdss_panel_get_vtotal(pinfo); if (pinfo->type == WRITEBACK_PANEL) is_writeback = true; } else { v_total = mixer->height; is_writeback = true; } *clk_rate = mixer->width * v_total * fps; if (is_writeback) { /* perf for bus writeback */ *bus_ab_quota = fps * mixer->width * mixer->height * 3; *bus_ab_quota >>= MDSS_MDP_BUS_FACTOR_SHIFT; *bus_ib_quota = *bus_ab_quota; } } for (i = 0; i < MDSS_MDP_MAX_STAGE; i++) { struct mdss_mdp_perf_params perf; pipe = mixer->stage_pipe[i]; if (pipe == NULL) continue; if (pipe->is_fg) { ab_total = 0; ib_total = 0; max_clk_rate = 0; } if (mdss_mdp_perf_calc_pipe(pipe, &perf)) continue; ab_total += perf.ab_quota >> MDSS_MDP_BUS_FACTOR_SHIFT; ib_total += perf.ib_quota >> MDSS_MDP_BUS_FACTOR_SHIFT; if (perf.mdp_clk_rate > max_clk_rate) max_clk_rate = perf.mdp_clk_rate; } *bus_ab_quota += ab_total; *bus_ib_quota += ib_total; if (max_clk_rate > *clk_rate) *clk_rate = max_clk_rate; pr_debug("final mixer=%d clk_rate=%u bus ab=%u ib=%u\n", mixer->num, *clk_rate, *bus_ab_quota, *bus_ib_quota); } static int mdss_mdp_ctl_perf_update(struct mdss_mdp_ctl *ctl) { int ret = MDSS_MDP_PERF_UPDATE_SKIP; u32 clk_rate, ab_quota, ib_quota; u32 max_clk_rate = 0, total_ab_quota = 0, total_ib_quota = 0; if (ctl->mixer_left) { mdss_mdp_perf_mixer_update(ctl->mixer_left, &ab_quota, &ib_quota, &clk_rate); total_ab_quota += ab_quota; total_ib_quota += ib_quota; max_clk_rate = clk_rate; } if (ctl->mixer_right) { mdss_mdp_perf_mixer_update(ctl->mixer_right, &ab_quota, &ib_quota, &clk_rate); total_ab_quota += ab_quota; total_ib_quota += ib_quota; if (clk_rate > max_clk_rate) max_clk_rate = clk_rate; if (ctl->intf_type) { 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 > max_clk_rate) max_clk_rate = clk_rate; } } /* request minimum bandwidth to have bus clock on when display is on */ if (total_ib_quota == 0) total_ib_quota = SZ_16M >> MDSS_MDP_BUS_FACTOR_SHIFT; if (max_clk_rate != ctl->clk_rate) { if (max_clk_rate > ctl->clk_rate) ret = MDSS_MDP_PERF_UPDATE_EARLY; else ret = MDSS_MDP_PERF_UPDATE_LATE; ctl->clk_rate = max_clk_rate; ctl->perf_changed |= MDSS_MDP_PERF_UPDATE_CLK; } if ((total_ab_quota != ctl->bus_ab_quota) || (total_ib_quota != ctl->bus_ib_quota)) { if (ret == MDSS_MDP_PERF_UPDATE_SKIP) { if (total_ib_quota >= ctl->bus_ib_quota) ret = MDSS_MDP_PERF_UPDATE_EARLY; else ret = MDSS_MDP_PERF_UPDATE_LATE; } ctl->bus_ab_quota = total_ab_quota; ctl->bus_ib_quota = total_ib_quota; ctl->perf_changed |= MDSS_MDP_PERF_UPDATE_BUS; } return ret; } static struct mdss_mdp_ctl *mdss_mdp_ctl_alloc(struct mdss_data_type *mdata, u32 off) { struct mdss_mdp_ctl *ctl = NULL; u32 cnum; mutex_lock(&mdss_mdp_ctl_lock); for (cnum = off; cnum < mdata->nctl; cnum++) { ctl = mdata->ctl_off + cnum; if (ctl->ref_cnt == 0) { ctl->ref_cnt++; ctl->mdata = mdata; mutex_init(&ctl->lock); pr_debug("alloc ctl_num=%d\n", ctl->num); break; } ctl = NULL; } mutex_unlock(&mdss_mdp_ctl_lock); return ctl; } static 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) { mdss_mdp_mixer_free(ctl->mixer_left); ctl->mixer_left = NULL; } if (ctl->mixer_right) { mdss_mdp_mixer_free(ctl->mixer_right); ctl->mixer_right = NULL; } mutex_lock(&mdss_mdp_ctl_lock); ctl->ref_cnt--; ctl->intf_num = MDSS_MDP_NO_INTF; ctl->is_secure = false; ctl->power_on = false; ctl->start_fnc = NULL; ctl->stop_fnc = NULL; ctl->prepare_fnc = NULL; ctl->display_fnc = NULL; ctl->wait_fnc = NULL; ctl->read_line_cnt_fnc = NULL; ctl->add_vsync_handler = NULL; ctl->remove_vsync_handler = NULL; mutex_unlock(&mdss_mdp_ctl_lock); return 0; } static struct mdss_mdp_mixer *mdss_mdp_mixer_alloc( struct mdss_mdp_ctl *ctl, u32 type, int mux) { struct mdss_mdp_mixer *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; 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; } /* early mdp revision only supports mux of dual pipe on mixers 0 and 1, * need to ensure that these pipes are readily available by using * mixer 2 if available and mux is not required */ if (!mux && (ctl->mdata->mdp_rev == MDSS_MDP_HW_REV_100) && (type == MDSS_MDP_MIXER_TYPE_INTF) && (nmixers >= MDSS_MDP_INTF_LAYERMIXER2) && (mixer_pool[MDSS_MDP_INTF_LAYERMIXER2].ref_cnt == 0)) mixer_pool += MDSS_MDP_INTF_LAYERMIXER2; for (i = 0; i < nmixers; i++) { mixer = mixer_pool + i; if (mixer->ref_cnt == 0) { mixer->ref_cnt++; mixer->params_changed++; mixer->ctl = ctl; pr_debug("alloc mixer num%d\n", mixer->num); break; } mixer = NULL; } mutex_unlock(&mdss_mdp_ctl_lock); return mixer; } static 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--; mutex_unlock(&mdss_mdp_ctl_lock); return 0; } struct mdss_mdp_mixer *mdss_mdp_wb_mixer_alloc(int rotator) { struct mdss_mdp_ctl *ctl = NULL; struct mdss_mdp_mixer *mixer = NULL; ctl = mdss_mdp_ctl_alloc(mdss_res, mdss_res->nmixers_intf); if (!ctl) return NULL; mixer = mdss_mdp_mixer_alloc(ctl, MDSS_MDP_MIXER_TYPE_WRITEBACK, false); if (!mixer) goto error; mixer->rotator_mode = rotator; switch (mixer->num) { case MDSS_MDP_WB_LAYERMIXER0: ctl->opmode = (rotator ? MDSS_MDP_CTL_OP_ROT0_MODE : MDSS_MDP_CTL_OP_WB0_MODE); break; case MDSS_MDP_WB_LAYERMIXER1: ctl->opmode = (rotator ? MDSS_MDP_CTL_OP_ROT1_MODE : MDSS_MDP_CTL_OP_WB1_MODE); break; default: pr_err("invalid layer mixer=%d\n", mixer->num); goto error; } ctl->mixer_left = mixer; ctl->start_fnc = mdss_mdp_writeback_start; ctl->power_on = true; if (ctl->start_fnc) ctl->start_fnc(ctl); return mixer; error: if (mixer) mdss_mdp_mixer_free(mixer); if (ctl) mdss_mdp_ctl_free(ctl); return NULL; } int mdss_mdp_wb_mixer_destroy(struct mdss_mdp_mixer *mixer) { struct mdss_mdp_ctl *ctl; ctl = mixer->ctl; pr_debug("destroy ctl=%d mixer=%d\n", ctl->num, mixer->num); if (ctl->stop_fnc) ctl->stop_fnc(ctl); mdss_mdp_ctl_free(ctl); mdss_mdp_ctl_perf_commit(ctl->mdata, MDSS_MDP_PERF_UPDATE_ALL); return 0; } void mdss_mdp_ctl_splash_start(struct mdss_panel_data *pdata) { switch (pdata->panel_info.type) { case MIPI_VIDEO_PANEL: mdss_mdp_video_copy_splash_screen(pdata); break; case MIPI_CMD_PANEL: default: break; } } int mdss_mdp_ctl_splash_finish(struct mdss_mdp_ctl *ctl) { switch (ctl->panel_data->panel_info.type) { case MIPI_VIDEO_PANEL: return mdss_mdp_video_reconfigure_splash_done(ctl); case MIPI_CMD_PANEL: default: return 0; } } static inline int mdss_mdp_set_split_ctl(struct mdss_mdp_ctl *ctl, struct mdss_mdp_ctl *split_ctl) { if (!ctl || !split_ctl) 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; return 0; } static inline struct mdss_mdp_ctl *mdss_mdp_get_split_ctl( struct mdss_mdp_ctl *ctl) { if (ctl && ctl->mixer_right && (ctl->mixer_right->ctl != ctl)) return ctl->mixer_right->ctl; return NULL; } 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; if (!pdata) { pr_err("Invalid pdata\n"); return -EINVAL; } fbc = &pdata->fbc; if (!fbc || !fbc->enabled) { pr_err("Invalid FBC structure\n"); return -EINVAL; } if (mixer->num == MDSS_MDP_INTF_LAYERMIXER0) pr_debug("Mixer supports FBC.\n"); else { pr_debug("Mixer doesn't support FBC.\n"); return -EINVAL; } if (enable) { mode = ((pdata->xres) << 16) | ((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->lossless_mode_thd) << 16) | ((fbc->lossy_mode_thd) << 8) | ((fbc->lossy_rgb_thd) << 3) | fbc->lossy_mode_idx; } mdss_mdp_pingpong_write(mixer, MDSS_MDP_REG_PP_FBC_MODE, mode); mdss_mdp_pingpong_write(mixer, MDSS_MDP_REG_PP_FBC_BUDGET_CTL, budget_ctl); mdss_mdp_pingpong_write(mixer, 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; if (!ctl || !ctl->panel_data) { pr_err("invalid ctl handle\n"); return -ENODEV; } split_ctl = mdss_mdp_get_split_ctl(ctl); width = ctl->panel_data->panel_info.xres; height = ctl->panel_data->panel_info.yres; 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; if (!ctl->mixer_left) { ctl->mixer_left = mdss_mdp_mixer_alloc(ctl, MDSS_MDP_MIXER_TYPE_INTF, (width > MAX_MIXER_WIDTH)); if (!ctl->mixer_left) { pr_err("unable to allocate layer mixer\n"); return -ENOMEM; } } if (width > MAX_MIXER_WIDTH) width /= 2; ctl->mixer_left->width = width; ctl->mixer_left->height = height; if (split_ctl) { pr_debug("split display detected\n"); return 0; } if (width < ctl->width) { if (ctl->mixer_right == NULL) { ctl->mixer_right = mdss_mdp_mixer_alloc(ctl, MDSS_MDP_MIXER_TYPE_INTF, true); 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->width = width; ctl->mixer_right->height = height; } else if (ctl->mixer_right) { mdss_mdp_mixer_free(ctl->mixer_right); ctl->mixer_right = NULL; } if (ctl->mixer_right) { 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; } static int mdss_mdp_ctl_setup_wfd(struct mdss_mdp_ctl *ctl) { struct mdss_data_type *mdata = ctl->mdata; struct mdss_mdp_mixer *mixer; int mixer_type; /* if WB2 is supported, try to allocate it first */ if (mdata->nmixers_intf >= MDSS_MDP_INTF_LAYERMIXER2) mixer_type = MDSS_MDP_MIXER_TYPE_INTF; else mixer_type = MDSS_MDP_MIXER_TYPE_WRITEBACK; mixer = mdss_mdp_mixer_alloc(ctl, mixer_type, false); if (!mixer && mixer_type == MDSS_MDP_MIXER_TYPE_INTF) mixer = mdss_mdp_mixer_alloc(ctl, MDSS_MDP_MIXER_TYPE_WRITEBACK, false); if (!mixer) { pr_err("Unable to allocate writeback mixer\n"); return -ENOMEM; } if (mixer->type == MDSS_MDP_MIXER_TYPE_INTF) { ctl->opmode = MDSS_MDP_CTL_OP_WFD_MODE; } else { switch (mixer->num) { case MDSS_MDP_WB_LAYERMIXER0: ctl->opmode = MDSS_MDP_CTL_OP_WB0_MODE; break; case MDSS_MDP_WB_LAYERMIXER1: ctl->opmode = MDSS_MDP_CTL_OP_WB1_MODE; break; default: pr_err("Incorrect writeback config num=%d\n", mixer->num); mdss_mdp_mixer_free(mixer); return -EINVAL; } } ctl->mixer_left = mixer; return 0; } struct mdss_mdp_ctl *mdss_mdp_ctl_init(struct mdss_panel_data *pdata, struct msm_fb_data_type *mfd) { struct mdss_mdp_ctl *ctl; int ret = 0; struct mdss_data_type *mdata = mfd_to_mdata(mfd); ctl = mdss_mdp_ctl_alloc(mdata, MDSS_MDP_CTL0); if (!ctl) { pr_err("unable to allocate ctl\n"); return ERR_PTR(-ENOMEM); } ctl->mfd = mfd; ctl->panel_data = pdata; ctl->is_video_mode = 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->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 = MDSS_MDP_INTF1; else ctl->intf_num = MDSS_MDP_INTF2; ctl->intf_type = MDSS_INTF_DSI; ctl->opmode = MDSS_MDP_CTL_OP_VIDEO_MODE; ctl->start_fnc = mdss_mdp_video_start; break; case MIPI_CMD_PANEL: if (pdata->panel_info.pdest == DISPLAY_1) ctl->intf_num = MDSS_MDP_INTF1; else ctl->intf_num = MDSS_MDP_INTF2; ctl->intf_type = MDSS_INTF_DSI; ctl->opmode = MDSS_MDP_CTL_OP_CMD_MODE; ctl->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->start_fnc = mdss_mdp_video_start; ret = mdss_mdp_limited_lut_igc_config(ctl); if (ret) pr_err("Unable to config IGC LUT data"); break; case WRITEBACK_PANEL: ctl->intf_num = MDSS_MDP_NO_INTF; ctl->start_fnc = mdss_mdp_writeback_start; ret = mdss_mdp_ctl_setup_wfd(ctl); if (ret) goto ctl_init_fail; 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 { struct mdp_dither_cfg_data dither = { .block = mfd->index + MDP_LOGICAL_BLOCK_DISP_0, .flags = MDP_PP_OPS_DISABLE, }; switch (pdata->panel_info.bpp) { case 18: ctl->dst_format = MDSS_MDP_PANEL_FORMAT_RGB666; dither.flags = MDP_PP_OPS_ENABLE | MDP_PP_OPS_WRITE; dither.g_y_depth = 2; dither.r_cr_depth = 2; dither.b_cb_depth = 2; break; case 24: default: ctl->dst_format = MDSS_MDP_PANEL_FORMAT_RGB888; break; } mdss_mdp_dither_config(ctl, &dither, NULL); } 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 > 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 = pdata->panel_info.xres; sctl->height = pdata->panel_info.yres; ctl->mixer_left = mdss_mdp_mixer_alloc(ctl, MDSS_MDP_MIXER_TYPE_INTF, false); 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); if (!mixer) { pr_err("unable to allocate layer mixer\n"); mdss_mdp_ctl_destroy(sctl); return -ENOMEM; } mixer->width = sctl->width; mixer->height = sctl->height; 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 slave ctl=%d intf=%d\n", main_ctl->num, main_ctl->intf_num, slave_ctl->num, slave_ctl->intf_num); if (enable) { if (main_ctl->opmode & MDSS_MDP_CTL_OP_CMD_MODE) { upper |= BIT(1); lower |= BIT(1); /* interface controlling sw trigger */ if (main_ctl->intf_num == MDSS_MDP_INTF2) upper |= BIT(4); else upper |= BIT(8); } else { /* video mode */ if (main_ctl->intf_num == MDSS_MDP_INTF2) lower |= BIT(4); else lower |= BIT(8); } } MDSS_MDP_REG_WRITE(MDSS_MDP_REG_SPLIT_DISPLAY_UPPER_PIPE_CTRL, upper); MDSS_MDP_REG_WRITE(MDSS_MDP_REG_SPLIT_DISPLAY_LOWER_PIPE_CTRL, lower); MDSS_MDP_REG_WRITE(MDSS_MDP_REG_SPLIT_DISPLAY_EN, enable); } 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); 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); if (sctl->mixer_left) mdss_mdp_mixer_free(sctl->mixer_left); mdss_mdp_ctl_free(sctl); } else if (ctl->mixer_right) { mdss_mdp_mixer_free(ctl->mixer_right); ctl->mixer_right = NULL; } if (ctl->mixer_left) { mdss_mdp_mixer_free(ctl->mixer_left); ctl->mixer_left = NULL; } mdss_mdp_ctl_free(ctl); return 0; } int mdss_mdp_ctl_intf_event(struct mdss_mdp_ctl *ctl, int event, void *arg) { struct mdss_panel_data *pdata; int rc = 0; if (!ctl || !ctl->panel_data) return -ENODEV; pdata = ctl->panel_data; pr_debug("sending ctl=%d event=%d\n", ctl->num, event); do { if (pdata->event_handler) rc = pdata->event_handler(pdata, event, arg); pdata = pdata->next; } while (rc == 0 && pdata); return rc; } static int mdss_mdp_ctl_start_sub(struct mdss_mdp_ctl *ctl) { struct mdss_mdp_mixer *mixer; u32 outsize, temp; int ret = 0; int i, nmixers; if (ctl->start_fnc) ret = ctl->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; } pr_debug("ctl_num=%d\n", ctl->num); 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); mixer = ctl->mixer_left; mdss_mdp_pp_resume(ctl, mixer->num); mixer->params_changed++; temp = MDSS_MDP_REG_READ(MDSS_MDP_REG_DISP_INTF_SEL); temp |= (ctl->intf_type << ((ctl->intf_num - MDSS_MDP_INTF0) * 8)); MDSS_MDP_REG_WRITE(MDSS_MDP_REG_DISP_INTF_SEL, temp); outsize = (mixer->height << 16) | mixer->width; mdp_mixer_write(mixer, MDSS_MDP_REG_LM_OUT_SIZE, outsize); if (ctl->panel_data->panel_info.fbc.enabled) { ret = mdss_mdp_ctl_fbc_enable(1, ctl->mixer_left, &ctl->panel_data->panel_info); } return ret; } int mdss_mdp_ctl_start(struct mdss_mdp_ctl *ctl) { struct mdss_mdp_ctl *sctl; int ret = 0; if (ctl->power_on) { 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); ctl->power_on = true; ctl->bus_ab_quota = 0; ctl->bus_ib_quota = 0; ctl->clk_rate = 0; mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false); ret = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_RESET, NULL); if (ret) { pr_err("panel power on failed ctl=%d\n", ctl->num); goto error; } ret = mdss_mdp_ctl_start_sub(ctl); if (ret == 0) { if (sctl) { /* split display is available */ ret = mdss_mdp_ctl_start_sub(sctl); 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, off; mdss_mdp_pp_resume(ctl, mixer->num); mixer->params_changed++; out = (mixer->height << 16) | mixer->width; off = MDSS_MDP_REG_LM_OFFSET(mixer->num); MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_OUT_SIZE, out); mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_PACK_3D, 0); } } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false); error: mutex_unlock(&ctl->lock); return ret; } int mdss_mdp_ctl_stop(struct mdss_mdp_ctl *ctl) { struct mdss_mdp_ctl *sctl; int ret = 0; if (!ctl->power_on) { pr_debug("%s %d already off!\n", __func__, __LINE__); return 0; } sctl = mdss_mdp_get_split_ctl(ctl); pr_debug("ctl_num=%d\n", ctl->num); mutex_lock(&ctl->lock); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false); if (ctl->stop_fnc) ret = ctl->stop_fnc(ctl); else pr_warn("no stop func for ctl=%d\n", ctl->num); if (sctl && sctl->stop_fnc) { ret = sctl->stop_fnc(sctl); mdss_mdp_ctl_split_display_enable(0, ctl, sctl); } if (ret) { pr_warn("error powering off intf ctl=%d\n", ctl->num); } else { mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_TOP, 0); if (sctl) mdss_mdp_ctl_write(sctl, MDSS_MDP_REG_CTL_TOP, 0); if (ctl->mixer_left) { mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_LAYER( ctl->mixer_left->num), 0); } if (ctl->mixer_right) { mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_LAYER( ctl->mixer_right->num), 0); } ctl->power_on = false; ctl->play_cnt = 0; ctl->clk_rate = 0; mdss_mdp_ctl_perf_commit(ctl->mdata, MDSS_MDP_PERF_UPDATE_ALL); } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false); mutex_unlock(&ctl->lock); return ret; } static int mdss_mdp_mixer_setup(struct mdss_mdp_ctl *ctl, struct mdss_mdp_mixer *mixer) { struct mdss_mdp_pipe *pipe; u32 off, blend_op, blend_stage; u32 mixercfg = 0, blend_color_out = 0, bgalpha = 0; int stage, secure = 0; if (!mixer) return -ENODEV; pr_debug("setup mixer=%d\n", mixer->num); pipe = mixer->stage_pipe[MDSS_MDP_STAGE_BASE]; if (pipe == NULL) { mixercfg = MDSS_MDP_LM_BORDER_COLOR; } else { mixercfg = 1 << (3 * pipe->num); if (pipe->src_fmt->alpha_enable) bgalpha = 1; secure = pipe->flags & MDP_SECURE_OVERLAY_SESSION; } for (stage = MDSS_MDP_STAGE_0; stage < MDSS_MDP_MAX_STAGE; stage++) { pipe = mixer->stage_pipe[stage]; if (pipe == NULL) continue; if (stage != pipe->mixer_stage) { mixer->stage_pipe[stage] = NULL; continue; } blend_stage = stage - MDSS_MDP_STAGE_0; off = MDSS_MDP_REG_LM_BLEND_OFFSET(blend_stage); if (pipe->is_fg) { bgalpha = 0; if (!secure) mixercfg = MDSS_MDP_LM_BORDER_COLOR; blend_op = (MDSS_MDP_BLEND_FG_ALPHA_FG_CONST | MDSS_MDP_BLEND_BG_ALPHA_BG_CONST); /* keep fg alpha */ blend_color_out |= 1 << (blend_stage + 1); pr_debug("pnum=%d stg=%d alpha=IS_FG\n", pipe->num, stage); } else if (pipe->src_fmt->alpha_enable) { bgalpha = 0; blend_op = (MDSS_MDP_BLEND_BG_ALPHA_FG_PIXEL | MDSS_MDP_BLEND_BG_INV_ALPHA); /* keep fg alpha */ blend_color_out |= 1 << (blend_stage + 1); pr_debug("pnum=%d stg=%d alpha=FG PIXEL\n", pipe->num, stage); } else if (bgalpha) { blend_op = (MDSS_MDP_BLEND_BG_ALPHA_BG_PIXEL | MDSS_MDP_BLEND_FG_ALPHA_BG_PIXEL | MDSS_MDP_BLEND_FG_INV_ALPHA); /* keep bg alpha */ pr_debug("pnum=%d stg=%d alpha=BG_PIXEL\n", pipe->num, stage); } else { blend_op = (MDSS_MDP_BLEND_FG_ALPHA_FG_CONST | MDSS_MDP_BLEND_BG_ALPHA_BG_CONST); pr_debug("pnum=%d stg=%d alpha=CONST\n", pipe->num, stage); } mixercfg |= stage << (3 * pipe->num); mdp_mixer_write(mixer, off + MDSS_MDP_REG_LM_OP_MODE, blend_op); mdp_mixer_write(mixer, off + MDSS_MDP_REG_LM_BLEND_FG_ALPHA, pipe->alpha); mdp_mixer_write(mixer, off + MDSS_MDP_REG_LM_BLEND_BG_ALPHA, 0xFF - pipe->alpha); } if (mixer->cursor_enabled) mixercfg |= MDSS_MDP_LM_CURSOR_OUT; pr_debug("mixer=%d mixer_cfg=%x\n", mixer->num, mixercfg); ctl->flush_bits |= BIT(6) << mixer->num; /* LAYER_MIXER */ mdp_mixer_write(mixer, MDSS_MDP_REG_LM_OP_MODE, blend_color_out); if (mixer->type == MDSS_MDP_MIXER_TYPE_INTF) off = MDSS_MDP_REG_CTL_LAYER(mixer->num); else off = MDSS_MDP_REG_CTL_LAYER(mixer->num + MDSS_MDP_INTF_MAX_LAYERMIXER); mdss_mdp_ctl_write(ctl, off, mixercfg); return 0; } 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; head = devm_kzalloc(&mdata->pdev->dev, sizeof(struct mdss_mdp_mixer) * len, 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->mdp_base + mixer_offsets[i]; head[i].ref_cnt = 0; head[i].num = i; if (type == MDSS_MDP_MIXER_TYPE_INTF) { head[i].dspp_base = mdata->mdp_base + dspp_offsets[i]; head[i].pingpong_base = mdata->mdp_base + pingpong_offsets[i]; } } 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 *wb_offsets, u32 len) { struct mdss_mdp_ctl *head; u32 i; head = devm_kzalloc(&mdata->pdev->dev, sizeof(struct mdss_mdp_ctl) * len, 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->mdp_base) + ctl_offsets[i]; head[i].wb_base = (mdata->mdp_base) + wb_offsets[i]; head[i].ref_cnt = 0; } mdata->ctl_off = head; return 0; } struct mdss_mdp_mixer *mdss_mdp_mixer_get(struct mdss_mdp_ctl *ctl, int mux) { struct mdss_mdp_mixer *mixer = NULL; struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(ctl->mfd); if (!ctl) return NULL; switch (mux) { case MDSS_MDP_MIXER_MUX_DEFAULT: case MDSS_MDP_MIXER_MUX_LEFT: mixer = mdp5_data->mixer_swap ? ctl->mixer_right : ctl->mixer_left; break; case MDSS_MDP_MIXER_MUX_RIGHT: mixer = mdp5_data->mixer_swap ? ctl->mixer_left : ctl->mixer_right; break; } return mixer; } struct mdss_mdp_pipe *mdss_mdp_mixer_stage_pipe(struct mdss_mdp_ctl *ctl, int mux, int stage) { struct mdss_mdp_pipe *pipe = NULL; struct mdss_mdp_mixer *mixer; if (!ctl) return NULL; if (mutex_lock_interruptible(&ctl->lock)) return NULL; mixer = mdss_mdp_mixer_get(ctl, mux); if (mixer) pipe = mixer->stage_pipe[stage]; mutex_unlock(&ctl->lock); return pipe; } int mdss_mdp_mixer_pipe_update(struct mdss_mdp_pipe *pipe, int params_changed) { struct mdss_mdp_ctl *ctl; struct mdss_mdp_mixer *mixer; int i; if (!pipe) return -EINVAL; mixer = pipe->mixer; 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); if (mutex_lock_interruptible(&ctl->lock)) return -EINTR; if (params_changed) { mixer->params_changed++; for (i = 0; i < MDSS_MDP_MAX_STAGE; i++) { if (i == pipe->mixer_stage) mixer->stage_pipe[i] = pipe; else if (mixer->stage_pipe[i] == pipe) mixer->stage_pipe[i] = NULL; } } if (pipe->type == MDSS_MDP_PIPE_TYPE_DMA) ctl->flush_bits |= BIT(pipe->num) << 5; else /* RGB/VIG pipe */ ctl->flush_bits |= BIT(pipe->num); mutex_unlock(&ctl->lock); return 0; } int mdss_mdp_mixer_pipe_unstage(struct mdss_mdp_pipe *pipe) { struct mdss_mdp_ctl *ctl; struct mdss_mdp_mixer *mixer; if (!pipe) return -EINVAL; mixer = pipe->mixer; if (!mixer) return -EINVAL; ctl = mixer->ctl; if (!ctl) return -EINVAL; pr_debug("unstage pnum=%d stage=%d mixer=%d\n", pipe->num, pipe->mixer_stage, mixer->num); if (mutex_lock_interruptible(&ctl->lock)) return -EINTR; if (pipe == mixer->stage_pipe[pipe->mixer_stage]) { mixer->params_changed++; mixer->stage_pipe[pipe->mixer_stage] = NULL; } mutex_unlock(&ctl->lock); return 0; } static int mdss_mdp_mixer_update(struct mdss_mdp_mixer *mixer) { mixer->params_changed = 0; /* skip mixer setup for rotator */ if (!mixer->rotator_mode) mdss_mdp_mixer_setup(mixer->ctl, mixer); return 0; } 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; ktime_t current_time = ktime_get(); if (!ctl->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->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 (!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; ret = mutex_lock_interruptible(&ctl->lock); if (ret) return ret; if (!ctl->power_on) { mutex_unlock(&ctl->lock); return 0; } if (ctl->wait_fnc) ret = ctl->wait_fnc(ctl, NULL); if (ctl->perf_changed) { mdss_mdp_ctl_perf_commit(ctl->mdata, ctl->perf_changed); ctl->perf_changed = 0; } mutex_unlock(&ctl->lock); return ret; } int mdss_mdp_display_wait4pingpong(struct mdss_mdp_ctl *ctl) { int ret; ret = mutex_lock_interruptible(&ctl->lock); if (ret) return ret; if (!ctl->power_on) { mutex_unlock(&ctl->lock); return 0; } if (ctl->wait_pingpong) ret = ctl->wait_pingpong(ctl, NULL); mutex_unlock(&ctl->lock); return ret; } int mdss_mdp_display_commit(struct mdss_mdp_ctl *ctl, void *arg) { struct mdss_mdp_ctl *sctl = NULL; int mixer1_changed, mixer2_changed; int ret = 0; int perf_update = MDSS_MDP_PERF_UPDATE_SKIP; if (!ctl) { pr_err("display function not set\n"); return -ENODEV; } pr_debug("commit ctl=%d play_cnt=%d\n", ctl->num, ctl->play_cnt); ret = mutex_lock_interruptible(&ctl->lock); if (ret) return ret; if (!ctl->power_on) { mutex_unlock(&ctl->lock); return 0; } sctl = mdss_mdp_get_split_ctl(ctl); mixer1_changed = (ctl->mixer_left && ctl->mixer_left->params_changed); mixer2_changed = (ctl->mixer_right && ctl->mixer_right->params_changed); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false); if (mixer1_changed || mixer2_changed) { perf_update = mdss_mdp_ctl_perf_update(ctl); if (ctl->prepare_fnc) ret = ctl->prepare_fnc(ctl, arg); if (ret) { pr_err("error preparing display\n"); goto done; } if (perf_update == MDSS_MDP_PERF_UPDATE_EARLY) { mdss_mdp_ctl_perf_commit(ctl->mdata, ctl->perf_changed); ctl->perf_changed = 0; } if (mixer1_changed) mdss_mdp_mixer_update(ctl->mixer_left); if (mixer2_changed) mdss_mdp_mixer_update(ctl->mixer_right); 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); } } /* postprocessing setup, including dspp */ mdss_mdp_pp_setup_locked(ctl); mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_FLUSH, ctl->flush_bits); if (sctl) { mdss_mdp_ctl_write(sctl, MDSS_MDP_REG_CTL_FLUSH, sctl->flush_bits); } wmb(); ctl->flush_bits = 0; if (ctl->display_fnc) ret = ctl->display_fnc(ctl, arg); /* kickoff */ if (ret) pr_warn("error displaying frame\n"); ctl->play_cnt++; done: mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false); mutex_unlock(&ctl->lock); return ret; } 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 ((ctl->power_on) && (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; }