M7350/kernel/drivers/video/msm/mdss/mdss_mdp_intf_video.c

1685 lines
46 KiB
C
Raw Normal View History

2024-09-09 08:57:42 +00:00
/* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
2024-09-09 08:52:07 +00:00
*
* 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>
2024-09-09 08:57:42 +00:00
#include <linux/memblock.h>
#include <video/msm_hdmi_modes.h>
2024-09-09 08:52:07 +00:00
#include "mdss_fb.h"
#include "mdss_mdp.h"
2024-09-09 08:57:42 +00:00
#include "mdss_panel.h"
#include "mdss_debug.h"
#include "mdss_mdp_trace.h"
2024-09-09 08:52:07 +00:00
/* wait for at least 2 vsyncs for lowest refresh rate (24hz) */
#define VSYNC_TIMEOUT_US 100000
2024-09-09 08:57:42 +00:00
/* 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
2024-09-09 08:52:07 +00:00
#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;
2024-09-09 08:57:42 +00:00
spinlock_t dfps_lock;
struct mutex vsync_mtx;
2024-09-09 08:52:07 +00:00
struct list_head vsync_handlers;
2024-09-09 08:57:42 +00:00
struct mdss_intf_recovery intf_recovery;
2024-09-09 08:52:07 +00:00
};
2024-09-09 08:57:42 +00:00
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);
2024-09-09 08:52:07 +00:00
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)
{
2024-09-09 08:57:42 +00:00
struct mdss_mdp_video_ctx *ctx;
2024-09-09 08:52:07 +00:00
u32 line_cnt = 0;
2024-09-09 08:57:42 +00:00
if (!ctl || !ctl->intf_ctx[MASTER_CTX])
goto line_count_exit;
ctx = ctl->intf_ctx[MASTER_CTX];
2024-09-09 08:52:07 +00:00
line_cnt = mdp_video_read(ctx, MDSS_MDP_REG_INTF_LINE_COUNT);
2024-09-09 08:57:42 +00:00
line_count_exit:
2024-09-09 08:52:07 +00:00
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++) {
2024-09-09 08:57:42 +00:00
head[i].base = mdata->mdss_io.base + offsets[i];
2024-09-09 08:52:07 +00:00
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;
}
2024-09-09 08:57:42 +00:00
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)
2024-09-09 08:52:07 +00:00
{
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;
2024-09-09 08:57:42 +00:00
struct mdss_data_type *mdata;
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
mdata = ctl->mdata;
2024-09-09 08:52:07 +00:00
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;
2024-09-09 08:57:42 +00:00
MDSS_XLOG(p->vsync_pulse_width, p->v_back_porch,
p->height, p->v_front_porch);
2024-09-09 08:52:07 +00:00
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;
}
2024-09-09 08:57:42 +00:00
/* 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);
2024-09-09 08:52:07 +00:00
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,
2024-09-09 08:57:42 +00:00
vsync_period * hsync_period);
2024-09-09 08:52:07 +00:00
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);
2024-09-09 08:57:42 +00:00
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);
}
2024-09-09 08:52:07 +00:00
return 0;
}
static inline void video_vsync_irq_enable(struct mdss_mdp_ctl *ctl, bool clear)
{
2024-09-09 08:57:42 +00:00
struct mdss_mdp_video_ctx *ctx = ctl->intf_ctx[MASTER_CTX];
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
mutex_lock(&ctx->vsync_mtx);
2024-09-09 08:52:07 +00:00
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);
2024-09-09 08:57:42 +00:00
mutex_unlock(&ctx->vsync_mtx);
2024-09-09 08:52:07 +00:00
}
static inline void video_vsync_irq_disable(struct mdss_mdp_ctl *ctl)
{
2024-09-09 08:57:42 +00:00
struct mdss_mdp_video_ctx *ctx = ctl->intf_ctx[MASTER_CTX];
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
mutex_lock(&ctx->vsync_mtx);
2024-09-09 08:52:07 +00:00
if (atomic_dec_return(&ctx->vsync_ref) == 0)
mdss_mdp_irq_disable(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num);
2024-09-09 08:57:42 +00:00
mutex_unlock(&ctx->vsync_mtx);
2024-09-09 08:52:07 +00:00
}
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;
}
2024-09-09 08:57:42 +00:00
ctx = (struct mdss_mdp_video_ctx *) ctl->intf_ctx[MASTER_CTX];
2024-09-09 08:52:07 +00:00
if (!ctx) {
pr_err("invalid ctx for ctl=%d\n", ctl->num);
ret = -ENODEV;
goto exit;
}
2024-09-09 08:57:42 +00:00
MDSS_XLOG(ctl->num, ctl->vsync_cnt, handle->enabled);
2024-09-09 08:52:07 +00:00
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);
2024-09-09 08:57:42 +00:00
if (irq_en) {
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON);
2024-09-09 08:52:07 +00:00
video_vsync_irq_enable(ctl, false);
2024-09-09 08:57:42 +00:00
}
2024-09-09 08:52:07 +00:00
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;
2024-09-09 08:57:42 +00:00
ctx = (struct mdss_mdp_video_ctx *) ctl->intf_ctx[MASTER_CTX];
2024-09-09 08:52:07 +00:00
if (!ctx) {
pr_err("invalid ctx for ctl=%d\n", ctl->num);
return -ENODEV;
}
2024-09-09 08:57:42 +00:00
MDSS_XLOG(ctl->num, ctl->vsync_cnt, handle->enabled);
2024-09-09 08:52:07 +00:00
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);
2024-09-09 08:57:42 +00:00
if (irq_dis) {
2024-09-09 08:52:07 +00:00
video_vsync_irq_disable(ctl);
2024-09-09 08:57:42 +00:00
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
}
2024-09-09 08:52:07 +00:00
return 0;
}
2024-09-09 08:57:42 +00:00
void mdss_mdp_turn_off_time_engine(struct mdss_mdp_ctl *ctl,
struct mdss_mdp_video_ctx *ctx, u32 sleep_time)
2024-09-09 08:52:07 +00:00
{
2024-09-09 08:57:42 +00:00
struct mdss_mdp_ctl *sctl;
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
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);
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
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);
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
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);
2024-09-09 08:52:07 +00:00
if (ctx->timegen_en) {
2024-09-09 08:57:42 +00:00
rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_BLANK, NULL,
CTL_INTF_EVENT_FLAG_DEFAULT);
2024-09-09 08:52:07 +00:00
if (rc == -EBUSY) {
pr_debug("intf #%d busy don't turn off\n",
ctl->intf_num);
2024-09-09 08:57:42 +00:00
goto end;
2024-09-09 08:52:07 +00:00
}
WARN(rc, "intf %d blank error (%d)\n", ctl->intf_num, rc);
2024-09-09 08:57:42 +00:00
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);
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_OFF, NULL,
CTL_INTF_EVENT_FLAG_DEFAULT);
2024-09-09 08:52:07 +00:00
WARN(rc, "intf %d timegen off error (%d)\n", ctl->intf_num, rc);
2024-09-09 08:57:42 +00:00
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;
}
2024-09-09 08:52:07 +00:00
}
list_for_each_entry_safe(handle, tmp, &ctx->vsync_handlers, list)
mdss_mdp_video_remove_vsync_handler(ctl, handle);
2024-09-09 08:57:42 +00:00
return 0;
}
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
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;
}
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
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;
}
2024-09-09 08:52:07 +00:00
return 0;
}
static void mdss_mdp_video_vsync_intr_done(void *arg)
{
struct mdss_mdp_ctl *ctl = arg;
2024-09-09 08:57:42 +00:00
struct mdss_mdp_video_ctx *ctx = ctl->intf_ctx[MASTER_CTX];
2024-09-09 08:52:07 +00:00
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++;
2024-09-09 08:57:42 +00:00
MDSS_XLOG(ctl->num, ctl->vsync_cnt, ctl->vsync_cnt);
2024-09-09 08:52:07 +00:00
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)
{
2024-09-09 08:57:42 +00:00
struct mdss_mdp_video_ctx *ctx = ctl->intf_ctx[MASTER_CTX];
2024-09-09 08:52:07 +00:00
u32 mask, status;
int rc;
mask = MDP_INTR_MASK_INTF_VSYNC(ctl->intf_num);
2024-09-09 08:57:42 +00:00
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON);
2024-09-09 08:52:07 +00:00
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);
2024-09-09 08:57:42 +00:00
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF);
2024-09-09 08:52:07 +00:00
if (rc == 0) {
2024-09-09 08:57:42 +00:00
MDSS_XLOG(ctl->num, ctl->vsync_cnt);
2024-09-09 08:52:07 +00:00
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;
2024-09-09 08:57:42 +00:00
ctx = (struct mdss_mdp_video_ctx *) ctl->intf_ctx[MASTER_CTX];
2024-09-09 08:52:07 +00:00
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 {
2024-09-09 08:57:42 +00:00
mutex_unlock(&ctl->lock);
rc = wait_for_completion_timeout(&ctx->vsync_comp,
2024-09-09 08:52:07 +00:00
usecs_to_jiffies(VSYNC_TIMEOUT_US));
2024-09-09 08:57:42 +00:00
mutex_lock(&ctl->lock);
if (rc == 0) {
2024-09-09 08:52:07 +00:00
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;
}
}
2024-09-09 08:57:42 +00:00
mdss_mdp_ctl_notify(ctl,
rc ? MDP_NOTIFY_FRAME_TIMEOUT : MDP_NOTIFY_FRAME_DONE);
2024-09-09 08:52:07 +00:00
if (ctx->wait_pending) {
ctx->wait_pending = 0;
video_vsync_irq_disable(ctl);
}
return rc;
}
2024-09-09 08:57:42 +00:00
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);
}
2024-09-09 08:52:07 +00:00
static void mdss_mdp_video_underrun_intr_done(void *arg)
{
struct mdss_mdp_ctl *ctl = arg;
if (unlikely(!ctl))
return;
ctl->underrun_cnt++;
2024-09-09 08:57:42 +00:00
MDSS_XLOG(ctl->num, ctl->underrun_cnt);
trace_mdp_video_underrun_done(ctl->num, ctl->underrun_cnt);
2024-09-09 08:52:07 +00:00
pr_debug("display underrun detected for ctl=%d count=%d\n", ctl->num,
ctl->underrun_cnt);
2024-09-09 08:57:42 +00:00
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;
2024-09-09 08:52:07 +00:00
}
static int mdss_mdp_video_display(struct mdss_mdp_ctl *ctl, void *arg)
{
struct mdss_mdp_video_ctx *ctx;
2024-09-09 08:57:42 +00:00
struct mdss_mdp_ctl *sctl;
struct mdss_panel_data *pdata = ctl->panel_data;
2024-09-09 08:52:07 +00:00
int rc;
pr_debug("kickoff ctl=%d\n", ctl->num);
2024-09-09 08:57:42 +00:00
ctx = (struct mdss_mdp_video_ctx *) ctl->intf_ctx[MASTER_CTX];
2024-09-09 08:52:07 +00:00
if (!ctx) {
pr_err("invalid ctx\n");
return -ENODEV;
}
if (!ctx->wait_pending) {
ctx->wait_pending++;
video_vsync_irq_enable(ctl, true);
2024-09-09 08:57:42 +00:00
reinit_completion(&ctx->vsync_comp);
2024-09-09 08:52:07 +00:00
} else {
WARN(1, "commit without wait! ctl=%d", ctl->num);
}
2024-09-09 08:57:42 +00:00
MDSS_XLOG(ctl->num, ctl->underrun_cnt);
2024-09-09 08:52:07 +00:00
if (!ctx->timegen_en) {
2024-09-09 08:57:42 +00:00
rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_LINK_READY, NULL,
CTL_INTF_EVENT_FLAG_DEFAULT);
2024-09-09 08:52:07 +00:00
if (rc) {
2024-09-09 08:57:42 +00:00
pr_warn("intf #%d link ready error (%d)\n",
2024-09-09 08:52:07 +00:00
ctl->intf_num, rc);
video_vsync_irq_disable(ctl);
ctx->wait_pending = 0;
return rc;
}
2024-09-09 08:57:42 +00:00
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);
2024-09-09 08:52:07 +00:00
pr_debug("enabling timing gen for intf=%d\n", ctl->intf_num);
2024-09-09 08:57:42 +00:00
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);
2024-09-09 08:52:07 +00:00
mdss_mdp_irq_enable(MDSS_MDP_IRQ_INTF_UNDER_RUN, ctl->intf_num);
2024-09-09 08:57:42 +00:00
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);
2024-09-09 08:52:07 +00:00
mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 1);
wmb();
2024-09-09 08:57:42 +00:00
rc = wait_for_completion_timeout(&ctx->vsync_comp,
2024-09-09 08:52:07 +00:00
usecs_to_jiffies(VSYNC_TIMEOUT_US));
2024-09-09 08:57:42 +00:00
WARN(rc == 0, "timeout (%d) enabling timegen on ctl=%d\n",
2024-09-09 08:52:07 +00:00
rc, ctl->num);
ctx->timegen_en = true;
2024-09-09 08:57:42 +00:00
rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_ON, NULL,
CTL_INTF_EVENT_FLAG_DEFAULT);
2024-09-09 08:52:07 +00:00
WARN(rc, "intf %d panel on error (%d)\n", ctl->intf_num, rc);
2024-09-09 08:57:42 +00:00
mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_POST_PANEL_ON, NULL,
CTL_INTF_EVENT_FLAG_DEFAULT);
2024-09-09 08:52:07 +00:00
}
return 0;
}
2024-09-09 08:57:42 +00:00
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;
2024-09-09 08:52:07 +00:00
}
2024-09-09 08:57:42 +00:00
pdata = ctl->panel_data;
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
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;
}
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
/* 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);
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 0);
/* wait for 1 VSYNC for the pipe to be unstaged */
msleep(20);
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
ret = mdss_mdp_ctl_intf_event(ctl,
MDSS_EVENT_CONT_SPLASH_FINISH, NULL,
CTL_INTF_EVENT_FLAG_DEFAULT);
}
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
return ret;
}
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
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);
}
2024-09-09 08:52:07 +00:00
}
2024-09-09 08:57:42 +00:00
static void mdss_mdp_fetch_end_config(struct mdss_mdp_video_ctx *ctx,
struct mdss_mdp_ctl *ctl)
2024-09-09 08:52:07 +00:00
{
2024-09-09 08:57:42 +00:00
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;
}
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
/* 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;
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
vblank_end_enable = mdp_video_read(ctx, MDSS_MDP_REG_INTF_CONFIG);
vblank_end_enable |= BIT(22);
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
pr_debug("ctl:%d fetch_stop:%d lines:%d\n",
ctl->num, fetch_stop, lines_before_active);
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
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;
2024-09-09 08:52:07 +00:00
}
2024-09-09 08:57:42 +00:00
/*
* 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);
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
fetch_start = (v_total - pinfo->prg_fet) * h_total + 1;
fetch_enable = BIT(31);
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
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);
2024-09-09 08:52:07 +00:00
}
2024-09-09 08:57:42 +00:00
static inline bool mdss_mdp_video_need_pixel_drop(u32 vic)
2024-09-09 08:52:07 +00:00
{
2024-09-09 08:57:42 +00:00
return vic == HDMI_VFRMT_4096x2160p50_256_135 ||
vic == HDMI_VFRMT_4096x2160p60_256_135;
}
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
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;
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
fmt = mdss_mdp_get_format_params(pinfo->out_format);
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
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;
2024-09-09 08:52:07 +00:00
}
2024-09-09 08:57:42 +00:00
break;
case MDSS_MDP_CHROMA_H1V2:
default:
pr_err("%s: unsupported chroma sampling type\n", __func__);
2024-09-09 08:52:07 +00:00
return -EINVAL;
}
2024-09-09 08:57:42 +00:00
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;
2024-09-09 08:52:07 +00:00
ctx->intf_type = ctl->intf_type;
init_completion(&ctx->vsync_comp);
spin_lock_init(&ctx->vsync_lock);
2024-09-09 08:57:42 +00:00
spin_lock_init(&ctx->dfps_lock);
mutex_init(&ctx->vsync_mtx);
2024-09-09 08:52:07 +00:00
atomic_set(&ctx->vsync_ref, 0);
2024-09-09 08:57:42 +00:00
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__);
}
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
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);
2024-09-09 08:52:07 +00:00
dst_bpp = pinfo->fbc.enabled ? (pinfo->fbc.target_bpp) : (pinfo->bpp);
2024-09-09 08:57:42 +00:00
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;
2024-09-09 08:52:07 +00:00
itp.border_clr = pinfo->lcdc.border_clr;
itp.underflow_clr = pinfo->lcdc.underflow_clr;
itp.hsync_skew = pinfo->lcdc.hsync_skew;
2024-09-09 08:57:42 +00:00
/* 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;
2024-09-09 08:52:07 +00:00
itp.vsync_pulse_width = pinfo->lcdc.v_pulse_width;
2024-09-09 08:57:42 +00:00
/*
* 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);
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
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);
2024-09-09 08:52:07 +00:00
}
2024-09-09 08:57:42 +00:00
mdss_mdp_disable_prefill(ctl);
2024-09-09 08:52:07 +00:00
mdp_video_write(ctx, MDSS_MDP_REG_INTF_PANEL_FORMAT, ctl->dst_format);
2024-09-09 08:57:42 +00:00
return 0;
}
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
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;
}
2024-09-09 08:52:07 +00:00
2024-09-09 08:57:42 +00:00
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;
}
}
2024-09-09 08:52:07 +00:00
return 0;
}
2024-09-09 08:57:42 +00:00
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);
}