1685 lines
46 KiB
C
1685 lines
46 KiB
C
/* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
|
|
|
#include <linux/iopoll.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/memblock.h>
|
|
#include <video/msm_hdmi_modes.h>
|
|
|
|
#include "mdss_fb.h"
|
|
#include "mdss_mdp.h"
|
|
#include "mdss_panel.h"
|
|
#include "mdss_debug.h"
|
|
#include "mdss_mdp_trace.h"
|
|
|
|
/* wait for at least 2 vsyncs for lowest refresh rate (24hz) */
|
|
#define VSYNC_TIMEOUT_US 100000
|
|
|
|
/* Poll time to do recovery during active region */
|
|
#define POLL_TIME_USEC_FOR_LN_CNT 500
|
|
|
|
/* Filter out input events for 1 vsync time after receiving an input event*/
|
|
#define INPUT_EVENT_HANDLER_DELAY_USECS 16000
|
|
|
|
#define MDP_INTR_MASK_INTF_VSYNC(intf_num) \
|
|
(1 << (2 * (intf_num - MDSS_MDP_INTF0) + MDSS_MDP_IRQ_INTF_VSYNC))
|
|
|
|
/* intf timing settings */
|
|
struct intf_timing_params {
|
|
u32 width;
|
|
u32 height;
|
|
u32 xres;
|
|
u32 yres;
|
|
|
|
u32 h_back_porch;
|
|
u32 h_front_porch;
|
|
u32 v_back_porch;
|
|
u32 v_front_porch;
|
|
u32 hsync_pulse_width;
|
|
u32 vsync_pulse_width;
|
|
|
|
u32 border_clr;
|
|
u32 underflow_clr;
|
|
u32 hsync_skew;
|
|
};
|
|
|
|
struct mdss_mdp_video_ctx {
|
|
u32 intf_num;
|
|
char __iomem *base;
|
|
u32 intf_type;
|
|
u8 ref_cnt;
|
|
|
|
u8 timegen_en;
|
|
bool polling_en;
|
|
u32 poll_cnt;
|
|
struct completion vsync_comp;
|
|
int wait_pending;
|
|
|
|
atomic_t vsync_ref;
|
|
spinlock_t vsync_lock;
|
|
spinlock_t dfps_lock;
|
|
struct mutex vsync_mtx;
|
|
struct list_head vsync_handlers;
|
|
struct mdss_intf_recovery intf_recovery;
|
|
};
|
|
|
|
static void mdss_mdp_fetch_start_config(struct mdss_mdp_video_ctx *ctx,
|
|
struct mdss_mdp_ctl *ctl);
|
|
|
|
static void mdss_mdp_fetch_end_config(struct mdss_mdp_video_ctx *ctx,
|
|
struct mdss_mdp_ctl *ctl);
|
|
|
|
static inline void mdp_video_write(struct mdss_mdp_video_ctx *ctx,
|
|
u32 reg, u32 val)
|
|
{
|
|
writel_relaxed(val, ctx->base + reg);
|
|
}
|
|
|
|
static inline u32 mdp_video_read(struct mdss_mdp_video_ctx *ctx,
|
|
u32 reg)
|
|
{
|
|
return readl_relaxed(ctx->base + reg);
|
|
}
|
|
|
|
static inline u32 mdss_mdp_video_line_count(struct mdss_mdp_ctl *ctl)
|
|
{
|
|
struct mdss_mdp_video_ctx *ctx;
|
|
u32 line_cnt = 0;
|
|
if (!ctl || !ctl->intf_ctx[MASTER_CTX])
|
|
goto line_count_exit;
|
|
ctx = ctl->intf_ctx[MASTER_CTX];
|
|
line_cnt = mdp_video_read(ctx, MDSS_MDP_REG_INTF_LINE_COUNT);
|
|
line_count_exit:
|
|
return line_cnt;
|
|
}
|
|
|
|
int mdss_mdp_video_addr_setup(struct mdss_data_type *mdata,
|
|
u32 *offsets, u32 count)
|
|
{
|
|
struct mdss_mdp_video_ctx *head;
|
|
u32 i;
|
|
|
|
head = devm_kzalloc(&mdata->pdev->dev,
|
|
sizeof(struct mdss_mdp_video_ctx) * count, GFP_KERNEL);
|
|
if (!head)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
head[i].base = mdata->mdss_io.base + offsets[i];
|
|
pr_debug("adding Video Intf #%d offset=0x%x virt=%p\n", i,
|
|
offsets[i], head[i].base);
|
|
head[i].ref_cnt = 0;
|
|
head[i].intf_num = i + MDSS_MDP_INTF0;
|
|
INIT_LIST_HEAD(&head[i].vsync_handlers);
|
|
}
|
|
|
|
mdata->video_intf = head;
|
|
mdata->nintf = count;
|
|
return 0;
|
|
}
|
|
|
|
static void mdss_mdp_video_intf_recovery(void *data, int event)
|
|
{
|
|
struct mdss_mdp_video_ctx *ctx;
|
|
struct mdss_mdp_ctl *ctl = data;
|
|
struct mdss_panel_info *pinfo;
|
|
u32 line_cnt, min_ln_cnt, active_lns_cnt;
|
|
u32 clk_rate, clk_period, time_of_line;
|
|
u32 delay;
|
|
|
|
if (!data) {
|
|
pr_err("%s: invalid ctl\n", __func__);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Currently, only intf_fifo_overflow is
|
|
* supported for recovery sequence for video
|
|
* mode DSI interface
|
|
*/
|
|
if (event != MDP_INTF_DSI_VIDEO_FIFO_OVERFLOW) {
|
|
pr_warn("%s: unsupported recovery event:%d\n",
|
|
__func__, event);
|
|
return;
|
|
}
|
|
|
|
ctx = ctl->intf_ctx[MASTER_CTX];
|
|
pr_debug("%s: ctl num = %d, event = %d\n",
|
|
__func__, ctl->num, event);
|
|
|
|
pinfo = &ctl->panel_data->panel_info;
|
|
clk_rate = ((ctl->intf_type == MDSS_INTF_DSI) ?
|
|
pinfo->mipi.dsi_pclk_rate :
|
|
pinfo->clk_rate);
|
|
|
|
clk_rate /= 1000; /* in kHz */
|
|
if (!clk_rate) {
|
|
pr_err("Unable to get proper clk_rate\n");
|
|
return;
|
|
}
|
|
/*
|
|
* calculate clk_period as pico second to maintain good
|
|
* accuracy with high pclk rate and this number is in 17 bit
|
|
* range.
|
|
*/
|
|
clk_period = 1000000000 / clk_rate;
|
|
if (!clk_period) {
|
|
pr_err("Unable to calculate clock period\n");
|
|
return;
|
|
}
|
|
min_ln_cnt = pinfo->lcdc.v_back_porch + pinfo->lcdc.v_pulse_width;
|
|
active_lns_cnt = pinfo->yres;
|
|
time_of_line = (pinfo->lcdc.h_back_porch +
|
|
pinfo->lcdc.h_front_porch +
|
|
pinfo->lcdc.h_pulse_width +
|
|
pinfo->xres) * clk_period;
|
|
|
|
/* delay in micro seconds */
|
|
delay = (time_of_line * (min_ln_cnt +
|
|
pinfo->lcdc.v_front_porch)) / 1000000;
|
|
|
|
/*
|
|
* Wait for max delay before
|
|
* polling to check active region
|
|
*/
|
|
if (delay > POLL_TIME_USEC_FOR_LN_CNT)
|
|
delay = POLL_TIME_USEC_FOR_LN_CNT;
|
|
|
|
mutex_lock(&ctl->offlock);
|
|
while (1) {
|
|
if (!ctl || ctl->mfd->shutdown_pending || !ctx ||
|
|
!ctx->timegen_en) {
|
|
pr_warn("Target is in suspend or shutdown pending\n");
|
|
mutex_unlock(&ctl->offlock);
|
|
return;
|
|
}
|
|
|
|
line_cnt = mdss_mdp_video_line_count(ctl);
|
|
|
|
if ((line_cnt >= min_ln_cnt) && (line_cnt <
|
|
(active_lns_cnt + min_ln_cnt))) {
|
|
pr_debug("%s, Needed lines left line_cnt=%d\n",
|
|
__func__, line_cnt);
|
|
mutex_unlock(&ctl->offlock);
|
|
return;
|
|
} else {
|
|
pr_warn("line count is less. line_cnt = %d\n",
|
|
line_cnt);
|
|
/* Add delay so that line count is in active region */
|
|
udelay(delay);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int mdss_mdp_video_timegen_setup(struct mdss_mdp_ctl *ctl,
|
|
struct intf_timing_params *p,
|
|
struct mdss_mdp_video_ctx *ctx)
|
|
{
|
|
u32 hsync_period, vsync_period;
|
|
u32 hsync_start_x, hsync_end_x, display_v_start, display_v_end;
|
|
u32 active_h_start, active_h_end, active_v_start, active_v_end;
|
|
u32 den_polarity, hsync_polarity, vsync_polarity;
|
|
u32 display_hctl, active_hctl, hsync_ctl, polarity_ctl;
|
|
struct mdss_data_type *mdata;
|
|
|
|
mdata = ctl->mdata;
|
|
hsync_period = p->hsync_pulse_width + p->h_back_porch +
|
|
p->width + p->h_front_porch;
|
|
vsync_period = p->vsync_pulse_width + p->v_back_porch +
|
|
p->height + p->v_front_porch;
|
|
|
|
MDSS_XLOG(p->vsync_pulse_width, p->v_back_porch,
|
|
p->height, p->v_front_porch);
|
|
|
|
display_v_start = ((p->vsync_pulse_width + p->v_back_porch) *
|
|
hsync_period) + p->hsync_skew;
|
|
display_v_end = ((vsync_period - p->v_front_porch) * hsync_period) +
|
|
p->hsync_skew - 1;
|
|
|
|
if (ctx->intf_type == MDSS_INTF_EDP) {
|
|
display_v_start += p->hsync_pulse_width + p->h_back_porch;
|
|
display_v_end -= p->h_front_porch;
|
|
}
|
|
|
|
/* TIMING_2 flush bit on 8939 is BIT 31 */
|
|
if (mdata->mdp_rev == MDSS_MDP_HW_REV_108 &&
|
|
ctx->intf_num == MDSS_MDP_INTF2)
|
|
ctl->flush_bits |= BIT(31);
|
|
else
|
|
ctl->flush_bits |= BIT(31) >>
|
|
(ctx->intf_num - MDSS_MDP_INTF0);
|
|
|
|
hsync_start_x = p->h_back_porch + p->hsync_pulse_width;
|
|
hsync_end_x = hsync_period - p->h_front_porch - 1;
|
|
|
|
if (p->width != p->xres) {
|
|
active_h_start = hsync_start_x;
|
|
active_h_end = active_h_start + p->xres - 1;
|
|
} else {
|
|
active_h_start = 0;
|
|
active_h_end = 0;
|
|
}
|
|
|
|
if (p->height != p->yres) {
|
|
active_v_start = display_v_start;
|
|
active_v_end = active_v_start + (p->yres * hsync_period) - 1;
|
|
} else {
|
|
active_v_start = 0;
|
|
active_v_end = 0;
|
|
}
|
|
|
|
|
|
if (active_h_end) {
|
|
active_hctl = (active_h_end << 16) | active_h_start;
|
|
active_hctl |= BIT(31); /* ACTIVE_H_ENABLE */
|
|
} else {
|
|
active_hctl = 0;
|
|
}
|
|
|
|
if (active_v_end)
|
|
active_v_start |= BIT(31); /* ACTIVE_V_ENABLE */
|
|
|
|
hsync_ctl = (hsync_period << 16) | p->hsync_pulse_width;
|
|
display_hctl = (hsync_end_x << 16) | hsync_start_x;
|
|
|
|
den_polarity = 0;
|
|
if (MDSS_INTF_HDMI == ctx->intf_type) {
|
|
hsync_polarity = p->yres >= 720 ? 0 : 1;
|
|
vsync_polarity = p->yres >= 720 ? 0 : 1;
|
|
} else {
|
|
hsync_polarity = 0;
|
|
vsync_polarity = 0;
|
|
}
|
|
polarity_ctl = (den_polarity << 2) | /* DEN Polarity */
|
|
(vsync_polarity << 1) | /* VSYNC Polarity */
|
|
(hsync_polarity << 0); /* HSYNC Polarity */
|
|
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_HSYNC_CTL, hsync_ctl);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0,
|
|
vsync_period * hsync_period);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PULSE_WIDTH_F0,
|
|
p->vsync_pulse_width * hsync_period);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_DISPLAY_HCTL, display_hctl);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_DISPLAY_V_START_F0,
|
|
display_v_start);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_DISPLAY_V_END_F0, display_v_end);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_ACTIVE_HCTL, active_hctl);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_ACTIVE_V_START_F0,
|
|
active_v_start);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_ACTIVE_V_END_F0, active_v_end);
|
|
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_BORDER_COLOR, p->border_clr);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_UNDERFLOW_COLOR,
|
|
p->underflow_clr);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_HSYNC_SKEW, p->hsync_skew);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_POLARITY_CTL, polarity_ctl);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_FRAME_LINE_COUNT_EN, 0x3);
|
|
MDSS_XLOG(hsync_period, vsync_period);
|
|
|
|
/*
|
|
* If CDM is present Interface should have destination
|
|
* format set to RGB
|
|
*/
|
|
if (ctl->cdm) {
|
|
u32 reg = mdp_video_read(ctx, MDSS_MDP_REG_INTF_CONFIG);
|
|
|
|
reg &= ~BIT(18); /* CSC_DST_DATA_FORMAT = RGB */
|
|
reg &= ~BIT(17); /* CSC_SRC_DATA_FROMAT = RGB */
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_CONFIG, reg);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static inline void video_vsync_irq_enable(struct mdss_mdp_ctl *ctl, bool clear)
|
|
{
|
|
struct mdss_mdp_video_ctx *ctx = ctl->intf_ctx[MASTER_CTX];
|
|
|
|
mutex_lock(&ctx->vsync_mtx);
|
|
if (atomic_inc_return(&ctx->vsync_ref) == 1)
|
|
mdss_mdp_irq_enable(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num);
|
|
else if (clear)
|
|
mdss_mdp_irq_clear(ctl->mdata, MDSS_MDP_IRQ_INTF_VSYNC,
|
|
ctl->intf_num);
|
|
mutex_unlock(&ctx->vsync_mtx);
|
|
}
|
|
|
|
static inline void video_vsync_irq_disable(struct mdss_mdp_ctl *ctl)
|
|
{
|
|
struct mdss_mdp_video_ctx *ctx = ctl->intf_ctx[MASTER_CTX];
|
|
|
|
mutex_lock(&ctx->vsync_mtx);
|
|
if (atomic_dec_return(&ctx->vsync_ref) == 0)
|
|
mdss_mdp_irq_disable(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num);
|
|
mutex_unlock(&ctx->vsync_mtx);
|
|
}
|
|
|
|
static int mdss_mdp_video_add_vsync_handler(struct mdss_mdp_ctl *ctl,
|
|
struct mdss_mdp_vsync_handler *handle)
|
|
{
|
|
struct mdss_mdp_video_ctx *ctx;
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
bool irq_en = false;
|
|
|
|
if (!handle || !(handle->vsync_handler)) {
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
ctx = (struct mdss_mdp_video_ctx *) ctl->intf_ctx[MASTER_CTX];
|
|
if (!ctx) {
|
|
pr_err("invalid ctx for ctl=%d\n", ctl->num);
|
|
ret = -ENODEV;
|
|
goto exit;
|
|
}
|
|
|
|
MDSS_XLOG(ctl->num, ctl->vsync_cnt, handle->enabled);
|
|
|
|
spin_lock_irqsave(&ctx->vsync_lock, flags);
|
|
if (!handle->enabled) {
|
|
handle->enabled = true;
|
|
list_add(&handle->list, &ctx->vsync_handlers);
|
|
irq_en = true;
|
|
}
|
|
spin_unlock_irqrestore(&ctx->vsync_lock, flags);
|
|
if (irq_en) {
|
|
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON);
|
|
video_vsync_irq_enable(ctl, false);
|
|
}
|
|
exit:
|
|
return ret;
|
|
}
|
|
|
|
static int mdss_mdp_video_remove_vsync_handler(struct mdss_mdp_ctl *ctl,
|
|
struct mdss_mdp_vsync_handler *handle)
|
|
{
|
|
struct mdss_mdp_video_ctx *ctx;
|
|
unsigned long flags;
|
|
bool irq_dis = false;
|
|
|
|
ctx = (struct mdss_mdp_video_ctx *) ctl->intf_ctx[MASTER_CTX];
|
|
if (!ctx) {
|
|
pr_err("invalid ctx for ctl=%d\n", ctl->num);
|
|
return -ENODEV;
|
|
}
|
|
|
|
MDSS_XLOG(ctl->num, ctl->vsync_cnt, handle->enabled);
|
|
|
|
spin_lock_irqsave(&ctx->vsync_lock, flags);
|
|
if (handle->enabled) {
|
|
handle->enabled = false;
|
|
list_del_init(&handle->list);
|
|
irq_dis = true;
|
|
}
|
|
spin_unlock_irqrestore(&ctx->vsync_lock, flags);
|
|
if (irq_dis) {
|
|
video_vsync_irq_disable(ctl);
|
|
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void mdss_mdp_turn_off_time_engine(struct mdss_mdp_ctl *ctl,
|
|
struct mdss_mdp_video_ctx *ctx, u32 sleep_time)
|
|
{
|
|
struct mdss_mdp_ctl *sctl;
|
|
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 0);
|
|
/* wait for at least one VSYNC for proper TG OFF */
|
|
msleep(sleep_time);
|
|
|
|
mdss_iommu_ctrl(0);
|
|
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
|
|
ctx->timegen_en = false;
|
|
|
|
mdss_mdp_irq_disable(MDSS_MDP_IRQ_INTF_UNDER_RUN, ctl->intf_num);
|
|
|
|
sctl = mdss_mdp_get_split_ctl(ctl);
|
|
if (sctl)
|
|
mdss_mdp_irq_disable(MDSS_MDP_IRQ_INTF_UNDER_RUN,
|
|
sctl->intf_num);
|
|
}
|
|
|
|
static int mdss_mdp_video_ctx_stop(struct mdss_mdp_ctl *ctl,
|
|
struct mdss_panel_info *pinfo, struct mdss_mdp_video_ctx *ctx)
|
|
{
|
|
int rc = 0;
|
|
u32 frame_rate = 0;
|
|
|
|
mutex_lock(&ctl->offlock);
|
|
if (ctx->timegen_en) {
|
|
rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_BLANK, NULL,
|
|
CTL_INTF_EVENT_FLAG_DEFAULT);
|
|
if (rc == -EBUSY) {
|
|
pr_debug("intf #%d busy don't turn off\n",
|
|
ctl->intf_num);
|
|
goto end;
|
|
}
|
|
WARN(rc, "intf %d blank error (%d)\n", ctl->intf_num, rc);
|
|
|
|
frame_rate = mdss_panel_get_framerate(pinfo);
|
|
if (!(frame_rate >= 24 && frame_rate <= 240))
|
|
frame_rate = 24;
|
|
|
|
frame_rate = (1000/frame_rate) + 1;
|
|
mdss_mdp_turn_off_time_engine(ctl, ctx, frame_rate);
|
|
|
|
rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_OFF, NULL,
|
|
CTL_INTF_EVENT_FLAG_DEFAULT);
|
|
WARN(rc, "intf %d timegen off error (%d)\n", ctl->intf_num, rc);
|
|
|
|
mdss_bus_bandwidth_ctrl(false);
|
|
}
|
|
|
|
mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_VSYNC,
|
|
ctx->intf_num, NULL, NULL);
|
|
mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_UNDER_RUN,
|
|
ctx->intf_num, NULL, NULL);
|
|
|
|
ctx->ref_cnt--;
|
|
end:
|
|
mutex_unlock(&ctl->offlock);
|
|
return rc;
|
|
}
|
|
|
|
static int mdss_mdp_video_intfs_stop(struct mdss_mdp_ctl *ctl,
|
|
struct mdss_panel_data *pdata, int inum)
|
|
{
|
|
struct mdss_data_type *mdata;
|
|
struct mdss_panel_info *pinfo;
|
|
struct mdss_mdp_video_ctx *ctx, *sctx = NULL;
|
|
struct mdss_mdp_vsync_handler *tmp, *handle;
|
|
int ret = 0;
|
|
|
|
if (pdata == NULL)
|
|
return 0;
|
|
|
|
mdata = ctl->mdata;
|
|
pinfo = &pdata->panel_info;
|
|
|
|
ctx = (struct mdss_mdp_video_ctx *) ctl->intf_ctx[MASTER_CTX];
|
|
if (!ctx->ref_cnt) {
|
|
pr_err("Intf %d not in use\n", (inum + MDSS_MDP_INTF0));
|
|
return -ENODEV;
|
|
}
|
|
pr_debug("stop ctl=%d video Intf #%d base=%p", ctl->num, ctx->intf_num,
|
|
ctx->base);
|
|
|
|
ret = mdss_mdp_video_ctx_stop(ctl, pinfo, ctx);
|
|
if (ret) {
|
|
pr_err("mdss_mdp_video_ctx_stop failed for intf: %d",
|
|
ctx->intf_num);
|
|
return -EPERM;
|
|
}
|
|
|
|
if (is_pingpong_split(ctl->mfd)) {
|
|
pinfo = &pdata->next->panel_info;
|
|
|
|
sctx = (struct mdss_mdp_video_ctx *) ctl->intf_ctx[SLAVE_CTX];
|
|
if (!sctx->ref_cnt) {
|
|
pr_err("Intf %d not in use\n", (inum + MDSS_MDP_INTF0));
|
|
return -ENODEV;
|
|
}
|
|
pr_debug("stop ctl=%d video Intf #%d base=%p", ctl->num,
|
|
sctx->intf_num, sctx->base);
|
|
|
|
ret = mdss_mdp_video_ctx_stop(ctl, pinfo, sctx);
|
|
if (ret) {
|
|
pr_err("mdss_mdp_video_ctx_stop failed for intf: %d",
|
|
sctx->intf_num);
|
|
return -EPERM;
|
|
}
|
|
}
|
|
|
|
list_for_each_entry_safe(handle, tmp, &ctx->vsync_handlers, list)
|
|
mdss_mdp_video_remove_vsync_handler(ctl, handle);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int mdss_mdp_video_stop(struct mdss_mdp_ctl *ctl, int panel_power_state)
|
|
{
|
|
int intfs_num, ret = 0;
|
|
|
|
intfs_num = ctl->intf_num - MDSS_MDP_INTF0;
|
|
ret = mdss_mdp_video_intfs_stop(ctl, ctl->panel_data, intfs_num);
|
|
if (IS_ERR_VALUE(ret)) {
|
|
pr_err("unable to stop video interface: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
MDSS_XLOG(ctl->num, ctl->vsync_cnt);
|
|
|
|
mdss_mdp_ctl_reset(ctl, false);
|
|
ctl->intf_ctx[MASTER_CTX] = NULL;
|
|
|
|
if (ctl->cdm) {
|
|
mdss_mdp_cdm_destroy(ctl->cdm);
|
|
ctl->cdm = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void mdss_mdp_video_vsync_intr_done(void *arg)
|
|
{
|
|
struct mdss_mdp_ctl *ctl = arg;
|
|
struct mdss_mdp_video_ctx *ctx = ctl->intf_ctx[MASTER_CTX];
|
|
struct mdss_mdp_vsync_handler *tmp;
|
|
ktime_t vsync_time;
|
|
|
|
if (!ctx) {
|
|
pr_err("invalid ctx\n");
|
|
return;
|
|
}
|
|
|
|
vsync_time = ktime_get();
|
|
ctl->vsync_cnt++;
|
|
|
|
MDSS_XLOG(ctl->num, ctl->vsync_cnt, ctl->vsync_cnt);
|
|
|
|
pr_debug("intr ctl=%d vsync cnt=%u vsync_time=%d\n",
|
|
ctl->num, ctl->vsync_cnt, (int)ktime_to_ms(vsync_time));
|
|
|
|
ctx->polling_en = false;
|
|
complete_all(&ctx->vsync_comp);
|
|
spin_lock(&ctx->vsync_lock);
|
|
list_for_each_entry(tmp, &ctx->vsync_handlers, list) {
|
|
tmp->vsync_handler(ctl, vsync_time);
|
|
}
|
|
spin_unlock(&ctx->vsync_lock);
|
|
}
|
|
|
|
static int mdss_mdp_video_pollwait(struct mdss_mdp_ctl *ctl)
|
|
{
|
|
struct mdss_mdp_video_ctx *ctx = ctl->intf_ctx[MASTER_CTX];
|
|
u32 mask, status;
|
|
int rc;
|
|
|
|
mask = MDP_INTR_MASK_INTF_VSYNC(ctl->intf_num);
|
|
|
|
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON);
|
|
rc = readl_poll_timeout(ctl->mdata->mdp_base + MDSS_MDP_REG_INTR_STATUS,
|
|
status,
|
|
(status & mask) || try_wait_for_completion(&ctx->vsync_comp),
|
|
1000,
|
|
VSYNC_TIMEOUT_US);
|
|
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
|
|
|
|
if (rc == 0) {
|
|
MDSS_XLOG(ctl->num, ctl->vsync_cnt);
|
|
pr_debug("vsync poll successful! rc=%d status=0x%x\n",
|
|
rc, status);
|
|
ctx->poll_cnt++;
|
|
if (status) {
|
|
struct mdss_mdp_vsync_handler *tmp;
|
|
unsigned long flags;
|
|
ktime_t vsync_time = ktime_get();
|
|
|
|
spin_lock_irqsave(&ctx->vsync_lock, flags);
|
|
list_for_each_entry(tmp, &ctx->vsync_handlers, list)
|
|
tmp->vsync_handler(ctl, vsync_time);
|
|
spin_unlock_irqrestore(&ctx->vsync_lock, flags);
|
|
}
|
|
} else {
|
|
pr_warn("vsync poll timed out! rc=%d status=0x%x mask=0x%x\n",
|
|
rc, status, mask);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int mdss_mdp_video_wait4comp(struct mdss_mdp_ctl *ctl, void *arg)
|
|
{
|
|
struct mdss_mdp_video_ctx *ctx;
|
|
int rc;
|
|
|
|
ctx = (struct mdss_mdp_video_ctx *) ctl->intf_ctx[MASTER_CTX];
|
|
if (!ctx) {
|
|
pr_err("invalid ctx\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
WARN(!ctx->wait_pending, "waiting without commit! ctl=%d", ctl->num);
|
|
|
|
if (ctx->polling_en) {
|
|
rc = mdss_mdp_video_pollwait(ctl);
|
|
} else {
|
|
mutex_unlock(&ctl->lock);
|
|
rc = wait_for_completion_timeout(&ctx->vsync_comp,
|
|
usecs_to_jiffies(VSYNC_TIMEOUT_US));
|
|
mutex_lock(&ctl->lock);
|
|
if (rc == 0) {
|
|
pr_warn("vsync wait timeout %d, fallback to poll mode\n",
|
|
ctl->num);
|
|
ctx->polling_en++;
|
|
rc = mdss_mdp_video_pollwait(ctl);
|
|
} else {
|
|
rc = 0;
|
|
}
|
|
}
|
|
mdss_mdp_ctl_notify(ctl,
|
|
rc ? MDP_NOTIFY_FRAME_TIMEOUT : MDP_NOTIFY_FRAME_DONE);
|
|
|
|
if (ctx->wait_pending) {
|
|
ctx->wait_pending = 0;
|
|
video_vsync_irq_disable(ctl);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void recover_underrun_work(struct work_struct *work)
|
|
{
|
|
struct mdss_mdp_ctl *ctl =
|
|
container_of(work, typeof(*ctl), recover_work);
|
|
|
|
if (!ctl || !ctl->ops.add_vsync_handler) {
|
|
pr_err("ctl or vsync handler is NULL\n");
|
|
return;
|
|
}
|
|
|
|
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON);
|
|
ctl->ops.add_vsync_handler(ctl, &ctl->recover_underrun_handler);
|
|
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
|
|
}
|
|
|
|
static void mdss_mdp_video_underrun_intr_done(void *arg)
|
|
{
|
|
struct mdss_mdp_ctl *ctl = arg;
|
|
if (unlikely(!ctl))
|
|
return;
|
|
|
|
ctl->underrun_cnt++;
|
|
MDSS_XLOG(ctl->num, ctl->underrun_cnt);
|
|
trace_mdp_video_underrun_done(ctl->num, ctl->underrun_cnt);
|
|
pr_debug("display underrun detected for ctl=%d count=%d\n", ctl->num,
|
|
ctl->underrun_cnt);
|
|
|
|
if (!test_bit(MDSS_CAPS_3D_MUX_UNDERRUN_RECOVERY_SUPPORTED,
|
|
ctl->mdata->mdss_caps_map) &&
|
|
(ctl->opmode & MDSS_MDP_CTL_OP_PACK_3D_ENABLE))
|
|
schedule_work(&ctl->recover_work);
|
|
}
|
|
|
|
/**
|
|
* mdss_mdp_video_hfp_fps_update() - configure mdp with new fps.
|
|
* @ctx: pointer to the master context.
|
|
* @pdata: panel information data.
|
|
*
|
|
* This function configures the hardware to modify the fps.
|
|
* within mdp for the hfp method.
|
|
* Function assumes that timings for the new fps configuration
|
|
* are already updated in the panel data passed as parameter.
|
|
*
|
|
* Return: 0 - succeed, otherwise - fail
|
|
*/
|
|
static int mdss_mdp_video_hfp_fps_update(struct mdss_mdp_video_ctx *ctx,
|
|
struct mdss_panel_data *pdata)
|
|
{
|
|
u32 hsync_period, vsync_period;
|
|
u32 hsync_start_x, hsync_end_x, display_v_start, display_v_end;
|
|
u32 display_hctl, hsync_ctl;
|
|
struct mdss_panel_info *pinfo = &pdata->panel_info;
|
|
|
|
hsync_period = mdss_panel_get_htotal(pinfo, true);
|
|
vsync_period = mdss_panel_get_vtotal(pinfo);
|
|
|
|
display_v_start = ((pinfo->lcdc.v_pulse_width +
|
|
pinfo->lcdc.v_back_porch) * hsync_period) +
|
|
pinfo->lcdc.hsync_skew;
|
|
display_v_end = ((vsync_period - pinfo->lcdc.v_front_porch) *
|
|
hsync_period) + pinfo->lcdc.hsync_skew - 1;
|
|
|
|
hsync_start_x = pinfo->lcdc.h_back_porch + pinfo->lcdc.h_pulse_width;
|
|
hsync_end_x = hsync_period - pinfo->lcdc.h_front_porch - 1;
|
|
|
|
hsync_ctl = (hsync_period << 16) | pinfo->lcdc.h_pulse_width;
|
|
display_hctl = (hsync_end_x << 16) | hsync_start_x;
|
|
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_HSYNC_CTL, hsync_ctl);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0,
|
|
vsync_period * hsync_period);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PULSE_WIDTH_F0,
|
|
pinfo->lcdc.v_pulse_width * hsync_period);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_DISPLAY_HCTL, display_hctl);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_DISPLAY_V_START_F0,
|
|
display_v_start);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_DISPLAY_V_END_F0, display_v_end);
|
|
MDSS_XLOG(ctx->intf_num, hsync_ctl, vsync_period, hsync_period);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* mdss_mdp_video_vfp_fps_update() - configure mdp with new fps.
|
|
* @ctx: pointer to the master context.
|
|
* @pdata: panel information data.
|
|
*
|
|
* This function configures the hardware to modify the fps.
|
|
* within mdp for the vfp method.
|
|
* Function assumes that timings for the new fps configuration
|
|
* are already updated in the panel data passed as parameter.
|
|
*
|
|
* Return: 0 - succeed, otherwise - fail
|
|
*/
|
|
static int mdss_mdp_video_vfp_fps_update(struct mdss_mdp_video_ctx *ctx,
|
|
struct mdss_panel_data *pdata)
|
|
{
|
|
u32 current_vsync_period_f0, new_vsync_period_f0;
|
|
int vsync_period, hsync_period;
|
|
|
|
/*
|
|
* Change in the blanking times are already in the
|
|
* panel info, so just get the vtotal and htotal expected
|
|
* for this panel to configure those in hw.
|
|
*/
|
|
vsync_period = mdss_panel_get_vtotal(&pdata->panel_info);
|
|
hsync_period = mdss_panel_get_htotal(&pdata->panel_info, true);
|
|
|
|
current_vsync_period_f0 = mdp_video_read(ctx,
|
|
MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0);
|
|
new_vsync_period_f0 = (vsync_period * hsync_period);
|
|
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0,
|
|
current_vsync_period_f0 | 0x800000);
|
|
if (new_vsync_period_f0 & 0x800000) {
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0,
|
|
new_vsync_period_f0);
|
|
} else {
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0,
|
|
new_vsync_period_f0 | 0x800000);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0,
|
|
new_vsync_period_f0 & 0x7fffff);
|
|
}
|
|
|
|
pr_debug("if:%d vtotal:%d htotal:%d f0:0x%x nw_f0:0x%x\n",
|
|
ctx->intf_num, vsync_period, hsync_period,
|
|
current_vsync_period_f0, new_vsync_period_f0);
|
|
|
|
MDSS_XLOG(ctx->intf_num, current_vsync_period_f0,
|
|
hsync_period, vsync_period, new_vsync_period_f0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mdss_mdp_video_fps_update(struct mdss_mdp_video_ctx *ctx,
|
|
struct mdss_panel_data *pdata, int new_fps)
|
|
{
|
|
int rc;
|
|
|
|
if (pdata->panel_info.dfps_update ==
|
|
DFPS_IMMEDIATE_PORCH_UPDATE_MODE_HFP)
|
|
rc = mdss_mdp_video_hfp_fps_update(ctx, pdata);
|
|
else
|
|
rc = mdss_mdp_video_vfp_fps_update(ctx, pdata);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int mdss_mdp_video_dfps_wait4vsync(struct mdss_mdp_ctl *ctl)
|
|
{
|
|
int rc = 0;
|
|
struct mdss_mdp_video_ctx *ctx;
|
|
|
|
ctx = (struct mdss_mdp_video_ctx *) ctl->intf_ctx[MASTER_CTX];
|
|
if (!ctx) {
|
|
pr_err("invalid ctx\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
video_vsync_irq_enable(ctl, true);
|
|
reinit_completion(&ctx->vsync_comp);
|
|
rc = wait_for_completion_timeout(&ctx->vsync_comp,
|
|
usecs_to_jiffies(VSYNC_TIMEOUT_US));
|
|
|
|
if (rc <= 0) {
|
|
pr_warn("vsync timeout %d fallback to poll mode\n",
|
|
ctl->num);
|
|
rc = mdss_mdp_video_pollwait(ctl);
|
|
if (rc) {
|
|
pr_err("error polling for vsync\n");
|
|
MDSS_XLOG_TOUT_HANDLER("mdp", "dsi0_ctrl", "dsi0_phy",
|
|
"dsi1_ctrl", "dsi1_phy", "vbif", "dbg_bus",
|
|
"vbif_dbg_bus", "panic");
|
|
}
|
|
} else {
|
|
rc = 0;
|
|
}
|
|
video_vsync_irq_disable(ctl);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int mdss_mdp_video_dfps_check_line_cnt(struct mdss_mdp_ctl *ctl)
|
|
{
|
|
struct mdss_panel_data *pdata;
|
|
u32 line_cnt;
|
|
pdata = ctl->panel_data;
|
|
if (pdata == NULL) {
|
|
pr_err("%s: Invalid panel data\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
line_cnt = mdss_mdp_video_line_count(ctl);
|
|
if (line_cnt >= pdata->panel_info.yres/2) {
|
|
pr_debug("Too few lines left line_cnt=%d yres/2=%d\n",
|
|
line_cnt,
|
|
pdata->panel_info.yres/2);
|
|
return -EPERM;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void mdss_mdp_video_timegen_flush(struct mdss_mdp_ctl *ctl,
|
|
struct mdss_mdp_video_ctx *sctx)
|
|
{
|
|
u32 ctl_flush;
|
|
struct mdss_data_type *mdata;
|
|
|
|
mdata = ctl->mdata;
|
|
ctl_flush = (BIT(31) >> (ctl->intf_num - MDSS_MDP_INTF0));
|
|
if (sctx) {
|
|
/* For 8939, sctx is always INTF2 and the flush bit is BIT 31 */
|
|
if (mdata->mdp_rev == MDSS_MDP_HW_REV_108)
|
|
ctl_flush |= BIT(31);
|
|
else
|
|
ctl_flush |= (BIT(31) >>
|
|
(sctx->intf_num - MDSS_MDP_INTF0));
|
|
}
|
|
mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_FLUSH, ctl_flush);
|
|
MDSS_XLOG(ctl->intf_num, sctx?sctx->intf_num:0xf00, ctl_flush);
|
|
}
|
|
|
|
/**
|
|
* mdss_mdp_video_config_fps() - modify the fps.
|
|
* @ctl: pointer to the master controller.
|
|
* @new_fps: new fps to be set.
|
|
*
|
|
* This function configures the hardware to modify the fps.
|
|
* Note that this function will flush the DSI and MDP
|
|
* to reconfigure the fps in VFP and HFP methods.
|
|
* Given above statement, is callers responsibility to call
|
|
* this function at the beginning of the frame, so it can be
|
|
* guaranteed that flush of both (DSI and MDP) happen within
|
|
* the same frame.
|
|
*
|
|
* Return: 0 - succeed, otherwise - fail
|
|
*/
|
|
static int mdss_mdp_video_config_fps(struct mdss_mdp_ctl *ctl, int new_fps)
|
|
{
|
|
struct mdss_mdp_video_ctx *ctx, *sctx = NULL;
|
|
struct mdss_panel_data *pdata;
|
|
int rc = 0;
|
|
struct mdss_data_type *mdata = ctl->mdata;
|
|
struct mdss_mdp_ctl *sctl = NULL;
|
|
|
|
ctx = (struct mdss_mdp_video_ctx *) ctl->intf_ctx[MASTER_CTX];
|
|
if (!ctx || !ctx->timegen_en || !ctx->ref_cnt) {
|
|
pr_err("invalid ctx or interface is powered off\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
sctl = mdss_mdp_get_split_ctl(ctl);
|
|
if (sctl) {
|
|
sctx = (struct mdss_mdp_video_ctx *) sctl->intf_ctx[MASTER_CTX];
|
|
if (!sctx) {
|
|
pr_err("invalid ctx\n");
|
|
return -ENODEV;
|
|
}
|
|
} else if (is_pingpong_split(ctl->mfd)) {
|
|
sctx = (struct mdss_mdp_video_ctx *) ctl->intf_ctx[SLAVE_CTX];
|
|
if (!sctx || !sctx->ref_cnt) {
|
|
pr_err("invalid sctx or interface is powered off\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
mutex_lock(&ctl->offlock);
|
|
pdata = ctl->panel_data;
|
|
if (pdata == NULL) {
|
|
pr_err("%s: Invalid panel data\n", __func__);
|
|
rc = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
pr_debug("ctl:%d dfps_update:%d fps:%d\n",
|
|
ctl->num, pdata->panel_info.dfps_update, new_fps);
|
|
MDSS_XLOG(ctl->num, pdata->panel_info.dfps_update,
|
|
new_fps, XLOG_FUNC_ENTRY);
|
|
|
|
if (pdata->panel_info.dfps_update
|
|
!= DFPS_SUSPEND_RESUME_MODE) {
|
|
if (pdata->panel_info.dfps_update
|
|
== DFPS_IMMEDIATE_CLK_UPDATE_MODE) {
|
|
if (!ctx->timegen_en) {
|
|
pr_err("TG is OFF. DFPS mode invalid\n");
|
|
rc = -EINVAL;
|
|
goto end;
|
|
}
|
|
rc = mdss_mdp_ctl_intf_event(ctl,
|
|
MDSS_EVENT_PANEL_UPDATE_FPS,
|
|
(void *) (unsigned long) new_fps,
|
|
CTL_INTF_EVENT_FLAG_DEFAULT);
|
|
|
|
WARN(rc, "intf %d panel fps update error (%d)\n",
|
|
ctl->intf_num, rc);
|
|
} else if (pdata->panel_info.dfps_update
|
|
== DFPS_IMMEDIATE_PORCH_UPDATE_MODE_VFP ||
|
|
pdata->panel_info.dfps_update
|
|
== DFPS_IMMEDIATE_PORCH_UPDATE_MODE_HFP) {
|
|
unsigned long flags;
|
|
if (!ctx->timegen_en) {
|
|
pr_err("TG is OFF. DFPS mode invalid\n");
|
|
rc = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON);
|
|
spin_lock_irqsave(&ctx->dfps_lock, flags);
|
|
|
|
if (mdata->mdp_rev < MDSS_MDP_HW_REV_105) {
|
|
rc = mdss_mdp_video_dfps_check_line_cnt(ctl);
|
|
if (rc < 0)
|
|
goto exit_dfps;
|
|
}
|
|
|
|
rc = mdss_mdp_video_fps_update(ctx, pdata, new_fps);
|
|
if (rc < 0) {
|
|
pr_err("%s: Error during DFPS: %d\n", __func__,
|
|
new_fps);
|
|
goto exit_dfps;
|
|
}
|
|
if (sctx) {
|
|
rc = mdss_mdp_video_fps_update(sctx,
|
|
pdata->next, new_fps);
|
|
if (rc < 0) {
|
|
pr_err("%s: DFPS error fps:%d\n",
|
|
__func__, new_fps);
|
|
goto exit_dfps;
|
|
}
|
|
}
|
|
rc = mdss_mdp_ctl_intf_event(ctl,
|
|
MDSS_EVENT_PANEL_UPDATE_FPS,
|
|
(void *) (unsigned long) new_fps,
|
|
CTL_INTF_EVENT_FLAG_DEFAULT);
|
|
WARN(rc, "intf %d panel fps update error (%d)\n",
|
|
ctl->intf_num, rc);
|
|
|
|
rc = 0;
|
|
mdss_mdp_fetch_start_config(ctx, ctl);
|
|
if (sctx)
|
|
mdss_mdp_fetch_start_config(sctx, ctl);
|
|
|
|
if (test_bit(MDSS_QOS_VBLANK_PANIC_CTRL,
|
|
mdata->mdss_qos_map)) {
|
|
mdss_mdp_fetch_end_config(ctx, ctl);
|
|
if (sctx)
|
|
mdss_mdp_fetch_end_config(sctx, ctl);
|
|
}
|
|
|
|
/*
|
|
* MDP INTF registers support DB on targets
|
|
* starting from MDP v1.5.
|
|
*/
|
|
if (mdata->mdp_rev >= MDSS_MDP_HW_REV_105)
|
|
mdss_mdp_video_timegen_flush(ctl, sctx);
|
|
|
|
exit_dfps:
|
|
spin_unlock_irqrestore(&ctx->dfps_lock, flags);
|
|
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
|
|
|
|
/*
|
|
* Wait for one vsync to make sure these changes
|
|
* are applied as part of one single frame and
|
|
* no mixer changes happen at the same time.
|
|
* A potential optimization would be not to wait
|
|
* here, but next mixer programming would need
|
|
* to wait before programming the flush bits.
|
|
*/
|
|
if (!rc) {
|
|
rc = mdss_mdp_video_dfps_wait4vsync(ctl);
|
|
if (rc < 0)
|
|
pr_err("Error in dfps_wait: %d\n", rc);
|
|
}
|
|
|
|
} else {
|
|
pr_err("intf %d panel, unknown FPS mode\n",
|
|
ctl->intf_num);
|
|
rc = -EINVAL;
|
|
goto end;
|
|
}
|
|
} else {
|
|
rc = mdss_mdp_ctl_intf_event(ctl,
|
|
MDSS_EVENT_PANEL_UPDATE_FPS,
|
|
(void *) (unsigned long) new_fps,
|
|
CTL_INTF_EVENT_FLAG_DEFAULT);
|
|
WARN(rc, "intf %d panel fps update error (%d)\n",
|
|
ctl->intf_num, rc);
|
|
}
|
|
|
|
end:
|
|
MDSS_XLOG(ctl->num, new_fps, XLOG_FUNC_EXIT);
|
|
mutex_unlock(&ctl->offlock);
|
|
return rc;
|
|
}
|
|
|
|
static int mdss_mdp_video_display(struct mdss_mdp_ctl *ctl, void *arg)
|
|
{
|
|
struct mdss_mdp_video_ctx *ctx;
|
|
struct mdss_mdp_ctl *sctl;
|
|
struct mdss_panel_data *pdata = ctl->panel_data;
|
|
int rc;
|
|
|
|
pr_debug("kickoff ctl=%d\n", ctl->num);
|
|
|
|
ctx = (struct mdss_mdp_video_ctx *) ctl->intf_ctx[MASTER_CTX];
|
|
if (!ctx) {
|
|
pr_err("invalid ctx\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!ctx->wait_pending) {
|
|
ctx->wait_pending++;
|
|
video_vsync_irq_enable(ctl, true);
|
|
reinit_completion(&ctx->vsync_comp);
|
|
} else {
|
|
WARN(1, "commit without wait! ctl=%d", ctl->num);
|
|
}
|
|
|
|
MDSS_XLOG(ctl->num, ctl->underrun_cnt);
|
|
|
|
if (!ctx->timegen_en) {
|
|
rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_LINK_READY, NULL,
|
|
CTL_INTF_EVENT_FLAG_DEFAULT);
|
|
if (rc) {
|
|
pr_warn("intf #%d link ready error (%d)\n",
|
|
ctl->intf_num, rc);
|
|
video_vsync_irq_disable(ctl);
|
|
ctx->wait_pending = 0;
|
|
return rc;
|
|
}
|
|
|
|
rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_UNBLANK, NULL,
|
|
CTL_INTF_EVENT_FLAG_DEFAULT);
|
|
WARN(rc, "intf %d unblank error (%d)\n", ctl->intf_num, rc);
|
|
|
|
pr_debug("enabling timing gen for intf=%d\n", ctl->intf_num);
|
|
|
|
if (pdata->panel_info.cont_splash_enabled &&
|
|
!ctl->mfd->splash_info.splash_logo_enabled) {
|
|
rc = wait_for_completion_timeout(&ctx->vsync_comp,
|
|
usecs_to_jiffies(VSYNC_TIMEOUT_US));
|
|
}
|
|
|
|
rc = mdss_iommu_ctrl(1);
|
|
if (IS_ERR_VALUE(rc)) {
|
|
pr_err("IOMMU attach failed\n");
|
|
return rc;
|
|
}
|
|
|
|
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON);
|
|
|
|
mdss_mdp_irq_enable(MDSS_MDP_IRQ_INTF_UNDER_RUN, ctl->intf_num);
|
|
sctl = mdss_mdp_get_split_ctl(ctl);
|
|
if (sctl)
|
|
mdss_mdp_irq_enable(MDSS_MDP_IRQ_INTF_UNDER_RUN,
|
|
sctl->intf_num);
|
|
|
|
mdss_bus_bandwidth_ctrl(true);
|
|
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 1);
|
|
wmb();
|
|
|
|
rc = wait_for_completion_timeout(&ctx->vsync_comp,
|
|
usecs_to_jiffies(VSYNC_TIMEOUT_US));
|
|
WARN(rc == 0, "timeout (%d) enabling timegen on ctl=%d\n",
|
|
rc, ctl->num);
|
|
|
|
ctx->timegen_en = true;
|
|
rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_ON, NULL,
|
|
CTL_INTF_EVENT_FLAG_DEFAULT);
|
|
WARN(rc, "intf %d panel on error (%d)\n", ctl->intf_num, rc);
|
|
mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_POST_PANEL_ON, NULL,
|
|
CTL_INTF_EVENT_FLAG_DEFAULT);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mdss_mdp_video_reconfigure_splash_done(struct mdss_mdp_ctl *ctl,
|
|
bool handoff)
|
|
{
|
|
struct mdss_panel_data *pdata;
|
|
int i, ret = 0, off;
|
|
u32 data, flush;
|
|
struct mdss_mdp_video_ctx *ctx;
|
|
struct mdss_mdp_ctl *sctl = mdss_mdp_get_split_ctl(ctl);
|
|
|
|
off = 0;
|
|
ctx = (struct mdss_mdp_video_ctx *) ctl->intf_ctx[MASTER_CTX];
|
|
if (!ctx) {
|
|
pr_err("invalid ctx for ctl=%d\n", ctl->num);
|
|
return -ENODEV;
|
|
}
|
|
|
|
pdata = ctl->panel_data;
|
|
|
|
pdata->panel_info.cont_splash_enabled = 0;
|
|
if (sctl)
|
|
sctl->panel_data->panel_info.cont_splash_enabled = 0;
|
|
else if (ctl->panel_data->next && is_pingpong_split(ctl->mfd))
|
|
ctl->panel_data->next->panel_info.cont_splash_enabled = 0;
|
|
|
|
if (!handoff) {
|
|
ret = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_CONT_SPLASH_BEGIN,
|
|
NULL, CTL_INTF_EVENT_FLAG_DEFAULT);
|
|
if (ret) {
|
|
pr_err("%s: Failed to handle 'CONT_SPLASH_BEGIN' event\n"
|
|
, __func__);
|
|
return ret;
|
|
}
|
|
|
|
/* clear up mixer0 and mixer1 */
|
|
flush = 0;
|
|
for (i = 0; i < 2; i++) {
|
|
data = mdss_mdp_ctl_read(ctl,
|
|
MDSS_MDP_REG_CTL_LAYER(i));
|
|
if (data) {
|
|
mdss_mdp_ctl_write(ctl,
|
|
MDSS_MDP_REG_CTL_LAYER(i),
|
|
MDSS_MDP_LM_BORDER_COLOR);
|
|
flush |= (0x40 << i);
|
|
}
|
|
}
|
|
mdss_mdp_ctl_write(ctl, MDSS_MDP_REG_CTL_FLUSH, flush);
|
|
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 0);
|
|
/* wait for 1 VSYNC for the pipe to be unstaged */
|
|
msleep(20);
|
|
|
|
ret = mdss_mdp_ctl_intf_event(ctl,
|
|
MDSS_EVENT_CONT_SPLASH_FINISH, NULL,
|
|
CTL_INTF_EVENT_FLAG_DEFAULT);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void mdss_mdp_disable_prefill(struct mdss_mdp_ctl *ctl)
|
|
{
|
|
struct mdss_panel_info *pinfo = &ctl->panel_data->panel_info;
|
|
struct mdss_data_type *mdata = ctl->mdata;
|
|
|
|
if ((pinfo->prg_fet + pinfo->lcdc.v_back_porch +
|
|
pinfo->lcdc.v_pulse_width) > mdata->min_prefill_lines) {
|
|
ctl->disable_prefill = true;
|
|
pr_debug("disable prefill vbp:%d vpw:%d prg_fet:%d\n",
|
|
pinfo->lcdc.v_back_porch, pinfo->lcdc.v_pulse_width,
|
|
pinfo->prg_fet);
|
|
}
|
|
}
|
|
|
|
static void mdss_mdp_fetch_end_config(struct mdss_mdp_video_ctx *ctx,
|
|
struct mdss_mdp_ctl *ctl)
|
|
{
|
|
int fetch_stop, h_total;
|
|
struct mdss_panel_info *pinfo = &ctl->panel_data->panel_info;
|
|
u32 lines_before_active = ctl->mdata->lines_before_active ? : 2;
|
|
u32 vblank_lines = pinfo->lcdc.v_back_porch + pinfo->lcdc.v_pulse_width;
|
|
u32 vblank_end_enable;
|
|
|
|
if (vblank_lines <= lines_before_active) {
|
|
pr_debug("cannot support fetch end vblank:%d lines:%d\n",
|
|
vblank_lines, lines_before_active);
|
|
return;
|
|
}
|
|
|
|
/* Fetch should always be stopped before the active start */
|
|
h_total = mdss_panel_get_htotal(pinfo, true);
|
|
fetch_stop = (vblank_lines - lines_before_active) * h_total;
|
|
|
|
vblank_end_enable = mdp_video_read(ctx, MDSS_MDP_REG_INTF_CONFIG);
|
|
vblank_end_enable |= BIT(22);
|
|
|
|
pr_debug("ctl:%d fetch_stop:%d lines:%d\n",
|
|
ctl->num, fetch_stop, lines_before_active);
|
|
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_VBLANK_END_CONF, fetch_stop);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_CONFIG, vblank_end_enable);
|
|
MDSS_XLOG(ctx->intf_num, fetch_stop, vblank_end_enable);
|
|
}
|
|
|
|
static void mdss_mdp_fetch_start_config(struct mdss_mdp_video_ctx *ctx,
|
|
struct mdss_mdp_ctl *ctl)
|
|
{
|
|
int fetch_start, fetch_enable, v_total, h_total;
|
|
struct mdss_data_type *mdata;
|
|
struct mdss_panel_info *pinfo = &ctl->panel_data->panel_info;
|
|
|
|
mdata = ctl->mdata;
|
|
|
|
pinfo->prg_fet = mdss_mdp_get_prefetch_lines(pinfo);
|
|
if (!pinfo->prg_fet) {
|
|
pr_debug("programmable fetch is not needed/supported\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Fetch should always be outside the active lines. If the fetching
|
|
* is programmed within active region, hardware behavior is unknown.
|
|
*/
|
|
v_total = mdss_panel_get_vtotal(pinfo);
|
|
h_total = mdss_panel_get_htotal(pinfo, true);
|
|
|
|
fetch_start = (v_total - pinfo->prg_fet) * h_total + 1;
|
|
fetch_enable = BIT(31);
|
|
|
|
if (pinfo->dynamic_fps && (pinfo->dfps_update ==
|
|
DFPS_IMMEDIATE_CLK_UPDATE_MODE))
|
|
fetch_enable |= BIT(23);
|
|
|
|
pr_debug("ctl:%d fetch_start:%d lines:%d\n",
|
|
ctl->num, fetch_start, pinfo->prg_fet);
|
|
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_PROG_FETCH_START, fetch_start);
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_CONFIG, fetch_enable);
|
|
MDSS_XLOG(ctx->intf_num, fetch_enable, fetch_start);
|
|
}
|
|
|
|
static inline bool mdss_mdp_video_need_pixel_drop(u32 vic)
|
|
{
|
|
return vic == HDMI_VFRMT_4096x2160p50_256_135 ||
|
|
vic == HDMI_VFRMT_4096x2160p60_256_135;
|
|
}
|
|
|
|
static int mdss_mdp_video_cdm_setup(struct mdss_mdp_cdm *cdm,
|
|
struct mdss_panel_info *pinfo)
|
|
{
|
|
struct mdss_mdp_format_params *fmt;
|
|
struct mdp_cdm_cfg setup;
|
|
|
|
fmt = mdss_mdp_get_format_params(pinfo->out_format);
|
|
|
|
if (!fmt) {
|
|
pr_err("%s: format %d not supported\n", __func__,
|
|
pinfo->out_format);
|
|
return -EINVAL;
|
|
}
|
|
setup.out_format = pinfo->out_format;
|
|
if (fmt->is_yuv)
|
|
setup.csc_type = MDSS_MDP_CSC_RGB2YUV_601L;
|
|
else
|
|
setup.csc_type = MDSS_MDP_CSC_RGB2RGB;
|
|
|
|
switch (fmt->chroma_sample) {
|
|
case MDSS_MDP_CHROMA_RGB:
|
|
setup.horz_downsampling_type = MDP_CDM_CDWN_DISABLE;
|
|
setup.vert_downsampling_type = MDP_CDM_CDWN_DISABLE;
|
|
break;
|
|
case MDSS_MDP_CHROMA_H2V1:
|
|
setup.horz_downsampling_type = MDP_CDM_CDWN_COSITE;
|
|
setup.vert_downsampling_type = MDP_CDM_CDWN_DISABLE;
|
|
break;
|
|
case MDSS_MDP_CHROMA_420:
|
|
if (mdss_mdp_video_need_pixel_drop(pinfo->vic)) {
|
|
setup.horz_downsampling_type = MDP_CDM_CDWN_PIXEL_DROP;
|
|
setup.vert_downsampling_type = MDP_CDM_CDWN_PIXEL_DROP;
|
|
} else {
|
|
setup.horz_downsampling_type = MDP_CDM_CDWN_COSITE;
|
|
setup.vert_downsampling_type = MDP_CDM_CDWN_OFFSITE;
|
|
}
|
|
break;
|
|
case MDSS_MDP_CHROMA_H1V2:
|
|
default:
|
|
pr_err("%s: unsupported chroma sampling type\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
setup.mdp_csc_bit_depth = MDP_CDM_CSC_8BIT;
|
|
setup.output_width = pinfo->xres + pinfo->lcdc.xres_pad;
|
|
setup.output_height = pinfo->yres + pinfo->lcdc.yres_pad;
|
|
return mdss_mdp_cdm_setup(cdm, &setup);
|
|
}
|
|
|
|
static void mdss_mdp_handoff_programmable_fetch(struct mdss_mdp_ctl *ctl,
|
|
struct mdss_mdp_video_ctx *ctx)
|
|
{
|
|
struct mdss_panel_info *pinfo = &ctl->panel_data->panel_info;
|
|
u32 fetch_start_handoff, v_total_handoff, h_total_handoff;
|
|
pinfo->prg_fet = 0;
|
|
if (mdp_video_read(ctx, MDSS_MDP_REG_INTF_CONFIG) & BIT(31)) {
|
|
fetch_start_handoff = mdp_video_read(ctx,
|
|
MDSS_MDP_REG_INTF_PROG_FETCH_START);
|
|
h_total_handoff = mdp_video_read(ctx,
|
|
MDSS_MDP_REG_INTF_HSYNC_CTL) >> 16;
|
|
v_total_handoff = mdp_video_read(ctx,
|
|
MDSS_MDP_REG_INTF_VSYNC_PERIOD_F0)/h_total_handoff;
|
|
pinfo->prg_fet = v_total_handoff -
|
|
((fetch_start_handoff - 1)/h_total_handoff);
|
|
pr_debug("programmable fetch lines %d start:%d\n",
|
|
pinfo->prg_fet, fetch_start_handoff);
|
|
MDSS_XLOG(pinfo->prg_fet, fetch_start_handoff,
|
|
h_total_handoff, v_total_handoff);
|
|
}
|
|
}
|
|
|
|
static int mdss_mdp_video_ctx_setup(struct mdss_mdp_ctl *ctl,
|
|
struct mdss_mdp_video_ctx *ctx, struct mdss_panel_info *pinfo)
|
|
{
|
|
struct intf_timing_params itp = {0};
|
|
u32 dst_bpp;
|
|
struct mdss_data_type *mdata = ctl->mdata;
|
|
struct dsc_desc *dsc = NULL;
|
|
|
|
ctx->intf_type = ctl->intf_type;
|
|
init_completion(&ctx->vsync_comp);
|
|
spin_lock_init(&ctx->vsync_lock);
|
|
spin_lock_init(&ctx->dfps_lock);
|
|
mutex_init(&ctx->vsync_mtx);
|
|
atomic_set(&ctx->vsync_ref, 0);
|
|
INIT_WORK(&ctl->recover_work, recover_underrun_work);
|
|
|
|
if (ctl->intf_type == MDSS_INTF_DSI) {
|
|
ctx->intf_recovery.fxn = mdss_mdp_video_intf_recovery;
|
|
ctx->intf_recovery.data = ctl;
|
|
if (mdss_mdp_ctl_intf_event(ctl,
|
|
MDSS_EVENT_REGISTER_RECOVERY_HANDLER,
|
|
(void *)&ctx->intf_recovery,
|
|
CTL_INTF_EVENT_FLAG_DEFAULT)) {
|
|
pr_err("Failed to register intf recovery handler\n");
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
ctx->intf_recovery.fxn = NULL;
|
|
ctx->intf_recovery.data = NULL;
|
|
}
|
|
|
|
if (mdss_mdp_is_cdm_supported(mdata, ctl->intf_type, 0)) {
|
|
ctl->cdm = mdss_mdp_cdm_init(ctl, MDP_CDM_CDWN_OUTPUT_HDMI);
|
|
if (!IS_ERR_OR_NULL(ctl->cdm)) {
|
|
if (mdss_mdp_video_cdm_setup(ctl->cdm, pinfo)) {
|
|
pr_err("%s: setting up cdm failed\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
ctl->flush_bits |= BIT(26);
|
|
} else {
|
|
pr_err("%s: failed to initialize cdm\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
pr_debug("%s: cdm not supported\n", __func__);
|
|
}
|
|
|
|
if (pinfo->compression_mode == COMPRESSION_DSC)
|
|
dsc = &pinfo->dsc;
|
|
|
|
mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_VSYNC,
|
|
ctx->intf_num, mdss_mdp_video_vsync_intr_done,
|
|
ctl);
|
|
mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_UNDER_RUN,
|
|
ctx->intf_num,
|
|
mdss_mdp_video_underrun_intr_done, ctl);
|
|
|
|
dst_bpp = pinfo->fbc.enabled ? (pinfo->fbc.target_bpp) : (pinfo->bpp);
|
|
|
|
itp.width = mult_frac((pinfo->xres + pinfo->lcdc.border_left +
|
|
pinfo->lcdc.border_right), dst_bpp, pinfo->bpp);
|
|
itp.height = pinfo->yres + pinfo->lcdc.border_top +
|
|
pinfo->lcdc.border_bottom;
|
|
itp.border_clr = pinfo->lcdc.border_clr;
|
|
itp.underflow_clr = pinfo->lcdc.underflow_clr;
|
|
itp.hsync_skew = pinfo->lcdc.hsync_skew;
|
|
|
|
/* tg active area is not work, hence yres should equal to height */
|
|
itp.xres = mult_frac((pinfo->xres + pinfo->lcdc.border_left +
|
|
pinfo->lcdc.border_right), dst_bpp, pinfo->bpp);
|
|
|
|
itp.yres = pinfo->yres + pinfo->lcdc.border_top +
|
|
pinfo->lcdc.border_bottom;
|
|
|
|
if (dsc) { /* compressed */
|
|
itp.width = dsc->pclk_per_line;
|
|
itp.xres = dsc->pclk_per_line;
|
|
}
|
|
|
|
itp.h_back_porch = pinfo->lcdc.h_back_porch;
|
|
itp.h_front_porch = pinfo->lcdc.h_front_porch;
|
|
itp.v_back_porch = pinfo->lcdc.v_back_porch;
|
|
itp.v_front_porch = pinfo->lcdc.v_front_porch;
|
|
itp.hsync_pulse_width = pinfo->lcdc.h_pulse_width;
|
|
itp.vsync_pulse_width = pinfo->lcdc.v_pulse_width;
|
|
/*
|
|
* In case of YUV420 output, MDP outputs data at half the rate. So
|
|
* reduce all horizontal parameters by half
|
|
*/
|
|
if (ctl->cdm && pinfo->out_format == MDP_Y_CBCR_H2V2) {
|
|
itp.width >>= 1;
|
|
itp.hsync_skew >>= 1;
|
|
itp.xres >>= 1;
|
|
itp.h_back_porch >>= 1;
|
|
itp.h_front_porch >>= 1;
|
|
itp.hsync_pulse_width >>= 1;
|
|
}
|
|
if (!ctl->panel_data->panel_info.cont_splash_enabled) {
|
|
if (mdss_mdp_video_timegen_setup(ctl, &itp, ctx)) {
|
|
pr_err("unable to set timing parameters intfs: %d\n",
|
|
ctx->intf_num);
|
|
return -EINVAL;
|
|
}
|
|
mdss_mdp_fetch_start_config(ctx, ctl);
|
|
|
|
if (test_bit(MDSS_QOS_VBLANK_PANIC_CTRL, mdata->mdss_qos_map))
|
|
mdss_mdp_fetch_end_config(ctx, ctl);
|
|
|
|
} else {
|
|
mdss_mdp_handoff_programmable_fetch(ctl, ctx);
|
|
}
|
|
|
|
mdss_mdp_disable_prefill(ctl);
|
|
|
|
mdp_video_write(ctx, MDSS_MDP_REG_INTF_PANEL_FORMAT, ctl->dst_format);
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int mdss_mdp_video_intfs_setup(struct mdss_mdp_ctl *ctl,
|
|
struct mdss_panel_data *pdata, int inum)
|
|
{
|
|
struct mdss_data_type *mdata;
|
|
struct mdss_panel_info *pinfo;
|
|
struct mdss_mdp_video_ctx *ctx;
|
|
int ret = 0;
|
|
|
|
if (pdata == NULL)
|
|
return 0;
|
|
|
|
mdata = ctl->mdata;
|
|
pinfo = &pdata->panel_info;
|
|
|
|
if (inum < mdata->nintf) {
|
|
ctx = ((struct mdss_mdp_video_ctx *) mdata->video_intf) + inum;
|
|
if (ctx->ref_cnt) {
|
|
pr_err("Intf %d already in use\n",
|
|
(inum + MDSS_MDP_INTF0));
|
|
return -EBUSY;
|
|
}
|
|
pr_debug("video Intf #%d base=%p", ctx->intf_num, ctx->base);
|
|
ctx->ref_cnt++;
|
|
} else {
|
|
pr_err("Invalid intf number: %d\n", (inum + MDSS_MDP_INTF0));
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctl->intf_ctx[MASTER_CTX] = ctx;
|
|
ret = mdss_mdp_video_ctx_setup(ctl, ctx, pinfo);
|
|
if (ret) {
|
|
pr_err("Video context setup failed for interface: %d\n",
|
|
ctx->intf_num);
|
|
ctx->ref_cnt--;
|
|
return -EPERM;
|
|
}
|
|
|
|
if (is_pingpong_split(ctl->mfd)) {
|
|
if ((inum + 1) >= mdata->nintf) {
|
|
pr_err("Intf not available for ping pong split: (%d)\n",
|
|
(inum + 1 + MDSS_MDP_INTF0));
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx = ((struct mdss_mdp_video_ctx *) mdata->video_intf) +
|
|
inum + 1;
|
|
if (ctx->ref_cnt) {
|
|
pr_err("Intf %d already in use\n",
|
|
(inum + MDSS_MDP_INTF0));
|
|
return -EBUSY;
|
|
}
|
|
pr_debug("video Intf #%d base=%p", ctx->intf_num, ctx->base);
|
|
ctx->ref_cnt++;
|
|
|
|
ctl->intf_ctx[SLAVE_CTX] = ctx;
|
|
pinfo = &pdata->next->panel_info;
|
|
ret = mdss_mdp_video_ctx_setup(ctl, ctx, pinfo);
|
|
if (ret) {
|
|
pr_err("Video context setup failed for interface: %d\n",
|
|
ctx->intf_num);
|
|
ctx->ref_cnt--;
|
|
return -EPERM;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void mdss_mdp_switch_to_cmd_mode(struct mdss_mdp_ctl *ctl, int prep)
|
|
{
|
|
struct mdss_mdp_video_ctx *ctx;
|
|
long int mode = MIPI_CMD_PANEL;
|
|
u32 frame_rate = 0;
|
|
int rc;
|
|
|
|
pr_debug("start, prep = %d\n", prep);
|
|
|
|
if (!prep) {
|
|
mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_DSI_RECONFIG_CMD,
|
|
(void *) mode, CTL_INTF_EVENT_FLAG_DEFAULT);
|
|
return;
|
|
}
|
|
|
|
ctx = (struct mdss_mdp_video_ctx *) ctl->intf_ctx[MASTER_CTX];
|
|
|
|
if (!ctx->timegen_en) {
|
|
pr_err("Time engine not enabled, cannot switch from vid\n");
|
|
return;
|
|
}
|
|
|
|
/* Start off by sending command to initial cmd mode */
|
|
rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_DSI_DYNAMIC_SWITCH,
|
|
(void *) mode, CTL_INTF_EVENT_FLAG_DEFAULT);
|
|
if (rc) {
|
|
pr_err("intf #%d busy don't turn off, rc=%d\n",
|
|
ctl->intf_num, rc);
|
|
return;
|
|
}
|
|
|
|
if (ctx->wait_pending) {
|
|
/* wait for at least commit to commplete */
|
|
wait_for_completion_interruptible_timeout(&ctx->vsync_comp,
|
|
usecs_to_jiffies(VSYNC_TIMEOUT_US));
|
|
}
|
|
frame_rate = mdss_panel_get_framerate
|
|
(&(ctl->panel_data->panel_info));
|
|
if (!(frame_rate >= 24 && frame_rate <= 240))
|
|
frame_rate = 24;
|
|
frame_rate = ((1000/frame_rate) + 1);
|
|
/*
|
|
* In order for panel to switch to cmd mode, we need
|
|
* to wait for one more video frame to be sent after
|
|
* issuing the switch command. We do this before
|
|
* turning off the timeing engine.
|
|
*/
|
|
msleep(frame_rate);
|
|
mdss_mdp_turn_off_time_engine(ctl, ctx, frame_rate);
|
|
mdss_bus_bandwidth_ctrl(false);
|
|
}
|
|
|
|
static int mdss_mdp_video_early_wake_up(struct mdss_mdp_ctl *ctl)
|
|
{
|
|
u64 curr_time;
|
|
|
|
curr_time = ktime_to_us(ktime_get());
|
|
|
|
if ((curr_time - ctl->last_input_time) <
|
|
INPUT_EVENT_HANDLER_DELAY_USECS)
|
|
return 0;
|
|
ctl->last_input_time = curr_time;
|
|
|
|
/*
|
|
* If the idle timer is running when input event happens, the timeout
|
|
* will be delayed by idle_time again to ensure user space does not get
|
|
* an idle event when new frames are expected.
|
|
*
|
|
* It would be nice to have this logic in mdss_fb.c itself by
|
|
* implementing a new frame notification event. But input event handler
|
|
* is called from interrupt context and scheduling a work item adds a
|
|
* lot of latency rendering the input events useless in preventing the
|
|
* idle time out.
|
|
*/
|
|
if (ctl->mfd->idle_state == MDSS_FB_IDLE_TIMER_RUNNING) {
|
|
if (ctl->mfd->idle_time)
|
|
mod_delayed_work(system_wq, &ctl->mfd->idle_notify_work,
|
|
msecs_to_jiffies(ctl->mfd->idle_time));
|
|
pr_debug("Delayed idle time\n");
|
|
} else {
|
|
pr_debug("Nothing to done for this state (%d)\n",
|
|
ctl->mfd->idle_state);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mdss_mdp_video_start(struct mdss_mdp_ctl *ctl)
|
|
{
|
|
int intfs_num, ret = 0;
|
|
|
|
intfs_num = ctl->intf_num - MDSS_MDP_INTF0;
|
|
ret = mdss_mdp_video_intfs_setup(ctl, ctl->panel_data, intfs_num);
|
|
if (IS_ERR_VALUE(ret)) {
|
|
pr_err("unable to set video interface: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ctl->ops.stop_fnc = mdss_mdp_video_stop;
|
|
ctl->ops.display_fnc = mdss_mdp_video_display;
|
|
ctl->ops.wait_fnc = mdss_mdp_video_wait4comp;
|
|
ctl->ops.read_line_cnt_fnc = mdss_mdp_video_line_count;
|
|
ctl->ops.add_vsync_handler = mdss_mdp_video_add_vsync_handler;
|
|
ctl->ops.remove_vsync_handler = mdss_mdp_video_remove_vsync_handler;
|
|
ctl->ops.config_fps_fnc = mdss_mdp_video_config_fps;
|
|
ctl->ops.early_wake_up_fnc = mdss_mdp_video_early_wake_up;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void *mdss_mdp_get_intf_base_addr(struct mdss_data_type *mdata,
|
|
u32 interface_id)
|
|
{
|
|
struct mdss_mdp_video_ctx *ctx;
|
|
ctx = ((struct mdss_mdp_video_ctx *) mdata->video_intf) + interface_id;
|
|
return (void *)(ctx->base);
|
|
}
|