diff --git a/drivers/video/fbdev/msm/mdss_dsi.c b/drivers/video/fbdev/msm/mdss_dsi.c index 26baac5e6c8d..2803b9142007 100644 --- a/drivers/video/fbdev/msm/mdss_dsi.c +++ b/drivers/video/fbdev/msm/mdss_dsi.c @@ -184,9 +184,9 @@ error: return ret; } -static int mdss_dsi_panel_power_doze(struct mdss_panel_data *pdata, int enable) +static int mdss_dsi_panel_power_lp(struct mdss_panel_data *pdata, int enable) { - /* Panel power control when entering/exiting doze mode */ + /* Panel power control when entering/exiting lp mode */ return 0; } @@ -223,12 +223,13 @@ static int mdss_dsi_panel_power_ctrl(struct mdss_panel_data *pdata, break; case MDSS_PANEL_POWER_ON: if (mdss_dsi_is_panel_on_lp(pdata)) - ret = mdss_dsi_panel_power_doze(pdata, false); + ret = mdss_dsi_panel_power_lp(pdata, false); else ret = mdss_dsi_panel_power_on(pdata); break; - case MDSS_PANEL_POWER_DOZE: - ret = mdss_dsi_panel_power_doze(pdata, true); + case MDSS_PANEL_POWER_LP1: + case MDSS_PANEL_POWER_LP2: + ret = mdss_dsi_panel_power_lp(pdata, true); break; default: pr_err("%s: unknown panel power state requested (%d)\n", @@ -472,7 +473,7 @@ static int mdss_dsi_off(struct mdss_panel_data *pdata, int power_state) goto end; } - if (power_state != MDSS_PANEL_POWER_OFF) { + if (mdss_panel_is_power_on(power_state)) { pr_debug("%s: dsi_off with panel always on\n", __func__); goto panel_power_ctrl; } @@ -745,7 +746,7 @@ static int mdss_dsi_blank(struct mdss_panel_data *pdata, int power_state) mdss_dsi_clk_ctrl(ctrl_pdata, DSI_ALL_CLKS, 1); - if (power_state == MDSS_PANEL_POWER_DOZE) { + if (mdss_panel_is_power_on_lp(power_state)) { pr_debug("%s: low power state requested\n", __func__); if (ctrl_pdata->low_power_config) ret = ctrl_pdata->low_power_config(pdata, true); diff --git a/drivers/video/fbdev/msm/mdss_fb.c b/drivers/video/fbdev/msm/mdss_fb.c index 192b9e0197a9..e54a1d605e50 100644 --- a/drivers/video/fbdev/msm/mdss_fb.c +++ b/drivers/video/fbdev/msm/mdss_fb.c @@ -67,6 +67,10 @@ #endif #define MAX_FBI_LIST 32 + +#define BLANK_FLAG_LP FB_BLANK_VSYNC_SUSPEND +#define BLANK_FLAG_ULP FB_BLANK_NORMAL + static struct fb_info *fbi_list[MAX_FBI_LIST]; static int fbi_list_index; @@ -854,10 +858,11 @@ static int mdss_fb_suspend_sub(struct msm_fb_data_type *mfd) /* * Ideally, display should have either been blanked by now, or * should have transitioned to a low power state. If not, then - * as a fall back option, blank the display. + * as a fall back option, enter ulp state to leave the display + * on, but turn off all interface clocks. */ - if (mdss_fb_is_power_on_interactive(mfd)) { - ret = mdss_fb_blank_sub(FB_BLANK_POWERDOWN, mfd->fbi, + if (mdss_fb_is_power_on(mfd)) { + ret = mdss_fb_blank_sub(BLANK_FLAG_ULP, mfd->fbi, mfd->suspend.op_enable); if (ret) { pr_err("can't turn off display!\n"); @@ -893,13 +898,16 @@ static int mdss_fb_resume_sub(struct msm_fb_data_type *mfd) mfd->op_enable = mfd->suspend.op_enable; /* - * If the fb was explicitly blanked during suspend, then unblank it - * during resume. + * If the fb was explicitly blanked or transitioned to ulp during + * suspend, then undo it during resume with the appropriate unblank + * flag. If fb was in ulp state when entering suspend, then nothing + * needs to be done. */ - if (mdss_panel_is_power_on(mfd->suspend.panel_power_state)) { + if (mdss_panel_is_power_on(mfd->suspend.panel_power_state) && + !mdss_panel_is_power_on_ulp(mfd->suspend.panel_power_state)) { int unblank_flag = mdss_panel_is_power_on_interactive( mfd->suspend.panel_power_state) ? FB_BLANK_UNBLANK : - FB_BLANK_VSYNC_SUSPEND; + BLANK_FLAG_LP; ret = mdss_fb_blank_sub(unblank_flag, mfd->fbi, mfd->op_enable); if (ret) @@ -1180,7 +1188,61 @@ static void mdss_panel_validate_debugfs_info(struct msm_fb_data_type *mfd) } } -static int mdss_fb_unblank_sub(struct msm_fb_data_type *mfd) +static int mdss_fb_blank_blank(struct msm_fb_data_type *mfd, + int req_power_state) +{ + int ret = 0; + int cur_power_state; + + if (!mfd) + return -EINVAL; + + if (!mdss_fb_is_power_on(mfd) || !mfd->mdp.off_fnc) + return 0; + + cur_power_state = mfd->panel_power_state; + + pr_debug("Transitioning from %d --> %d\n", cur_power_state, + req_power_state); + + if (cur_power_state == req_power_state) { + pr_debug("No change in power state\n"); + return 0; + } + + mutex_lock(&mfd->update.lock); + mfd->update.type = NOTIFY_TYPE_SUSPEND; + mfd->update.is_suspend = 1; + mutex_unlock(&mfd->update.lock); + complete(&mfd->update.comp); + del_timer(&mfd->no_update.timer); + mfd->no_update.value = NOTIFY_TYPE_SUSPEND; + complete(&mfd->no_update.comp); + + mfd->op_enable = false; + mutex_lock(&mfd->bl_lock); + if (mdss_panel_is_power_off(req_power_state)) { + /* Stop Display thread */ + if (mfd->disp_thread) + mdss_fb_stop_disp_thread(mfd); + mdss_fb_set_backlight(mfd, 0); + mfd->bl_updated = 0; + } + mfd->panel_power_state = req_power_state; + mutex_unlock(&mfd->bl_lock); + + ret = mfd->mdp.off_fnc(mfd); + if (ret) + mfd->panel_power_state = cur_power_state; + else if (mdss_panel_is_power_off(req_power_state)) + mdss_fb_release_fences(mfd); + mfd->op_enable = true; + complete(&mfd->power_off_comp); + + return ret; +} + +static int mdss_fb_blank_unblank(struct msm_fb_data_type *mfd) { int ret = 0; int cur_power_state; @@ -1199,16 +1261,23 @@ static int mdss_fb_unblank_sub(struct msm_fb_data_type *mfd) } cur_power_state = mfd->panel_power_state; - if (!mdss_panel_is_power_on_interactive(cur_power_state) && - mfd->mdp.on_fnc) { + pr_debug("Transitioning from %d --> %d\n", cur_power_state, + MDSS_PANEL_POWER_ON); + + if (mdss_panel_is_power_on_interactive(cur_power_state)) { + pr_debug("No change in power state\n"); + return 0; + } + + if (mfd->mdp.on_fnc) { ret = mfd->mdp.on_fnc(mfd); - if (ret == 0) { - mfd->panel_power_state = MDSS_PANEL_POWER_ON; - mfd->panel_info->panel_dead = false; - } else if (mfd->disp_thread) { + if (ret) { mdss_fb_stop_disp_thread(mfd); goto error; } + + mfd->panel_power_state = MDSS_PANEL_POWER_ON; + mfd->panel_info->panel_dead = false; mutex_lock(&mfd->update.lock); mfd->update.type = NOTIFY_TYPE_UPDATE; mfd->update.is_suspend = 0; @@ -1253,82 +1322,67 @@ static int mdss_fb_blank_sub(int blank_mode, struct fb_info *info, cur_power_state = mfd->panel_power_state; /* - * If doze mode is requested for video mode panels, treat - * the request as full unblank as there are no low power mode - * settings for video mode panels. + * Low power (lp) and ultra low pwoer (ulp) modes are currently only + * supported for command mode panels. For all other panel, treat lp + * mode as full unblank and ulp mode as full blank. */ - if ((FB_BLANK_VSYNC_SUSPEND == blank_mode) && - (mfd->panel_info->type != MIPI_CMD_PANEL)) { - pr_debug("Doze mode only valid for cmd mode panels\n"); - - if (mdss_panel_is_power_on(cur_power_state)) - return 0; - else - blank_mode = FB_BLANK_UNBLANK; + if (mfd->panel_info->type != MIPI_CMD_PANEL) { + if (BLANK_FLAG_LP == blank_mode) { + pr_debug("lp mode only valid for cmd mode panels\n"); + if (mdss_fb_is_power_on_interactive(mfd)) + return 0; + else + blank_mode = FB_BLANK_UNBLANK; + } else if (BLANK_FLAG_ULP == blank_mode) { + pr_debug("ulp mode valid for cmd mode panels\n"); + if (mdss_fb_is_power_off(mfd)) + return 0; + else + blank_mode = FB_BLANK_POWERDOWN; + } } switch (blank_mode) { case FB_BLANK_UNBLANK: pr_debug("unblank called. cur pwr state=%d\n", cur_power_state); - ret = mdss_fb_unblank_sub(mfd); + ret = mdss_fb_blank_unblank(mfd); break; + case BLANK_FLAG_ULP: + req_power_state = MDSS_PANEL_POWER_LP2; + pr_debug("ultra low power mode requested\n"); + if (mdss_fb_is_power_off(mfd)) { + pr_debug("Unsupp transition: off --> ulp\n"); + return 0; + } - case FB_BLANK_VSYNC_SUSPEND: - req_power_state = MDSS_PANEL_POWER_DOZE; - pr_debug("Doze power mode requested\n"); + ret = mdss_fb_blank_blank(mfd, req_power_state); + break; + case BLANK_FLAG_LP: + req_power_state = MDSS_PANEL_POWER_LP1; + pr_debug(" power mode requested\n"); /* - * If doze mode is requested when panel is already off, - * then first unblank the panel before entering doze mode + * If low power mode is requested when panel is already off, + * then first unblank the panel before entering low power mode */ if (mdss_fb_is_power_off(mfd) && mfd->mdp.on_fnc) { - pr_debug("off --> doze. switch to on first\n"); - ret = mdss_fb_unblank_sub(mfd); + pr_debug("off --> lp. switch to on first\n"); + ret = mdss_fb_blank_unblank(mfd); + if (ret) + break; } - /* Enter doze mode only if unblank succeeded */ - if (ret) - break; + ret = mdss_fb_blank_blank(mfd, req_power_state); + break; case FB_BLANK_HSYNC_SUSPEND: - case FB_BLANK_NORMAL: case FB_BLANK_POWERDOWN: default: - pr_debug("blank powerdown called. cur mode=%d, req mode=%d\n", - cur_power_state, req_power_state); - if (mdss_fb_is_power_on(mfd) && mfd->mdp.off_fnc) { - cur_power_state = mfd->panel_power_state; - - mutex_lock(&mfd->update.lock); - mfd->update.type = NOTIFY_TYPE_SUSPEND; - mfd->update.is_suspend = 1; - mutex_unlock(&mfd->update.lock); - complete(&mfd->update.comp); - del_timer(&mfd->no_update.timer); - mfd->no_update.value = NOTIFY_TYPE_SUSPEND; - complete(&mfd->no_update.comp); - - mfd->op_enable = false; - mutex_lock(&mfd->bl_lock); - if (mdss_panel_is_power_off(req_power_state)) { - /* Stop Display thread */ - if (mfd->disp_thread) - mdss_fb_stop_disp_thread(mfd); - mdss_fb_set_backlight(mfd, 0); - mfd->bl_updated = 0; - } - mfd->panel_power_state = req_power_state; - mutex_unlock(&mfd->bl_lock); - - ret = mfd->mdp.off_fnc(mfd); - if (ret) - mfd->panel_power_state = cur_power_state; - else if (mdss_panel_is_power_off(req_power_state)) - mdss_fb_release_fences(mfd); - mfd->op_enable = true; - complete(&mfd->power_off_comp); - } + req_power_state = MDSS_PANEL_POWER_OFF; + pr_debug("blank powerdown called\n"); + ret = mdss_fb_blank_blank(mfd, req_power_state); break; } + /* Notify listeners */ sysfs_notify(&mfd->fbi->dev->kobj, NULL, "show_blank_event"); @@ -1344,8 +1398,10 @@ static int mdss_fb_blank(int blank_mode, struct fb_info *info) if (mfd->op_enable == 0) { if (blank_mode == FB_BLANK_UNBLANK) mfd->suspend.panel_power_state = MDSS_PANEL_POWER_ON; - else if (blank_mode == FB_BLANK_VSYNC_SUSPEND) - mfd->suspend.panel_power_state = MDSS_PANEL_POWER_DOZE; + else if (blank_mode == BLANK_FLAG_ULP) + mfd->suspend.panel_power_state = MDSS_PANEL_POWER_LP2; + else if (blank_mode == BLANK_FLAG_LP) + mfd->suspend.panel_power_state = MDSS_PANEL_POWER_LP1; else mfd->suspend.panel_power_state = MDSS_PANEL_POWER_OFF; return 0; diff --git a/drivers/video/fbdev/msm/mdss_mdp_ctl.c b/drivers/video/fbdev/msm/mdss_mdp_ctl.c index 25d3639ae08c..bcbb54f3aca4 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_ctl.c +++ b/drivers/video/fbdev/msm/mdss_mdp_ctl.c @@ -2579,11 +2579,12 @@ int mdss_mdp_ctl_stop(struct mdss_mdp_ctl *ctl, int power_state) mdss_mdp_ctl_write(ctl, off, 0); } - ctl->power_state = power_state; ctl->play_cnt = 0; mdss_mdp_ctl_perf_update(ctl, 0); end: + if (!ret) + ctl->power_state = power_state; mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); mutex_unlock(&ctl->lock); diff --git a/drivers/video/fbdev/msm/mdss_mdp_intf_cmd.c b/drivers/video/fbdev/msm/mdss_mdp_intf_cmd.c index 0fcfff8879b2..45a9c9e6bac9 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_intf_cmd.c +++ b/drivers/video/fbdev/msm/mdss_mdp_intf_cmd.c @@ -731,6 +731,8 @@ static int mdss_mdp_cmd_panel_on(struct mdss_mdp_ctl *ctl, mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_REGISTER_RECOVERY_HANDLER, (void *)&ctx->intf_recovery); + + ctx->intf_stopped = 0; } else { pr_err("%s: Panel already on\n", __func__); } @@ -878,7 +880,6 @@ int mdss_mdp_cmd_intfs_stop(struct mdss_mdp_ctl *ctl, int session, pr_err("invalid ctx session: %d\n", session); return -ENODEV; } - ctx->ref_cnt--; /* intf stopped, no more kickoff */ ctx->intf_stopped = 1; @@ -919,6 +920,11 @@ int mdss_mdp_cmd_intfs_stop(struct mdss_mdp_ctl *ctl, int session, flush_work(&ctx->pp_done_work); mdss_mdp_cmd_tearcheck_setup(ctl, false); + if (mdss_panel_is_power_on(panel_power_state)) { + pr_debug("%s: intf stopped with panel on\n", __func__); + goto end; + } + mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_PING_PONG_RD_PTR, ctx->pp_num, NULL, NULL); mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_PING_PONG_COMP, @@ -926,6 +932,7 @@ int mdss_mdp_cmd_intfs_stop(struct mdss_mdp_ctl *ctl, int session, memset(ctx, 0, sizeof(*ctx)); +end: pr_debug("%s:-\n", __func__); return 0; @@ -944,17 +951,6 @@ static int mdss_mdp_cmd_stop_sub(struct mdss_mdp_ctl *ctl, return -ENODEV; } - /* if power state already updated, skip this */ - if (ctx->panel_power_state == panel_power_state) - return 0; - - /* - * If the panel will be left on, then we do not need to turn off - * interface clocks since we may continue to get display updates. - */ - if (mdss_panel_is_power_on(panel_power_state)) - return 0; - list_for_each_entry_safe(handle, tmp, &ctx->vsync_handlers, list) mdss_mdp_cmd_remove_vsync_handler(ctl, handle); MDSS_XLOG(ctl->num, atomic_read(&ctx->koff_cnt), ctx->clk_enabled, @@ -969,30 +965,92 @@ int mdss_mdp_cmd_stop(struct mdss_mdp_ctl *ctl, int panel_power_state) { struct mdss_mdp_cmd_ctx *ctx = ctl->priv_data; struct mdss_mdp_ctl *sctl = mdss_mdp_get_split_ctl(ctl); - int ret; + bool panel_off = false; + bool turn_off_clocks = false; + bool send_panel_events = false; + int ret = 0; if (!ctx) { pr_err("invalid ctx\n"); return -ENODEV; } - if (ctx->panel_power_state != panel_power_state) { - ret = mdss_mdp_cmd_stop_sub(ctl, panel_power_state); + if (ctx->panel_power_state == panel_power_state) + return 0; + + if (__mdss_mdp_cmd_is_panel_power_off(ctx)) { + pr_debug("%s: panel already off\n", __func__); + return 0; + } + + if (ctx->panel_power_state == panel_power_state) { + pr_debug("%s: no transition needed %d --> %d\n", __func__, + ctx->panel_power_state, panel_power_state); + return 0; + } + + pr_debug("%s: transition from %d --> %d\n", __func__, + ctx->panel_power_state, panel_power_state); + + if (__mdss_mdp_cmd_is_panel_power_on_interactive(ctx)) { + if (mdss_panel_is_power_on_lp(panel_power_state)) { + /* + * If we are transitioning from interactive to low + * power, then we need to send events to the interface + * so that the panel can be configured in low power + * mode. + */ + send_panel_events = true; + if (mdss_panel_is_power_on_ulp(panel_power_state)) + turn_off_clocks = true; + } else if (mdss_panel_is_power_off(panel_power_state)) { + send_panel_events = true; + turn_off_clocks = true; + panel_off = true; + } + } else { + if (mdss_panel_is_power_on_ulp(panel_power_state)) { + /* + * If we are transitioning from low power to ultra low + * power mode, no more display updates are expected. + * Turn off the interface clocks. + */ + pr_debug("%s: turn off clocks\n", __func__); + turn_off_clocks = true; + } else { + /* + * Transition from ultra low power to low power does + * not require any special handling. The clocks would + * get turned on when the first update comes. + */ + pr_debug("%s: nothing to be done.\n", __func__); + return 0; + } + } + + if (!turn_off_clocks) + goto panel_events; + + pr_debug("%s: turn off interface clocks\n", __func__); + 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); + goto end; + } + + if (sctl) { + mdss_mdp_cmd_stop_sub(sctl, panel_power_state); if (IS_ERR_VALUE(ret)) { - pr_err("%s: unable to stop interface: %d\n", + pr_err("%s: unable to stop slave intf: %d\n", __func__, ret); - return ret; - } - - if (sctl) { - 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); - return ret; - } + goto end; } + } +panel_events: + if ((ctl->num == 0) && send_panel_events) { + pr_debug("%s: send panel events\n", __func__); ret = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_BLANK, (void *) (long int) panel_power_state); WARN(ret, "intf %d unblank error (%d)\n", ctl->intf_num, ret); @@ -1004,11 +1062,12 @@ int mdss_mdp_cmd_stop(struct mdss_mdp_ctl *ctl, int panel_power_state) ctx->panel_power_state = panel_power_state; - if (mdss_panel_is_power_on(panel_power_state)) { + if (!panel_off) { pr_debug("%s: cmd_stop with panel always on\n", __func__); goto end; } + pr_debug("%s: turn off panel\n", __func__); ctl->priv_data = NULL; ctl->stop_fnc = NULL; ctl->display_fnc = NULL; @@ -1021,7 +1080,7 @@ end: ctx->rdptr_enabled, XLOG_FUNC_EXIT); pr_debug("%s:-\n", __func__); - return 0; + return ret; } static int mdss_mdp_cmd_intfs_setup(struct mdss_mdp_ctl *ctl, diff --git a/drivers/video/fbdev/msm/mdss_panel.h b/drivers/video/fbdev/msm/mdss_panel.h index 980310fb3841..4397e1c5eda7 100644 --- a/drivers/video/fbdev/msm/mdss_panel.h +++ b/drivers/video/fbdev/msm/mdss_panel.h @@ -91,7 +91,8 @@ enum { enum { MDSS_PANEL_POWER_OFF = 0, MDSS_PANEL_POWER_ON, - MDSS_PANEL_POWER_DOZE, + MDSS_PANEL_POWER_LP1, + MDSS_PANEL_POWER_LP2, }; enum { @@ -623,8 +624,8 @@ static inline bool mdss_panel_is_power_on(int panel_power_state) * @panel_power_state: enum identifying the power state to be checked * * This function returns true if the panel is in an intermediate low power - * state where it is still on but not fully interactive. It may still accept - * commands and display updates but would be operating in a low power mode. + * state where it is still on but not fully interactive. It may or may not + * accept any commands and display updates. */ static inline bool mdss_panel_is_power_on_lp(int panel_power_state) { @@ -632,6 +633,19 @@ static inline bool mdss_panel_is_power_on_lp(int panel_power_state) !mdss_panel_is_power_on_interactive(panel_power_state); } +/** + * mdss_panel_is_panel_power_on_ulp: - checks if panel is in ultra low power mode + * @pdata: pointer to the panel struct associated to the panel + * @panel_power_state: enum identifying the power state to be checked + * + * This function returns true if the panel is in a ultra low power + * state where it is still on but cannot recieve any display updates. + */ +static inline bool mdss_panel_is_power_on_ulp(int panel_power_state) +{ + return panel_power_state == MDSS_PANEL_POWER_LP2; +} + /** * mdss_panel_intf_type: - checks if a given intf type is primary * @intf_val: panel interface type of the individual controller