diff --git a/drivers/video/fbdev/msm/mdss_fb.c b/drivers/video/fbdev/msm/mdss_fb.c index 6c4c466d484f..02eb1b944d13 100644 --- a/drivers/video/fbdev/msm/mdss_fb.c +++ b/drivers/video/fbdev/msm/mdss_fb.c @@ -336,7 +336,7 @@ static int mdss_fb_get_panel_xres(struct mdss_panel_info *pinfo) pdata = container_of(pinfo, struct mdss_panel_data, panel_info); xres = pinfo->xres; - if (pdata->next) + if (pdata->next && pdata->next->active) xres += mdss_fb_get_panel_xres(&pdata->next->panel_info); return xres; @@ -916,6 +916,82 @@ static void mdss_fb_unregister_input_handler(struct msm_fb_data_type *mfd) kfree(mfd->input_handler); } +static void mdss_fb_videomode_from_panel_timing(struct fb_videomode *videomode, + struct mdss_panel_timing *pt) +{ + videomode->name = pt->name; + videomode->xres = pt->xres; + videomode->yres = pt->yres; + videomode->left_margin = pt->h_back_porch; + videomode->right_margin = pt->h_front_porch; + videomode->hsync_len = pt->h_pulse_width; + videomode->upper_margin = pt->v_back_porch; + videomode->lower_margin = pt->v_front_porch; + videomode->vsync_len = pt->v_pulse_width; + videomode->pixclock = pt->clk_rate; + videomode->refresh = pt->frame_rate; + videomode->flag = 0; + videomode->vmode = 0; + videomode->sync = 0; +} + +static int mdss_fb_init_panel_modes(struct msm_fb_data_type *mfd, + struct mdss_panel_data *pdata) +{ + struct fb_info *fbi = mfd->fbi; + struct fb_videomode *modedb; + struct mdss_panel_timing *pt; + struct list_head *pos; + int num_timings = 0; + int i = 0; + + /* check if multiple modes are supported */ + if (!fbi || !pdata->current_timing) + return 0; + + list_for_each(pos, &pdata->timings_list) + num_timings++; + + modedb = devm_kzalloc(fbi->dev, num_timings * sizeof(*modedb), + GFP_KERNEL); + if (!modedb) + return -ENOMEM; + + list_for_each_entry(pt, &pdata->timings_list, list) { + struct mdss_panel_timing *spt = NULL; + + mdss_fb_videomode_from_panel_timing(modedb + i, pt); + if (pdata->next) { + spt = mdss_panel_get_timing_by_name(pdata->next, + modedb[i].name); + if (!IS_ERR_OR_NULL(spt)) + modedb[i].xres += spt->xres; + else + pr_debug("no matching split config for %s\n", + modedb[i].name); + + /* + * if no panel timing found for current, need to + * disable it otherwise mark it as active + */ + if (pt == pdata->current_timing) + pdata->next->active = !IS_ERR_OR_NULL(spt); + } + + if (pt == pdata->current_timing) { + pr_debug("found current mode: %s\n", pt->name); + fbi->mode = modedb + i; + } + i++; + } + + fbi->monspecs.modedb = modedb; + fbi->monspecs.modedb_len = num_timings; + fb_videomode_to_modelist(modedb, num_timings, &fbi->modelist); + + return 0; +} + static int mdss_fb_probe(struct platform_device *pdev) { struct msm_fb_data_type *mfd = NULL; @@ -1033,6 +1109,8 @@ static int mdss_fb_probe(struct platform_device *pdev) lcd_backlight_registered = 1; } + mdss_fb_init_panel_modes(mfd, pdata); + mdss_fb_create_sysfs(mfd); mdss_fb_send_panel_event(mfd, MDSS_EVENT_FB_REGISTERED, fbi); @@ -2946,34 +3024,38 @@ u32 mdss_fb_get_mode_switch(struct msm_fb_data_type *mfd) * and return values (such as buffer release fences) are based on the * panel mode being switching into. */ -int __ioctl_transition_dyn_mode_state(struct msm_fb_data_type *mfd, +static int __ioctl_transition_dyn_mode_state(struct msm_fb_data_type *mfd, unsigned int cmd, int validate) { - if (cmd == MDSS_MDP_NO_UPDATE_REQUESTED) + if (mfd->switch_state == MDSS_MDP_NO_UPDATE_REQUESTED) return 0; mutex_lock(&mfd->switch_lock); switch (cmd) { case MSMFB_BUFFER_SYNC: if (mfd->switch_state == MDSS_MDP_WAIT_FOR_SYNC) { - mdss_fb_set_mdp_sync_pt_threshold(mfd, - mfd->switch_new_mode); + if (mfd->switch_new_mode != SWITCH_RESOLUTION) + mdss_fb_set_mdp_sync_pt_threshold(mfd, + mfd->switch_new_mode); mfd->switch_state = MDSS_MDP_WAIT_FOR_COMMIT; } break; case MSMFB_OVERLAY_PREPARE: if (mfd->switch_state == MDSS_MDP_WAIT_FOR_PREP) { - mfd->pending_switch = true; + if (mfd->switch_new_mode != SWITCH_RESOLUTION) + mfd->pending_switch = true; mfd->switch_state = MDSS_MDP_WAIT_FOR_SYNC; } break; case MSMFB_ATOMIC_COMMIT: if ((mfd->switch_state == MDSS_MDP_WAIT_FOR_PREP) && validate) { - mfd->pending_switch = true; + if (mfd->switch_new_mode != SWITCH_RESOLUTION) + mfd->pending_switch = true; mfd->switch_state = MDSS_MDP_WAIT_FOR_SYNC; } else if (mfd->switch_state == MDSS_MDP_WAIT_FOR_SYNC) { - mdss_fb_set_mdp_sync_pt_threshold(mfd, - mfd->switch_new_mode); + if (mfd->switch_new_mode != SWITCH_RESOLUTION) + mdss_fb_set_mdp_sync_pt_threshold(mfd, + mfd->switch_new_mode); mfd->switch_state = MDSS_MDP_WAIT_FOR_COMMIT; } break; @@ -3064,6 +3146,21 @@ static int mdss_fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) { struct mdp_display_commit disp_commit; + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + + /* + * during mode switch through mode sysfs node, it will trigger a + * pan_display after switch. This assumes that fb has been adjusted, + * however when using overlays we may not have the right size at this + * point, so it needs to go through PREPARE first. Abort pan_display + * operations until that happens + */ + if (mfd->switch_state != MDSS_MDP_NO_UPDATE_REQUESTED) { + pr_debug("fb%d: pan_display skipped during switch\n", + mfd->index); + return 0; + } + memset(&disp_commit, 0, sizeof(disp_commit)); disp_commit.wait_for_finish = true; memcpy(&disp_commit.var, var, sizeof(struct fb_var_screeninfo)); @@ -3363,7 +3460,13 @@ static int mdss_fb_check_var(struct fb_var_screeninfo *var, if (var->yoffset > (var->yres_virtual - var->yres)) return -EINVAL; - if (mfd->panel_info) { + if (info->mode) { + const struct fb_videomode *mode; + + mode = fb_match_mode(var, &info->modelist); + if (mode == NULL) + return -EINVAL; + } else if (mfd->panel_info && !(var->activate & FB_ACTIVATE_TEST)) { int rc; memcpy(&mfd->reconfig_panel_info, mfd->panel_info, @@ -3379,6 +3482,66 @@ static int mdss_fb_check_var(struct fb_var_screeninfo *var, return 0; } +static int mdss_fb_videomode_switch(struct msm_fb_data_type *mfd, + const struct fb_videomode *mode) +{ + int ret = 0; + struct mdss_panel_data *pdata, *tmp; + struct mdss_panel_timing *timing; + + pdata = dev_get_platdata(&mfd->pdev->dev); + if (!pdata) { + pr_err("no panel connected\n"); + return -ENODEV; + } + + /* make sure that we are idle while switching */ + mdss_fb_wait_for_kickoff(mfd); + + pr_debug("fb%d: changing display mode to %s\n", mfd->index, mode->name); + + tmp = pdata; + do { + if (!tmp->event_handler) { + pr_warn("no event handler for panel\n"); + continue; + } + timing = mdss_panel_get_timing_by_name(tmp, mode->name); + ret = tmp->event_handler(tmp, + MDSS_EVENT_PANEL_TIMING_SWITCH, timing); + + tmp->active = timing != NULL; + tmp = tmp->next; + } while (tmp && !ret); + + if (!ret && mfd->mdp.configure_panel) { + int dest_ctrl = 1; + + /* todo: currently assumes no changes in video/cmd mode */ + if (!mdss_fb_is_power_off(mfd)) { + mutex_lock(&mfd->switch_lock); + mfd->switch_state = MDSS_MDP_WAIT_FOR_PREP; + mfd->switch_new_mode = SWITCH_RESOLUTION; + mutex_unlock(&mfd->switch_lock); + dest_ctrl = 0; + } + ret = mfd->mdp.configure_panel(mfd, + pdata->panel_info.mipi.mode, dest_ctrl); + } + + if (!ret) { + if (pdata->next && pdata->next->active) + mfd->split_mode = MDP_DUAL_LM_DUAL_DISPLAY; + else + mfd->split_mode = MDP_SPLIT_MODE_NONE; + mdss_fb_validate_split(0, 0, mfd); + } + + pr_debug("fb%d: %s mode change complete\n", mfd->index, mode->name); + + return ret; +} + static int mdss_fb_set_par(struct fb_info *info) { struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; @@ -3435,6 +3598,17 @@ static int mdss_fb_set_par(struct fb_info *info) return -EINVAL; } + if (info->mode) { + const struct fb_videomode *mode; + + mode = fb_match_mode(var, &info->modelist); + if (!mode) + return -EINVAL; + + ret = mdss_fb_videomode_switch(mfd, mode); + if (ret) + return ret; + } if (mfd->mdp.fb_stride) mfd->fbi->fix.line_length = mfd->mdp.fb_stride(mfd->index, @@ -4261,6 +4435,7 @@ int mdss_register_panel(struct platform_device *pdev, goto mdss_notfound; } + pdata->active = true; fb_pdev = of_find_device_by_node(node); if (fb_pdev) { rc = mdss_fb_register_extra_panel(fb_pdev, pdata); diff --git a/drivers/video/fbdev/msm/mdss_mdp.h b/drivers/video/fbdev/msm/mdss_mdp.h index d7b82a9bb882..8e77ad8634e4 100644 --- a/drivers/video/fbdev/msm/mdss_mdp.h +++ b/drivers/video/fbdev/msm/mdss_mdp.h @@ -237,6 +237,13 @@ struct mdss_mdp_ctl_intfs_ops { struct mdss_mdp_ctl *sctl, int new_fps); int (*restore_fnc)(struct mdss_mdp_ctl *ctl); int (*early_wake_up_fnc)(struct mdss_mdp_ctl *ctl); + + /* + * reconfigure interface for new resolution, called before (pre=1) + * and after interface has been reconfigured (pre=0) + */ + int (*reconfigure)(struct mdss_mdp_ctl *ctl, + enum dynamic_switch_modes mode, bool pre); }; struct mdss_mdp_ctl { @@ -320,6 +327,7 @@ struct mdss_mdp_ctl { bool force_ctl_start; u64 last_input_time; + int pending_mode_switch; }; struct mdss_mdp_mixer { diff --git a/drivers/video/fbdev/msm/mdss_mdp_ctl.c b/drivers/video/fbdev/msm/mdss_mdp_ctl.c index 00c360b6a0c2..84df1d98cebb 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_ctl.c +++ b/drivers/video/fbdev/msm/mdss_mdp_ctl.c @@ -2595,11 +2595,6 @@ static int mdss_mdp_ctl_fbc_enable(int enable, fbc = &pdata->fbc; - if (!fbc || !fbc->enabled) { - pr_err("Invalid FBC structure\n"); - return -EINVAL; - } - if (mixer->num == MDSS_MDP_INTF_LAYERMIXER0 || mixer->num == MDSS_MDP_INTF_LAYERMIXER1) { pr_debug("Mixer supports FBC.\n"); @@ -2767,8 +2762,8 @@ int mdss_mdp_ctl_setup(struct mdss_mdp_ctl *ctl) int mdss_mdp_ctl_reconfig(struct mdss_mdp_ctl *ctl, struct mdss_panel_data *pdata) { - int (*stop_fnc)(struct mdss_mdp_ctl *ctl, int panel_power_state); - int ret; + void *tmp; + int ret = 0; /* * Switch first to prevent deleting important data in the case @@ -2780,13 +2775,17 @@ int mdss_mdp_ctl_reconfig(struct mdss_mdp_ctl *ctl, return -EINVAL; } + /* if only changing resolution there is no need for intf reconfig */ + if (!ctl->is_video_mode == (pdata->panel_info.type == MIPI_CMD_PANEL)) + goto skip_intf_reconfig; + /* * Intentionally not clearing stop function, as stop will * be called after panel is instructed mode switch is happening */ - stop_fnc = ctl->ops.stop_fnc; + tmp = ctl->ops.stop_fnc; memset(&ctl->ops, 0, sizeof(ctl->ops)); - ctl->ops.stop_fnc = stop_fnc; + ctl->ops.stop_fnc = tmp; switch (pdata->panel_info.type) { case MIPI_VIDEO_PANEL: @@ -2810,6 +2809,16 @@ int mdss_mdp_ctl_reconfig(struct mdss_mdp_ctl *ctl, ctl->opmode |= (ctl->intf_num << 4); +skip_intf_reconfig: + ctl->width = get_panel_xres(&pdata->panel_info); + ctl->height = get_panel_yres(&pdata->panel_info); + if (ctl->mixer_left) { + ctl->mixer_left->width = ctl->width; + ctl->mixer_left->height = ctl->height; + } + ctl->border_x_off = pdata->panel_info.lcdc.border_left; + ctl->border_y_off = pdata->panel_info.lcdc.border_top; + return ret; } @@ -3098,7 +3107,7 @@ int mdss_mdp_ctl_intf_event(struct mdss_mdp_ctl *ctl, int event, void *arg, if (pdata->event_handler) rc = pdata->event_handler(pdata, event, arg); pdata = pdata->next; - } while (rc == 0 && pdata && !skip_broadcast); + } while (rc == 0 && pdata && pdata->active && !skip_broadcast); return rc; } @@ -3242,7 +3251,7 @@ int mdss_mdp_ctl_start(struct mdss_mdp_ctl *ctl, bool handoff) pr_debug("ctl_num=%d, power_state=%d\n", ctl->num, ctl->power_state); if (mdss_mdp_ctl_is_power_on_interactive(ctl) - && !(ctl->force_ctl_start)) { + && !(ctl->pending_mode_switch)) { pr_debug("%d: panel already on!\n", __LINE__); return 0; } @@ -3262,7 +3271,7 @@ int mdss_mdp_ctl_start(struct mdss_mdp_ctl *ctl, bool handoff) * keep power_on false during handoff to avoid unexpected * operations to overlay. */ - if (!handoff || ctl->force_ctl_start) + if (!handoff || ctl->pending_mode_switch) ctl->power_state = MDSS_PANEL_POWER_ON; mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); @@ -3328,8 +3337,7 @@ int mdss_mdp_ctl_stop(struct mdss_mdp_ctl *ctl, int power_state) if (ctl->ops.stop_fnc) { ret = ctl->ops.stop_fnc(ctl, power_state); - if (ctl->panel_data->panel_info.fbc.enabled) - mdss_mdp_ctl_fbc_enable(0, ctl->mixer_left, + mdss_mdp_ctl_fbc_enable(0, ctl->mixer_left, &ctl->panel_data->panel_info); } else { pr_warn("no stop func for ctl=%d\n", ctl->num); @@ -3337,8 +3345,7 @@ int mdss_mdp_ctl_stop(struct mdss_mdp_ctl *ctl, int power_state) if (sctl && sctl->ops.stop_fnc) { ret = sctl->ops.stop_fnc(sctl, power_state); - if (ctl->panel_data->panel_info.fbc.enabled) - mdss_mdp_ctl_fbc_enable(0, sctl->mixer_left, + mdss_mdp_ctl_fbc_enable(0, sctl->mixer_left, &sctl->panel_data->panel_info); } if (ret) { @@ -3373,7 +3380,8 @@ int mdss_mdp_ctl_stop(struct mdss_mdp_ctl *ctl, int power_state) end: if (!ret) { ctl->power_state = power_state; - mdss_mdp_ctl_perf_update(ctl, 0, true); + if (!ctl->pending_mode_switch) + mdss_mdp_ctl_perf_update(ctl, 0, true); } mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); @@ -4593,7 +4601,7 @@ int mdss_mdp_display_commit(struct mdss_mdp_ctl *ctl, void *arg, ctl->valid_roi = 0; if (ret) - pr_warn("error displaying frame\n"); + pr_warn("ctl %d error displaying frame\n", ctl->num); ctl->play_cnt++; ATRACE_END("flush_kickoff"); diff --git a/drivers/video/fbdev/msm/mdss_mdp_intf_cmd.c b/drivers/video/fbdev/msm/mdss_mdp_intf_cmd.c index 0883708f0d32..14ebd4abb817 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_intf_cmd.c +++ b/drivers/video/fbdev/msm/mdss_mdp_intf_cmd.c @@ -24,8 +24,6 @@ #define MAX_SESSIONS 2 #define SPLIT_MIXER_OFFSET 0x800 -/* wait for at most 2 vsync for lowest refresh rate (24hz) */ -#define KOFF_TIMEOUT msecs_to_jiffies(84) #define STOP_TIMEOUT(hz) msecs_to_jiffies((1000 / hz) * (6 + 2)) #define POWER_COLLAPSE_TIME msecs_to_jiffies(100) @@ -66,7 +64,6 @@ struct mdss_mdp_cmd_ctx { struct mdss_mdp_cmd_ctx *sync_ctx; /* for partial update */ u32 pp_timeout_report_cnt; int pingpong_split_slave; - bool pending_mode_switch; /* Used to prevent powering down in stop */ }; struct mdss_mdp_cmd_ctx mdss_mdp_cmd_ctx_list[MAX_SESSIONS]; @@ -1436,21 +1433,26 @@ static int mdss_mdp_cmd_panel_on(struct mdss_mdp_ctl *ctl, sctx = (struct mdss_mdp_cmd_ctx *) sctl->intf_ctx[MASTER_CTX]; if (!__mdss_mdp_cmd_is_panel_power_on_interactive(ctx)) { - rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_LINK_READY, NULL, - false); - WARN(rc, "intf %d link ready error (%d)\n", ctl->intf_num, rc); + if (ctl->pending_mode_switch != SWITCH_RESOLUTION) { + rc = mdss_mdp_ctl_intf_event(ctl, + MDSS_EVENT_LINK_READY, NULL, false); + WARN(rc, "intf %d link ready error (%d)\n", + ctl->intf_num, rc); - rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_UNBLANK, NULL, - false); - WARN(rc, "intf %d unblank error (%d)\n", ctl->intf_num, rc); + rc = mdss_mdp_ctl_intf_event(ctl, + MDSS_EVENT_UNBLANK, NULL, false); + WARN(rc, "intf %d unblank error (%d)\n", + ctl->intf_num, rc); - rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_ON, NULL, - false); - WARN(rc, "intf %d panel on error (%d)\n", ctl->intf_num, rc); + rc = mdss_mdp_ctl_intf_event(ctl, + MDSS_EVENT_PANEL_ON, NULL, false); + WARN(rc, "intf %d panel on error (%d)\n", + ctl->intf_num, rc); - rc = mdss_mdp_tearcheck_enable(ctl, true); - WARN(rc, "intf %d tearcheck enable error (%d)\n", - ctl->intf_num, rc); + rc = mdss_mdp_tearcheck_enable(ctl, true); + WARN(rc, "intf %d tearcheck enable error (%d)\n", + ctl->intf_num, rc); + } ctx->panel_power_state = MDSS_PANEL_POWER_ON; if (sctx) @@ -1698,8 +1700,7 @@ int mdss_mdp_cmd_restore(struct mdss_mdp_ctl *ctl) } int mdss_mdp_cmd_ctx_stop(struct mdss_mdp_ctl *ctl, - struct mdss_mdp_cmd_ctx *ctx, int panel_power_state, - bool pend_switch) + struct mdss_mdp_cmd_ctx *ctx, int panel_power_state) { struct mdss_mdp_cmd_ctx *sctx = NULL; struct mdss_mdp_ctl *sctl = NULL; @@ -1724,7 +1725,7 @@ int mdss_mdp_cmd_ctx_stop(struct mdss_mdp_ctl *ctl, ; } - if (!pend_switch) { + if (!ctl->pending_mode_switch) { mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_REGISTER_RECOVERY_HANDLER, NULL, @@ -1753,7 +1754,7 @@ int mdss_mdp_cmd_ctx_stop(struct mdss_mdp_ctl *ctl, } int mdss_mdp_cmd_intfs_stop(struct mdss_mdp_ctl *ctl, int session, - int panel_power_state, bool pend_switch) + int panel_power_state) { struct mdss_mdp_cmd_ctx *ctx; @@ -1766,7 +1767,7 @@ int mdss_mdp_cmd_intfs_stop(struct mdss_mdp_ctl *ctl, int session, return -ENODEV; } - mdss_mdp_cmd_ctx_stop(ctl, ctx, panel_power_state, pend_switch); + mdss_mdp_cmd_ctx_stop(ctl, ctx, panel_power_state); if (is_pingpong_split(ctl->mfd)) { session += 1; @@ -1779,14 +1780,14 @@ int mdss_mdp_cmd_intfs_stop(struct mdss_mdp_ctl *ctl, int session, pr_err("invalid ctx session: %d\n", session); return -ENODEV; } - mdss_mdp_cmd_ctx_stop(ctl, ctx, panel_power_state, pend_switch); + mdss_mdp_cmd_ctx_stop(ctl, ctx, panel_power_state); } pr_debug("%s:-\n", __func__); return 0; } static int mdss_mdp_cmd_stop_sub(struct mdss_mdp_ctl *ctl, - int panel_power_state, bool pend_switch) + int panel_power_state) { struct mdss_mdp_cmd_ctx *ctx; struct mdss_mdp_vsync_handler *tmp, *handle; @@ -1804,8 +1805,7 @@ static int mdss_mdp_cmd_stop_sub(struct mdss_mdp_ctl *ctl, /* Command mode is supported only starting at INTF1 */ session = ctl->intf_num - MDSS_MDP_INTF1; - return mdss_mdp_cmd_intfs_stop(ctl, session, panel_power_state, - pend_switch); + return mdss_mdp_cmd_intfs_stop(ctl, session, panel_power_state); } int mdss_mdp_cmd_stop(struct mdss_mdp_ctl *ctl, int panel_power_state) @@ -1815,7 +1815,6 @@ int mdss_mdp_cmd_stop(struct mdss_mdp_ctl *ctl, int panel_power_state) bool panel_off = false; bool turn_off_clocks = false; bool send_panel_events = false; - bool pend_switch = false; int ret = 0; if (!ctx) { @@ -1901,14 +1900,11 @@ int mdss_mdp_cmd_stop(struct mdss_mdp_ctl *ctl, int panel_power_state) if (!turn_off_clocks) goto panel_events; - if (ctx->pending_mode_switch) { - pend_switch = true; + if (ctl->pending_mode_switch) send_panel_events = false; - ctx->pending_mode_switch = 0; - } pr_debug("%s: turn off interface clocks\n", __func__); - ret = mdss_mdp_cmd_stop_sub(ctl, panel_power_state, pend_switch); + ret = mdss_mdp_cmd_stop_sub(ctl, panel_power_state); if (IS_ERR_VALUE(ret)) { pr_err("%s: unable to stop interface: %d\n", __func__, ret); @@ -1916,7 +1912,7 @@ int mdss_mdp_cmd_stop(struct mdss_mdp_ctl *ctl, int panel_power_state) } if (sctl) { - mdss_mdp_cmd_stop_sub(sctl, panel_power_state, pend_switch); + mdss_mdp_cmd_stop_sub(sctl, panel_power_state); if (IS_ERR_VALUE(ret)) { pr_err("%s: unable to stop slave intf: %d\n", __func__, ret); @@ -1951,6 +1947,7 @@ panel_events: ctl->ops.wait_pingpong = NULL; ctl->ops.add_vsync_handler = NULL; ctl->ops.remove_vsync_handler = NULL; + ctl->ops.reconfigure = NULL; end: if (!IS_ERR_VALUE(ret)) @@ -2165,7 +2162,6 @@ void mdss_mdp_switch_roi_reset(struct mdss_mdp_ctl *ctl) void mdss_mdp_switch_to_vid_mode(struct mdss_mdp_ctl *ctl, int prep) { - struct mdss_mdp_cmd_ctx *ctx = ctl->intf_ctx[MASTER_CTX]; long int mode = MIPI_VIDEO_PANEL; int rc = 0; @@ -2180,7 +2176,6 @@ void mdss_mdp_switch_to_vid_mode(struct mdss_mdp_ctl *ctl, int prep) rc = mdss_mdp_ctl_intf_event (ctl, MDSS_EVENT_PANEL_CLK_CTRL, (void *)1, false); - ctx->pending_mode_switch = 1; return; } @@ -2188,6 +2183,56 @@ void mdss_mdp_switch_to_vid_mode(struct mdss_mdp_ctl *ctl, int prep) (void *) mode, false); } +static int mdss_mdp_cmd_reconfigure(struct mdss_mdp_ctl *ctl, + enum dynamic_switch_modes mode, bool prep) +{ + int ret, rc = 0; + + if (mdss_mdp_ctl_is_power_off(ctl)) + return 0; + + pr_debug("%s: ctl=%d mode=%d prep=%d\n", __func__, + ctl->num, mode, prep); + + if (mode == SWITCH_TO_VIDEO_MODE) { + mdss_mdp_switch_to_vid_mode(ctl, prep); + } else if (mode == SWITCH_RESOLUTION) { + if (prep) { + /* make sure any pending transfer is finished */ + ret = mdss_mdp_cmd_wait4pingpong(ctl, NULL); + if (ret) + return ret; + + /* + * keep a ref count on clocks to prevent them from + * being disabled while switch happens + */ + mdss_bus_bandwidth_ctrl(true); + rc = mdss_iommu_ctrl(1); + if (IS_ERR_VALUE(rc)) + pr_err("IOMMU attach failed\n"); + + mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); + mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_CLK_CTRL, + (void *)1, false); + + mdss_mdp_ctl_stop(ctl, MDSS_PANEL_POWER_OFF); + mdss_mdp_ctl_intf_event(ctl, + MDSS_EVENT_DSI_DYNAMIC_SWITCH, + (void *) mode, false); + } else { + /* release ref count after switch is complete */ + mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_CLK_CTRL, + (void *)0, false); + mdss_iommu_ctrl(0); + mdss_bus_bandwidth_ctrl(false); + mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); + } + } + + return 0; +} + int mdss_mdp_cmd_start(struct mdss_mdp_ctl *ctl) { int ret, session = 0; @@ -2210,6 +2255,7 @@ int mdss_mdp_cmd_start(struct mdss_mdp_ctl *ctl) ctl->ops.read_line_cnt_fnc = mdss_mdp_cmd_line_count; ctl->ops.restore_fnc = mdss_mdp_cmd_restore; ctl->ops.early_wake_up_fnc = mdss_mdp_cmd_early_wake_up; + ctl->ops.reconfigure = mdss_mdp_cmd_reconfigure; pr_debug("%s:-\n", __func__); return 0; diff --git a/drivers/video/fbdev/msm/mdss_mdp_overlay.c b/drivers/video/fbdev/msm/mdss_mdp_overlay.c index cc22f07dbe20..92285f4550ed 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_overlay.c +++ b/drivers/video/fbdev/msm/mdss_mdp_overlay.c @@ -1652,12 +1652,32 @@ int mdss_mode_switch(struct msm_fb_data_type *mfd, u32 mode) { struct mdss_rect l_roi, r_roi; struct mdss_mdp_ctl *ctl = mfd_to_ctl(mfd); + struct mdss_mdp_ctl *sctl; int rc; - pr_debug("%s, start\n", __func__); + pr_debug("fb%d switch to mode=%x\n", mfd->index, mode); + ATRACE_FUNC(); + + ctl->pending_mode_switch = mode; + sctl = mdss_mdp_get_split_ctl(ctl); + if (sctl) + sctl->pending_mode_switch = mode; /* No need for mode validation. It has been done in ioctl call */ - if (mode == MIPI_CMD_PANEL) { + if (mode == SWITCH_RESOLUTION) { + if (ctl->ops.reconfigure) { + rc = ctl->ops.reconfigure(ctl, mode, 1); + if (rc) + return rc; + /* + * For Video mode panels, reconfigure is not defined. + * So doing an explicit ctrl stop during resolution switch + * to balance the ctrl start at the end of this function. + */ + } else { + mdss_mdp_ctl_stop(ctl, MDSS_PANEL_POWER_OFF); + } + } else if (mode == MIPI_CMD_PANEL) { /* * Need to reset roi if there was partial update in previous * Command frame @@ -1689,17 +1709,16 @@ int mdss_mode_switch(struct msm_fb_data_type *mfd, u32 mode) return -EINVAL; } - ctl->force_ctl_start = 1; mdss_mdp_ctl_start(ctl, true); - ctl->force_ctl_start = 0; + ATRACE_END(__func__); - pr_debug("%s, end\n", __func__); return 0; } int mdss_mode_switch_post(struct msm_fb_data_type *mfd, u32 mode) { struct mdss_mdp_ctl *ctl = mfd_to_ctl(mfd); + struct mdss_mdp_ctl *sctl; int rc = 0; u32 frame_rate = 0; @@ -1730,7 +1749,15 @@ int mdss_mode_switch_post(struct msm_fb_data_type *mfd, u32 mode) mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_CLK_CTRL, (void *)0, false); + } else if (mode == SWITCH_RESOLUTION) { + if (ctl->ops.reconfigure) + rc = ctl->ops.reconfigure(ctl, mode, 0); } + ctl->pending_mode_switch = 0; + sctl = mdss_mdp_get_split_ctl(ctl); + if (sctl) + sctl->pending_mode_switch = 0; + return rc; } @@ -4804,18 +4831,42 @@ static int mdss_mdp_update_panel_info(struct msm_fb_data_type *mfd, mdss_mdp_ctl_destroy(mdp5_data->ctl); mdp5_data->ctl = NULL; } else { + if (is_panel_split(mfd) && mdp5_data->mdata->has_pingpong_split) + mfd->split_mode = MDP_PINGPONG_SPLIT; /* * Dynamic change so we need to reconfig instead of * destroying current ctrl sturcture. */ pdata = dev_get_platdata(&mfd->pdev->dev); mdss_mdp_ctl_reconfig(ctl, pdata); + sctl = mdss_mdp_get_split_ctl(ctl); - if (sctl) - mdss_mdp_ctl_reconfig(sctl, pdata->next); + if (sctl) { + if (mfd->split_mode == MDP_DUAL_LM_DUAL_DISPLAY) { + mdss_mdp_ctl_reconfig(sctl, pdata->next); + sctl->border_x_off += + pdata->panel_info.lcdc.border_left + + pdata->panel_info.lcdc.border_right; + } else { + /* + * todo: need to revisit this and properly + * cleanup slave resources + */ + mdss_mdp_ctl_destroy(sctl); + ctl->mixer_right = NULL; + } + } else if (mfd->split_mode == MDP_DUAL_LM_DUAL_DISPLAY) { + /* enable split display for the first time */ + ret = mdss_mdp_ctl_split_display_setup(ctl, + pdata->next); + if (ret) { + mdss_mdp_ctl_destroy(ctl); + mdp5_data->ctl = NULL; + } + } } - return 0; + return ret; } int mdss_mdp_input_event_handler(struct msm_fb_data_type *mfd) diff --git a/drivers/video/fbdev/msm/mdss_panel.c b/drivers/video/fbdev/msm/mdss_panel.c index ca344f1e51c1..7e8b78ada511 100644 --- a/drivers/video/fbdev/msm/mdss_panel.c +++ b/drivers/video/fbdev/msm/mdss_panel.c @@ -575,3 +575,61 @@ void mdss_panel_debugfsinfo_to_panelinfo(struct mdss_panel_info *panel_info) pdata = pdata->next; } while (pdata); } + +struct mdss_panel_timing *mdss_panel_get_timing_by_name( + struct mdss_panel_data *pdata, + const char *name) +{ + struct mdss_panel_timing *pt; + + if (pdata && name) { + list_for_each_entry(pt, &pdata->timings_list, list) + if (pt->name && !strcmp(pt->name, name)) + return pt; + } + + return NULL; +} + +void mdss_panel_info_from_timing(struct mdss_panel_timing *pt, + struct mdss_panel_info *pinfo) +{ + if (!pt || !pinfo) + return; + + pinfo->clk_rate = pt->clk_rate; + pinfo->xres = pt->xres; + pinfo->lcdc.h_front_porch = pt->h_front_porch; + pinfo->lcdc.h_back_porch = pt->h_back_porch; + pinfo->lcdc.h_pulse_width = pt->h_pulse_width; + + pinfo->yres = pt->yres; + pinfo->lcdc.v_front_porch = pt->v_front_porch; + pinfo->lcdc.v_back_porch = pt->v_back_porch; + pinfo->lcdc.v_pulse_width = pt->v_pulse_width; + + pinfo->lcdc.border_bottom = pt->border_bottom; + pinfo->lcdc.border_top = pt->border_top; + pinfo->lcdc.border_left = pt->border_left; + pinfo->lcdc.border_right = pt->border_right; + pinfo->lcdc.xres_pad = pt->border_left + pt->border_right; + pinfo->lcdc.yres_pad = pt->border_top + pt->border_bottom; + + pinfo->lm_widths[0] = pt->lm_widths[0]; + pinfo->lm_widths[1] = pt->lm_widths[1]; + + pinfo->mipi.frame_rate = pt->frame_rate; + pinfo->edp.frame_rate = pinfo->mipi.frame_rate; + + pinfo->dsc = pt->dsc; + pinfo->dsc_enc_total = pt->dsc_enc_total; + pinfo->fbc = pt->fbc; + pinfo->compression_mode = pt->compression_mode; + + pinfo->te = pt->te; + + /* override te parameters if panel is in sw te mode */ + if (pinfo->sim_panel_mode == SIM_SW_TE_MODE) + mdss_panel_override_te_params(pinfo); + +} diff --git a/drivers/video/fbdev/msm/mdss_panel.h b/drivers/video/fbdev/msm/mdss_panel.h index e33fc66236cf..81277c14c3ef 100644 --- a/drivers/video/fbdev/msm/mdss_panel.h +++ b/drivers/video/fbdev/msm/mdss_panel.h @@ -204,7 +204,7 @@ struct mdss_intf_recovery { * - 1: update to command mode * @MDSS_EVENT_REGISTER_RECOVERY_HANDLER: Event to recover the interface in * case there was any errors detected. - * @ MDSS_EVENT_DSI_PANEL_STATUS:Event to check the panel status + * @MDSS_EVENT_DSI_PANEL_STATUS: Event to check the panel status * <= 0: panel check fail * > 0: panel check success * @MDSS_EVENT_DSI_DYNAMIC_SWITCH: Send DCS command to panel to initiate @@ -216,6 +216,8 @@ struct mdss_intf_recovery { * - MIPI_CMD_PANEL: switch to command mode * @MDSS_EVENT_DSI_RESET_WRITE_PTR: Reset the write pointer coordinates on * the panel. + * @MDSS_EVENT_PANEL_TIMING_SWITCH: Panel timing switch is requested. + * Argument provided is new panel timing. */ enum mdss_intf_events { MDSS_EVENT_RESET = 1, @@ -243,6 +245,7 @@ enum mdss_intf_events { MDSS_EVENT_DSI_DYNAMIC_SWITCH, MDSS_EVENT_DSI_RECONFIG_CMD, MDSS_EVENT_DSI_RESET_WRITE_PTR, + MDSS_EVENT_PANEL_TIMING_SWITCH, }; struct lcd_panel_info { @@ -283,12 +286,57 @@ struct mdss_dsi_phy_ctrl { char lanecfg_len; }; +/** + * enum dynamic_mode_switch - Dynamic mode switch methods + * @DYNAMIC_MODE_SWITCH_DISABLED: Dynamic mode switch is not supported + * @DYNAMIC_MODE_SWITCH_SUSPEND_RESUME: Switch requires panel suspend/resume + * @DYNAMIC_MODE_SWITCH_IMMEDIATE: Supports video/cmd mode switch immediately + * @DYNAMIC_MODE_RESOLUTION_SWITCH_IMMEDIATE: Panel supports display resolution + * switch immediately. + **/ enum dynamic_mode_switch { DYNAMIC_MODE_SWITCH_DISABLED = 0, DYNAMIC_MODE_SWITCH_SUSPEND_RESUME, DYNAMIC_MODE_SWITCH_IMMEDIATE, + DYNAMIC_MODE_RESOLUTION_SWITCH_IMMEDIATE, }; +/** + * enum dynamic_switch_modes - Type of dynamic mode switch to be given as + * argument to MDSS_EVENT_DSI_DYNAMIC_SWITCH event + * @SWITCH_TO_CMD_MODE: Switch from DSI video mode to command mode + * @SWITCH_TO_VIDEO_MODE: Switch from DSI command mode to video mode + * @SWITCH_RESOLUTION: Switch only display resolution + **/ +enum dynamic_switch_modes { + SWITCH_MODE_UNKNOWN = 0, + SWITCH_TO_CMD_MODE, + SWITCH_TO_VIDEO_MODE, + SWITCH_RESOLUTION, +}; + +/** + * struct mdss_panel_timing - structure for panel timing information + * @list: List head ptr to track within panel data timings list + * @name: A unique name of this timing that can be used to identify it + * @xres: Panel width + * @yres: Panel height + * @h_back_porch: Horizontal back porch + * @h_front_porch: Horizontal front porch + * @h_pulse_width: Horizontal pulse width + * @hsync_skew: Horizontal sync skew + * @v_back_porch: Vertical back porch + * @v_front_porch: Vertical front porch + * @v_pulse_width: Vertical pulse width + * @border_top: Border color on top + * @border_bottom: Border color on bottom + * @border_left: Border color on left + * @border_right: Border color on right + * @clk_rate: Pixel clock rate of this panel timing + * @frame_rate: Display refresh rate + * @fbc: Framebuffer compression parameters for this display timing + * @te: Tearcheck parameters for this display timing + **/ struct mipi_panel_info { char boot_mode; /* identify if mode switched from starting mode */ char mode; /* video/cmd */ @@ -585,6 +633,39 @@ struct mdss_panel_info { struct mdss_panel_debugfs_info *debugfs_info; }; +struct mdss_panel_timing { + struct list_head list; + const char *name; + + u32 xres; + u32 yres; + + u32 h_back_porch; + u32 h_front_porch; + u32 h_pulse_width; + u32 hsync_skew; + u32 v_back_porch; + u32 v_front_porch; + u32 v_pulse_width; + + u32 border_top; + u32 border_bottom; + u32 border_left; + u32 border_right; + + u32 lm_widths[2]; + + u32 clk_rate; + char frame_rate; + + u8 dsc_enc_total; + struct dsc_desc dsc; + struct fbc_panel_info fbc; + u32 compression_mode; + + struct mdss_mdp_pp_tear_check te; +}; + struct mdss_panel_data { struct mdss_panel_info panel_info; void (*set_backlight) (struct mdss_panel_data *pdata, u32 bl_level); @@ -604,6 +685,10 @@ struct mdss_panel_data { */ int (*event_handler) (struct mdss_panel_data *pdata, int e, void *arg); + struct list_head timings_list; + struct mdss_panel_timing *current_timing; + bool active; + struct mdss_panel_data *next; }; @@ -826,6 +911,28 @@ int mdss_panel_debugfs_init(struct mdss_panel_info *panel_info, char const *panel_name); void mdss_panel_debugfs_cleanup(struct mdss_panel_info *panel_info); void mdss_panel_debugfsinfo_to_panelinfo(struct mdss_panel_info *panel_info); + +/* + * mdss_panel_info_from_timing() - populate panel info from panel timing + * @pt: pointer to source panel timing + * @pinfo: pointer to destination panel info + * + * Populates relevant data from panel timing into panel info + */ +void mdss_panel_info_from_timing(struct mdss_panel_timing *pt, + struct mdss_panel_info *pinfo); + +/** + * mdss_panel_get_timing_by_name() - return panel timing with matching name + * @pdata: pointer to panel data struct containing list of panel timings + * @name: name of the panel timing to be returned + * + * Looks through list of timings provided in panel data and returns pointer + * to panel timing matching it. If none is found, NULL is returned. + */ +struct mdss_panel_timing *mdss_panel_get_timing_by_name( + struct mdss_panel_data *pdata, + const char *name); #else static inline int mdss_panel_debugfs_init( struct mdss_panel_info *panel_info) { return 0; }; @@ -833,5 +940,10 @@ static inline void mdss_panel_debugfs_cleanup( struct mdss_panel_info *panel_info) { }; static inline void mdss_panel_debugfsinfo_to_panelinfo( struct mdss_panel_info *panel_info) { }; +static inline void mdss_panel_info_from_timing(struct mdss_panel_timing *pt, + struct mdss_panel_info *pinfo) { }; +static inline struct mdss_panel_timing *mdss_panel_get_timing_by_name( + struct mdss_panel_data *pdata, + const char *name) { return NULL; }; #endif #endif /* MDSS_PANEL_H */