blob: c68a5d444c8e7f23fd1be173403605684f3881cc [file] [log] [blame]
/* Copyright (c) 2013-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.
*
*/
#include <linux/kernel.h>
#include <linux/pm_runtime.h>
#include <linux/delay.h>
#include "mdss_mdp.h"
#include "mdss_panel.h"
#include "mdss_debug.h"
#include "mdss_mdp_trace.h"
#define VSYNC_EXPIRE_ACTIVE_TICK 4
#define VSYNC_EXPIRE_LOWPOWER_TICK 2
#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 POWER_COLLAPSE_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 list_head vsync_handlers;
int panel_power_state;
atomic_t koff_cnt;
atomic_t intf_stopped;
int clk_enabled;
int vsync_enabled;
int rdptr_enabled;
struct mutex clk_mtx;
spinlock_t clk_lock;
spinlock_t koff_lock;
struct work_struct clk_work;
struct work_struct pp_done_work;
atomic_t pp_done_cnt;
struct mdss_panel_recovery recovery;
struct mdss_mdp_cmd_ctx *sync_ctx; /* for partial update */
};
struct mdss_mdp_cmd_ctx mdss_mdp_cmd_ctx_list[MAX_SESSIONS];
static int mdss_mdp_cmd_do_notifier(struct mdss_mdp_cmd_ctx *ctx);
static bool __mdss_mdp_cmd_is_panel_power_off(struct mdss_mdp_cmd_ctx *ctx)
{
return mdss_panel_is_power_off(ctx->panel_power_state);
}
static bool __mdss_mdp_cmd_is_panel_power_on_interactive(
struct mdss_mdp_cmd_ctx *ctx)
{
return mdss_panel_is_power_on_interactive(ctx->panel_power_state);
}
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);
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);
goto exit;
}
}
init = mdss_mdp_pingpong_read(mixer->pingpong_base,
MDSS_MDP_REG_PP_VSYNC_INIT_VAL) & 0xffff;
height = mdss_mdp_pingpong_read(mixer->pingpong_base,
MDSS_MDP_REG_PP_SYNC_CONFIG_HEIGHT) & 0xffff;
if (height < init) {
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
goto exit;
}
cnt = mdss_mdp_pingpong_read(mixer->pingpong_base,
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);
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, bool enable)
{
struct mdss_mdp_pp_tear_check *te = NULL;
struct mdss_panel_info *pinfo;
u32 vsync_clk_speed_hz, total_lines, vclks_line, cfg = 0;
char __iomem *pingpong_base;
struct mdss_mdp_cmd_ctx *ctx = ctl->priv_data;
if (IS_ERR_OR_NULL(ctl->panel_data)) {
pr_err("no panel data\n");
return -ENODEV;
}
if (enable) {
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\n",
__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);
}
pingpong_base = mixer->pingpong_base;
/* for dst split pp_num is cmd session (0 and 1) */
if (is_split_dst(ctl->mfd))
pingpong_base += ctx->pp_num * SPLIT_MIXER_OFFSET;
mdss_mdp_pingpong_write(pingpong_base,
MDSS_MDP_REG_PP_SYNC_CONFIG_VSYNC, cfg);
mdss_mdp_pingpong_write(pingpong_base,
MDSS_MDP_REG_PP_SYNC_CONFIG_HEIGHT,
te ? te->sync_cfg_height : 0);
mdss_mdp_pingpong_write(pingpong_base,
MDSS_MDP_REG_PP_VSYNC_INIT_VAL,
te ? te->vsync_init_val : 0);
mdss_mdp_pingpong_write(pingpong_base,
MDSS_MDP_REG_PP_RD_PTR_IRQ,
te ? te->rd_ptr_irq : 0);
mdss_mdp_pingpong_write(pingpong_base,
MDSS_MDP_REG_PP_START_POS,
te ? te->start_pos : 0);
mdss_mdp_pingpong_write(pingpong_base,
MDSS_MDP_REG_PP_SYNC_THRESH,
te ? ((te->sync_threshold_continue << 16) |
te->sync_threshold_start) : 0);
mdss_mdp_pingpong_write(pingpong_base,
MDSS_MDP_REG_PP_TEAR_CHECK_EN,
te ? te->tear_check_en : 0);
return 0;
}
static int mdss_mdp_cmd_tearcheck_setup(struct mdss_mdp_ctl *ctl, bool enable)
{
int rc = 0;
struct mdss_mdp_mixer *mixer;
mixer = mdss_mdp_mixer_get(ctl, MDSS_MDP_MIXER_MUX_LEFT);
if (mixer) {
rc = mdss_mdp_cmd_tearcheck_cfg(ctl, mixer, enable);
if (rc)
goto err;
}
if (!(ctl->opmode & MDSS_MDP_CTL_OP_PACK_3D_ENABLE)) {
mixer = mdss_mdp_mixer_get(ctl, MDSS_MDP_MIXER_MUX_RIGHT);
if (mixer)
rc = mdss_mdp_cmd_tearcheck_cfg(ctl, mixer, enable);
}
err:
return rc;
}
static inline void mdss_mdp_cmd_clk_on(struct mdss_mdp_cmd_ctx *ctx)
{
unsigned long flags;
struct mdss_data_type *mdata = mdss_mdp_get_mdata();
int irq_en, rc;
if (__mdss_mdp_cmd_is_panel_power_off(ctx))
return;
mutex_lock(&ctx->clk_mtx);
MDSS_XLOG(ctx->pp_num, atomic_read(&ctx->koff_cnt), ctx->clk_enabled,
ctx->rdptr_enabled);
if (!ctx->clk_enabled) {
mdss_bus_bandwidth_ctrl(true);
ctx->clk_enabled = 1;
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
(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);
irq_en = !ctx->rdptr_enabled;
if (mdss_mdp_ctl_is_power_on_lp(ctx->ctl))
ctx->rdptr_enabled = VSYNC_EXPIRE_LOWPOWER_TICK;
else
ctx->rdptr_enabled = VSYNC_EXPIRE_ACTIVE_TICK;
spin_unlock_irqrestore(&ctx->clk_lock, flags);
if (irq_en)
mdss_mdp_irq_enable(MDSS_MDP_IRQ_PING_PONG_RD_PTR, ctx->pp_num);
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;
mutex_lock(&ctx->clk_mtx);
MDSS_XLOG(ctx->pp_num, atomic_read(&ctx->koff_cnt), ctx->clk_enabled,
ctx->rdptr_enabled);
spin_lock_irqsave(&ctx->clk_lock, flags);
if (!ctx->rdptr_enabled)
set_clk_off = 1;
spin_unlock_irqrestore(&ctx->clk_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_iommu_ctrl(0);
mdss_bus_bandwidth_ctrl(false);
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
}
mutex_unlock(&ctx->clk_mtx);
}
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++;
MDSS_XLOG(ctl->num, atomic_read(&ctx->koff_cnt), ctx->clk_enabled,
ctx->rdptr_enabled);
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 && atomic_read(&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);
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;
bool notify_frame_timeout = false;
if (!data) {
pr_err("%s: invalid ctx\n", __func__);
return;
}
if (!ctx->ctl)
return;
spin_lock_irqsave(&ctx->koff_lock, flags);
if (atomic_read(&ctx->koff_cnt)) {
mdss_mdp_ctl_reset(ctx->ctl);
pr_debug("%s: intf_num=%d\n", __func__,
ctx->ctl->intf_num);
atomic_dec(&ctx->koff_cnt);
mdss_mdp_irq_disable_nosync(MDSS_MDP_IRQ_PING_PONG_COMP,
ctx->pp_num);
complete_all(&ctx->pp_comp);
notify_frame_timeout = true;
}
spin_unlock_irqrestore(&ctx->koff_lock, flags);
if (notify_frame_timeout)
mdss_mdp_ctl_notify(ctx->ctl, MDP_NOTIFY_FRAME_TIMEOUT);
}
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);
}
spin_unlock(&ctx->clk_lock);
spin_lock(&ctx->koff_lock);
mdss_mdp_irq_disable_nosync(MDSS_MDP_IRQ_PING_PONG_COMP, ctx->pp_num);
complete_all(&ctx->pp_comp);
MDSS_XLOG(ctl->num, atomic_read(&ctx->koff_cnt), ctx->clk_enabled,
ctx->rdptr_enabled);
if (atomic_read(&ctx->koff_cnt)) {
if (atomic_dec_return(&ctx->koff_cnt)) {
pr_err("%s: too many kickoffs=%d!\n", __func__,
atomic_read(&ctx->koff_cnt));
atomic_set(&ctx->koff_cnt, 0);
}
if (mdss_mdp_cmd_do_notifier(ctx)) {
atomic_inc(&ctx->pp_done_cnt);
schedule_work(&ctx->pp_done_work);
}
} else
pr_err("%s: should not have pingpong interrupt!\n", __func__);
trace_mdp_cmd_pingpong_done(ctl, ctx->pp_num,
atomic_read(&ctx->koff_cnt));
pr_debug("%s: ctl_num=%d intf_num=%d ctx=%d kcnt=%d\n", __func__,
ctl->num, ctl->intf_num, ctx->pp_num,
atomic_read(&ctx->koff_cnt));
spin_unlock(&ctx->koff_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 int mdss_mdp_cmd_add_vsync_handler(struct mdss_mdp_ctl *ctl,
struct mdss_mdp_vsync_handler *handle)
{
struct mdss_mdp_ctl *sctl = NULL;
struct mdss_mdp_cmd_ctx *ctx, *sctx = NULL;
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;
}
MDSS_XLOG(ctl->num, atomic_read(&ctx->koff_cnt), ctx->clk_enabled,
ctx->rdptr_enabled);
sctl = mdss_mdp_get_split_ctl(ctl);
if (sctl)
sctx = (struct mdss_mdp_cmd_ctx *) sctl->priv_data;
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++;
if (sctx)
sctx->vsync_enabled++;
}
}
spin_unlock_irqrestore(&ctx->clk_lock, flags);
if (enable_rdptr) {
mdss_mdp_cmd_clk_on(ctx);
if (sctx)
mdss_mdp_cmd_clk_on(sctx);
}
return 0;
}
static int mdss_mdp_cmd_remove_vsync_handler(struct mdss_mdp_ctl *ctl,
struct mdss_mdp_vsync_handler *handle)
{
struct mdss_mdp_ctl *sctl;
struct mdss_mdp_cmd_ctx *ctx, *sctx = NULL;
unsigned long flags;
ctx = (struct mdss_mdp_cmd_ctx *) ctl->priv_data;
if (!ctx) {
pr_err("%s: invalid ctx\n", __func__);
return -ENODEV;
}
MDSS_XLOG(ctl->num, atomic_read(&ctx->koff_cnt), ctx->clk_enabled,
ctx->rdptr_enabled, 0x88888);
sctl = mdss_mdp_get_split_ctl(ctl);
if (sctl)
sctx = (struct mdss_mdp_cmd_ctx *) sctl->priv_data;
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--;
if (sctx)
sctx->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;
struct mdss_mdp_ctl *sctl = mdss_mdp_get_split_ctl(ctl);
int ret = 0;
pdata = ctl->panel_data;
mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_CLK_CTRL, (void *)0);
pdata->panel_info.cont_splash_enabled = 0;
if (sctl)
sctl->panel_data->panel_info.cont_splash_enabled = 0;
return ret;
}
static int mdss_mdp_cmd_wait4pingpong(struct mdss_mdp_ctl *ctl, void *arg)
{
struct mdss_mdp_cmd_ctx *ctx;
struct mdss_panel_data *pdata;
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;
}
pdata = ctl->panel_data;
spin_lock_irqsave(&ctx->koff_lock, flags);
if ((atomic_read(&ctx->koff_cnt) > 0)
&& !mdss_mdp_ctl_is_panel_dead(ctl))
need_wait = 1;
spin_unlock_irqrestore(&ctx->koff_lock, flags);
ctl->roi_bkup.w = ctl->width;
ctl->roi_bkup.h = ctl->height;
MDSS_XLOG(ctl->num, atomic_read(&ctx->koff_cnt), ctx->clk_enabled,
ctx->rdptr_enabled, ctl->roi_bkup.w,
ctl->roi_bkup.h);
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);
trace_mdp_cmd_wait_pingpong(ctl->num,
atomic_read(&ctx->koff_cnt));
if (rc <= 0) {
u32 status, mask;
mask = BIT(MDSS_MDP_IRQ_PING_PONG_COMP + ctx->pp_num);
status = mask & readl_relaxed(ctl->mdata->mdp_base +
MDSS_MDP_REG_INTR_STATUS);
writel_relaxed(mask, ctl->mdata->mdp_base +
MDSS_MDP_REG_INTR_CLEAR);
if (status || try_wait_for_completion(&ctx->pp_comp)) {
WARN(status, "pp done but irq not triggered\n");
rc = 1;
}
}
if (rc <= 0) {
WARN(1, "cmd kickoff timed out (%d) ctl=%d\n",
rc, ctl->num);
MDSS_XLOG_TOUT_HANDLER("mdp", "dsi0", "dsi1",
"edp", "hdmi", "panic");
rc = -EPERM;
mdss_mdp_ctl_notify(ctl, MDP_NOTIFY_FRAME_TIMEOUT);
} else {
rc = 0;
}
}
MDSS_XLOG(ctl->num, atomic_read(&ctx->koff_cnt), ctx->clk_enabled,
ctx->rdptr_enabled, rc);
return rc;
}
static int mdss_mdp_cmd_do_notifier(struct mdss_mdp_cmd_ctx *ctx)
{
struct mdss_mdp_cmd_ctx *sctx;
sctx = ctx->sync_ctx;
if (!sctx || atomic_read(&sctx->koff_cnt) == 0)
return 1;
return 0;
}
static void mdss_mdp_cmd_set_sync_ctx(
struct mdss_mdp_ctl *ctl, struct mdss_mdp_ctl *sctl)
{
struct mdss_mdp_cmd_ctx *ctx, *sctx;
ctx = (struct mdss_mdp_cmd_ctx *)ctl->priv_data;
if (!sctl) {
ctx->sync_ctx = NULL;
return;
}
sctx = (struct mdss_mdp_cmd_ctx *)sctl->priv_data;
if (!sctl->roi.w && !sctl->roi.h) {
/* left only */
ctx->sync_ctx = NULL;
sctx->sync_ctx = NULL;
} else {
/* left + right */
ctx->sync_ctx = sctx;
sctx->sync_ctx = ctx;
}
}
static int mdss_mdp_cmd_set_partial_roi(struct mdss_mdp_ctl *ctl)
{
int rc = 0;
if (!ctl->panel_data->panel_info.partial_update_enabled)
return rc;
/* set panel col and page addr */
rc = mdss_mdp_ctl_intf_event(ctl,
MDSS_EVENT_ENABLE_PARTIAL_ROI, NULL);
return rc;
}
static int mdss_mdp_cmd_set_stream_size(struct mdss_mdp_ctl *ctl)
{
int rc = 0;
if (!ctl->panel_data->panel_info.partial_update_enabled)
return rc;
/* set dsi controller stream size */
rc = mdss_mdp_ctl_intf_event(ctl,
MDSS_EVENT_DSI_STREAM_SIZE, NULL);
return rc;
}
static int mdss_mdp_cmd_panel_on(struct mdss_mdp_ctl *ctl,
struct mdss_mdp_ctl *sctl)
{
struct mdss_mdp_cmd_ctx *ctx, *sctx = NULL;
int rc = 0;
ctx = (struct mdss_mdp_cmd_ctx *) ctl->priv_data;
if (!ctx) {
pr_err("invalid ctx\n");
return -ENODEV;
}
if (sctl)
sctx = (struct mdss_mdp_cmd_ctx *) sctl->priv_data;
if (!__mdss_mdp_cmd_is_panel_power_on_interactive(ctx)) {
rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_UNBLANK, NULL);
WARN(rc, "intf %d unblank error (%d)\n", ctl->intf_num, rc);
if (rc)
mdss_mdp_ctl_notify(ctl, MDP_NOTIFY_PANEL_DEAD);
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 (rc)
mdss_mdp_ctl_notify(ctl, MDP_NOTIFY_PANEL_DEAD);
ctx->panel_power_state = MDSS_PANEL_POWER_ON;
if (sctx)
sctx->panel_power_state = MDSS_PANEL_POWER_ON;
mdss_mdp_ctl_intf_event(ctl,
MDSS_EVENT_REGISTER_RECOVERY_HANDLER,
(void *)&ctx->recovery);
atomic_set(&ctx->intf_stopped, 0);
} else {
pr_err("%s: Panel already on\n", __func__);
}
return rc;
}
/*
* There are 3 partial update possibilities
* left only ==> enable left pingpong_done
* left + right ==> enable both pingpong_done
* right only ==> enable right pingpong_done
*
* notification is triggered at pingpong_done which will
* signal timeline to release source buffer
*
* for left+right case, pingpong_done is enabled for both and
* only the last pingpong_done should trigger the notification
*/
int mdss_mdp_cmd_kickoff(struct mdss_mdp_ctl *ctl, void *arg)
{
struct mdss_mdp_ctl *sctl = NULL;
struct mdss_mdp_cmd_ctx *ctx, *sctx = NULL;
unsigned long flags;
ctx = (struct mdss_mdp_cmd_ctx *) ctl->priv_data;
if (!ctx) {
pr_err("invalid ctx\n");
return -ENODEV;
}
if (atomic_read(&ctx->intf_stopped)) {
pr_err("ctx=%d stopped already\n", ctx->pp_num);
return -EPERM;
}
/* sctl will be null for right only in the case of Partial update */
sctl = mdss_mdp_get_split_ctl(ctl);
if (sctl && (sctl->roi.w == 0 || sctl->roi.h == 0)) {
/* left update only */
sctl = NULL;
}
mdss_mdp_ctl_perf_set_transaction_status(ctl,
PERF_HW_MDP_STATE, PERF_STATUS_BUSY);
if (sctl) {
sctx = (struct mdss_mdp_cmd_ctx *) sctl->priv_data;
mdss_mdp_ctl_perf_set_transaction_status(sctl,
PERF_HW_MDP_STATE, PERF_STATUS_BUSY);
}
/*
* Turn on the panel, if not already. This is because the panel is
* turned on only when we send the first frame and not during cmd
* start. This is to ensure that no artifacts are seen on the panel.
*/
if (__mdss_mdp_cmd_is_panel_power_off(ctx))
mdss_mdp_cmd_panel_on(ctl, sctl);
MDSS_XLOG(ctl->num, ctl->roi.x, ctl->roi.y, ctl->roi.w,
ctl->roi.h);
spin_lock_irqsave(&ctx->koff_lock, flags);
atomic_inc(&ctx->koff_cnt);
INIT_COMPLETION(ctx->pp_comp);
if (sctx) {
atomic_inc(&sctx->koff_cnt);
INIT_COMPLETION(sctx->pp_comp);
}
spin_unlock_irqrestore(&ctx->koff_lock, flags);
trace_mdp_cmd_kickoff(ctl->num, atomic_read(&ctx->koff_cnt));
mdss_mdp_cmd_clk_on(ctx);
mdss_mdp_cmd_set_partial_roi(ctl);
/*
* tx dcs command if had any
*/
mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_DSI_CMDLIST_KOFF, NULL);
mdss_mdp_cmd_set_stream_size(ctl);
mdss_mdp_cmd_set_sync_ctx(ctl, sctl);
mdss_mdp_irq_enable(MDSS_MDP_IRQ_PING_PONG_COMP, ctx->pp_num);
if (sctx)
mdss_mdp_irq_enable(MDSS_MDP_IRQ_PING_PONG_COMP, sctx->pp_num);
mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_START, 1); /* Kickoff */
mdss_mdp_ctl_perf_set_transaction_status(ctl,
PERF_SW_COMMIT_STATE, PERF_STATUS_DONE);
if (sctl) {
mdss_mdp_ctl_perf_set_transaction_status(sctl,
PERF_SW_COMMIT_STATE, PERF_STATUS_DONE);
}
mb();
MDSS_XLOG(ctl->num, atomic_read(&ctx->koff_cnt), ctx->clk_enabled,
ctx->rdptr_enabled);
if (ctl->panel_data->panel_info.dummy_panel_enabled
|| mdss_mdp_ctl_is_panel_dead(ctl)) {
mdss_mdp_cmd_pingpong_done(ctl);
if (sctl)
mdss_mdp_cmd_pingpong_done(sctl);
}
return 0;
}
int mdss_mdp_cmd_restore(struct mdss_mdp_ctl *ctl)
{
pr_debug("%s: called for ctl%d\n", __func__, ctl->num);
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON);
if (mdss_mdp_cmd_tearcheck_setup(ctl, true))
pr_warn("%s: tearcheck setup failed\n", __func__);
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
return 0;
}
int mdss_mdp_cmd_intfs_stop(struct mdss_mdp_ctl *ctl, int session,
int panel_power_state)
{
struct mdss_mdp_ctl *sctl = NULL;
struct mdss_mdp_cmd_ctx *sctx = NULL;
struct mdss_mdp_cmd_ctx *ctx;
unsigned long flags;
int need_reset = 0;
int ret = 0;
if (session >= MAX_SESSIONS)
return 0;
if (is_split_dst(ctl->mfd)) {
ret = mdss_mdp_cmd_intfs_stop(ctl, (session + 1),
panel_power_state);
if (IS_ERR_VALUE(ret))
return ret;
}
sctl = mdss_mdp_get_split_ctl(ctl);
if (sctl)
sctx = (struct mdss_mdp_cmd_ctx *) sctl->priv_data;
ctx = &mdss_mdp_cmd_ctx_list[session];
if (!ctx->ref_cnt) {
pr_err("invalid ctx session: %d\n", session);
return -ENODEV;
}
spin_lock_irqsave(&ctx->clk_lock, flags);
/* intf stopped, no more kickoff */
atomic_set(&ctx->intf_stopped, 1);
if (ctx->rdptr_enabled) {
need_reset = 1;
/*
* clk off at next vsync after pp_done OR
* next vsync if there has no kickoff pending
*/
ctx->rdptr_enabled = 1;
if (sctx)
sctx->rdptr_enabled = 1;
}
spin_unlock_irqrestore(&ctx->clk_lock, flags);
if (need_reset) {
if (!mdss_mdp_ctl_is_panel_dead(ctl) &&
atomic_read(&ctx->koff_cnt)) {
ret = wait_for_completion_timeout
(&ctx->pp_comp, KOFF_TIMEOUT);
if (ret <= 0)
WARN(1, "stop cmd with kickoff left %d\n",
atomic_read(&ctx->koff_cnt));
else /* sleep for waiting frame done */
msleep_interruptible(33);
}
mdss_mdp_irq_disable(MDSS_MDP_IRQ_PING_PONG_RD_PTR,
ctx->pp_num);
ctx->rdptr_enabled = 0;
atomic_set(&ctx->koff_cnt, 0);
}
if (cancel_work_sync(&ctx->clk_work))
pr_debug("no pending clk work\n");
mdss_mdp_ctl_intf_event(ctl,
MDSS_EVENT_REGISTER_RECOVERY_HANDLER,
NULL);
mdss_mdp_cmd_clk_off(ctx);
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;
}
ctx->ref_cnt--;
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));
end:
pr_debug("%s:-\n", __func__);
return 0;
}
int mdss_mdp_cmd_stop(struct mdss_mdp_ctl *ctl, int panel_power_state)
{
struct mdss_mdp_cmd_ctx *ctx;
struct mdss_mdp_vsync_handler *tmp, *handle;
int ret = 0;
int session = 0;
bool panel_off = false;
bool turn_off_clocks = false;
bool send_panel_events = false;
ctx = (struct mdss_mdp_cmd_ctx *) ctl->priv_data;
if (!ctx) {
pr_err("invalid ctx\n");
return -ENODEV;
}
if (__mdss_mdp_cmd_is_panel_power_off(ctx)) {
pr_debug("%s: panel already off\n", __func__);
return 0;
}
/* when display was transmitted form lp to on mode, it may not
* set ctx->panel_power_state to power_on_interactive, so sync
* mdp_ctl status to avoid missing state change updates
*/
if (mdss_mdp_ctl_is_power_on_interactive(ctl))
ctx->panel_power_state = MDSS_PANEL_POWER_ON;
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_panel_is_power_off(panel_power_state)) {
/* Transition to display off */
send_panel_events = true;
turn_off_clocks = true;
panel_off = true;
} else 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 {
/* Transitions between low power and ultra low power */
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. Just rest the
* intf_stopped flag so that the clocks would
* get turned on when the first update comes.
*/
pr_debug("%s: reset intf_stopped flag.\n", __func__);
atomic_set(&ctx->intf_stopped, 0);
goto end;
}
}
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);
ret = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_OFF,
(void *) (long int) panel_power_state);
WARN(ret, "intf %d unblank error (%d)\n", ctl->intf_num, ret);
}
if (turn_off_clocks) {
pr_debug("%s: turn off interface clocks\n", __func__);
/* disable vsync only when panel is off */
if (panel_off) {
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, ctx->rdptr_enabled,
XLOG_FUNC_ENTRY);
/* Command mode is supported only starting at INTF1 */
session = ctl->intf_num - MDSS_MDP_INTF1;
ret = mdss_mdp_cmd_intfs_stop(ctl, session, panel_power_state);
if (IS_ERR_VALUE(ret)) {
pr_err("unable to stop cmd interface: %d\n", ret);
goto end;
}
}
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;
ctl->wait_pingpong = NULL;
ctl->add_vsync_handler = NULL;
ctl->remove_vsync_handler = NULL;
end:
if (!IS_ERR_VALUE(ret))
ctx->panel_power_state = panel_power_state;
MDSS_XLOG(ctl->num, atomic_read(&ctx->koff_cnt), ctx->clk_enabled,
ctx->rdptr_enabled, XLOG_FUNC_EXIT);
pr_debug("%s:-\n", __func__);
return ret;
}
static int mdss_mdp_cmd_intfs_setup(struct mdss_mdp_ctl *ctl,
int session)
{
struct mdss_mdp_cmd_ctx *ctx;
struct mdss_mdp_ctl *sctl = NULL;
struct mdss_mdp_mixer *mixer;
int ret;
if (session >= MAX_SESSIONS)
return 0;
if (is_split_dst(ctl->mfd)) {
ret = mdss_mdp_cmd_intfs_setup(ctl, (session + 1));
if (IS_ERR_VALUE(ret))
return ret;
}
sctl = mdss_mdp_get_split_ctl(ctl);
ctx = &mdss_mdp_cmd_ctx_list[session];
if (ctx->ref_cnt) {
if (mdss_panel_is_power_on(ctx->panel_power_state)) {
pr_debug("%s: cmd_start with panel always on\n",
__func__);
/*
* It is possible that the resume was called from the
* panel always on state without MDSS every
* power-collapsed (such as a case with any other
* interfaces connected). In such cases, we need to
* explictly call the restore function to enable
* tearcheck logic.
*/
mdss_mdp_cmd_restore(ctl);
/* Turn on panel so that it can exit low power mode */
return mdss_mdp_cmd_panel_on(ctl, sctl);
} else {
pr_err("Intf %d already in use\n", session);
return -EBUSY;
}
}
ctx->ref_cnt++;
mixer = mdss_mdp_mixer_get(ctl, MDSS_MDP_MIXER_MUX_LEFT);
if (!mixer) {
pr_err("mixer not setup correctly\n");
return -ENODEV;
}
ctl->priv_data = ctx;
if (!ctx) {
pr_err("invalid ctx\n");
return -ENODEV;
}
ctx->ctl = ctl;
ctx->pp_num = (is_split_dst(ctl->mfd) ? session : mixer->num);
init_completion(&ctx->pp_comp);
spin_lock_init(&ctx->clk_lock);
spin_lock_init(&ctx->koff_lock);
mutex_init(&ctx->clk_mtx);
INIT_WORK(&ctx->clk_work, clk_ctrl_work);
INIT_WORK(&ctx->pp_done_work, pingpong_done_work);
atomic_set(&ctx->pp_done_cnt, 0);
INIT_LIST_HEAD(&ctx->vsync_handlers);
ctx->recovery.fxn = mdss_mdp_cmd_underflow_recovery;
ctx->recovery.data = ctx;
atomic_set(&ctx->intf_stopped, 0);
pr_debug("%s: ctx=%p num=%d mixer=%d\n", __func__,
ctx, ctx->pp_num, mixer->num);
MDSS_XLOG(ctl->num, atomic_read(&ctx->koff_cnt), ctx->clk_enabled,
ctx->rdptr_enabled);
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, true);
if (ret) {
pr_err("tearcheck setup failed\n");
return ret;
}
return 0;
}
int mdss_mdp_cmd_start(struct mdss_mdp_ctl *ctl)
{
int ret, session = 0;
pr_debug("%s:+\n", __func__);
/* Command mode is supported only starting at INTF1 */
session = ctl->intf_num - MDSS_MDP_INTF1;
ret = mdss_mdp_cmd_intfs_setup(ctl, session);
if (IS_ERR_VALUE(ret)) {
pr_err("unable to set cmd interface: %d\n", ret);
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->restore_fnc = mdss_mdp_cmd_restore;
pr_debug("%s:-\n", __func__);
return 0;
}