blob: c2abeddf799e9056e314fde844614f7155a72cb7 [file] [log] [blame]
/* Copyright (c) 2013-2014, 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.
*
*/
#include <linux/kernel.h>
#include <linux/bootmem.h>
#include <linux/memblock.h>
#include "mdss_mdp.h"
#include "mdss_panel.h"
#define VSYNC_EXPIRE_TICK 4
#define MAX_SESSIONS 2
/* wait for at most 2 vsync for lowest refresh rate (24hz) */
#define KOFF_TIMEOUT msecs_to_jiffies(84)
#define STOP_TIMEOUT msecs_to_jiffies(16 * (VSYNC_EXPIRE_TICK + 2))
/*
* STOP_TIMEOUT need to wait for cmd stop depends on fps
* if the command panel support 60fps the timeout value
* generated using 16ms(1frame). If that support 15fps the timeout value
* generated by 40ms(1frame)
*/
#define STOP_TIMEOUT_FOR_ALPM msecs_to_jiffies(40 * (VSYNC_EXPIRE_TICK + 2))
#define ULPS_ENTER_TIME msecs_to_jiffies(100)
struct mdss_mdp_cmd_ctx {
struct mdss_mdp_ctl *ctl;
u32 pp_num;
u8 ref_cnt;
struct completion pp_comp;
struct completion stop_comp;
struct list_head vsync_handlers;
int panel_on;
int koff_cnt;
int clk_enabled;
int vsync_enabled;
int rdptr_enabled;
struct mutex clk_mtx;
spinlock_t clk_lock;
struct work_struct clk_work;
struct delayed_work ulps_work;
struct work_struct pp_done_work;
atomic_t pp_done_cnt;
struct mdss_panel_recovery recovery;
bool ulps;
bool off_pan_on;
int cmd_pending;
spinlock_t cmd_lock;
struct mutex ulps_lock;
};
struct mdss_mdp_cmd_ctx mdss_mdp_cmd_ctx_list[MAX_SESSIONS];
struct mdss_mdp_cmd_ctx *stored_ctx;
static inline u32 mdss_mdp_cmd_line_count(struct mdss_mdp_ctl *ctl)
{
struct mdss_mdp_mixer *mixer;
u32 cnt = 0xffff; /* init it to an invalid value */
u32 init;
u32 height;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
mixer = mdss_mdp_mixer_get(ctl, MDSS_MDP_MIXER_MUX_LEFT);
if (!mixer) {
mixer = mdss_mdp_mixer_get(ctl, MDSS_MDP_MIXER_MUX_RIGHT);
if (!mixer) {
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
goto exit;
}
}
init = mdss_mdp_pingpong_read
(mixer, MDSS_MDP_REG_PP_VSYNC_INIT_VAL) & 0xffff;
height = mdss_mdp_pingpong_read
(mixer, MDSS_MDP_REG_PP_SYNC_CONFIG_HEIGHT) & 0xffff;
if (height < init) {
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
goto exit;
}
cnt = mdss_mdp_pingpong_read
(mixer, MDSS_MDP_REG_PP_INT_COUNT_VAL) & 0xffff;
if (cnt < init) /* wrap around happened at height */
cnt += (height - init);
else
cnt -= init;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
pr_debug("cnt=%d init=%d height=%d\n", cnt, init, height);
exit:
return cnt;
}
static int mdss_mdp_cmd_tearcheck_cfg(struct mdss_mdp_ctl *ctl,
struct mdss_mdp_mixer *mixer)
{
struct mdss_mdp_pp_tear_check *te;
struct mdss_panel_info *pinfo;
u32 vsync_clk_speed_hz, total_lines, vclks_line, cfg;
if (IS_ERR_OR_NULL(ctl->panel_data)) {
pr_err("no panel data\n");
return -ENODEV;
}
pinfo = &ctl->panel_data->panel_info;
te = &ctl->panel_data->panel_info.te;
mdss_mdp_vsync_clk_enable(1);
vsync_clk_speed_hz =
mdss_mdp_get_clk_rate(MDSS_CLK_MDP_VSYNC);
total_lines = mdss_panel_get_vtotal(pinfo);
total_lines *= pinfo->mipi.frame_rate;
vclks_line = (total_lines) ? vsync_clk_speed_hz / total_lines : 0;
cfg = BIT(19);
if (pinfo->mipi.hw_vsync_mode)
cfg |= BIT(20);
if (te->refx100)
vclks_line = vclks_line * pinfo->mipi.frame_rate *
100 / te->refx100;
else {
pr_warn("refx100 cannot be zero! Use 6000 as default\n");
vclks_line = vclks_line * pinfo->mipi.frame_rate *
100 / 6000;
}
cfg |= vclks_line;
pr_debug("%s: yres=%d vclks=%x height=%d init=%d rd=%d start=%d ",
__func__, pinfo->yres, vclks_line, te->sync_cfg_height,
te->vsync_init_val, te->rd_ptr_irq, te->start_pos);
pr_debug("thrd_start =%d thrd_cont=%d\n",
te->sync_threshold_start, te->sync_threshold_continue);
mdss_mdp_pingpong_write(mixer, MDSS_MDP_REG_PP_SYNC_CONFIG_VSYNC, cfg);
mdss_mdp_pingpong_write(mixer, MDSS_MDP_REG_PP_SYNC_CONFIG_HEIGHT,
te->sync_cfg_height);
mdss_mdp_pingpong_write(mixer, MDSS_MDP_REG_PP_VSYNC_INIT_VAL,
te->vsync_init_val);
mdss_mdp_pingpong_write(mixer, MDSS_MDP_REG_PP_RD_PTR_IRQ,
te->rd_ptr_irq);
mdss_mdp_pingpong_write(mixer, MDSS_MDP_REG_PP_START_POS,
te->start_pos);
mdss_mdp_pingpong_write(mixer, MDSS_MDP_REG_PP_SYNC_THRESH,
((te->sync_threshold_continue << 16) |
te->sync_threshold_start));
mdss_mdp_pingpong_write(mixer, MDSS_MDP_REG_PP_TEAR_CHECK_EN,
te->tear_check_en);
return 0;
}
static int mdss_mdp_cmd_tearcheck_setup(struct mdss_mdp_ctl *ctl)
{
struct mdss_mdp_mixer *mixer;
int rc = 0;
mixer = mdss_mdp_mixer_get(ctl, MDSS_MDP_MIXER_MUX_LEFT);
if (mixer) {
rc = mdss_mdp_cmd_tearcheck_cfg(ctl, mixer);
if (rc)
goto err;
}
mixer = mdss_mdp_mixer_get(ctl, MDSS_MDP_MIXER_MUX_RIGHT);
if (mixer)
rc = mdss_mdp_cmd_tearcheck_cfg(ctl, mixer);
err:
return rc;
}
static inline void mdss_mdp_cmd_clk_on(struct mdss_mdp_cmd_ctx *ctx, bool cmd_pending)
{
unsigned long flags;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
struct mdss_panel_info pinfo =
ctx->ctl->panel_data->panel_info;
if (!ctx->panel_on)
return;
mutex_lock(&ctx->clk_mtx);
if (!ctx->clk_enabled) {
ctx->clk_enabled = 1;
ctx->off_pan_on = false;
if (pinfo.ulps_feature_enabled &&
cancel_delayed_work_sync(&ctx->ulps_work))
pr_debug("deleted pending ulps work\n");
if (ctx->ulps) {
mutex_lock(&ctx->ulps_lock);
mdss_mdp_footswitch_ctrl_ulps(1,
&ctx->ctl->mfd->pdev->dev);
mdss_mdp_ctl_restore(ctx->ctl);
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
if (mdss_mdp_cmd_tearcheck_setup(ctx->ctl))
pr_warn("tearcheck setup failed\n");
mdss_mdp_ctl_intf_event(ctx->ctl,
MDSS_EVENT_DSI_ULPS_CTRL, (void *)0);
ctx->ulps = false;
mutex_unlock(&ctx->ulps_lock);
} else {
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
}
mdss_mdp_ctl_intf_event
(ctx->ctl, MDSS_EVENT_PANEL_CLK_CTRL, (void *)1);
mdss_mdp_hist_intr_setup(&mdata->hist_intr, MDSS_IRQ_RESUME);
}
spin_lock_irqsave(&ctx->clk_lock, flags);
if (!ctx->rdptr_enabled && !cmd_pending)
mdss_mdp_irq_enable(MDSS_MDP_IRQ_PING_PONG_RD_PTR, ctx->pp_num);
if (!cmd_pending)
ctx->rdptr_enabled = VSYNC_EXPIRE_TICK;
spin_unlock_irqrestore(&ctx->clk_lock, flags);
mutex_unlock(&ctx->clk_mtx);
}
static inline void mdss_mdp_cmd_clk_off(struct mdss_mdp_cmd_ctx *ctx)
{
unsigned long flags;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
int set_clk_off = 0;
struct mdss_panel_info pinfo =
ctx->ctl->panel_data->panel_info;
mutex_lock(&ctx->clk_mtx);
spin_lock_irqsave(&ctx->clk_lock, flags);
if (!ctx->rdptr_enabled)
set_clk_off = 1;
spin_unlock_irqrestore(&ctx->clk_lock, flags);
spin_lock_irqsave(&ctx->cmd_lock, flags);
if (set_clk_off && !ctx->cmd_pending)
set_clk_off = 1;
else
set_clk_off = 0;
spin_unlock_irqrestore(&ctx->cmd_lock, flags);
if (ctx->clk_enabled && set_clk_off) {
ctx->clk_enabled = 0;
mdss_mdp_hist_intr_setup(&mdata->hist_intr, MDSS_IRQ_SUSPEND);
mdss_mdp_ctl_intf_event
(ctx->ctl, MDSS_EVENT_PANEL_CLK_CTRL, (void *)0);
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
if (pinfo.ulps_feature_enabled &&
ctx->panel_on && !ctx->off_pan_on)
schedule_delayed_work(&ctx->ulps_work, ULPS_ENTER_TIME);
}
mutex_unlock(&ctx->clk_mtx);
}
void mdss_mdp_cmd_clk_enable(void)
{
if (stored_ctx) {
spin_lock(&stored_ctx->cmd_lock);
stored_ctx->cmd_pending++;
spin_unlock(&stored_ctx->cmd_lock);
mdss_mdp_cmd_clk_on(stored_ctx, true);
}
}
void mdss_mdp_cmd_clk_disable(void)
{
int cmd_pending;
if (stored_ctx) {
spin_lock(&stored_ctx->cmd_lock);
if (stored_ctx->cmd_pending)
stored_ctx->cmd_pending--;
cmd_pending = stored_ctx->cmd_pending;
spin_unlock(&stored_ctx->cmd_lock);
if (cmd_pending)
schedule_work(&stored_ctx->clk_work);
}
}
int mdss_mdp_cmd_disable_ulps(struct mdss_mdp_ctl *ctl)
{
struct mdss_mdp_cmd_ctx *ctx;
ctx = (struct mdss_mdp_cmd_ctx *) ctl->priv_data;
if (!ctx) {
pr_err("%s: invalid ctx\n", __func__);
return -ENODEV;
}
mdss_mdp_cmd_clk_on(ctx, false);
return 0;
}
static void mdss_mdp_cmd_readptr_done(void *arg)
{
struct mdss_mdp_ctl *ctl = arg;
struct mdss_mdp_cmd_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++;
spin_lock(&ctx->clk_lock);
list_for_each_entry(tmp, &ctx->vsync_handlers, list) {
if (tmp->enabled && !tmp->cmd_post_flush)
tmp->vsync_handler(ctl, vsync_time);
}
if (!ctx->vsync_enabled) {
if (ctx->rdptr_enabled)
ctx->rdptr_enabled--;
/* keep clk on during kickoff */
if (ctx->rdptr_enabled == 0 && ctx->koff_cnt)
ctx->rdptr_enabled++;
}
if (ctx->rdptr_enabled == 0) {
mdss_mdp_irq_disable_nosync
(MDSS_MDP_IRQ_PING_PONG_RD_PTR, ctx->pp_num);
complete(&ctx->stop_comp);
schedule_work(&ctx->clk_work);
}
spin_unlock(&ctx->clk_lock);
}
static void mdss_mdp_cmd_underflow_recovery(void *data)
{
struct mdss_mdp_cmd_ctx *ctx = data;
unsigned long flags;
if (!data) {
pr_err("%s: invalid ctx\n", __func__);
return;
}
if (!ctx->ctl)
return;
spin_lock_irqsave(&ctx->clk_lock, flags);
if (ctx->koff_cnt) {
mdss_mdp_ctl_reset(ctx->ctl);
pr_debug("%s: intf_num=%d\n", __func__,
ctx->ctl->intf_num);
ctx->koff_cnt--;
mdss_mdp_irq_disable_nosync(MDSS_MDP_IRQ_PING_PONG_COMP,
ctx->pp_num);
complete_all(&ctx->pp_comp);
}
spin_unlock_irqrestore(&ctx->clk_lock, flags);
}
static void mdss_mdp_cmd_pingpong_done(void *arg)
{
struct mdss_mdp_ctl *ctl = arg;
struct mdss_mdp_cmd_ctx *ctx = ctl->priv_data;
struct mdss_mdp_vsync_handler *tmp;
ktime_t vsync_time;
if (!ctx) {
pr_err("%s: invalid ctx\n", __func__);
return;
}
mdss_mdp_ctl_perf_set_transaction_status(ctl,
PERF_HW_MDP_STATE, PERF_STATUS_DONE);
spin_lock(&ctx->clk_lock);
list_for_each_entry(tmp, &ctx->vsync_handlers, list) {
if (tmp->enabled && tmp->cmd_post_flush)
tmp->vsync_handler(ctl, vsync_time);
}
mdss_mdp_irq_disable_nosync(MDSS_MDP_IRQ_PING_PONG_COMP, ctx->pp_num);
complete_all(&ctx->pp_comp);
if (ctx->koff_cnt) {
atomic_inc(&ctx->pp_done_cnt);
schedule_work(&ctx->pp_done_work);
ctx->koff_cnt--;
if (ctx->koff_cnt) {
pr_err("%s: too many kickoffs=%d!\n", __func__,
ctx->koff_cnt);
ctx->koff_cnt = 0;
}
} else
pr_err("%s: should not have pingpong interrupt!\n", __func__);
pr_debug("%s: ctl_num=%d intf_num=%d ctx=%d kcnt=%d\n", __func__,
ctl->num, ctl->intf_num, ctx->pp_num, ctx->koff_cnt);
spin_unlock(&ctx->clk_lock);
}
static void pingpong_done_work(struct work_struct *work)
{
struct mdss_mdp_cmd_ctx *ctx =
container_of(work, typeof(*ctx), pp_done_work);
if (ctx->ctl) {
while (atomic_add_unless(&ctx->pp_done_cnt, -1, 0))
mdss_mdp_ctl_notify(ctx->ctl, MDP_NOTIFY_FRAME_DONE);
mdss_mdp_ctl_perf_release_bw(ctx->ctl);
}
}
static void clk_ctrl_work(struct work_struct *work)
{
struct mdss_mdp_cmd_ctx *ctx =
container_of(work, typeof(*ctx), clk_work);
if (!ctx) {
pr_err("%s: invalid ctx\n", __func__);
return;
}
mdss_mdp_cmd_clk_off(ctx);
}
static void __mdss_mdp_cmd_ulps_work(struct work_struct *work)
{
struct delayed_work *dw = to_delayed_work(work);
struct mdss_mdp_cmd_ctx *ctx =
container_of(dw, struct mdss_mdp_cmd_ctx, ulps_work);
if (!ctx) {
pr_err("%s: invalid ctx\n", __func__);
return;
}
if (!ctx->panel_on) {
pr_err("Panel is off. skipping ULPS configuration\n");
return;
}
mutex_lock(&ctx->ulps_lock);
if (!ctx->ulps) {
pr_debug("%s: entring in ulps state\n", __func__);
if (!mdss_mdp_ctl_intf_event(ctx->ctl, MDSS_EVENT_DSI_ULPS_CTRL,
(void *)1)) {
ctx->ulps = true;
ctx->ctl->play_cnt = 0;
mdss_mdp_footswitch_ctrl_ulps(0, &ctx->ctl->mfd->pdev->dev);
}
}
mutex_unlock(&ctx->ulps_lock);
}
static int mdss_mdp_cmd_add_vsync_handler(struct mdss_mdp_ctl *ctl,
struct mdss_mdp_vsync_handler *handle)
{
struct mdss_mdp_cmd_ctx *ctx;
unsigned long flags;
bool enable_rdptr = false;
ctx = (struct mdss_mdp_cmd_ctx *) ctl->priv_data;
if (!ctx) {
pr_err("%s: invalid ctx\n", __func__);
return -ENODEV;
}
spin_lock_irqsave(&ctx->clk_lock, flags);
if (!handle->enabled) {
handle->enabled = true;
list_add(&handle->list, &ctx->vsync_handlers);
enable_rdptr = !handle->cmd_post_flush;
if (enable_rdptr)
ctx->vsync_enabled++;
}
spin_unlock_irqrestore(&ctx->clk_lock, flags);
if (enable_rdptr)
mdss_mdp_cmd_clk_on(ctx, false);
return 0;
}
static int mdss_mdp_cmd_remove_vsync_handler(struct mdss_mdp_ctl *ctl,
struct mdss_mdp_vsync_handler *handle)
{
struct mdss_mdp_cmd_ctx *ctx;
unsigned long flags;
ctx = (struct mdss_mdp_cmd_ctx *) ctl->priv_data;
if (!ctx) {
pr_err("%s: invalid ctx\n", __func__);
return -ENODEV;
}
spin_lock_irqsave(&ctx->clk_lock, flags);
if (handle->enabled) {
handle->enabled = false;
list_del_init(&handle->list);
if (!handle->cmd_post_flush) {
if (ctx->vsync_enabled)
ctx->vsync_enabled--;
else
WARN(1, "unbalanced vsync disable");
}
}
spin_unlock_irqrestore(&ctx->clk_lock, flags);
return 0;
}
int mdss_mdp_cmd_reconfigure_splash_done(struct mdss_mdp_ctl *ctl, bool handoff)
{
struct mdss_panel_data *pdata;
int ret = 0;
pdata = ctl->panel_data;
pdata->panel_info.cont_splash_enabled = 0;
mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_CLK_CTRL, (void *)0);
return ret;
}
static int mdss_mdp_cmd_wait4pingpong(struct mdss_mdp_ctl *ctl, void *arg)
{
struct mdss_mdp_cmd_ctx *ctx;
unsigned long flags;
int need_wait = 0;
int rc = 0;
ctx = (struct mdss_mdp_cmd_ctx *) ctl->priv_data;
if (!ctx) {
pr_err("invalid ctx\n");
return -ENODEV;
}
spin_lock_irqsave(&ctx->clk_lock, flags);
if (ctx->koff_cnt > 0)
need_wait = 1;
spin_unlock_irqrestore(&ctx->clk_lock, flags);
pr_debug("%s: need_wait=%d intf_num=%d ctx=%p\n",
__func__, need_wait, ctl->intf_num, ctx);
if (need_wait) {
rc = wait_for_completion_timeout(
&ctx->pp_comp, KOFF_TIMEOUT);
if (rc <= 0) {
WARN(1, "cmd kickoff timed out (%d) ctl=%d\n",
rc, ctl->num);
rc = -EPERM;
mdss_mdp_ctl_notify(ctl, MDP_NOTIFY_FRAME_TIMEOUT);
} else {
rc = 0;
}
}
return rc;
}
static int mdss_mdp_cmd_set_partial_roi(struct mdss_mdp_ctl *ctl)
{
int rc = 0;
if (ctl->roi.w && ctl->roi.h && ctl->roi_changed &&
ctl->panel_data->panel_info.partial_update_enabled) {
ctl->panel_data->panel_info.roi_x = ctl->roi.x;
ctl->panel_data->panel_info.roi_y = ctl->roi.y;
ctl->panel_data->panel_info.roi_w = ctl->roi.w;
ctl->panel_data->panel_info.roi_h = ctl->roi.h;
rc = mdss_mdp_ctl_intf_event(ctl,
MDSS_EVENT_ENABLE_PARTIAL_UPDATE, NULL);
}
return rc;
}
int mdss_mdp_cmd_kickoff(struct mdss_mdp_ctl *ctl, void *arg)
{
struct mdss_mdp_cmd_ctx *ctx;
struct mdss_panel_info *pinfo;
struct mdss_panel_data *pdata;
unsigned long flags;
int rc;
ctx = (struct mdss_mdp_cmd_ctx *) ctl->priv_data;
if (!ctx) {
pr_err("invalid ctx\n");
return -ENODEV;
}
stored_ctx = ctx;
pdata = ctl->panel_data;
pinfo = &pdata->panel_info;
mdss_mdp_ctl_perf_set_transaction_status(ctl,
PERF_HW_MDP_STATE, PERF_STATUS_BUSY);
if (ctx->panel_on == 0) {
rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_UNBLANK, NULL);
WARN(rc, "intf %d unblank error (%d)\n", ctl->intf_num, rc);
ctx->panel_on++;
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);
}
mdss_mdp_cmd_clk_on(ctx, false);
mdss_mdp_cmd_set_partial_roi(ctl);
/*
* tx dcs command if had any
*/
mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_DSI_CMDLIST_KOFF,
(void *)&ctx->recovery);
INIT_COMPLETION(ctx->pp_comp);
mdss_mdp_irq_enable(MDSS_MDP_IRQ_PING_PONG_COMP, ctx->pp_num);
mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_START, 1);
spin_lock_irqsave(&ctx->clk_lock, flags);
ctx->koff_cnt++;
spin_unlock_irqrestore(&ctx->clk_lock, flags);
mdss_mdp_ctl_perf_set_transaction_status(ctl,
PERF_SW_COMMIT_STATE, PERF_STATUS_DONE);
mb();
if (pinfo->alpm_event &&
!pinfo->alpm_event(CHECK_CURRENT_STATUS) &&
pinfo->alpm_event(CHECK_PREVIOUS_STATUS)) {
mdss_dsi_panel_bl_dim(pdata, PANEL_BACKLIGHT_RESTORE);
pinfo->alpm_event(CLEAR_MODE_STATUS);
pr_info("[ALPM_DEBUG] %s: Clear mode status\n", __func__);
}
return 0;
}
int mdss_mdp_cmd_off_pan_on(struct mdss_mdp_ctl *ctl)
{
struct mdss_mdp_cmd_ctx *ctx;
unsigned long flags;
int need_wait = 0;
ctx = (struct mdss_mdp_cmd_ctx *) ctl->priv_data;
if (!ctx) {
pr_err("invalid ctx\n");
return -ENODEV;
}
pr_debug("%s: clk_enabled=%d\n", __func__, ctx->clk_enabled);
spin_lock_irqsave(&ctx->clk_lock, flags);
if (ctx->rdptr_enabled) {
INIT_COMPLETION(ctx->stop_comp);
need_wait = 1;
}
spin_unlock_irqrestore(&ctx->clk_lock, flags);
spin_lock_irqsave(&ctx->cmd_lock, flags);
if (ctx->cmd_pending) {
INIT_COMPLETION(ctx->stop_comp);
need_wait = 1;
}
spin_unlock_irqrestore(&ctx->cmd_lock, flags);
if (need_wait) {
if (wait_for_completion_timeout(&ctx->stop_comp, STOP_TIMEOUT)
<= 0) {
pr_err("%s: stop cmd time out\n", __func__);
mdss_mdp_irq_disable(MDSS_MDP_IRQ_PING_PONG_RD_PTR,
ctx->pp_num);
ctx->rdptr_enabled = 0;
ctx->koff_cnt = 0;
}
}
ctx->off_pan_on = true;
ctx->cmd_pending = 0;
if (cancel_work_sync(&ctx->clk_work))
pr_debug("no pending clk work\n");
if (cancel_delayed_work_sync(&ctx->ulps_work))
pr_debug("deleted pending ulps work\n");
mdss_mdp_cmd_clk_off(ctx);
mutex_lock(&ctx->ulps_lock);
if (!ctx->ulps) {
pr_debug("%s: forcing ulps with panel always on feature\n",
__func__);
if (!mdss_mdp_ctl_intf_event(ctx->ctl,
MDSS_EVENT_DSI_ULPS_CTRL, (void *)1)) {
ctx->ulps = true;
ctx->ctl->play_cnt = 0;
mdss_mdp_footswitch_ctrl_ulps(0,
&ctx->ctl->mfd->pdev->dev);
}
}
mutex_unlock(&ctx->ulps_lock);
return 0;
}
int mdss_mdp_cmd_stop(struct mdss_mdp_ctl *ctl)
{
struct mdss_mdp_cmd_ctx *ctx;
struct mdss_panel_info *pinfo;
unsigned long flags;
struct mdss_mdp_vsync_handler *tmp, *handle;
int need_wait = 0;
int ret = 0;
u8 timeout_status = 0;
ctx = (struct mdss_mdp_cmd_ctx *) ctl->priv_data;
if (!ctx) {
pr_err("invalid ctx\n");
return -ENODEV;
}
pinfo = &ctl->panel_data->panel_info;
list_for_each_entry_safe(handle, tmp, &ctx->vsync_handlers, list) {
list_add(&handle->saved_list, &ctl->saved_vsync_handlers);
mdss_mdp_cmd_remove_vsync_handler(ctl, handle);
}
spin_lock_irqsave(&ctx->clk_lock, flags);
if (ctx->rdptr_enabled) {
INIT_COMPLETION(ctx->stop_comp);
need_wait = 1;
}
spin_unlock_irqrestore(&ctx->clk_lock, flags);
spin_lock_irqsave(&ctx->cmd_lock, flags);
if (ctx->cmd_pending) {
INIT_COMPLETION(ctx->stop_comp);
need_wait = 1;
}
spin_unlock_irqrestore(&ctx->cmd_lock, flags);
if (need_wait) {
if (pinfo->alpm_event && pinfo->alpm_event(CHECK_CURRENT_STATUS))
timeout_status = wait_for_completion_timeout(&ctx->stop_comp,\
STOP_TIMEOUT_FOR_ALPM);
else
timeout_status = wait_for_completion_timeout(&ctx->stop_comp,\
STOP_TIMEOUT);
if (timeout_status <= 0) {
WARN(1, "stop cmd time out\n");
mdss_mdp_irq_disable
(MDSS_MDP_IRQ_PING_PONG_RD_PTR,
ctx->pp_num);
ctx->rdptr_enabled = 0;
}
}
if (cancel_work_sync(&ctx->clk_work))
pr_debug("no pending clk work\n");
if (pinfo->ulps_feature_enabled &&
cancel_delayed_work_sync(&ctx->ulps_work))
pr_debug("deleted pending ulps work\n");
ctx->panel_on = 0;
ctx->cmd_pending = 0;
mdss_mdp_cmd_clk_off(ctx);
flush_work(&ctx->pp_done_work);
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, ctx->pp_num,
NULL, NULL);
memset(ctx, 0, sizeof(*ctx));
stored_ctx = NULL;
ctl->priv_data = NULL;
if (ctl->num == 0) {
ret = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_BLANK, NULL);
WARN(ret, "intf %d unblank error (%d)\n", ctl->intf_num, ret);
ret = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_OFF, NULL);
WARN(ret, "intf %d unblank error (%d)\n", ctl->intf_num, ret);
}
ctl->stop_fnc = NULL;
ctl->display_fnc = NULL;
ctl->wait_pingpong = NULL;
ctl->add_vsync_handler = NULL;
ctl->remove_vsync_handler = NULL;
pr_debug("%s:-\n", __func__);
return 0;
}
int mdss_mdp_cmd_start(struct mdss_mdp_ctl *ctl)
{
struct mdss_mdp_cmd_ctx *ctx;
struct mdss_mdp_mixer *mixer;
struct mdss_mdp_vsync_handler *tmp, *handle;
int i, ret;
struct mdss_panel_info pinfo =
ctl->panel_data->panel_info;
pr_debug("%s:+\n", __func__);
mixer = mdss_mdp_mixer_get(ctl, MDSS_MDP_MIXER_MUX_LEFT);
if (!mixer) {
pr_err("mixer not setup correctly\n");
return -ENODEV;
}
for (i = 0; i < MAX_SESSIONS; i++) {
ctx = &mdss_mdp_cmd_ctx_list[i];
if (ctx->ref_cnt == 0) {
ctx->ref_cnt++;
break;
}
}
if (i == MAX_SESSIONS) {
pr_err("too many sessions\n");
return -ENOMEM;
}
ctl->priv_data = ctx;
if (!ctx) {
pr_err("invalid ctx\n");
return -ENODEV;
}
ctx->ctl = ctl;
ctx->pp_num = mixer->num;
init_completion(&ctx->pp_comp);
init_completion(&ctx->stop_comp);
spin_lock_init(&ctx->clk_lock);
spin_lock_init(&ctx->cmd_lock);
mutex_init(&ctx->clk_mtx);
mutex_init(&ctx->ulps_lock);
INIT_WORK(&ctx->clk_work, clk_ctrl_work);
if (pinfo.ulps_feature_enabled)
INIT_DELAYED_WORK(&ctx->ulps_work, __mdss_mdp_cmd_ulps_work);
INIT_WORK(&ctx->pp_done_work, pingpong_done_work);
atomic_set(&ctx->pp_done_cnt, 0);
INIT_LIST_HEAD(&ctx->vsync_handlers);
list_for_each_entry_safe(handle, tmp, &ctl->saved_vsync_handlers, saved_list) {
mdss_mdp_cmd_add_vsync_handler(ctl, handle);
list_del_init(&handle->saved_list);
}
ctx->recovery.fxn = mdss_mdp_cmd_underflow_recovery;
ctx->recovery.data = ctx;
pr_debug("%s: ctx=%p num=%d mixer=%d\n", __func__,
ctx, ctx->pp_num, mixer->num);
mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_PING_PONG_RD_PTR, ctx->pp_num,
mdss_mdp_cmd_readptr_done, ctl);
mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_PING_PONG_COMP, ctx->pp_num,
mdss_mdp_cmd_pingpong_done, ctl);
ret = mdss_mdp_cmd_tearcheck_setup(ctl);
if (ret) {
pr_err("tearcheck setup failed\n");
return ret;
}
ctl->stop_fnc = mdss_mdp_cmd_stop;
ctl->display_fnc = mdss_mdp_cmd_kickoff;
ctl->wait_pingpong = mdss_mdp_cmd_wait4pingpong;
ctl->add_vsync_handler = mdss_mdp_cmd_add_vsync_handler;
ctl->remove_vsync_handler = mdss_mdp_cmd_remove_vsync_handler;
ctl->read_line_cnt_fnc = mdss_mdp_cmd_line_count;
ctl->off_pan_on = mdss_mdp_cmd_off_pan_on;
ctl->disable_ulps = mdss_mdp_cmd_disable_ulps;
pr_debug("%s:-\n", __func__);
stored_ctx = ctx;
ctx->cmd_pending = 0;
return 0;
}