| /* 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 <linux/errno.h> |
| #include <linux/mutex.h> |
| #include <linux/platform_device.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/delay.h> |
| |
| #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_HIGH_IB(val) (val << 1) |
| #define MDSS_MDP_BUS_FUDGE_FACTOR_AB(val) (val << 1) |
| #define MDSS_MDP_BUS_FLOOR_BW (1600000000ULL >> MDSS_MDP_BUS_FACTOR_SHIFT) |
| |
| /* 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 int __mdss_mdp_ctl_get_mixer_off(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 u32 __mdss_mdp_ctrl_perf_ovrd_helper(struct mdss_mdp_mixer *mixer, |
| u32 *npipe) |
| { |
| struct mdss_panel_info *pinfo; |
| struct mdss_mdp_pipe *pipe; |
| u32 mnum, ovrd = 0; |
| |
| if (!mixer || !mixer->ctl->panel_data) |
| return 0; |
| |
| pinfo = &mixer->ctl->panel_data->panel_info; |
| for (mnum = 0; mnum < MDSS_MDP_MAX_STAGE; mnum++) { |
| pipe = mixer->stage_pipe[mnum]; |
| if (pipe && pinfo) { |
| *npipe = *npipe + 1; |
| if ((pipe->src.w >= pipe->src.h) && |
| (pipe->src.w >= pinfo->xres)) |
| ovrd = 1; |
| } |
| } |
| |
| return ovrd; |
| } |
| |
| /** |
| * mdss_mdp_ctrl_perf_ovrd() - Determines if performance override is needed |
| * @mdata: Struct containing references to all MDP5 hardware structures |
| * and status info such as interupts, target caps etc. |
| * @ab_quota: Arbitrated bandwidth quota |
| * @ib_quota: Instantaneous bandwidth quota |
| * |
| * Function calculates the minimum required MDP and BIMC clocks to avoid MDP |
| * underflow during portrait video playback. 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. |
| */ |
| static void __mdss_mdp_ctrl_perf_ovrd(struct mdss_data_type *mdata, |
| u64 *ab_quota, u64 *ib_quota) |
| { |
| struct mdss_mdp_ctl *ctl; |
| u32 i, npipe = 0, ovrd = 0; |
| |
| for (i = 0; i < mdata->nctl; i++) { |
| ctl = mdata->ctl_off + i; |
| if (!ctl->power_on) |
| continue; |
| ovrd |= __mdss_mdp_ctrl_perf_ovrd_helper( |
| ctl->mixer_left, &npipe); |
| ovrd |= __mdss_mdp_ctrl_perf_ovrd_helper( |
| ctl->mixer_right, &npipe); |
| } |
| |
| *ab_quota = MDSS_MDP_BUS_FUDGE_FACTOR_AB(*ab_quota); |
| if (npipe > 1) |
| *ib_quota = MDSS_MDP_BUS_FUDGE_FACTOR_HIGH_IB(*ib_quota); |
| else |
| *ib_quota = MDSS_MDP_BUS_FUDGE_FACTOR_IB(*ib_quota); |
| |
| if (ovrd && (*ib_quota < MDSS_MDP_BUS_FLOOR_BW)) { |
| *ib_quota = MDSS_MDP_BUS_FLOOR_BW; |
| pr_debug("forcing the BIMC clock to 200 MHz : %llu bytes", |
| *ib_quota); |
| } else { |
| pr_debug("ib quota : %llu bytes", *ib_quota); |
| } |
| } |
| |
| 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_ctrl_perf_ovrd(mdata, &bus_ab_quota, &bus_ib_quota); |
| bus_ib_quota <<= MDSS_MDP_BUS_FACTOR_SHIFT; |
| bus_ab_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 HZ\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) |
| /* |
| * 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; |
| |
| 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; |
| struct mdss_panel_info *pinfo = NULL; |
| 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) { |
| if (mixer->type == MDSS_MDP_MIXER_TYPE_INTF) { |
| 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) |
| pinfo = NULL; |
| } else { |
| v_total = mixer->height; |
| } |
| *clk_rate = mixer->width * v_total * fps; |
| if (pinfo && pinfo->lcdc.v_back_porch < MDP_MIN_VBP) |
| *clk_rate = MDSS_MDP_CLK_FUDGE_FACTOR(*clk_rate); |
| |
| if (!pinfo) { |
| /* 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 (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; |
| u32 nctl = mdata->nctl; |
| |
| mutex_lock(&mdss_mdp_ctl_lock); |
| if (!mdata->has_wfd_blk) |
| 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); |
| 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; |
| } |
| |
| 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->intf_type = 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; |
| ctl->panel_data = NULL; |
| ctl->config_fps_fnc = 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, *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) { |
| alt_mixer = mixer_pool; |
| mixer_pool++; |
| nmixers--; |
| } |
| 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; |
| |
| /*Allocate virtual wb mixer if no dedicated wfd wb blk is present*/ |
| if (!ctl->mdata->has_wfd_blk && (type == MDSS_MDP_MIXER_TYPE_WRITEBACK)) |
| nmixers += 1; |
| |
| 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 for ctl=%d\n", |
| mixer->num, ctl->num); |
| break; |
| } |
| mixer = NULL; |
| } |
| |
| if (!mixer && alt_mixer && (alt_mixer->ref_cnt == 0)) |
| mixer = alt_mixer; |
| 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) { |
| pr_debug("unable to allocate wb ctl\n"); |
| return NULL; |
| } |
| |
| mixer = mdss_mdp_mixer_alloc(ctl, MDSS_MDP_MIXER_TYPE_WRITEBACK, false); |
| if (!mixer) { |
| pr_debug("unable to allocate wb mixer\n"); |
| 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; |
| ctl->wb_type = (rotator ? MDSS_MDP_WB_CTL_TYPE_BLOCK : |
| MDSS_MDP_WB_CTL_TYPE_LINE); |
| mixer->ctl = ctl; |
| |
| 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; |
| |
| 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->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; |
| } |
| |
| int mdss_mdp_ctl_splash_finish(struct mdss_mdp_ctl *ctl, bool handoff) |
| { |
| switch (ctl->panel_data->panel_info.type) { |
| case MIPI_VIDEO_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) |
| { |
| 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; |
| int split_fb; |
| |
| 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; |
| |
| split_fb = (ctl->mfd->split_fb_left && |
| ctl->mfd->split_fb_right && |
| (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_mdp_img_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)); |
| if (!ctl->mixer_left) { |
| pr_err("unable to allocate layer mixer\n"); |
| return -ENOMEM; |
| } |
| } |
| |
| if (split_fb) |
| width = ctl->mfd->split_fb_left; |
| else if (width > MAX_MIXER_WIDTH) |
| width /= 2; |
| |
| ctl->mixer_left->width = width; |
| ctl->mixer_left->height = height; |
| ctl->mixer_left->roi = (struct mdss_mdp_img_rect) {0, 0, width, height}; |
| |
| if (split_ctl) { |
| pr_debug("split 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); |
| 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; |
| ctl->mixer_right->roi = (struct mdss_mdp_img_rect) |
| {0, 0, width, 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->wb_type = MDSS_MDP_WB_CTL_TYPE_LINE; |
| } |
| 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(&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, 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->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; |
| } |
| } |
| |
| 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); |
| } |
| |
| 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, bool handoff) |
| { |
| struct mdss_mdp_ctl *sctl; |
| struct mdss_data_type *mdata = mdss_mdp_get_mdata(); |
| 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); |
| |
| /* |
| * keep power_on false during handoff to avoid unexpected |
| * operations to overlay. |
| */ |
| if (!handoff) |
| 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, handoff); |
| if (ret == 0) { |
| if (sctl) { /* split display is 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, 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_hist_intr_setup(&mdata->hist_intr, MDSS_IRQ_RESUME); |
| |
| 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; |
| struct mdss_data_type *mdata = mdss_mdp_get_mdata(); |
| u32 off; |
| |
| 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); |
| |
| mdss_mdp_hist_intr_setup(&mdata->hist_intr, MDSS_IRQ_SUSPEND); |
| |
| 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) { |
| off = __mdss_mdp_ctl_get_mixer_off(ctl->mixer_left); |
| mdss_mdp_ctl_write(ctl, off, 0); |
| } |
| |
| if (ctl->mixer_right) { |
| off = __mdss_mdp_ctl_get_mixer_off(ctl->mixer_right); |
| mdss_mdp_ctl_write(ctl, off, 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; |
| } |
| |
| void mdss_mdp_set_roi(struct mdss_mdp_ctl *ctl, |
| struct mdp_display_commit *data) |
| { |
| struct mdss_mdp_img_rect temp_roi, mixer_roi; |
| |
| temp_roi.x = data->roi.x; |
| temp_roi.y = data->roi.y; |
| temp_roi.w = data->roi.w; |
| temp_roi.h = data->roi.h; |
| |
| /* |
| * No Partial Update for: |
| * 1) dual DSI panels |
| * 2) non-cmd mode panels |
| */ |
| if (!temp_roi.w || !temp_roi.h || ctl->mixer_right || |
| (ctl->panel_data->panel_info.type != MIPI_CMD_PANEL) || |
| !ctl->panel_data->panel_info.partial_update_enabled) { |
| temp_roi = (struct mdss_mdp_img_rect) |
| {0, 0, ctl->mixer_left->width, |
| ctl->mixer_left->height}; |
| } |
| |
| ctl->roi_changed = 0; |
| if (((temp_roi.x != ctl->roi.x) || |
| (temp_roi.y != ctl->roi.y)) || |
| ((temp_roi.w != ctl->roi.w) || |
| (temp_roi.h != ctl->roi.h))) { |
| ctl->roi = temp_roi; |
| ctl->roi_changed++; |
| |
| mixer_roi = ctl->mixer_left->roi; |
| if ((mixer_roi.w != temp_roi.w) || |
| (mixer_roi.h != temp_roi.h)) { |
| ctl->mixer_left->roi = temp_roi; |
| ctl->mixer_left->params_changed++; |
| } |
| } |
| pr_debug("ROI requested: [%d, %d, %d, %d]\n", |
| ctl->roi.x, ctl->roi.y, ctl->roi.w, ctl->roi.h); |
| } |
| |
| /* |
| * 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) |
| { |
| u32 status = 1; |
| int cnt = 20; |
| |
| mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_SW_RESET, 1); |
| |
| /* |
| * 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\n", status); |
| cnt--; |
| if (cnt == 0) { |
| pr_err("timeout\n"); |
| return -EAGAIN; |
| } |
| } while (status); |
| |
| return 0; |
| } |
| |
| 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, bg_alpha_enable = 0; |
| u32 fg_alpha = 0, bg_alpha = 0; |
| int stage, secure = 0; |
| int screen_state; |
| int outsize = 0; |
| |
| screen_state = ctl->force_screen_state; |
| |
| if (!mixer) |
| return -ENODEV; |
| |
| pr_debug("setup mixer=%d\n", mixer->num); |
| |
| outsize = (mixer->roi.h << 16) | mixer->roi.w; |
| mdp_mixer_write(mixer, 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]; |
| if (pipe == NULL) { |
| mixercfg = MDSS_MDP_LM_BORDER_COLOR; |
| } else { |
| if (pipe->num == MDSS_MDP_SSPP_VIG3 || |
| pipe->num == MDSS_MDP_SSPP_RGB3) { |
| /* Add 2 to account for Cursor & Border bits */ |
| mixercfg = 1 << ((3 * pipe->num)+2); |
| } else { |
| mixercfg = 1 << (3 * pipe->num); |
| } |
| if (pipe->src_fmt->alpha_enable) |
| bg_alpha_enable = 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); |
| |
| 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 */ |
| blend_color_out |= 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) |
| blend_color_out = 0; |
| |
| mixercfg |= stage << (3 * pipe->num); |
| |
| pr_debug("stg=%d op=%x fg_alpha=%x bg_alpha=%x\n", stage, |
| blend_op, fg_alpha, bg_alpha); |
| 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, |
| fg_alpha); |
| mdp_mixer_write(mixer, off + MDSS_MDP_REG_LM_BLEND_BG_ALPHA, |
| bg_alpha); |
| } |
| |
| if (mixer->cursor_enabled) |
| mixercfg |= MDSS_MDP_LM_CURSOR_OUT; |
| |
| update_mixer: |
| pr_debug("mixer=%d mixer_cfg=%x\n", mixer->num, mixercfg); |
| |
| if (mixer->num == MDSS_MDP_INTF_LAYERMIXER3) |
| ctl->flush_bits |= BIT(20); |
| else |
| ctl->flush_bits |= BIT(6) << mixer->num; |
| |
| mdp_mixer_write(mixer, MDSS_MDP_REG_LM_OP_MODE, blend_color_out); |
| off = __mdss_mdp_ctl_get_mixer_off(mixer); |
| 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; |
| u32 size = len; |
| |
| if ((type == MDSS_MDP_MIXER_TYPE_WRITEBACK) && !mdata->has_wfd_blk) |
| 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->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]; |
| } |
| } |
| |
| /* |
| * Duplicate the last writeback mixer for concurrent line and block mode |
| * operations |
| */ |
| if ((type == MDSS_MDP_MIXER_TYPE_WRITEBACK) && !mdata->has_wfd_blk) |
| 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 *wb_offsets, u32 len) |
| { |
| struct mdss_mdp_ctl *head; |
| struct mutex *shared_lock = NULL; |
| u32 i; |
| u32 size = len; |
| |
| if (!mdata->has_wfd_blk) { |
| 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->mdp_base) + ctl_offsets[i]; |
| head[i].wb_base = (mdata->mdp_base) + wb_offsets[i]; |
| head[i].ref_cnt = 0; |
| } |
| |
| if (!mdata->has_wfd_blk) { |
| 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; |
| } |
| |
| 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 = NULL; |
| if (!ctl || !ctl->mfd) { |
| pr_err("ctl not initialized\n"); |
| return NULL; |
| } |
| |
| mdp5_data = mfd_to_mdp5_data(ctl->mfd); |
| if (!mdp5_data) { |
| pr_err("ctl not initialized\n"); |
| 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 if (pipe->num == MDSS_MDP_SSPP_VIG3 || |
| pipe->num == MDSS_MDP_SSPP_RGB3) |
| ctl->flush_bits |= BIT(pipe->num) << 10; |
| else /* RGB/VIG 0-2 pipes */ |
| 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) |
| { |
| u32 off = 0; |
| if (!mixer) |
| return -EINVAL; |
| |
| mixer->params_changed = 0; |
| |
| /* skip mixer setup for rotator */ |
| if (!mixer->rotator_mode) { |
| mdss_mdp_mixer_setup(mixer->ctl, mixer); |
| } else { |
| off = __mdss_mdp_ctl_get_mixer_off(mixer); |
| mdss_mdp_ctl_write(mixer->ctl, off, 0); |
| } |
| |
| return 0; |
| } |
| |
| int mdss_mdp_ctl_update_fps(struct mdss_mdp_ctl *ctl, int fps) |
| { |
| int ret = 0; |
| |
| if (ctl->config_fps_fnc) |
| ret = ctl->config_fps_fnc(ctl, fps); |
| |
| 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; |
| 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; |
| |
| if (!ctl) { |
| pr_err("invalid ctl\n"); |
| return -ENODEV; |
| } |
| |
| 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; |
| } |
| |
| mutex_lock(&ctl->lock); |
| pr_debug("commit ctl=%d play_cnt=%d\n", ctl->num, ctl->play_cnt); |
| |
| 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 |
| || ctl->force_screen_state) { |
| 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); |
| } |
| } |
| |
| mdss_mdp_ctl_notify(ctl, MDP_NOTIFY_FRAME_READY); |
| |
| if (ctl->wait_pingpong) |
| ctl->wait_pingpong(ctl, NULL); |
| |
| /* 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; |
| } |
| |
| void mdss_mdp_ctl_notifier_register(struct mdss_mdp_ctl *ctl, |
| struct notifier_block *notifier) |
| { |
| blocking_notifier_chain_register(&ctl->notifier_head, notifier); |
| } |
| |
| void mdss_mdp_ctl_notifier_unregister(struct mdss_mdp_ctl *ctl, |
| struct notifier_block *notifier) |
| { |
| blocking_notifier_chain_unregister(&ctl->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 ((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; |
| } |
| |
| /** |
| * @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 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 int __mdss_mdp_mixer_handoff_helper(struct mdss_mdp_mixer *mixer, |
| struct mdss_mdp_pipe *pipe) |
| { |
| int rc = 0; |
| |
| if (!mixer) { |
| rc = -EINVAL; |
| goto error; |
| } |
| |
| if (mixer->stage_pipe[MDSS_MDP_STAGE_UNUSED] != NULL) { |
| pr_err("More than one pipe staged on mixer num %d\n", |
| mixer->num); |
| rc = -EINVAL; |
| goto error; |
| } |
| |
| pr_debug("Staging pipe num %d on mixer num %d\n", |
| pipe->num, mixer->num); |
| mixer->stage_pipe[MDSS_MDP_STAGE_UNUSED] = pipe; |
| pipe->mixer = 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; |
| } |