/* 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 #include #include #include #include #include #include #include #include "mdss.h" #include "mdss_debug.h" #include "mdss_fb.h" #include "mdss_mdp.h" #include "mdss_mdp_rotator.h" #define VSYNC_PERIOD 16 #define BORDERFILL_NDX 0x0BF000BF #define CHECK_BOUNDS(offset, size, max_size) \ (((size) > (max_size)) || ((offset) > ((max_size) - (size)))) static atomic_t ov_active_panels = ATOMIC_INIT(0); static int mdss_mdp_overlay_free_fb_pipe(struct msm_fb_data_type *mfd); static int mdss_mdp_overlay_fb_parse_dt(struct msm_fb_data_type *mfd); static int mdss_mdp_overlay_off(struct msm_fb_data_type *mfd); static int mdss_mdp_overlay_get(struct msm_fb_data_type *mfd, struct mdp_overlay *req) { struct mdss_mdp_pipe *pipe; struct mdss_data_type *mdata = mfd_to_mdata(mfd); pipe = mdss_mdp_pipe_get(mdata, req->id); if (IS_ERR_OR_NULL(pipe)) { pr_err("invalid pipe ndx=%x\n", req->id); return pipe ? PTR_ERR(pipe) : -ENODEV; } *req = pipe->req_data; mdss_mdp_pipe_unmap(pipe); return 0; } static int mdss_mdp_overlay_req_check(struct msm_fb_data_type *mfd, struct mdp_overlay *req, struct mdss_mdp_format_params *fmt) { u32 xres, yres; u32 min_src_size, min_dst_size; int content_secure; struct mdss_data_type *mdata = mfd_to_mdata(mfd); struct mdss_mdp_ctl *ctl = mfd_to_ctl(mfd); xres = mfd->fbi->var.xres; yres = mfd->fbi->var.yres; content_secure = (req->flags & MDP_SECURE_OVERLAY_SESSION); if (!ctl->is_secure && content_secure && (mfd->panel.type == WRITEBACK_PANEL)) { pr_debug("return due to security concerns\n"); return -EPERM; } if (mdata->mdp_rev >= MDSS_MDP_HW_REV_102) { min_src_size = fmt->is_yuv ? 2 : 1; min_dst_size = 1; } else { min_src_size = fmt->is_yuv ? 10 : 5; min_dst_size = 2; } if (req->z_order >= MDSS_MDP_MAX_STAGE) { pr_err("zorder %d out of range\n", req->z_order); return -ERANGE; } if (req->src.width > MAX_IMG_WIDTH || req->src.height > MAX_IMG_HEIGHT || req->src_rect.w < min_src_size || req->src_rect.h < min_src_size || CHECK_BOUNDS(req->src_rect.x, req->src_rect.w, req->src.width) || CHECK_BOUNDS(req->src_rect.y, req->src_rect.h, req->src.height)) { pr_err("invalid source image img wh=%dx%d rect=%d,%d,%d,%d\n", req->src.width, req->src.height, req->src_rect.x, req->src_rect.y, req->src_rect.w, req->src_rect.h); return -EOVERFLOW; } if (req->dst_rect.w < min_dst_size || req->dst_rect.h < min_dst_size) { pr_err("invalid destination resolution (%dx%d)", req->dst_rect.w, req->dst_rect.h); return -EOVERFLOW; } if (req->horz_deci || req->vert_deci) { if (!mdata->has_decimation) { pr_err("No Decimation in MDP V=%x\n", mdata->mdp_rev); return -EINVAL; } else if ((req->horz_deci > MAX_DECIMATION) || (req->vert_deci > MAX_DECIMATION)) { pr_err("Invalid decimation factors horz=%d vert=%d\n", req->horz_deci, req->vert_deci); return -EINVAL; } } if (!(req->flags & MDSS_MDP_ROT_ONLY)) { u32 src_w, src_h, dst_w, dst_h; if ((CHECK_BOUNDS(req->dst_rect.x, req->dst_rect.w, xres) || CHECK_BOUNDS(req->dst_rect.y, req->dst_rect.h, yres))) { pr_err("invalid destination rect=%d,%d,%d,%d\n", req->dst_rect.x, req->dst_rect.y, req->dst_rect.w, req->dst_rect.h); return -EOVERFLOW; } if (req->flags & MDP_ROT_90) { dst_h = req->dst_rect.w; dst_w = req->dst_rect.h; } else { dst_w = req->dst_rect.w; dst_h = req->dst_rect.h; } src_w = req->src_rect.w >> req->horz_deci; src_h = req->src_rect.h >> req->vert_deci; if (src_w > MAX_MIXER_WIDTH) { pr_err("invalid source width=%d HDec=%d\n", req->src_rect.w, req->horz_deci); return -EINVAL; } if ((src_w * MAX_UPSCALE_RATIO) < dst_w) { pr_err("too much upscaling Width %d->%d\n", req->src_rect.w, req->dst_rect.w); return -EINVAL; } if ((src_h * MAX_UPSCALE_RATIO) < dst_h) { pr_err("too much upscaling. Height %d->%d\n", req->src_rect.h, req->dst_rect.h); return -EINVAL; } if (src_w > (dst_w * MAX_DOWNSCALE_RATIO)) { pr_err("too much downscaling. Width %d->%d H Dec=%d\n", src_w, req->dst_rect.w, req->horz_deci); return -EINVAL; } if (src_h > (dst_h * MAX_DOWNSCALE_RATIO)) { pr_err("too much downscaling. Height %d->%d V Dec=%d\n", src_h, req->dst_rect.h, req->vert_deci); return -EINVAL; } if (req->flags & MDP_BWC_EN) { if ((req->src.width != req->src_rect.w) || (req->src.height != req->src_rect.h)) { pr_err("BWC: unequal src img and rect w,h\n"); return -EINVAL; } } if (req->flags & MDP_DEINTERLACE) { if (req->flags & MDP_SOURCE_ROTATED_90) { if ((req->src_rect.w % 4) != 0) { pr_err("interlaced rect not h/4\n"); return -EINVAL; } } else if ((req->src_rect.h % 4) != 0) { pr_err("interlaced rect not h/4\n"); return -EINVAL; } } } else { if (req->flags & MDP_DEINTERLACE) { if ((req->src_rect.h % 4) != 0) { pr_err("interlaced rect h not multiple of 4\n"); return -EINVAL; } } } if (fmt->is_yuv) { if ((req->src_rect.x & 0x1) || (req->src_rect.y & 0x1) || (req->src_rect.w & 0x1) || (req->src_rect.h & 0x1)) { pr_err("invalid odd src resolution or coordinates\n"); return -EINVAL; } } return 0; } static int mdss_mdp_overlay_rotator_setup(struct msm_fb_data_type *mfd, struct mdp_overlay *req) { struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); struct mdss_mdp_rotator_session *rot; struct mdss_mdp_format_params *fmt; int ret = 0; u32 bwc_enabled; pr_debug("rot ctl=%u req id=%x\n", mdp5_data->ctl->num, req->id); fmt = mdss_mdp_get_format_params(req->src.format); if (!fmt) { pr_err("invalid rot format %d\n", req->src.format); return -EINVAL; } ret = mdss_mdp_overlay_req_check(mfd, req, fmt); if (ret) return ret; if (req->id == MSMFB_NEW_REQUEST) { rot = mdss_mdp_rotator_session_alloc(); if (!rot) { pr_err("unable to allocate rotator session\n"); return -ENOMEM; } } else if (req->id & MDSS_MDP_ROT_SESSION_MASK) { rot = mdss_mdp_rotator_session_get(req->id); if (!rot) { pr_err("rotator session=%x not found\n", req->id); return -ENODEV; } } else { pr_err("invalid rotator session id=%x\n", req->id); return -EINVAL; } /* keep only flags of interest to rotator */ rot->flags = req->flags & (MDP_ROT_90 | MDP_FLIP_LR | MDP_FLIP_UD | MDP_SECURE_OVERLAY_SESSION); bwc_enabled = req->flags & MDP_BWC_EN; if (bwc_enabled && !mdp5_data->mdata->has_bwc) { pr_err("BWC is not supported in MDP version %x\n", mdp5_data->mdata->mdp_rev); rot->bwc_mode = 0; } else { rot->bwc_mode = bwc_enabled ? 1 : 0; } rot->format = fmt->format; rot->img_width = req->src.width; rot->img_height = req->src.height; rot->src_rect.x = req->src_rect.x; rot->src_rect.y = req->src_rect.y; rot->src_rect.w = req->src_rect.w; rot->src_rect.h = req->src_rect.h; if (req->flags & MDP_DEINTERLACE) { rot->flags |= MDP_DEINTERLACE; rot->src_rect.h /= 2; } ret = mdss_mdp_rotator_setup(rot); if (ret == 0) { req->id = rot->session_id; } else { pr_err("Unable to setup rotator session\n"); mdss_mdp_rotator_release(rot->session_id); } return ret; } static int __mdp_pipe_tune_perf(struct mdss_mdp_pipe *pipe) { struct mdss_data_type *mdata = pipe->mixer->ctl->mdata; struct mdss_mdp_perf_params perf; int rc; for (;;) { rc = mdss_mdp_perf_calc_pipe(pipe, &perf); if (!rc && (perf.mdp_clk_rate <= mdata->max_mdp_clk_rate)) break; /* * if decimation is available try to reduce minimum clock rate * requirement by applying vertical decimation and reduce * mdp clock requirement */ if (mdata->has_decimation && (pipe->vert_deci < MAX_DECIMATION)) pipe->vert_deci++; else return -EPERM; } return 0; } static int mdss_mdp_overlay_pipe_setup(struct msm_fb_data_type *mfd, struct mdp_overlay *req, struct mdss_mdp_pipe **ppipe) { struct mdss_mdp_format_params *fmt; struct mdss_mdp_pipe *pipe; struct mdss_mdp_mixer *mixer = NULL; u32 pipe_type, mixer_mux, len; struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); struct mdp_histogram_start_req hist; int ret; u32 bwc_enabled; if (mdp5_data->ctl == NULL) return -ENODEV; if (req->flags & MDP_ROT_90) { pr_err("unsupported inline rotation\n"); return -ENOTSUPP; } if ((req->dst_rect.w > MAX_DST_W) || (req->dst_rect.h > MAX_DST_H)) { pr_err("exceeded max mixer supported resolution %dx%d\n", req->dst_rect.w, req->dst_rect.h); return -EOVERFLOW; } if (req->flags & MDSS_MDP_RIGHT_MIXER) mixer_mux = MDSS_MDP_MIXER_MUX_RIGHT; else mixer_mux = MDSS_MDP_MIXER_MUX_LEFT; pr_debug("pipe ctl=%u req id=%x mux=%d\n", mdp5_data->ctl->num, req->id, mixer_mux); if (req->flags & (MDP_SOURCE_ROTATED_90 | MDP_BWC_EN)) req->src.format = mdss_mdp_get_rotator_dst_format(req->src.format); fmt = mdss_mdp_get_format_params(req->src.format); if (!fmt) { pr_err("invalid pipe format %d\n", req->src.format); return -EINVAL; } ret = mdss_mdp_overlay_req_check(mfd, req, fmt); if (ret) return ret; pipe = mdss_mdp_mixer_stage_pipe(mdp5_data->ctl, mixer_mux, req->z_order); if (pipe && pipe->ndx != req->id) { pr_debug("replacing pnum=%d at stage=%d mux=%d\n", pipe->num, req->z_order, mixer_mux); mdss_mdp_mixer_pipe_unstage(pipe); } mixer = mdss_mdp_mixer_get(mdp5_data->ctl, mixer_mux); if (!mixer) { pr_err("unable to get mixer\n"); return -ENODEV; } if (req->id == MSMFB_NEW_REQUEST) { if (req->flags & MDP_OV_PIPE_FORCE_DMA) pipe_type = MDSS_MDP_PIPE_TYPE_DMA; else if (fmt->is_yuv || (req->flags & MDP_OV_PIPE_SHARE)) pipe_type = MDSS_MDP_PIPE_TYPE_VIG; else pipe_type = MDSS_MDP_PIPE_TYPE_RGB; pipe = mdss_mdp_pipe_alloc(mixer, pipe_type); /* VIG pipes can also support RGB format */ if (!pipe && pipe_type == MDSS_MDP_PIPE_TYPE_RGB) { pipe_type = MDSS_MDP_PIPE_TYPE_VIG; pipe = mdss_mdp_pipe_alloc(mixer, pipe_type); } if (pipe == NULL) { pr_err("error allocating pipe\n"); return -ENOMEM; } ret = mdss_mdp_pipe_map(pipe); if (ret) { pr_err("unable to map pipe=%d\n", pipe->num); return ret; } mutex_lock(&mfd->lock); list_add(&pipe->used_list, &mdp5_data->pipes_used); mutex_unlock(&mfd->lock); pipe->mixer = mixer; pipe->mfd = mfd; pipe->play_cnt = 0; } else { pipe = mdss_mdp_pipe_get(mdp5_data->mdata, req->id); if (IS_ERR_OR_NULL(pipe)) { pr_err("invalid pipe ndx=%x\n", req->id); return pipe ? PTR_ERR(pipe) : -ENODEV; } if (pipe->mixer != mixer) { if (!mixer->ctl || (mixer->ctl->mfd != mfd)) { pr_err("Can't switch mixer %d->%d pnum %d!\n", pipe->mixer->num, mixer->num, pipe->num); ret = -EINVAL; goto exit_fail; } pr_debug("switching pipe mixer %d->%d pnum %d\n", pipe->mixer->num, mixer->num, pipe->num); mdss_mdp_mixer_pipe_unstage(pipe); pipe->mixer = mixer; } } pipe->flags = req->flags; bwc_enabled = req->flags & MDP_BWC_EN; if (bwc_enabled && !mdp5_data->mdata->has_bwc) { pr_err("BWC is not supported in MDP version %x\n", mdp5_data->mdata->mdp_rev); pipe->bwc_mode = 0; } else { pipe->bwc_mode = pipe->mixer->rotator_mode ? 0 : (bwc_enabled ? 1 : 0) ; } pipe->img_width = req->src.width & 0x3fff; pipe->img_height = req->src.height & 0x3fff; pipe->src.x = req->src_rect.x; pipe->src.y = req->src_rect.y; pipe->src.w = req->src_rect.w; pipe->src.h = req->src_rect.h; pipe->dst.x = req->dst_rect.x; pipe->dst.y = req->dst_rect.y; pipe->dst.w = req->dst_rect.w; pipe->dst.h = req->dst_rect.h; pipe->horz_deci = req->horz_deci; pipe->vert_deci = req->vert_deci; pipe->src_fmt = fmt; pipe->mixer_stage = req->z_order; pipe->is_fg = req->is_fg; pipe->alpha = req->alpha; pipe->transp = req->transp_mask; pipe->overfetch_disable = fmt->is_yuv && !(pipe->flags & MDP_SOURCE_ROTATED_90); pipe->req_data = *req; if (pipe->flags & MDP_OVERLAY_PP_CFG_EN) { memcpy(&pipe->pp_cfg, &req->overlay_pp_cfg, sizeof(struct mdp_overlay_pp_params)); len = pipe->pp_cfg.igc_cfg.len; if ((pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_IGC_CFG) && (len == IGC_LUT_ENTRIES)) { ret = copy_from_user(pipe->pp_res.igc_c0_c1, pipe->pp_cfg.igc_cfg.c0_c1_data, sizeof(uint32_t) * len); if (ret) return -ENOMEM; ret = copy_from_user(pipe->pp_res.igc_c2, pipe->pp_cfg.igc_cfg.c2_data, sizeof(uint32_t) * len); if (ret) return -ENOMEM; pipe->pp_cfg.igc_cfg.c0_c1_data = pipe->pp_res.igc_c0_c1; pipe->pp_cfg.igc_cfg.c2_data = pipe->pp_res.igc_c2; } if (pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_HIST_CFG) { if (pipe->pp_cfg.hist_cfg.ops & MDP_PP_OPS_ENABLE) { hist.block = pipe->pp_cfg.hist_cfg.block; hist.frame_cnt = pipe->pp_cfg.hist_cfg.frame_cnt; hist.bit_mask = pipe->pp_cfg.hist_cfg.bit_mask; hist.num_bins = pipe->pp_cfg.hist_cfg.num_bins; mdss_mdp_histogram_start(pipe->mixer->ctl, &hist); } else if (pipe->pp_cfg.hist_cfg.ops & MDP_PP_OPS_DISABLE) { mdss_mdp_histogram_stop(pipe->mixer->ctl, pipe->pp_cfg.hist_cfg.block); } } len = pipe->pp_cfg.hist_lut_cfg.len; if ((pipe->pp_cfg.config_ops & MDP_OVERLAY_PP_HIST_LUT_CFG) && (len == ENHIST_LUT_ENTRIES)) { ret = copy_from_user(pipe->pp_res.hist_lut, pipe->pp_cfg.hist_lut_cfg.data, sizeof(uint32_t) * len); if (ret) return -ENOMEM; pipe->pp_cfg.hist_lut_cfg.data = pipe->pp_res.hist_lut; } } if (pipe->flags & MDP_DEINTERLACE) { if (pipe->flags & MDP_SOURCE_ROTATED_90) { pipe->src.w /= 2; pipe->img_width /= 2; } else { pipe->src.h /= 2; } } ret = __mdp_pipe_tune_perf(pipe); if (ret) { pr_debug("unable to satisfy performance. ret=%d\n", ret); goto exit_fail; } ret = mdss_mdp_smp_reserve(pipe); if (ret) { pr_debug("mdss_mdp_smp_reserve failed. ret=%d\n", ret); goto exit_fail; } pipe->params_changed++; req->id = pipe->ndx; req->vert_deci = pipe->vert_deci; *ppipe = pipe; mdss_mdp_pipe_unmap(pipe); return ret; exit_fail: mdss_mdp_pipe_unmap(pipe); mutex_lock(&mfd->lock); if (pipe->play_cnt == 0) { pr_debug("failed for pipe %d\n", pipe->num); list_del(&pipe->used_list); mdss_mdp_pipe_destroy(pipe); } /* invalidate any overlays in this framebuffer after failure */ list_for_each_entry(pipe, &mdp5_data->pipes_used, used_list) { pr_debug("freeing allocations for pipe %d\n", pipe->num); mdss_mdp_smp_unreserve(pipe); pipe->params_changed = 0; } mutex_unlock(&mfd->lock); return ret; } static int mdss_mdp_overlay_set(struct msm_fb_data_type *mfd, struct mdp_overlay *req) { struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); int ret; ret = mutex_lock_interruptible(&mdp5_data->ov_lock); if (ret) return ret; if (!mfd->panel_power_on) { mutex_unlock(&mdp5_data->ov_lock); return -EPERM; } if (req->flags & MDSS_MDP_ROT_ONLY) { ret = mdss_mdp_overlay_rotator_setup(mfd, req); } else if (req->src.format == MDP_RGB_BORDERFILL) { req->id = BORDERFILL_NDX; } else { struct mdss_mdp_pipe *pipe; /* userspace zorder start with stage 0 */ req->z_order += MDSS_MDP_STAGE_0; ret = mdss_mdp_overlay_pipe_setup(mfd, req, &pipe); req->z_order -= MDSS_MDP_STAGE_0; } mutex_unlock(&mdp5_data->ov_lock); return ret; } static inline int mdss_mdp_overlay_get_buf(struct msm_fb_data_type *mfd, struct mdss_mdp_data *data, struct msmfb_data *planes, int num_planes, u32 flags) { int i, rc = 0; if ((num_planes <= 0) || (num_planes > MAX_PLANES)) return -EINVAL; memset(data, 0, sizeof(*data)); for (i = 0; i < num_planes; i++) { data->p[i].flags = flags; rc = mdss_mdp_get_img(&planes[i], &data->p[i]); if (rc) { pr_err("failed to map buf p=%d flags=%x\n", i, flags); while (i > 0) { i--; mdss_mdp_put_img(&data->p[i]); } break; } } data->num_planes = i; return rc; } static inline int mdss_mdp_overlay_free_buf(struct mdss_mdp_data *data) { int i; for (i = 0; i < data->num_planes && data->p[i].len; i++) mdss_mdp_put_img(&data->p[i]); data->num_planes = 0; return 0; } static void mdss_mdp_overlay_cleanup(struct msm_fb_data_type *mfd) { struct mdss_mdp_pipe *pipe, *tmp; struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); LIST_HEAD(destroy_pipes); mutex_lock(&mfd->lock); list_for_each_entry_safe(pipe, tmp, &mdp5_data->pipes_cleanup, cleanup_list) { list_move(&pipe->cleanup_list, &destroy_pipes); mdss_mdp_overlay_free_buf(&pipe->back_buf); mdss_mdp_overlay_free_buf(&pipe->front_buf); pipe->mfd = NULL; } list_for_each_entry(pipe, &mdp5_data->pipes_used, used_list) { if (pipe->back_buf.num_planes) { /* make back buffer active */ mdss_mdp_overlay_free_buf(&pipe->front_buf); swap(pipe->back_buf, pipe->front_buf); } } mutex_unlock(&mfd->lock); list_for_each_entry_safe(pipe, tmp, &destroy_pipes, cleanup_list) mdss_mdp_pipe_destroy(pipe); } static int mdss_mdp_overlay_start(struct msm_fb_data_type *mfd) { int rc; struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); if (mdp5_data->ctl->power_on) return 0; pr_debug("starting fb%d overlay\n", mfd->index); rc = pm_runtime_get_sync(&mfd->pdev->dev); if (IS_ERR_VALUE(rc)) { pr_err("unable to resume with pm_runtime_get_sync rc=%d\n", rc); return rc; } if (mfd->panel_info->cont_splash_enabled) { mdss_mdp_ctl_splash_finish(mdp5_data->ctl); mdss_mdp_footswitch_ctrl_splash(0); } if (!is_mdss_iommu_attached()) { mdss_iommu_attach(mdss_res); mdss_hw_init(mdss_res); } rc = mdss_mdp_ctl_start(mdp5_data->ctl); if (rc == 0) { atomic_inc(&ov_active_panels); } else { pr_err("overlay start failed.\n"); mdss_mdp_ctl_destroy(mdp5_data->ctl); mdp5_data->ctl = NULL; pm_runtime_put(&mfd->pdev->dev); } return rc; } static void mdss_mdp_overlay_update_pm(struct mdss_overlay_private *mdp5_data) { ktime_t wakeup_time; if (!mdp5_data->cpu_pm_hdl) return; if (mdss_mdp_display_wakeup_time(mdp5_data->ctl, &wakeup_time)) return; activate_event_timer(mdp5_data->cpu_pm_hdl, wakeup_time); } int mdss_mdp_overlay_kickoff(struct msm_fb_data_type *mfd) { struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); struct mdss_mdp_pipe *pipe, *next; struct mdss_mdp_ctl *ctl = mfd_to_ctl(mfd); int ret; mutex_lock(&mdp5_data->ov_lock); mutex_lock(&mfd->lock); ret = mdss_mdp_display_wait4pingpong(mdp5_data->ctl); if (ret) { mutex_unlock(&mfd->lock); mutex_unlock(&mdp5_data->ov_lock); return ret; } list_for_each_entry_safe(pipe, next, &mdp5_data->pipes_used, used_list) { struct mdss_mdp_data *buf; if (pipe->back_buf.num_planes) { buf = &pipe->back_buf; } else if (ctl->play_cnt == 0) { pipe->params_changed++; buf = &pipe->front_buf; } else if (!pipe->params_changed) { if (pipe->mixer) { if (!mdss_mdp_pipe_is_staged(pipe)) { list_del(&pipe->used_list); list_add(&pipe->cleanup_list, &mdp5_data->pipes_cleanup); } } continue; } else if (pipe->front_buf.num_planes) { buf = &pipe->front_buf; } else { pr_warn("pipe queue w/o buffer. unstaging layer\n"); pipe->params_changed = 0; mdss_mdp_mixer_pipe_unstage(pipe); continue; } ret = mdss_mdp_pipe_queue_data(pipe, buf); if (IS_ERR_VALUE(ret)) { pr_warn("Unable to queue data for pnum=%d\n", pipe->num); mdss_mdp_mixer_pipe_unstage(pipe); } } if (mfd->panel.type == WRITEBACK_PANEL) ret = mdss_mdp_wb_kickoff(mfd); else ret = mdss_mdp_display_commit(mdp5_data->ctl, NULL); mutex_unlock(&mfd->lock); if (IS_ERR_VALUE(ret)) goto commit_fail; mdss_mdp_overlay_update_pm(mdp5_data); ret = mdss_mdp_display_wait4comp(mdp5_data->ctl); mdss_fb_update_notify_update(mfd); commit_fail: mdss_mdp_overlay_cleanup(mfd); mutex_unlock(&mdp5_data->ov_lock); return ret; } static int mdss_mdp_overlay_release(struct msm_fb_data_type *mfd, int ndx) { struct mdss_mdp_pipe *pipe; struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); u32 pipe_ndx, unset_ndx = 0; int i; for (i = 0; unset_ndx != ndx && i < MDSS_MDP_MAX_SSPP; i++) { pipe_ndx = BIT(i); if (pipe_ndx & ndx) { unset_ndx |= pipe_ndx; pipe = mdss_mdp_pipe_get(mdp5_data->mdata, pipe_ndx); if (IS_ERR_OR_NULL(pipe)) { pr_warn("unknown pipe ndx=%x\n", pipe_ndx); continue; } mutex_lock(&mfd->lock); list_del(&pipe->used_list); list_add(&pipe->cleanup_list, &mdp5_data->pipes_cleanup); mutex_unlock(&mfd->lock); mdss_mdp_mixer_pipe_unstage(pipe); mdss_mdp_pipe_unmap(pipe); } } return 0; } static int mdss_mdp_overlay_unset(struct msm_fb_data_type *mfd, int ndx) { int ret = 0; struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); if (!mfd || !mdp5_data->ctl) return -ENODEV; ret = mutex_lock_interruptible(&mdp5_data->ov_lock); if (ret) return ret; if (ndx == BORDERFILL_NDX) { pr_debug("borderfill disable\n"); mdp5_data->borderfill_enable = false; ret = 0; goto done; } if (!mfd->panel_power_on) { ret = -EPERM; goto done; } pr_debug("unset ndx=%x\n", ndx); if (ndx & MDSS_MDP_ROT_SESSION_MASK) ret = mdss_mdp_rotator_release(ndx); else ret = mdss_mdp_overlay_release(mfd, ndx); done: mutex_unlock(&mdp5_data->ov_lock); return ret; } static int mdss_mdp_overlay_release_all(struct msm_fb_data_type *mfd) { struct mdss_mdp_pipe *pipe; struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); u32 unset_ndx = 0; int cnt = 0; mutex_lock(&mdp5_data->ov_lock); mutex_lock(&mfd->lock); list_for_each_entry(pipe, &mdp5_data->pipes_used, used_list) { unset_ndx |= pipe->ndx; cnt++; } if (cnt == 0 && !list_empty(&mdp5_data->pipes_cleanup)) { pr_debug("overlay release on fb%d called without commit!", mfd->index); cnt++; } mutex_unlock(&mfd->lock); if (unset_ndx) { pr_debug("%d pipes need cleanup (%x)\n", cnt, unset_ndx); mdss_mdp_overlay_release(mfd, unset_ndx); } mutex_unlock(&mdp5_data->ov_lock); if (cnt) mfd->mdp.kickoff_fnc(mfd); return 0; } static int mdss_mdp_overlay_play_wait(struct msm_fb_data_type *mfd, struct msmfb_overlay_data *req) { int ret = 0; if (!mfd) return -ENODEV; ret = mfd->mdp.kickoff_fnc(mfd); if (!ret) pr_err("error displaying\n"); return ret; } static int mdss_mdp_overlay_rotate(struct msm_fb_data_type *mfd, struct msmfb_overlay_data *req) { struct mdss_mdp_rotator_session *rot; struct mdss_mdp_data src_data, dst_data; int ret; u32 flgs; rot = mdss_mdp_rotator_session_get(req->id); if (!rot) { pr_err("invalid session id=%x\n", req->id); return -ENOENT; } flgs = rot->flags & MDP_SECURE_OVERLAY_SESSION; ret = mdss_mdp_overlay_get_buf(mfd, &src_data, &req->data, 1, flgs); if (ret) { pr_err("src_data pmem error\n"); return ret; } ret = mdss_mdp_overlay_get_buf(mfd, &dst_data, &req->dst_data, 1, flgs); if (ret) { pr_err("dst_data pmem error\n"); goto dst_buf_fail; } ret = mdss_mdp_rotator_queue(rot, &src_data, &dst_data); if (ret) pr_err("rotator queue error session id=%x\n", req->id); mdss_mdp_overlay_free_buf(&dst_data); dst_buf_fail: mdss_mdp_overlay_free_buf(&src_data); return ret; } static int mdss_mdp_overlay_queue(struct msm_fb_data_type *mfd, struct msmfb_overlay_data *req) { struct mdss_mdp_pipe *pipe; struct mdss_mdp_data *src_data; int ret; u32 flags; struct mdss_data_type *mdata = mfd_to_mdata(mfd); pipe = mdss_mdp_pipe_get(mdata, req->id); if (IS_ERR_OR_NULL(pipe)) { pr_err("pipe ndx=%x doesn't exist\n", req->id); return pipe ? PTR_ERR(pipe) : -ENODEV; } pr_debug("ov queue pnum=%d\n", pipe->num); flags = (pipe->flags & MDP_SECURE_OVERLAY_SESSION); src_data = &pipe->back_buf; if (src_data->num_planes) { pr_warn("dropped buffer pnum=%d play=%d addr=0x%x\n", pipe->num, pipe->play_cnt, src_data->p[0].addr); mdss_mdp_overlay_free_buf(src_data); } ret = mdss_mdp_overlay_get_buf(mfd, src_data, &req->data, 1, flags); if (IS_ERR_VALUE(ret)) { pr_err("src_data pmem error\n"); } mdss_mdp_pipe_unmap(pipe); return ret; } static void mdss_mdp_overlay_force_cleanup(struct msm_fb_data_type *mfd) { struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); struct mdss_mdp_ctl *ctl = mdp5_data->ctl; int ret; pr_debug("forcing cleanup to unset dma pipes on fb%d\n", mfd->index); /* * video mode panels require the layer to be unstaged and wait for * vsync to be able to release buffer. */ if (ctl && ctl->is_video_mode) { ret = mdss_mdp_display_commit(ctl, NULL); if (!IS_ERR_VALUE(ret)) mdss_mdp_display_wait4comp(ctl); } mdss_mdp_overlay_cleanup(mfd); } static void mdss_mdp_overlay_force_dma_cleanup(struct mdss_data_type *mdata) { struct mdss_mdp_pipe *pipe; int i; for (i = 0; i < mdata->ndma_pipes; i++) { pipe = mdata->dma_pipes + i; if (atomic_read(&pipe->ref_cnt) && pipe->mfd) mdss_mdp_overlay_force_cleanup(pipe->mfd); } } static int mdss_mdp_overlay_play(struct msm_fb_data_type *mfd, struct msmfb_overlay_data *req) { struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); int ret = 0; pr_debug("play req id=%x\n", req->id); ret = mutex_lock_interruptible(&mdp5_data->ov_lock); if (ret) return ret; if (!mfd->panel_power_on) { ret = -EPERM; goto done; } ret = mdss_mdp_overlay_start(mfd); if (ret) { pr_err("unable to start overlay %d (%d)\n", mfd->index, ret); goto done; } if (req->id & MDSS_MDP_ROT_SESSION_MASK) { mdss_mdp_overlay_force_dma_cleanup(mfd_to_mdata(mfd)); ret = mdss_mdp_overlay_rotate(mfd, req); } else if (req->id == BORDERFILL_NDX) { pr_debug("borderfill enable\n"); mdp5_data->borderfill_enable = true; ret = mdss_mdp_overlay_free_fb_pipe(mfd); } else { ret = mdss_mdp_overlay_queue(mfd, req); } done: mutex_unlock(&mdp5_data->ov_lock); return ret; } static int mdss_mdp_overlay_free_fb_pipe(struct msm_fb_data_type *mfd) { struct mdss_mdp_pipe *pipe; u32 fb_ndx = 0; struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); pipe = mdss_mdp_mixer_stage_pipe(mdp5_data->ctl, MDSS_MDP_MIXER_MUX_LEFT, MDSS_MDP_STAGE_BASE); if (pipe) fb_ndx |= pipe->ndx; pipe = mdss_mdp_mixer_stage_pipe(mdp5_data->ctl, MDSS_MDP_MIXER_MUX_RIGHT, MDSS_MDP_STAGE_BASE); if (pipe) fb_ndx |= pipe->ndx; if (fb_ndx) { pr_debug("unstaging framebuffer pipes %x\n", fb_ndx); mdss_mdp_overlay_release(mfd, fb_ndx); } return 0; } static int mdss_mdp_overlay_get_fb_pipe(struct msm_fb_data_type *mfd, struct mdss_mdp_pipe **ppipe, int mixer_mux) { struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); struct mdss_mdp_pipe *pipe; pipe = mdss_mdp_mixer_stage_pipe(mdp5_data->ctl, mixer_mux, MDSS_MDP_STAGE_BASE); if (pipe == NULL) { struct mdp_overlay req; struct fb_info *fbi = mfd->fbi; struct mdss_mdp_mixer *mixer; int ret, bpp; mixer = mdss_mdp_mixer_get(mdp5_data->ctl, MDSS_MDP_MIXER_MUX_LEFT); if (!mixer) { pr_err("unable to retrieve mixer\n"); return -ENODEV; } memset(&req, 0, sizeof(req)); bpp = fbi->var.bits_per_pixel / 8; req.id = MSMFB_NEW_REQUEST; req.src.format = mfd->fb_imgType; req.src.height = fbi->var.yres; req.src.width = fbi->fix.line_length / bpp; if (mixer_mux == MDSS_MDP_MIXER_MUX_RIGHT) { if (req.src.width <= mixer->width) { pr_warn("right fb pipe not needed\n"); return -EINVAL; } req.flags |= MDSS_MDP_RIGHT_MIXER; req.src_rect.x = mixer->width; req.src_rect.w = fbi->var.xres - mixer->width; } else { req.src_rect.x = 0; req.src_rect.w = MIN(fbi->var.xres, mixer->width); } req.src_rect.y = 0; req.src_rect.h = req.src.height; req.dst_rect.x = 0; req.dst_rect.y = 0; req.dst_rect.w = req.src_rect.w; req.dst_rect.h = req.src_rect.h; req.z_order = MDSS_MDP_STAGE_BASE; pr_debug("allocating base pipe mux=%d\n", mixer_mux); ret = mdss_mdp_overlay_pipe_setup(mfd, &req, &pipe); if (ret) return ret; pr_debug("ctl=%d pnum=%d\n", mdp5_data->ctl->num, pipe->num); } *ppipe = pipe; return 0; } static void mdss_mdp_overlay_pan_display(struct msm_fb_data_type *mfd) { struct mdss_mdp_data data; struct mdss_mdp_pipe *pipe; struct fb_info *fbi; struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); u32 offset; int bpp, ret; if (!mfd || !mdp5_data->ctl) return; fbi = mfd->fbi; if (!fbi->fix.smem_start || fbi->fix.smem_len == 0 || mdp5_data->borderfill_enable) { mfd->mdp.kickoff_fnc(mfd); return; } if (mutex_lock_interruptible(&mdp5_data->ov_lock)) return; if (!mfd->panel_power_on) { mutex_unlock(&mdp5_data->ov_lock); return; } memset(&data, 0, sizeof(data)); bpp = fbi->var.bits_per_pixel / 8; offset = fbi->var.xoffset * bpp + fbi->var.yoffset * fbi->fix.line_length; if (offset > fbi->fix.smem_len) { pr_err("invalid fb offset=%u total length=%u\n", offset, fbi->fix.smem_len); goto pan_display_error; } ret = mdss_mdp_overlay_start(mfd); if (ret) { pr_err("unable to start overlay %d (%d)\n", mfd->index, ret); goto pan_display_error; } if (is_mdss_iommu_attached()) { if (!mfd->iova) { pr_err("mfd iova is zero\n"); goto pan_display_error; } data.p[0].addr = mfd->iova; } else data.p[0].addr = fbi->fix.smem_start; data.p[0].addr += offset; data.p[0].len = fbi->fix.smem_len - offset; data.num_planes = 1; ret = mdss_mdp_overlay_get_fb_pipe(mfd, &pipe, MDSS_MDP_MIXER_MUX_LEFT); if (ret) { pr_err("unable to allocate base pipe\n"); goto pan_display_error; } if (mdss_mdp_pipe_map(pipe)) { pr_err("unable to map base pipe\n"); goto pan_display_error; } ret = mdss_mdp_pipe_queue_data(pipe, &data); mdss_mdp_pipe_unmap(pipe); if (ret) { pr_err("unable to queue data\n"); goto pan_display_error; } if (fbi->var.xres > MAX_MIXER_WIDTH || mfd->split_display) { ret = mdss_mdp_overlay_get_fb_pipe(mfd, &pipe, MDSS_MDP_MIXER_MUX_RIGHT); if (ret) { pr_err("unable to allocate right base pipe\n"); goto pan_display_error; } if (mdss_mdp_pipe_map(pipe)) { pr_err("unable to map right base pipe\n"); goto pan_display_error; } ret = mdss_mdp_pipe_queue_data(pipe, &data); mdss_mdp_pipe_unmap(pipe); if (ret) { pr_err("unable to queue right data\n"); goto pan_display_error; } } mutex_unlock(&mdp5_data->ov_lock); if ((fbi->var.activate & FB_ACTIVATE_VBL) || (fbi->var.activate & FB_ACTIVATE_FORCE)) mfd->mdp.kickoff_fnc(mfd); return; pan_display_error: mutex_unlock(&mdp5_data->ov_lock); } /* function is called in irq context should have minimum processing */ static void mdss_mdp_overlay_handle_vsync(struct mdss_mdp_ctl *ctl, ktime_t t) { struct msm_fb_data_type *mfd = ctl->mfd; struct mdss_overlay_private *mdp5_data; if (!mfd || !mfd->mdp.private1) { pr_warn("Invalid handle for vsync\n"); return; } mdp5_data = mfd_to_mdp5_data(mfd); pr_debug("vsync on fb%d play_cnt=%d\n", mfd->index, ctl->play_cnt); spin_lock(&mdp5_data->vsync_lock); mdp5_data->vsync_time = t; complete(&mdp5_data->vsync_comp); spin_unlock(&mdp5_data->vsync_lock); } int mdss_mdp_overlay_vsync_ctrl(struct msm_fb_data_type *mfd, int en) { struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); struct mdss_mdp_ctl *ctl = mfd_to_ctl(mfd); unsigned long flags; int rc; if (!ctl) return -ENODEV; if (!ctl->add_vsync_handler || !ctl->remove_vsync_handler) return -ENOTSUPP; rc = mutex_lock_interruptible(&ctl->lock); if (rc) return rc; if (!ctl->power_on) { pr_debug("fb%d vsync pending first update en=%d\n", mfd->index, en); mdp5_data->vsync_pending = en; mutex_unlock(&ctl->lock); return 0; } pr_debug("fb%d vsync en=%d\n", mfd->index, en); spin_lock_irqsave(&mdp5_data->vsync_lock, flags); INIT_COMPLETION(mdp5_data->vsync_comp); spin_unlock_irqrestore(&mdp5_data->vsync_lock, flags); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false); if (en) rc = ctl->add_vsync_handler(ctl, &ctl->vsync_handler); else rc = ctl->remove_vsync_handler(ctl, &ctl->vsync_handler); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false); mutex_unlock(&ctl->lock); return rc; } static ssize_t mdss_mdp_vsync_show_event(struct device *dev, struct device_attribute *attr, char *buf) { struct fb_info *fbi = dev_get_drvdata(dev); struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par; struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); unsigned long flags; u64 vsync_ticks; int ret; if (!mdp5_data->ctl || !mdp5_data->ctl->power_on) return 0; ret = wait_for_completion_interruptible(&mdp5_data->vsync_comp); if (ret < 0) return ret; spin_lock_irqsave(&mdp5_data->vsync_lock, flags); vsync_ticks = ktime_to_ns(mdp5_data->vsync_time); spin_unlock_irqrestore(&mdp5_data->vsync_lock, flags); pr_debug("fb%d vsync=%llu", mfd->index, vsync_ticks); ret = snprintf(buf, PAGE_SIZE, "VSYNC=%llu", vsync_ticks); return ret; } static DEVICE_ATTR(vsync_event, S_IRUGO, mdss_mdp_vsync_show_event, NULL); static struct attribute *vsync_fs_attrs[] = { &dev_attr_vsync_event.attr, NULL, }; static struct attribute_group vsync_fs_attr_group = { .attrs = vsync_fs_attrs, }; static int mdss_mdp_hw_cursor_update(struct msm_fb_data_type *mfd, struct fb_cursor *cursor) { struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); struct mdss_mdp_mixer *mixer; struct fb_image *img = &cursor->image; u32 blendcfg; int off, ret = 0; if (!mfd->cursor_buf && (cursor->set & FB_CUR_SETIMAGE)) { mfd->cursor_buf = dma_alloc_coherent(NULL, MDSS_MDP_CURSOR_SIZE, (dma_addr_t *) &mfd->cursor_buf_phys, GFP_KERNEL); if (!mfd->cursor_buf) { pr_err("can't allocate cursor buffer\n"); return -ENOMEM; } ret = msm_iommu_map_contig_buffer(mfd->cursor_buf_phys, mdss_get_iommu_domain(MDSS_IOMMU_DOMAIN_UNSECURE), 0, MDSS_MDP_CURSOR_SIZE, SZ_4K, 0, &(mfd->cursor_buf_iova)); if (IS_ERR_VALUE(ret)) { dma_free_coherent(NULL, MDSS_MDP_CURSOR_SIZE, mfd->cursor_buf, (dma_addr_t) mfd->cursor_buf_phys); pr_err("unable to map cursor buffer to iommu(%d)\n", ret); return -ENOMEM; } } mixer = mdss_mdp_mixer_get(mdp5_data->ctl, MDSS_MDP_MIXER_MUX_DEFAULT); off = MDSS_MDP_REG_LM_OFFSET(mixer->num); if ((img->width > MDSS_MDP_CURSOR_WIDTH) || (img->height > MDSS_MDP_CURSOR_HEIGHT) || (img->depth != 32)) return -EINVAL; pr_debug("mixer=%d enable=%x set=%x\n", mixer->num, cursor->enable, cursor->set); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false); blendcfg = MDSS_MDP_REG_READ(off + MDSS_MDP_REG_LM_CURSOR_BLEND_CONFIG); if (cursor->set & FB_CUR_SETPOS) MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_START_XY, (img->dy << 16) | img->dx); if (cursor->set & FB_CUR_SETIMAGE) { int calpha_en, transp_en, alpha, size, cursor_addr; ret = copy_from_user(mfd->cursor_buf, img->data, img->width * img->height * 4); if (ret) return ret; if (is_mdss_iommu_attached()) cursor_addr = mfd->cursor_buf_iova; else cursor_addr = mfd->cursor_buf_phys; if (img->bg_color == 0xffffffff) transp_en = 0; else transp_en = 1; alpha = (img->fg_color & 0xff000000) >> 24; if (alpha) calpha_en = 0x0; /* xrgb */ else calpha_en = 0x2; /* argb */ size = (img->height << 16) | img->width; MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_IMG_SIZE, size); MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_SIZE, size); MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_STRIDE, img->width * 4); MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_BASE_ADDR, cursor_addr); wmb(); blendcfg &= ~0x1; blendcfg |= (transp_en << 3) | (calpha_en << 1); MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_BLEND_CONFIG, blendcfg); if (calpha_en) MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_BLEND_PARAM, alpha); if (transp_en) { MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_BLEND_TRANSP_LOW0, ((img->bg_color & 0xff00) << 8) | (img->bg_color & 0xff)); MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_BLEND_TRANSP_LOW1, ((img->bg_color & 0xff0000) >> 16)); MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_BLEND_TRANSP_HIGH0, ((img->bg_color & 0xff00) << 8) | (img->bg_color & 0xff)); MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_BLEND_TRANSP_HIGH1, ((img->bg_color & 0xff0000) >> 16)); } } if (!cursor->enable != !(blendcfg & 0x1)) { if (cursor->enable) { pr_debug("enable hw cursor on mixer=%d\n", mixer->num); blendcfg |= 0x1; } else { pr_debug("disable hw cursor on mixer=%d\n", mixer->num); blendcfg &= ~0x1; } MDSS_MDP_REG_WRITE(off + MDSS_MDP_REG_LM_CURSOR_BLEND_CONFIG, blendcfg); mixer->cursor_enabled = cursor->enable; mixer->params_changed++; } mixer->ctl->flush_bits |= BIT(6) << mixer->num; mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false); return 0; } static int mdss_bl_scale_config(struct msm_fb_data_type *mfd, struct mdp_bl_scale_data *data) { int ret = 0; int curr_bl; mutex_lock(&mfd->bl_lock); curr_bl = mfd->bl_level; mfd->bl_scale = data->scale; mfd->bl_min_lvl = data->min_lvl; pr_debug("update scale = %d, min_lvl = %d\n", mfd->bl_scale, mfd->bl_min_lvl); /* update current backlight to use new scaling*/ mdss_fb_set_backlight(mfd, curr_bl); mutex_unlock(&mfd->bl_lock); return ret; } static int mdss_mdp_pp_ioctl(struct msm_fb_data_type *mfd, void __user *argp) { int ret; struct msmfb_mdp_pp mdp_pp; u32 copyback = 0; u32 copy_from_kernel = 0; struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); ret = copy_from_user(&mdp_pp, argp, sizeof(mdp_pp)); if (ret) return ret; switch (mdp_pp.op) { case mdp_op_pa_cfg: ret = mdss_mdp_pa_config(mdp5_data->ctl, &mdp_pp.data.pa_cfg_data, ©back); break; case mdp_op_pcc_cfg: ret = mdss_mdp_pcc_config(mdp5_data->ctl, &mdp_pp.data.pcc_cfg_data, ©back); break; case mdp_op_lut_cfg: switch (mdp_pp.data.lut_cfg_data.lut_type) { case mdp_lut_igc: ret = mdss_mdp_igc_lut_config( mdp5_data->ctl, (struct mdp_igc_lut_data *) &mdp_pp.data.lut_cfg_data.data, ©back, copy_from_kernel); break; case mdp_lut_pgc: ret = mdss_mdp_argc_config( mdp5_data->ctl, &mdp_pp.data.lut_cfg_data.data.pgc_lut_data, ©back); break; case mdp_lut_hist: ret = mdss_mdp_hist_lut_config( mdp5_data->ctl, (struct mdp_hist_lut_data *) &mdp_pp.data.lut_cfg_data.data, ©back); break; default: ret = -ENOTSUPP; break; } break; case mdp_op_dither_cfg: ret = mdss_mdp_dither_config( mdp5_data->ctl, &mdp_pp.data.dither_cfg_data, ©back); break; case mdp_op_gamut_cfg: ret = mdss_mdp_gamut_config( mdp5_data->ctl, &mdp_pp.data.gamut_cfg_data, ©back); break; case mdp_bl_scale_cfg: ret = mdss_bl_scale_config(mfd, (struct mdp_bl_scale_data *) &mdp_pp.data.bl_scale_data); break; case mdp_op_ad_cfg: ret = mdss_mdp_ad_config(mfd, &mdp_pp.data.ad_init_cfg); break; case mdp_op_ad_input: ret = mdss_mdp_ad_input(mfd, &mdp_pp.data.ad_input, 1); if (ret > 0) { ret = 0; copyback = 1; } break; case mdp_op_calib_cfg: ret = mdss_mdp_calib_config((struct mdp_calib_config_data *) &mdp_pp.data.calib_cfg, ©back); break; case mdp_op_calib_mode: ret = mdss_mdp_calib_mode(mfd, &mdp_pp.data.mdss_calib_cfg); break; default: pr_err("Unsupported request to MDP_PP IOCTL. %d = op\n", mdp_pp.op); ret = -EINVAL; break; } if ((ret == 0) && copyback) ret = copy_to_user(argp, &mdp_pp, sizeof(struct msmfb_mdp_pp)); return ret; } static int mdss_mdp_histo_ioctl(struct msm_fb_data_type *mfd, u32 cmd, void __user *argp) { int ret = -ENOSYS; struct mdp_histogram_data hist; struct mdp_histogram_start_req hist_req; u32 block; struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); switch (cmd) { case MSMFB_HISTOGRAM_START: if (!mfd->panel_power_on) return -EPERM; ret = copy_from_user(&hist_req, argp, sizeof(hist_req)); if (ret) return ret; ret = mdss_mdp_histogram_start(mdp5_data->ctl, &hist_req); break; case MSMFB_HISTOGRAM_STOP: ret = copy_from_user(&block, argp, sizeof(int)); if (ret) return ret; ret = mdss_mdp_histogram_stop(mdp5_data->ctl, block); break; case MSMFB_HISTOGRAM: if (!mfd->panel_power_on) return -EPERM; ret = copy_from_user(&hist, argp, sizeof(hist)); if (ret) return ret; ret = mdss_mdp_hist_collect(mdp5_data->ctl, &hist); if (!ret) ret = copy_to_user(argp, &hist, sizeof(hist)); break; default: break; } return ret; } static int mdss_fb_set_metadata(struct msm_fb_data_type *mfd, struct msmfb_metadata *metadata) { struct mdss_data_type *mdata = mfd_to_mdata(mfd); int ret = 0; switch (metadata->op) { case metadata_op_vic: if (mfd->panel_info) mfd->panel_info->vic = metadata->data.video_info_code; else ret = -EINVAL; break; case metadata_op_crc: if (!mfd->panel_power_on) return -EPERM; ret = mdss_misr_crc_set(mdata, &metadata->data.misr_request); break; default: pr_warn("unsupported request to MDP META IOCTL\n"); ret = -EINVAL; break; } return ret; } static int mdss_fb_get_hw_caps(struct msm_fb_data_type *mfd, struct mdss_hw_caps *caps) { struct mdss_data_type *mdata = mfd_to_mdata(mfd); caps->mdp_rev = mdata->mdp_rev; caps->vig_pipes = mdata->nvig_pipes; caps->rgb_pipes = mdata->nrgb_pipes; caps->dma_pipes = mdata->ndma_pipes; if (mdata->has_bwc) caps->features |= MDP_BWC_EN; if (mdata->has_decimation) caps->features |= MDP_DECIMATION_EN; return 0; } static int mdss_fb_get_metadata(struct msm_fb_data_type *mfd, struct msmfb_metadata *metadata) { struct mdss_data_type *mdata = mfd_to_mdata(mfd); int ret = 0; switch (metadata->op) { case metadata_op_frame_rate: metadata->data.panel_frame_rate = mdss_panel_get_framerate(mfd->panel_info); break; case metadata_op_get_caps: ret = mdss_fb_get_hw_caps(mfd, &metadata->data.caps); break; case metadata_op_crc: if (!mfd->panel_power_on) return -EPERM; ret = mdss_misr_crc_get(mdata, &metadata->data.misr_request); break; default: pr_warn("Unsupported request to MDP META IOCTL.\n"); ret = -EINVAL; break; } return ret; } static int mdss_mdp_overlay_ioctl_handler(struct msm_fb_data_type *mfd, u32 cmd, void __user *argp) { struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); struct mdp_overlay req; int val, ret = -ENOSYS; struct msmfb_metadata metadata; switch (cmd) { case MSMFB_MDP_PP: ret = mdss_mdp_pp_ioctl(mfd, argp); break; case MSMFB_HISTOGRAM_START: case MSMFB_HISTOGRAM_STOP: case MSMFB_HISTOGRAM: ret = mdss_mdp_histo_ioctl(mfd, cmd, argp); break; case MSMFB_OVERLAY_GET: ret = copy_from_user(&req, argp, sizeof(req)); if (!ret) { ret = mdss_mdp_overlay_get(mfd, &req); if (!IS_ERR_VALUE(ret)) ret = copy_to_user(argp, &req, sizeof(req)); } if (ret) pr_debug("OVERLAY_GET failed (%d)\n", ret); break; case MSMFB_OVERLAY_SET: ret = copy_from_user(&req, argp, sizeof(req)); if (!ret) { ret = mdss_mdp_overlay_set(mfd, &req); if (!IS_ERR_VALUE(ret)) ret = copy_to_user(argp, &req, sizeof(req)); } if (ret) pr_debug("OVERLAY_SET failed (%d)\n", ret); break; case MSMFB_OVERLAY_UNSET: if (!IS_ERR_VALUE(copy_from_user(&val, argp, sizeof(val)))) ret = mdss_mdp_overlay_unset(mfd, val); break; case MSMFB_OVERLAY_PLAY_ENABLE: if (!copy_from_user(&val, argp, sizeof(val))) { mdp5_data->overlay_play_enable = val; } else { pr_err("OVERLAY_PLAY_ENABLE failed (%d)\n", ret); ret = -EFAULT; } break; case MSMFB_OVERLAY_PLAY: if (mdp5_data->overlay_play_enable) { struct msmfb_overlay_data data; ret = copy_from_user(&data, argp, sizeof(data)); if (!ret) ret = mdss_mdp_overlay_play(mfd, &data); if (ret) pr_debug("OVERLAY_PLAY failed (%d)\n", ret); } else { ret = 0; } break; case MSMFB_OVERLAY_PLAY_WAIT: if (mdp5_data->overlay_play_enable) { struct msmfb_overlay_data data; ret = copy_from_user(&data, argp, sizeof(data)); if (!ret) ret = mdss_mdp_overlay_play_wait(mfd, &data); if (ret) pr_err("OVERLAY_PLAY_WAIT failed (%d)\n", ret); } else { ret = 0; } break; case MSMFB_VSYNC_CTRL: case MSMFB_OVERLAY_VSYNC_CTRL: if (!copy_from_user(&val, argp, sizeof(val))) { ret = mdss_mdp_overlay_vsync_ctrl(mfd, val); } else { pr_err("MSMFB_OVERLAY_VSYNC_CTRL failed (%d)\n", ret); ret = -EFAULT; } break; case MSMFB_OVERLAY_COMMIT: mdss_fb_wait_for_fence(mfd); ret = mfd->mdp.kickoff_fnc(mfd); mdss_fb_signal_timeline(mfd); break; case MSMFB_METADATA_SET: ret = copy_from_user(&metadata, argp, sizeof(metadata)); if (ret) return ret; ret = mdss_fb_set_metadata(mfd, &metadata); break; case MSMFB_METADATA_GET: ret = copy_from_user(&metadata, argp, sizeof(metadata)); if (ret) return ret; ret = mdss_fb_get_metadata(mfd, &metadata); if (!ret) ret = copy_to_user(argp, &metadata, sizeof(metadata)); break; default: if (mfd->panel.type == WRITEBACK_PANEL) ret = mdss_mdp_wb_ioctl_handler(mfd, cmd, argp); break; } return ret; } static int mdss_mdp_overlay_on(struct msm_fb_data_type *mfd) { int rc; struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); if (!mfd) return -ENODEV; if (mfd->key != MFD_KEY) return -EINVAL; if (!mdp5_data->ctl) { struct mdss_mdp_ctl *ctl; struct mdss_panel_data *pdata; pdata = dev_get_platdata(&mfd->pdev->dev); if (!pdata) { pr_err("no panel connected for fb%d\n", mfd->index); return -ENODEV; } ctl = mdss_mdp_ctl_init(pdata, mfd); if (IS_ERR_OR_NULL(ctl)) { pr_err("Unable to initialize ctl for fb%d\n", mfd->index); return PTR_ERR(ctl); } ctl->vsync_handler.vsync_handler = mdss_mdp_overlay_handle_vsync; if (mfd->split_display && pdata->next) { /* enable split display */ rc = mdss_mdp_ctl_split_display_setup(ctl, pdata->next); if (rc) { mdss_mdp_ctl_destroy(ctl); return rc; } } mdp5_data->ctl = ctl; } if (!mfd->panel_info->cont_splash_enabled) { rc = mdss_mdp_overlay_start(mfd); if (!IS_ERR_VALUE(rc) && (mfd->panel_info->type != DTV_PANEL)) rc = mdss_mdp_overlay_kickoff(mfd); } else { rc = mdss_mdp_ctl_setup(mdp5_data->ctl); if (rc) return rc; } if (IS_ERR_VALUE(rc)) { pr_err("Failed to turn on fb%d\n", mfd->index); mdss_mdp_overlay_off(mfd); } else if (mdp5_data->vsync_pending) { mdp5_data->vsync_pending = 0; mdss_mdp_overlay_vsync_ctrl(mfd, mdp5_data->vsync_pending); } return rc; } static int mdss_mdp_overlay_off(struct msm_fb_data_type *mfd) { int rc; struct mdss_overlay_private *mdp5_data = mfd_to_mdp5_data(mfd); if (!mfd) return -ENODEV; if (mfd->key != MFD_KEY) return -EINVAL; if (!mdp5_data->ctl) { pr_err("ctl not initialized\n"); return -ENODEV; } if (!mdp5_data->ctl->power_on) return 0; if (!mfd->ref_cnt) { mdss_mdp_overlay_release_all(mfd); } else { int need_cleanup; mutex_lock(&mfd->lock); need_cleanup = !list_empty(&mdp5_data->pipes_cleanup); mutex_unlock(&mfd->lock); if (need_cleanup) { pr_debug("cleaning up some pipes\n"); mdss_mdp_overlay_kickoff(mfd); } } rc = mdss_mdp_ctl_stop(mdp5_data->ctl); if (rc == 0) { if (!mfd->ref_cnt) { mdp5_data->borderfill_enable = false; mdss_mdp_ctl_destroy(mdp5_data->ctl); mdp5_data->ctl = NULL; } if (atomic_dec_return(&ov_active_panels) == 0) mdss_mdp_rotator_release_all(); rc = pm_runtime_put(&mfd->pdev->dev); if (rc) pr_err("unable to suspend w/pm_runtime_put (%d)\n", rc); } return rc; } int mdss_panel_register_done(struct mdss_panel_data *pdata) { /* * Clocks are already on if continuous splash is enabled, * increasing ref_cnt to help balance clocks once done. */ if (pdata->panel_info.cont_splash_enabled) { mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false); mdss_mdp_footswitch_ctrl_splash(1); mdss_mdp_ctl_splash_start(pdata); } return 0; } int mdss_mdp_overlay_init(struct msm_fb_data_type *mfd) { struct device *dev = mfd->fbi->dev; struct msm_mdp_interface *mdp5_interface = &mfd->mdp; struct mdss_overlay_private *mdp5_data = NULL; int rc; mdp5_interface->on_fnc = mdss_mdp_overlay_on; mdp5_interface->off_fnc = mdss_mdp_overlay_off; mdp5_interface->do_histogram = NULL; mdp5_interface->cursor_update = mdss_mdp_hw_cursor_update; mdp5_interface->dma_fnc = mdss_mdp_overlay_pan_display; mdp5_interface->ioctl_handler = mdss_mdp_overlay_ioctl_handler; mdp5_interface->panel_register_done = mdss_panel_register_done; mdp5_interface->kickoff_fnc = mdss_mdp_overlay_kickoff; mdp5_data = kmalloc(sizeof(struct mdss_overlay_private), GFP_KERNEL); if (!mdp5_data) { pr_err("fail to allocate mdp5 private data structure"); return -ENOMEM; } memset(mdp5_data, 0, sizeof(struct mdss_overlay_private)); INIT_LIST_HEAD(&mdp5_data->pipes_used); INIT_LIST_HEAD(&mdp5_data->pipes_cleanup); init_completion(&mdp5_data->vsync_comp); spin_lock_init(&mdp5_data->vsync_lock); mutex_init(&mdp5_data->ov_lock); mdp5_data->hw_refresh = true; mdp5_data->overlay_play_enable = true; mdp5_data->mdata = dev_get_drvdata(mfd->pdev->dev.parent); if (!mdp5_data->mdata) { pr_err("unable to initialize overlay for fb%d\n", mfd->index); rc = -ENODEV; goto init_fail; } mfd->mdp.private1 = mdp5_data; rc = mdss_mdp_overlay_fb_parse_dt(mfd); if (rc) return rc; rc = sysfs_create_group(&dev->kobj, &vsync_fs_attr_group); if (rc) { pr_err("vsync sysfs group creation failed, ret=%d\n", rc); goto init_fail; } pm_runtime_set_suspended(&mfd->pdev->dev); pm_runtime_enable(&mfd->pdev->dev); kobject_uevent(&dev->kobj, KOBJ_ADD); pr_debug("vsync kobject_uevent(KOBJ_ADD)\n"); mdp5_data->cpu_pm_hdl = add_event_timer(NULL, (void *)mdp5_data); if (!mdp5_data->cpu_pm_hdl) pr_warn("%s: unable to add event timer\n", __func__); return rc; init_fail: kfree(mdp5_data); return rc; } static int mdss_mdp_overlay_fb_parse_dt(struct msm_fb_data_type *mfd) { struct platform_device *pdev = mfd->pdev; struct mdss_overlay_private *mdp5_mdata = mfd_to_mdp5_data(mfd); mdp5_mdata->mixer_swap = of_property_read_bool(pdev->dev.of_node, "qcom,mdss-mixer-swap"); if (mdp5_mdata->mixer_swap) { pr_info("mixer swap is enabled for fb device=%s\n", pdev->name); } return 0; }