android_kernel_samsung_msm8226/drivers/video/msm/mdss/mdss_mdp_intf_video.c

1042 lines
28 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 "mdss_fb.h"
#include "mdss_mdp.h"
#include "mdss_panel.h"
#include "mdss_debug.h"
#include "mdss_mdp_trace.h"
#include "mdss_dsi.h"
#include "mdss_edp.h"
int count_wait_for_timeout = 0;
int get_lcd_attached(void);
/* wait for at least 2 vsyncs for lowest refresh rate (24hz) */
#define VSYNC_TIMEOUT_US 100000
#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;
struct completion pp_comp;
int wait_pending;
atomic_t vsync_ref;
spinlock_t vsync_lock;
struct mutex vsync_mtx;
struct list_head vsync_handlers;
};
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->priv_data)
goto line_count_exit;
ctx = ctl->priv_data;
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
line_cnt = mdp_video_read(ctx, MDSS_MDP_REG_INTF_LINE_COUNT);
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
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->mdp_base + offsets[i];
pr_debug("adding Video Intf #%d offset=0x%x virt=%pK\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 int mdss_mdp_video_timegen_setup(struct mdss_mdp_ctl *ctl,
struct intf_timing_params *p)
{
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_mdp_video_ctx *ctx;
ctx = ctl->priv_data;
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;
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;
}
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);
return 0;
}
static inline void video_vsync_irq_enable(struct mdss_mdp_ctl *ctl, bool clear)
{
struct mdss_mdp_video_ctx *ctx = ctl->priv_data;
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->priv_data;
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->priv_data;
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)
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->priv_data;
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);
return 0;
}
static int mdss_mdp_video_stop(struct mdss_mdp_ctl *ctl)
{
struct mdss_mdp_video_ctx *ctx;
struct mdss_mdp_vsync_handler *tmp, *handle;
struct mdss_mdp_ctl *sctl;
int rc;
u32 frame_rate = 0;
pr_debug("stop ctl=%d\n", ctl->num);
ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data;
if (!ctx) {
pr_err("invalid ctx for ctl=%d\n", ctl->num);
return -ENODEV;
}
MDSS_XLOG(ctl->num, ctl->vsync_cnt);
if (ctx->timegen_en) {
rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_BLANK, NULL);
if (rc == -EBUSY) {
pr_debug("intf #%d busy don't turn off\n",
ctl->intf_num);
return rc;
}
WARN(rc, "intf %d blank error (%d)\n", ctl->intf_num, rc);
mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 0);
/* wait for at least one VSYNC on HDMI intf for proper TG OFF */
if (MDSS_INTF_HDMI == ctx->intf_type) {
frame_rate = mdss_panel_get_framerate
(&(ctl->panel_data->panel_info));
if (!(frame_rate >= 24 && frame_rate <= 240))
frame_rate = 24;
msleep((1000/frame_rate) + 1);
}
mdss_iommu_ctrl(0);
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
ctx->timegen_en = false;
rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_OFF, NULL);
WARN(rc, "intf %d timegen off error (%d)\n", ctl->intf_num, rc);
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);
mdss_bus_bandwidth_ctrl(false);
}
list_for_each_entry_safe(handle, tmp, &ctx->vsync_handlers, list)
mdss_mdp_video_remove_vsync_handler(ctl, handle);
mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num,
NULL, NULL);
mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_UNDER_RUN, ctl->intf_num,
NULL, NULL);
mdss_mdp_ctl_reset(ctl);
ctx->ref_cnt--;
ctl->priv_data = 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->priv_data;
struct mdss_mdp_vsync_handler *tmp;
ktime_t vsync_time;
if (!ctx) {
pr_err("invalid ctx\n");
return;
}
vsync_time = ktime_get();
ctl->vsync_cnt++;
MDSS_XLOG(ctl->num, 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->priv_data;
u32 mask, status;
int rc;
mask = MDP_INTR_MASK_INTF_VSYNC(ctl->intf_num);
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
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, false);
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->priv_data;
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=true;
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->add_vsync_handler) {
pr_err("ctl or vsync handler is NULL\n");
return;
}
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON, false);
ctl->add_vsync_handler(ctl, &ctl->recover_underrun_handler);
mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF, false);
}
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);
MDSS_XLOG_TOUT_HANDLER("mdp", "dsi0", "dsi1", "edp", "hdmi", "panic");
trace_mdp_video_underrun_done(ctl->num, ctl->underrun_cnt);
pr_info("display underrun detected for ctl=%d count=%d\n", ctl->num,
ctl->underrun_cnt);
if (ctl->opmode & MDSS_MDP_CTL_OP_PACK_3D_ENABLE)
schedule_work(&ctl->recover_work);
mdss_mdp_underrun_dump_info(ctl->mfd);
}
static int mdss_mdp_video_vfp_fps_update(struct mdss_mdp_ctl *ctl, int new_fps)
{
int curr_fps;
u32 add_v_lines = 0;
u32 current_vsync_period_f0, new_vsync_period_f0;
struct mdss_panel_data *pdata;
struct mdss_mdp_video_ctx *ctx;
u32 vsync_period, hsync_period;
ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data;
if (!ctx) {
pr_err("invalid ctx\n");
return -ENODEV;
}
pdata = ctl->panel_data;
if (pdata == NULL) {
pr_err("%s: Invalid panel data\n", __func__);
return -EINVAL;
}
vsync_period = mdss_panel_get_vtotal(&pdata->panel_info);
hsync_period = mdss_panel_get_htotal(&pdata->panel_info);
curr_fps = mdss_panel_get_framerate(&pdata->panel_info);
if (curr_fps > new_fps) {
add_v_lines = mult_frac(vsync_period,
(curr_fps - new_fps), new_fps);
pdata->panel_info.lcdc.v_front_porch += add_v_lines;
} else {
add_v_lines = mult_frac(vsync_period,
(new_fps - curr_fps), new_fps);
pdata->panel_info.lcdc.v_front_porch -= add_v_lines;
}
vsync_period = mdss_panel_get_vtotal(&pdata->panel_info);
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);
}
return 0;
}
static int mdss_mdp_video_config_fps(struct mdss_mdp_ctl *ctl,
struct mdss_mdp_ctl *sctl, int new_fps)
{
struct mdss_mdp_video_ctx *ctx;
struct mdss_panel_data *pdata;
int rc = 0;
u32 hsync_period, vsync_period;
pr_debug("Updating fps for ctl=%d\n", ctl->num);
ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data;
if (!ctx) {
pr_err("invalid ctx\n");
return -ENODEV;
}
pdata = ctl->panel_data;
if (pdata == NULL) {
pr_err("%s: Invalid panel data\n", __func__);
return -EINVAL;
}
if (!pdata->panel_info.dynamic_fps) {
pr_err("%s: Dynamic fps not enabled for this panel\n",
__func__);
return -EINVAL;
}
vsync_period = mdss_panel_get_vtotal(&pdata->panel_info);
hsync_period = mdss_panel_get_htotal(&pdata->panel_info);
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");
return -EINVAL;
}
ctl->force_screen_state = MDSS_SCREEN_FORCE_BLANK;
mdss_mdp_display_commit(ctl, NULL);
mdss_mdp_display_wait4comp(ctl);
mdp_video_write(ctx,
MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 0);
/*
* Need to wait for atleast one vsync time for proper
* TG OFF before doing changes on interfaces
*/
msleep(20);
rc = mdss_mdp_ctl_intf_event(ctl,
MDSS_EVENT_PANEL_UPDATE_FPS,
(void *)new_fps);
WARN(rc, "intf %d panel fps update error (%d)\n",
ctl->intf_num, rc);
mdp_video_write(ctx,
MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 1);
/*
* Add memory barrier to make sure the MDP Video
* mode engine is enabled before next frame is sent
*/
mb();
ctl->force_screen_state = MDSS_SCREEN_DEFAULT;
mdss_mdp_display_commit(ctl, NULL);
mdss_mdp_display_wait4comp(ctl);
} else if (pdata->panel_info.dfps_update
== DFPS_IMMEDIATE_PORCH_UPDATE_MODE){
if (!ctx->timegen_en) {
pr_err("TG is OFF. DFPS mode invalid\n");
return -EINVAL;
}
video_vsync_irq_enable(ctl, true);
INIT_COMPLETION(ctx->vsync_comp);
rc = wait_for_completion_timeout(&ctx->vsync_comp,
usecs_to_jiffies(VSYNC_TIMEOUT_US));
WARN(rc <= 0, "timeout (%d) vsync interrupt on ctl=%d\n",
rc, ctl->num);
video_vsync_irq_disable(ctl);
/* Do not configure fps on vsync timeout */
if (rc <= 0)
return rc;
if (mdss_mdp_video_line_count(ctl) >=
pdata->panel_info.yres/2) {
pr_err("Too few lines left line_cnt = %d y_res/2 = %d\n",
mdss_mdp_video_line_count(ctl),
pdata->panel_info.yres/2);
return -EPERM;
}
rc = mdss_mdp_video_vfp_fps_update(ctl, new_fps);
if (rc < 0) {
pr_err("%s: Error during DFPS\n", __func__);
return rc;
}
if (sctl) {
rc = mdss_mdp_video_vfp_fps_update(sctl,
new_fps);
if (rc < 0) {
pr_err("%s: DFPS error\n", __func__);
return rc;
}
}
rc = mdss_mdp_ctl_intf_event(ctl,
MDSS_EVENT_PANEL_UPDATE_FPS,
(void *)new_fps);
WARN(rc, "intf %d panel fps update error (%d)\n",
ctl->intf_num, rc);
} else {
pr_err("intf %d panel, unknown FPS mode\n",
ctl->intf_num);
return -EINVAL;
}
} else {
rc = mdss_mdp_ctl_intf_event(ctl,
MDSS_EVENT_PANEL_UPDATE_FPS,
(void *)new_fps);
WARN(rc, "intf %d panel fps update error (%d)\n",
ctl->intf_num, rc);
}
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;
#if defined(CONFIG_FB_MSM_MDSS_S6E8AA0A_HD_PANEL)
int rc;
#else
int rc = 0;
#endif
pr_debug("kickoff ctl=%d\n", ctl->num);
ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data;
if (!ctx) {
pr_err("invalid ctx\n");
return -ENODEV;
}
#if !defined(CONFIG_FB_MSM8x26_MDSS_CHECK_LCD_CONNECTION) && \
!defined(CONFIG_FB_MSM_MIPI_MAGNA_OCTA_VIDEO_WXGA_PT_DUAL_PANEL)
if (get_lcd_attached() == 0) {
pr_err("%s : lcd is not attached..\n",__func__);
return -ENODEV;
}
#endif
if (!ctx->wait_pending) {
ctx->wait_pending++;
video_vsync_irq_enable(ctl, true);
INIT_COMPLETION(ctx->vsync_comp);
} else {
WARN(1, "commit without wait! ctl=%d", ctl->num);
}
#if !defined(CONFIG_FB_MSM_MDSS_S6E8AA0A_HD_PANEL)
if(pdata->panel_info.cont_splash_enabled)
return rc;
#endif
MDSS_XLOG(ctl->num, ctl->underrun_cnt);
if (!ctx->timegen_en) {
rc = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_UNBLANK, NULL);
if (rc) {
pr_warn("intf #%d unblank error (%d)\n",
ctl->intf_num, rc);
video_vsync_irq_disable(ctl);
ctx->wait_pending = 0;
return 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)
|| (ctl->mfd->splash_info.splash_logo_enabled
&& ctl->mfd->splash_info.splash_thread
&& !is_mdss_iommu_attached())) {
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, false);
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);
WARN(rc, "intf %d panel on error (%d)\n", ctl->intf_num, rc);
#if defined(CONFIG_FB_MSM_EDP_SAMSUNG)
set_backlight_first_kick_off();
#endif
}
return 0;
}
int mdss_mdp_video_copy_splash_screen(struct mdss_panel_data *pdata)
{
void *virt = NULL;
unsigned long bl_fb_addr = 0;
unsigned long *bl_fb_addr_va;
unsigned long pipe_addr, pipe_src_size;
u32 height, width, rgb_size, bpp;
size_t size;
static struct ion_handle *ihdl;
struct ion_client *iclient = mdss_get_ionclient();
static ion_phys_addr_t phys;
pipe_addr = MDSS_MDP_REG_SSPP_OFFSET(3) +
MDSS_MDP_REG_SSPP_SRC0_ADDR;
pipe_src_size =
MDSS_MDP_REG_SSPP_OFFSET(3) + MDSS_MDP_REG_SSPP_SRC_SIZE;
bpp = 3;
rgb_size = MDSS_MDP_REG_READ(pipe_src_size);
bl_fb_addr = MDSS_MDP_REG_READ(pipe_addr);
height = (rgb_size >> 16) & 0xffff;
width = rgb_size & 0xffff;
size = PAGE_ALIGN(height * width * bpp);
pr_debug("%s:%d splash_height=%d splash_width=%d Buffer size=%d\n",
__func__, __LINE__, height, width, size);
ihdl = ion_alloc(iclient, size, SZ_1M,
ION_HEAP(ION_QSECOM_HEAP_ID), 0);
if (IS_ERR_OR_NULL(ihdl)) {
pr_err("unable to alloc fbmem from ion (%pK)\n", ihdl);
return -ENOMEM;
}
pdata->panel_info.splash_ihdl = ihdl;
virt = ion_map_kernel(iclient, ihdl);
ion_phys(iclient, ihdl, &phys, &size);
pr_debug("%s %d Allocating %u bytes at 0x%lx (%pa phys)\n",
__func__, __LINE__, size,
(unsigned long int)virt, &phys);
bl_fb_addr_va = (unsigned long *)ioremap(bl_fb_addr, size);
memcpy(virt, bl_fb_addr_va, size);
iounmap(bl_fb_addr_va);
MDSS_MDP_REG_WRITE(pipe_addr, phys);
MDSS_MDP_REG_WRITE(MDSS_MDP_REG_CTL_FLUSH + MDSS_MDP_REG_CTL_OFFSET(0),
0x48);
return 0;
}
int mdss_mdp_video_reconfigure_splash_done(struct mdss_mdp_ctl *ctl,
bool handoff)
{
struct mdss_panel_data *pdata = ctl->panel_data;
int i, ret = 0;
struct mdss_mdp_video_ctx *ctx;
struct mdss_data_type *mdata = ctl->mdata;
#if !defined(CONFIG_FB_MSM_MIPI_SAMSUNG_OCTA_VIDEO_FULL_HD_PT_PANEL) && !defined(CONFIG_FB_MSM_MDSS_S6E8AA0A_HD_PANEL)
ret = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_FIRST_FRAME_UPDATE, NULL);
#endif
i = ctl->intf_num - MDSS_MDP_INTF0;
if (i < mdata->nintf) {
ctx = ((struct mdss_mdp_video_ctx *) mdata->video_intf) + i;
pr_debug("video Intf #%d base=%pK", ctx->intf_num, ctx->base);
} else {
pr_err("Invalid intf number: %d\n", ctl->intf_num);
ret = -EINVAL;
goto error;
}
#if defined(CONFIG_FB_MSM_MDSS_S6E8AA0A_HD_PANEL)
ret = mdss_mdp_ctl_intf_event(ctl, MTP_READ,NULL);
#endif
pr_err("[QC] handoff : %d\n", handoff);
if (!handoff) {
ret = mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_CONT_SPLASH_BEGIN,
NULL);
if (ret) {
pr_err("%s: Failed to handle 'CONT_SPLASH_BEGIN' event\n"
, __func__);
return ret;
}
mdss_mdp_ctl_write(ctl, 0, MDSS_MDP_LM_BORDER_COLOR);
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);
}
#if !defined(CONFIG_FB_MSM_EDP_SAMSUNG)
else
{
mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_CONT_SPLASH_BEGIN, NULL);
mdp_video_write(ctx, MDSS_MDP_REG_INTF_TIMING_ENGINE_EN, 0);
ctx->timegen_en = false;
/* Panel off command causes white screen flash at contsplash end so removing for LVDS panels */
#if !defined(CONFIG_FB_MSM_MDSS_TC_DSI2LVDS_WXGA_PANEL)
mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_OFF, NULL);
#endif
mdss_mdp_ctl_intf_event(ctl,MDSS_EVENT_CONT_SPLASH_FINISH, NULL);
mdss_mdp_ctl_intf_event(ctl,MDSS_EVENT_UNBLANK, NULL);
}
#endif
error:
pdata->panel_info.cont_splash_enabled = 0;
return ret;
}
#if !defined(CONFIG_FB_MSM_MDSS_S6E8AA0A_HD_PANEL)
static void mdss_mdp_video_pingpong_done(void *arg)
{
struct mdss_mdp_ctl *ctl = arg;
struct mdss_mdp_video_ctx *ctx;
ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data;
pr_info("%s:mdss_mdp_isr 2222\n", __func__);
if (!ctx) {
pr_err("invalid ctx\n");
return;
}
mdss_mdp_irq_disable_nosync(MDSS_MDP_IRQ_PING_PONG_COMP, ctl->num);
complete_all(&ctx->pp_comp);
}
static int mdss_mdp_video_wait4pingpong(struct mdss_mdp_ctl *ctl, void *arg)
{
struct mdss_mdp_video_ctx *ctx;
int rc = 0;
ctx = (struct mdss_mdp_video_ctx *) ctl->priv_data;
pr_info("%s:mdss_mdp_isr 1111\n", __func__);
if (!ctx) {
pr_err("invalid ctx\n");
return -ENODEV;
}
INIT_COMPLETION(ctx->pp_comp);
rc = wait_for_completion_timeout(
&ctx->pp_comp, msecs_to_jiffies(20));
return rc;
}
#endif
int mdss_mdp_video_start(struct mdss_mdp_ctl *ctl)
{
struct mdss_data_type *mdata;
struct mdss_panel_info *pinfo;
struct mdss_mdp_video_ctx *ctx;
struct mdss_mdp_mixer *mixer;
struct intf_timing_params itp = {0};
u32 dst_bpp;
int i;
mdata = ctl->mdata;
pinfo = &ctl->panel_data->panel_info;
mixer = mdss_mdp_mixer_get(ctl, MDSS_MDP_MIXER_MUX_LEFT);
if (!mixer) {
pr_err("mixer not setup correctly\n");
return -ENODEV;
}
#if defined (CONFIG_FB_MSM_MIPI_SAMSUNG_TFT_VIDEO_WQXGA_PT_PANEL)
if (get_lcd_attached() == 0) {
pr_err("%s : lcd is not attached..\n",__func__);
return 0;
}
#endif
i = ctl->intf_num - MDSS_MDP_INTF0;
if (i < mdata->nintf) {
ctx = ((struct mdss_mdp_video_ctx *) mdata->video_intf) + i;
if (ctx->ref_cnt) {
pr_err("Intf %d already in use\n", ctl->intf_num);
return -EBUSY;
}
pr_debug("video Intf #%d base=%pK", ctx->intf_num, ctx->base);
ctx->ref_cnt++;
} else {
pr_err("Invalid intf number: %d\n", ctl->intf_num);
return -EINVAL;
}
MDSS_XLOG(ctl->num, ctl->vsync_cnt);
pr_debug("start ctl=%u\n", ctl->num);
ctl->priv_data = ctx;
ctx->intf_type = ctl->intf_type;
init_completion(&ctx->vsync_comp);
init_completion(&ctx->pp_comp);
spin_lock_init(&ctx->vsync_lock);
mutex_init(&ctx->vsync_mtx);
atomic_set(&ctx->vsync_ref, 0);
INIT_WORK(&ctl->recover_work, recover_underrun_work);
mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_VSYNC, ctl->intf_num,
mdss_mdp_video_vsync_intr_done, ctl);
mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_UNDER_RUN, ctl->intf_num,
mdss_mdp_video_underrun_intr_done, ctl);
#if !defined(CONFIG_FB_MSM_MDSS_S6E8AA0A_HD_PANEL)
mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_PING_PONG_COMP,
mixer->num, mdss_mdp_video_pingpong_done, ctl);
#endif
dst_bpp = pinfo->fbc.enabled ? (pinfo->fbc.target_bpp) : (pinfo->bpp);
itp.width = mult_frac((pinfo->xres + pinfo->lcdc.xres_pad),
dst_bpp, pinfo->bpp);
itp.height = pinfo->yres + pinfo->lcdc.yres_pad;
itp.border_clr = pinfo->lcdc.border_clr;
itp.underflow_clr = pinfo->lcdc.underflow_clr;
itp.hsync_skew = pinfo->lcdc.hsync_skew;
itp.xres = mult_frac(pinfo->xres, dst_bpp, pinfo->bpp);
itp.yres = pinfo->yres;
itp.h_back_porch = mult_frac(pinfo->lcdc.h_back_porch, dst_bpp,
pinfo->bpp);
itp.h_front_porch = mult_frac(pinfo->lcdc.h_front_porch, dst_bpp,
pinfo->bpp);
itp.v_back_porch = mult_frac(pinfo->lcdc.v_back_porch, dst_bpp,
pinfo->bpp);
itp.v_front_porch = mult_frac(pinfo->lcdc.v_front_porch, dst_bpp,
pinfo->bpp);
itp.hsync_pulse_width = mult_frac(pinfo->lcdc.h_pulse_width, dst_bpp,
pinfo->bpp);
itp.vsync_pulse_width = pinfo->lcdc.v_pulse_width;
if (mdss_mdp_video_timegen_setup(ctl, &itp)) {
pr_err("unable to get timing parameters\n");
return -EINVAL;
}
mdp_video_write(ctx, MDSS_MDP_REG_INTF_PANEL_FORMAT, ctl->dst_format);
ctl->stop_fnc = mdss_mdp_video_stop;
ctl->display_fnc = mdss_mdp_video_display;
ctl->wait_fnc = mdss_mdp_video_wait4comp;
ctl->read_line_cnt_fnc = mdss_mdp_video_line_count;
ctl->add_vsync_handler = mdss_mdp_video_add_vsync_handler;
ctl->remove_vsync_handler = mdss_mdp_video_remove_vsync_handler;
ctl->config_fps_fnc = mdss_mdp_video_config_fps;
#if !defined(CONFIG_FB_MSM_MDSS_S6E8AA0A_HD_PANEL)
ctl->wait_video_pingpong = mdss_mdp_video_wait4pingpong;
#endif
return 0;
}