/* Copyright (c) 2012-2015, 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 "mdss_fb.h" #include "mdss_mdp.h" #include "mdss_panel.h" #include "mdss_debug.h" #include "mdss_mdp_trace.h" #include "mdss_dsi.h" #include "mdss_edp.h" int count_wait_for_timeout = 0; int get_lcd_attached(void); /* wait for at least 2 vsyncs for lowest refresh rate (24hz) */ #define VSYNC_TIMEOUT_US 100000 #define MDP_INTR_MASK_INTF_VSYNC(intf_num) \ (1 << (2 * (intf_num - MDSS_MDP_INTF0) + MDSS_MDP_IRQ_INTF_VSYNC)) /* intf timing settings */ struct intf_timing_params { u32 width; u32 height; u32 xres; u32 yres; u32 h_back_porch; u32 h_front_porch; u32 v_back_porch; u32 v_front_porch; u32 hsync_pulse_width; u32 vsync_pulse_width; u32 border_clr; u32 underflow_clr; u32 hsync_skew; }; struct mdss_mdp_video_ctx { u32 intf_num; char __iomem *base; u32 intf_type; u8 ref_cnt; u8 timegen_en; bool polling_en; u32 poll_cnt; struct completion vsync_comp; struct completion pp_comp; int wait_pending; atomic_t vsync_ref; spinlock_t vsync_lock; struct mutex vsync_mtx; struct list_head vsync_handlers; }; static inline void mdp_video_write(struct mdss_mdp_video_ctx *ctx, u32 reg, u32 val) { writel_relaxed(val, ctx->base + reg); } static inline u32 mdp_video_read(struct mdss_mdp_video_ctx *ctx, u32 reg) { return readl_relaxed(ctx->base + reg); } static inline u32 mdss_mdp_video_line_count(struct mdss_mdp_ctl *ctl) { struct mdss_mdp_video_ctx *ctx; u32 line_cnt = 0; if (!ctl || !ctl->priv_data) goto line_count_exit; ctx = ctl->priv_data; mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false); line_cnt = mdp_video_read(ctx, MDSS_MDP_REG_INTF_LINE_COUNT); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false); line_count_exit: return line_cnt; } int mdss_mdp_video_addr_setup(struct mdss_data_type *mdata, u32 *offsets, u32 count) { struct mdss_mdp_video_ctx *head; u32 i; head = devm_kzalloc(&mdata->pdev->dev, sizeof(struct mdss_mdp_video_ctx) * count, GFP_KERNEL); if (!head) return -ENOMEM; for (i = 0; i < count; i++) { head[i].base = mdata->mdp_base + offsets[i]; pr_debug("adding Video Intf #%d offset=0x%x virt=%pK\n", i, offsets[i], head[i].base); head[i].ref_cnt = 0; head[i].intf_num = i + MDSS_MDP_INTF0; INIT_LIST_HEAD(&head[i].vsync_handlers); } mdata->video_intf = head; mdata->nintf = count; return 0; } static int mdss_mdp_video_timegen_setup(struct mdss_mdp_ctl *ctl, struct intf_timing_params *p) { u32 hsync_period, vsync_period; u32 hsync_start_x, hsync_end_x, display_v_start, display_v_end; u32 active_h_start, active_h_end, active_v_start, active_v_end; u32 den_polarity, hsync_polarity, vsync_polarity; u32 display_hctl, active_hctl, hsync_ctl, polarity_ctl; struct mdss_mdp_video_ctx *ctx; ctx = ctl->priv_data; hsync_period = p->hsync_pulse_width + p->h_back_porch + p->width + p->h_front_porch; vsync_period = p->vsync_pulse_width + p->v_back_porch + p->height + p->v_front_porch; display_v_start = ((p->vsync_pulse_width + p->v_back_porch) * hsync_period) + p->hsync_skew; display_v_end = ((vsync_period - p->v_front_porch) * hsync_period) + p->hsync_skew - 1; if (ctx->intf_type == MDSS_INTF_EDP) { display_v_start += p->hsync_pulse_width + p->h_back_porch; display_v_end -= p->h_front_porch; } hsync_start_x = p->h_back_porch + p->hsync_pulse_width; hsync_end_x = hsync_period - p->h_front_porch - 1; if (p->width != p->xres) { active_h_start = hsync_start_x; active_h_end = active_h_start + p->xres - 1; } else { active_h_start = 0; active_h_end = 0; } if (p->height != p->yres) { active_v_start = display_v_start; active_v_end = active_v_start + (p->yres * hsync_period) - 1; } else { active_v_start = 0; active_v_end = 0; } if (active_h_end) { active_hctl = (active_h_end << 16) | active_h_start; active_hctl |= BIT(31); /* ACTIVE_H_ENABLE */ } else { active_hctl = 0; } if (active_v_end) active_v_start |= BIT(31); /* ACTIVE_V_ENABLE */ hsync_ctl = (hsync_period << 16) | p->hsync_pulse_width; display_hctl = (hsync_end_x << 16) | hsync_start_x; den_polarity = 0; if (MDSS_INTF_HDMI == ctx->intf_type) { hsync_polarity = p->yres >= 720 ? 0 : 1; vsync_polarity = p->yres >= 720 ? 0 : 1; } else { hsync_polarity = 0; vsync_polarity = 0; } polarity_ctl = (den_polarity << 2) | /* DEN Polarity */ (vsync_polarity << 1) | /* VSYNC Polarity */ (hsync_polarity << 0); /* HSYNC Polarity */ mdp_video_write(ctx, MDSS_MDP_REG_INTF_HSYNC_CTL, hsync_ctl); mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0, vsync_period * hsync_period); mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PULSE_WIDTH_F0, p->vsync_pulse_width * hsync_period); mdp_video_write(ctx, MDSS_MDP_REG_INTF_DISPLAY_HCTL, display_hctl); mdp_video_write(ctx, MDSS_MDP_REG_INTF_DISPLAY_V_START_F0, display_v_start); mdp_video_write(ctx, MDSS_MDP_REG_INTF_DISPLAY_V_END_F0, display_v_end); mdp_video_write(ctx, MDSS_MDP_REG_INTF_ACTIVE_HCTL, active_hctl); mdp_video_write(ctx, MDSS_MDP_REG_INTF_ACTIVE_V_START_F0, active_v_start); mdp_video_write(ctx, MDSS_MDP_REG_INTF_ACTIVE_V_END_F0, active_v_end); mdp_video_write(ctx, MDSS_MDP_REG_INTF_BORDER_COLOR, p->border_clr); mdp_video_write(ctx, MDSS_MDP_REG_INTF_UNDERFLOW_COLOR, p->underflow_clr); mdp_video_write(ctx, MDSS_MDP_REG_INTF_HSYNC_SKEW, p->hsync_skew); mdp_video_write(ctx, MDSS_MDP_REG_INTF_POLARITY_CTL, polarity_ctl); mdp_video_write(ctx, MDSS_MDP_REG_INTF_FRAME_LINE_COUNT_EN, 0x3); return 0; } static inline void video_vsync_irq_enable(struct mdss_mdp_ctl *ctl, bool clear) { struct mdss_mdp_video_ctx *ctx = ctl->priv_data; mutex_lock(&ctx->vsync_mtx); if (atomic_inc_return(&ctx->vsync_ref) == 1) mdss_mdp_irq_enable(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num); else if (clear) mdss_mdp_irq_clear(ctl->mdata, MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num); mutex_unlock(&ctx->vsync_mtx); } static inline void video_vsync_irq_disable(struct mdss_mdp_ctl *ctl) { struct mdss_mdp_video_ctx *ctx = ctl->priv_data; mutex_lock(&ctx->vsync_mtx); if (atomic_dec_return(&ctx->vsync_ref) == 0) mdss_mdp_irq_disable(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num); mutex_unlock(&ctx->vsync_mtx); } static int mdss_mdp_video_add_vsync_handler(struct mdss_mdp_ctl *ctl, struct mdss_mdp_vsync_handler *handle) { struct mdss_mdp_video_ctx *ctx; unsigned long flags; int ret = 0; bool irq_en = false; if (!handle || !(handle->vsync_handler)) { ret = -EINVAL; goto exit; } ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data; if (!ctx) { pr_err("invalid ctx for ctl=%d\n", ctl->num); ret = -ENODEV; goto exit; } MDSS_XLOG(ctl->num, ctl->vsync_cnt, handle->enabled); spin_lock_irqsave(&ctx->vsync_lock, flags); if (!handle->enabled) { handle->enabled = true; list_add(&handle->list, &ctx->vsync_handlers); irq_en = true; } spin_unlock_irqrestore(&ctx->vsync_lock, flags); if (irq_en) video_vsync_irq_enable(ctl, false); exit: return ret; } static int mdss_mdp_video_remove_vsync_handler(struct mdss_mdp_ctl *ctl, struct mdss_mdp_vsync_handler *handle) { struct mdss_mdp_video_ctx *ctx; unsigned long flags; bool irq_dis = false; ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data; if (!ctx) { pr_err("invalid ctx for ctl=%d\n", ctl->num); return -ENODEV; } MDSS_XLOG(ctl->num, ctl->vsync_cnt, handle->enabled); spin_lock_irqsave(&ctx->vsync_lock, flags); if (handle->enabled) { handle->enabled = false; list_del_init(&handle->list); irq_dis = true; } spin_unlock_irqrestore(&ctx->vsync_lock, flags); if (irq_dis) video_vsync_irq_disable(ctl); return 0; } static int mdss_mdp_video_stop(struct mdss_mdp_ctl *ctl) { struct mdss_mdp_video_ctx *ctx; struct mdss_mdp_vsync_handler *tmp, *handle; struct mdss_mdp_ctl *sctl; int rc; u32 frame_rate = 0; pr_debug("stop ctl=%d\n", ctl->num); ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data; if (!ctx) { pr_err("invalid ctx for ctl=%d\n", ctl->num); return -ENODEV; } MDSS_XLOG(ctl->num, ctl->vsync_cnt); if (ctx->timegen_en) { rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_BLANK, NULL); if (rc == -EBUSY) { pr_debug("intf #%d busy don't turn off\n", ctl->intf_num); return rc; } WARN(rc, "intf %d blank error (%d)\n", ctl->intf_num, rc); mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 0); /* wait for at least one VSYNC on HDMI intf for proper TG OFF */ if (MDSS_INTF_HDMI == ctx->intf_type) { frame_rate = mdss_panel_get_framerate (&(ctl->panel_data->panel_info)); if (!(frame_rate >= 24 && frame_rate <= 240)) frame_rate = 24; msleep((1000/frame_rate) + 1); } mdss_iommu_ctrl(0); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false); ctx->timegen_en = false; rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_OFF, NULL); WARN(rc, "intf %d timegen off error (%d)\n", ctl->intf_num, rc); mdss_mdp_irq_disable(MDSS_MDP_IRQ_INTF_UNDER_RUN, ctl->intf_num); sctl = mdss_mdp_get_split_ctl(ctl); if (sctl) mdss_mdp_irq_disable(MDSS_MDP_IRQ_INTF_UNDER_RUN, sctl->intf_num); mdss_bus_bandwidth_ctrl(false); } list_for_each_entry_safe(handle, tmp, &ctx->vsync_handlers, list) mdss_mdp_video_remove_vsync_handler(ctl, handle); mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num, NULL, NULL); mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_UNDER_RUN, ctl->intf_num, NULL, NULL); mdss_mdp_ctl_reset(ctl); ctx->ref_cnt--; ctl->priv_data = NULL; return 0; } static void mdss_mdp_video_vsync_intr_done(void *arg) { struct mdss_mdp_ctl *ctl = arg; struct mdss_mdp_video_ctx *ctx = ctl->priv_data; struct mdss_mdp_vsync_handler *tmp; ktime_t vsync_time; if (!ctx) { pr_err("invalid ctx\n"); return; } vsync_time = ktime_get(); ctl->vsync_cnt++; MDSS_XLOG(ctl->num, ctl->vsync_cnt, ctl->vsync_cnt); pr_debug("intr ctl=%d vsync cnt=%u vsync_time=%d\n", ctl->num, ctl->vsync_cnt, (int)ktime_to_ms(vsync_time)); ctx->polling_en = false; complete_all(&ctx->vsync_comp); spin_lock(&ctx->vsync_lock); list_for_each_entry(tmp, &ctx->vsync_handlers, list) { tmp->vsync_handler(ctl, vsync_time); } spin_unlock(&ctx->vsync_lock); } static int mdss_mdp_video_pollwait(struct mdss_mdp_ctl *ctl) { struct mdss_mdp_video_ctx *ctx = ctl->priv_data; u32 mask, status; int rc; mask = MDP_INTR_MASK_INTF_VSYNC(ctl->intf_num); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false); rc = readl_poll_timeout(ctl->mdata->mdp_base + MDSS_MDP_REG_INTR_STATUS, status, (status & mask) || try_wait_for_completion(&ctx->vsync_comp), 1000, VSYNC_TIMEOUT_US); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false); if (rc == 0) { MDSS_XLOG(ctl->num, ctl->vsync_cnt); pr_debug("vsync poll successful! rc=%d status=0x%x\n", rc, status); ctx->poll_cnt++; if (status) { struct mdss_mdp_vsync_handler *tmp; unsigned long flags; ktime_t vsync_time = ktime_get(); spin_lock_irqsave(&ctx->vsync_lock, flags); list_for_each_entry(tmp, &ctx->vsync_handlers, list) tmp->vsync_handler(ctl, vsync_time); spin_unlock_irqrestore(&ctx->vsync_lock, flags); } } else { pr_warn("vsync poll timed out! rc=%d status=0x%x mask=0x%x\n", rc, status, mask); } return rc; } static int mdss_mdp_video_wait4comp(struct mdss_mdp_ctl *ctl, void *arg) { struct mdss_mdp_video_ctx *ctx; int rc; ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data; if (!ctx) { pr_err("invalid ctx\n"); return -ENODEV; } WARN(!ctx->wait_pending, "waiting without commit! ctl=%d", ctl->num); if (ctx->polling_en) { rc = mdss_mdp_video_pollwait(ctl); } else { mutex_unlock(&ctl->lock); rc = wait_for_completion_timeout(&ctx->vsync_comp, usecs_to_jiffies(VSYNC_TIMEOUT_US)); mutex_lock(&ctl->lock); if (rc == 0) { pr_warn("vsync wait timeout %d, fallback to poll mode\n", ctl->num); ctx->polling_en=true; rc = mdss_mdp_video_pollwait(ctl); } else { rc = 0; } } mdss_mdp_ctl_notify(ctl, rc ? MDP_NOTIFY_FRAME_TIMEOUT : MDP_NOTIFY_FRAME_DONE); if (ctx->wait_pending) { ctx->wait_pending = 0; video_vsync_irq_disable(ctl); } return rc; } static void recover_underrun_work(struct work_struct *work) { struct mdss_mdp_ctl *ctl = container_of(work, typeof(*ctl), recover_work); if (!ctl || !ctl->add_vsync_handler) { pr_err("ctl or vsync handler is NULL\n"); return; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false); ctl->add_vsync_handler(ctl, &ctl->recover_underrun_handler); mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false); } static void mdss_mdp_video_underrun_intr_done(void *arg) { struct mdss_mdp_ctl *ctl = arg; if (unlikely(!ctl)) return; ctl->underrun_cnt++; MDSS_XLOG(ctl->num, ctl->underrun_cnt); MDSS_XLOG_TOUT_HANDLER("mdp", "dsi0", "dsi1", "edp", "hdmi", "panic"); trace_mdp_video_underrun_done(ctl->num, ctl->underrun_cnt); pr_info("display underrun detected for ctl=%d count=%d\n", ctl->num, ctl->underrun_cnt); if (ctl->opmode & MDSS_MDP_CTL_OP_PACK_3D_ENABLE) schedule_work(&ctl->recover_work); mdss_mdp_underrun_dump_info(ctl->mfd); } static int mdss_mdp_video_vfp_fps_update(struct mdss_mdp_ctl *ctl, int new_fps) { int curr_fps; u32 add_v_lines = 0; u32 current_vsync_period_f0, new_vsync_period_f0; struct mdss_panel_data *pdata; struct mdss_mdp_video_ctx *ctx; u32 vsync_period, hsync_period; ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data; if (!ctx) { pr_err("invalid ctx\n"); return -ENODEV; } pdata = ctl->panel_data; if (pdata == NULL) { pr_err("%s: Invalid panel data\n", __func__); return -EINVAL; } vsync_period = mdss_panel_get_vtotal(&pdata->panel_info); hsync_period = mdss_panel_get_htotal(&pdata->panel_info); curr_fps = mdss_panel_get_framerate(&pdata->panel_info); if (curr_fps > new_fps) { add_v_lines = mult_frac(vsync_period, (curr_fps - new_fps), new_fps); pdata->panel_info.lcdc.v_front_porch += add_v_lines; } else { add_v_lines = mult_frac(vsync_period, (new_fps - curr_fps), new_fps); pdata->panel_info.lcdc.v_front_porch -= add_v_lines; } vsync_period = mdss_panel_get_vtotal(&pdata->panel_info); current_vsync_period_f0 = mdp_video_read(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0); new_vsync_period_f0 = (vsync_period * hsync_period); mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0, current_vsync_period_f0 | 0x800000); if (new_vsync_period_f0 & 0x800000) { mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0, new_vsync_period_f0); } else { mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0, new_vsync_period_f0 | 0x800000); mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0, new_vsync_period_f0 & 0x7fffff); } return 0; } static int mdss_mdp_video_config_fps(struct mdss_mdp_ctl *ctl, struct mdss_mdp_ctl *sctl, int new_fps) { struct mdss_mdp_video_ctx *ctx; struct mdss_panel_data *pdata; int rc = 0; u32 hsync_period, vsync_period; pr_debug("Updating fps for ctl=%d\n", ctl->num); ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data; if (!ctx) { pr_err("invalid ctx\n"); return -ENODEV; } pdata = ctl->panel_data; if (pdata == NULL) { pr_err("%s: Invalid panel data\n", __func__); return -EINVAL; } if (!pdata->panel_info.dynamic_fps) { pr_err("%s: Dynamic fps not enabled for this panel\n", __func__); return -EINVAL; } vsync_period = mdss_panel_get_vtotal(&pdata->panel_info); hsync_period = mdss_panel_get_htotal(&pdata->panel_info); if (pdata->panel_info.dfps_update != DFPS_SUSPEND_RESUME_MODE) { if (pdata->panel_info.dfps_update == DFPS_IMMEDIATE_CLK_UPDATE_MODE) { if (!ctx->timegen_en) { pr_err("TG is OFF. DFPS mode invalid\n"); return -EINVAL; } ctl->force_screen_state = MDSS_SCREEN_FORCE_BLANK; mdss_mdp_display_commit(ctl, NULL); mdss_mdp_display_wait4comp(ctl); mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 0); /* * Need to wait for atleast one vsync time for proper * TG OFF before doing changes on interfaces */ msleep(20); rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_UPDATE_FPS, (void *)new_fps); WARN(rc, "intf %d panel fps update error (%d)\n", ctl->intf_num, rc); mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 1); /* * Add memory barrier to make sure the MDP Video * mode engine is enabled before next frame is sent */ mb(); ctl->force_screen_state = MDSS_SCREEN_DEFAULT; mdss_mdp_display_commit(ctl, NULL); mdss_mdp_display_wait4comp(ctl); } else if (pdata->panel_info.dfps_update == DFPS_IMMEDIATE_PORCH_UPDATE_MODE){ if (!ctx->timegen_en) { pr_err("TG is OFF. DFPS mode invalid\n"); return -EINVAL; } video_vsync_irq_enable(ctl, true); INIT_COMPLETION(ctx->vsync_comp); rc = wait_for_completion_timeout(&ctx->vsync_comp, usecs_to_jiffies(VSYNC_TIMEOUT_US)); WARN(rc <= 0, "timeout (%d) vsync interrupt on ctl=%d\n", rc, ctl->num); video_vsync_irq_disable(ctl); /* Do not configure fps on vsync timeout */ if (rc <= 0) return rc; if (mdss_mdp_video_line_count(ctl) >= pdata->panel_info.yres/2) { pr_err("Too few lines left line_cnt = %d y_res/2 = %d\n", mdss_mdp_video_line_count(ctl), pdata->panel_info.yres/2); return -EPERM; } rc = mdss_mdp_video_vfp_fps_update(ctl, new_fps); if (rc < 0) { pr_err("%s: Error during DFPS\n", __func__); return rc; } if (sctl) { rc = mdss_mdp_video_vfp_fps_update(sctl, new_fps); if (rc < 0) { pr_err("%s: DFPS error\n", __func__); return rc; } } rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_UPDATE_FPS, (void *)new_fps); WARN(rc, "intf %d panel fps update error (%d)\n", ctl->intf_num, rc); } else { pr_err("intf %d panel, unknown FPS mode\n", ctl->intf_num); return -EINVAL; } } else { rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_UPDATE_FPS, (void *)new_fps); WARN(rc, "intf %d panel fps update error (%d)\n", ctl->intf_num, rc); } return rc; } static int mdss_mdp_video_display(struct mdss_mdp_ctl *ctl, void *arg) { struct mdss_mdp_video_ctx *ctx; struct mdss_mdp_ctl *sctl; struct mdss_panel_data *pdata = ctl->panel_data; #if defined(CONFIG_FB_MSM_MDSS_S6E8AA0A_HD_PANEL) int rc; #else int rc = 0; #endif pr_debug("kickoff ctl=%d\n", ctl->num); ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data; if (!ctx) { pr_err("invalid ctx\n"); return -ENODEV; } #if !defined(CONFIG_FB_MSM8x26_MDSS_CHECK_LCD_CONNECTION) && \ !defined(CONFIG_FB_MSM_MIPI_MAGNA_OCTA_VIDEO_WXGA_PT_DUAL_PANEL) if (get_lcd_attached() == 0) { pr_err("%s : lcd is not attached..\n",__func__); return -ENODEV; } #endif if (!ctx->wait_pending) { ctx->wait_pending++; video_vsync_irq_enable(ctl, true); INIT_COMPLETION(ctx->vsync_comp); } else { WARN(1, "commit without wait! ctl=%d", ctl->num); } #if !defined(CONFIG_FB_MSM_MDSS_S6E8AA0A_HD_PANEL) if(pdata->panel_info.cont_splash_enabled) return rc; #endif MDSS_XLOG(ctl->num, ctl->underrun_cnt); if (!ctx->timegen_en) { rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_UNBLANK, NULL); if (rc) { pr_warn("intf #%d unblank error (%d)\n", ctl->intf_num, rc); video_vsync_irq_disable(ctl); ctx->wait_pending = 0; return rc; } pr_debug("enabling timing gen for intf=%d\n", ctl->intf_num); if ((pdata->panel_info.cont_splash_enabled && !ctl->mfd->splash_info.splash_logo_enabled) || (ctl->mfd->splash_info.splash_logo_enabled && ctl->mfd->splash_info.splash_thread && !is_mdss_iommu_attached())) { rc = wait_for_completion_timeout(&ctx->vsync_comp, usecs_to_jiffies(VSYNC_TIMEOUT_US)); } rc = mdss_iommu_ctrl(1); if (IS_ERR_VALUE(rc)) { pr_err("IOMMU attach failed\n"); return rc; } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false); mdss_mdp_irq_enable(MDSS_MDP_IRQ_INTF_UNDER_RUN, ctl->intf_num); sctl = mdss_mdp_get_split_ctl(ctl); if (sctl) mdss_mdp_irq_enable(MDSS_MDP_IRQ_INTF_UNDER_RUN, sctl->intf_num); mdss_bus_bandwidth_ctrl(true); mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 1); wmb(); rc = wait_for_completion_timeout(&ctx->vsync_comp, usecs_to_jiffies(VSYNC_TIMEOUT_US)); WARN(rc == 0, "timeout (%d) enabling timegen on ctl=%d\n", rc, ctl->num); ctx->timegen_en = true; rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_ON, NULL); WARN(rc, "intf %d panel on error (%d)\n", ctl->intf_num, rc); #if defined(CONFIG_FB_MSM_EDP_SAMSUNG) set_backlight_first_kick_off(); #endif } return 0; } int mdss_mdp_video_copy_splash_screen(struct mdss_panel_data *pdata) { void *virt = NULL; unsigned long bl_fb_addr = 0; unsigned long *bl_fb_addr_va; unsigned long pipe_addr, pipe_src_size; u32 height, width, rgb_size, bpp; size_t size; static struct ion_handle *ihdl; struct ion_client *iclient = mdss_get_ionclient(); static ion_phys_addr_t phys; pipe_addr = MDSS_MDP_REG_SSPP_OFFSET(3) + MDSS_MDP_REG_SSPP_SRC0_ADDR; pipe_src_size = MDSS_MDP_REG_SSPP_OFFSET(3) + MDSS_MDP_REG_SSPP_SRC_SIZE; bpp = 3; rgb_size = MDSS_MDP_REG_READ(pipe_src_size); bl_fb_addr = MDSS_MDP_REG_READ(pipe_addr); height = (rgb_size >> 16) & 0xffff; width = rgb_size & 0xffff; size = PAGE_ALIGN(height * width * bpp); pr_debug("%s:%d splash_height=%d splash_width=%d Buffer size=%d\n", __func__, __LINE__, height, width, size); ihdl = ion_alloc(iclient, size, SZ_1M, ION_HEAP(ION_QSECOM_HEAP_ID), 0); if (IS_ERR_OR_NULL(ihdl)) { pr_err("unable to alloc fbmem from ion (%pK)\n", ihdl); return -ENOMEM; } pdata->panel_info.splash_ihdl = ihdl; virt = ion_map_kernel(iclient, ihdl); ion_phys(iclient, ihdl, &phys, &size); pr_debug("%s %d Allocating %u bytes at 0x%lx (%pa phys)\n", __func__, __LINE__, size, (unsigned long int)virt, &phys); bl_fb_addr_va = (unsigned long *)ioremap(bl_fb_addr, size); memcpy(virt, bl_fb_addr_va, size); iounmap(bl_fb_addr_va); MDSS_MDP_REG_WRITE(pipe_addr, phys); MDSS_MDP_REG_WRITE(MDSS_MDP_REG_CTL_FLUSH + MDSS_MDP_REG_CTL_OFFSET(0), 0x48); return 0; } int mdss_mdp_video_reconfigure_splash_done(struct mdss_mdp_ctl *ctl, bool handoff) { struct mdss_panel_data *pdata = ctl->panel_data; int i, ret = 0; struct mdss_mdp_video_ctx *ctx; struct mdss_data_type *mdata = ctl->mdata; #if !defined(CONFIG_FB_MSM_MIPI_SAMSUNG_OCTA_VIDEO_FULL_HD_PT_PANEL) && !defined(CONFIG_FB_MSM_MDSS_S6E8AA0A_HD_PANEL) ret = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_FIRST_FRAME_UPDATE, NULL); #endif i = ctl->intf_num - MDSS_MDP_INTF0; if (i < mdata->nintf) { ctx = ((struct mdss_mdp_video_ctx *) mdata->video_intf) + i; pr_debug("video Intf #%d base=%pK", ctx->intf_num, ctx->base); } else { pr_err("Invalid intf number: %d\n", ctl->intf_num); ret = -EINVAL; goto error; } #if defined(CONFIG_FB_MSM_MDSS_S6E8AA0A_HD_PANEL) ret = mdss_mdp_ctl_intf_event(ctl, MTP_READ,NULL); #endif pr_err("[QC] handoff : %d\n", handoff); if (!handoff) { ret = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_CONT_SPLASH_BEGIN, NULL); if (ret) { pr_err("%s: Failed to handle 'CONT_SPLASH_BEGIN' event\n" , __func__); return ret; } mdss_mdp_ctl_write(ctl, 0, MDSS_MDP_LM_BORDER_COLOR); mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 0); /* wait for 1 VSYNC for the pipe to be unstaged */ msleep(20); ret = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_CONT_SPLASH_FINISH, NULL); } #if !defined(CONFIG_FB_MSM_EDP_SAMSUNG) else { mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_CONT_SPLASH_BEGIN, NULL); mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 0); ctx->timegen_en = false; /* Panel off command causes white screen flash at contsplash end so removing for LVDS panels */ #if !defined(CONFIG_FB_MSM_MDSS_TC_DSI2LVDS_WXGA_PANEL) mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_OFF, NULL); #endif mdss_mdp_ctl_intf_event(ctl,MDSS_EVENT_CONT_SPLASH_FINISH, NULL); mdss_mdp_ctl_intf_event(ctl,MDSS_EVENT_UNBLANK, NULL); } #endif error: pdata->panel_info.cont_splash_enabled = 0; return ret; } #if !defined(CONFIG_FB_MSM_MDSS_S6E8AA0A_HD_PANEL) static void mdss_mdp_video_pingpong_done(void *arg) { struct mdss_mdp_ctl *ctl = arg; struct mdss_mdp_video_ctx *ctx; ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data; pr_info("%s:mdss_mdp_isr 2222\n", __func__); if (!ctx) { pr_err("invalid ctx\n"); return; } mdss_mdp_irq_disable_nosync(MDSS_MDP_IRQ_PING_PONG_COMP, ctl->num); complete_all(&ctx->pp_comp); } static int mdss_mdp_video_wait4pingpong(struct mdss_mdp_ctl *ctl, void *arg) { struct mdss_mdp_video_ctx *ctx; int rc = 0; ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data; pr_info("%s:mdss_mdp_isr 1111\n", __func__); if (!ctx) { pr_err("invalid ctx\n"); return -ENODEV; } INIT_COMPLETION(ctx->pp_comp); rc = wait_for_completion_timeout( &ctx->pp_comp, msecs_to_jiffies(20)); return rc; } #endif int mdss_mdp_video_start(struct mdss_mdp_ctl *ctl) { struct mdss_data_type *mdata; struct mdss_panel_info *pinfo; struct mdss_mdp_video_ctx *ctx; struct mdss_mdp_mixer *mixer; struct intf_timing_params itp = {0}; u32 dst_bpp; int i; mdata = ctl->mdata; pinfo = &ctl->panel_data->panel_info; mixer = mdss_mdp_mixer_get(ctl, MDSS_MDP_MIXER_MUX_LEFT); if (!mixer) { pr_err("mixer not setup correctly\n"); return -ENODEV; } #if defined (CONFIG_FB_MSM_MIPI_SAMSUNG_TFT_VIDEO_WQXGA_PT_PANEL) if (get_lcd_attached() == 0) { pr_err("%s : lcd is not attached..\n",__func__); return 0; } #endif i = ctl->intf_num - MDSS_MDP_INTF0; if (i < mdata->nintf) { ctx = ((struct mdss_mdp_video_ctx *) mdata->video_intf) + i; if (ctx->ref_cnt) { pr_err("Intf %d already in use\n", ctl->intf_num); return -EBUSY; } pr_debug("video Intf #%d base=%pK", ctx->intf_num, ctx->base); ctx->ref_cnt++; } else { pr_err("Invalid intf number: %d\n", ctl->intf_num); return -EINVAL; } MDSS_XLOG(ctl->num, ctl->vsync_cnt); pr_debug("start ctl=%u\n", ctl->num); ctl->priv_data = ctx; ctx->intf_type = ctl->intf_type; init_completion(&ctx->vsync_comp); init_completion(&ctx->pp_comp); spin_lock_init(&ctx->vsync_lock); mutex_init(&ctx->vsync_mtx); atomic_set(&ctx->vsync_ref, 0); INIT_WORK(&ctl->recover_work, recover_underrun_work); mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num, mdss_mdp_video_vsync_intr_done, ctl); mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_UNDER_RUN, ctl->intf_num, mdss_mdp_video_underrun_intr_done, ctl); #if !defined(CONFIG_FB_MSM_MDSS_S6E8AA0A_HD_PANEL) mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_PING_PONG_COMP, mixer->num, mdss_mdp_video_pingpong_done, ctl); #endif dst_bpp = pinfo->fbc.enabled ? (pinfo->fbc.target_bpp) : (pinfo->bpp); itp.width = mult_frac((pinfo->xres + pinfo->lcdc.xres_pad), dst_bpp, pinfo->bpp); itp.height = pinfo->yres + pinfo->lcdc.yres_pad; itp.border_clr = pinfo->lcdc.border_clr; itp.underflow_clr = pinfo->lcdc.underflow_clr; itp.hsync_skew = pinfo->lcdc.hsync_skew; itp.xres = mult_frac(pinfo->xres, dst_bpp, pinfo->bpp); itp.yres = pinfo->yres; itp.h_back_porch = mult_frac(pinfo->lcdc.h_back_porch, dst_bpp, pinfo->bpp); itp.h_front_porch = mult_frac(pinfo->lcdc.h_front_porch, dst_bpp, pinfo->bpp); itp.v_back_porch = mult_frac(pinfo->lcdc.v_back_porch, dst_bpp, pinfo->bpp); itp.v_front_porch = mult_frac(pinfo->lcdc.v_front_porch, dst_bpp, pinfo->bpp); itp.hsync_pulse_width = mult_frac(pinfo->lcdc.h_pulse_width, dst_bpp, pinfo->bpp); itp.vsync_pulse_width = pinfo->lcdc.v_pulse_width; if (mdss_mdp_video_timegen_setup(ctl, &itp)) { pr_err("unable to get timing parameters\n"); return -EINVAL; } mdp_video_write(ctx, MDSS_MDP_REG_INTF_PANEL_FORMAT, ctl->dst_format); ctl->stop_fnc = mdss_mdp_video_stop; ctl->display_fnc = mdss_mdp_video_display; ctl->wait_fnc = mdss_mdp_video_wait4comp; ctl->read_line_cnt_fnc = mdss_mdp_video_line_count; ctl->add_vsync_handler = mdss_mdp_video_add_vsync_handler; ctl->remove_vsync_handler = mdss_mdp_video_remove_vsync_handler; ctl->config_fps_fnc = mdss_mdp_video_config_fps; #if !defined(CONFIG_FB_MSM_MDSS_S6E8AA0A_HD_PANEL) ctl->wait_video_pingpong = mdss_mdp_video_wait4pingpong; #endif return 0; }