#include <linux/iopoll.h>
#include <drm/drm_print.h>
#include <drm/drm_vblank.h>
#include "intel_color.h"
#include "intel_crtc.h"
#include "intel_de.h"
#include "intel_display_jiffies.h"
#include "intel_display_regs.h"
#include "intel_display_types.h"
#include "intel_display_utils.h"
#include "intel_vblank.h"
#include "intel_vrr.h"
u32 i915_get_vblank_counter(struct drm_crtc *crtc)
{
struct intel_display *display = to_intel_display(crtc->dev);
struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
const struct drm_display_mode *mode = &vblank->hwmode;
enum pipe pipe = to_intel_crtc(crtc)->pipe;
u32 pixel, vbl_start, hsync_start, htotal;
u64 frame;
if (!vblank->max_vblank_count)
return 0;
htotal = mode->crtc_htotal;
hsync_start = mode->crtc_hsync_start;
vbl_start = intel_mode_vblank_start(mode);
vbl_start *= htotal;
vbl_start -= htotal - hsync_start;
frame = intel_de_read64_2x32(display, PIPEFRAMEPIXEL(display, pipe),
PIPEFRAME(display, pipe));
pixel = frame & PIPE_PIXEL_MASK;
frame = (frame >> PIPE_FRAME_LOW_SHIFT) & 0xffffff;
return (frame + (pixel >= vbl_start)) & 0xffffff;
}
u32 g4x_get_vblank_counter(struct drm_crtc *crtc)
{
struct intel_display *display = to_intel_display(crtc->dev);
struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
enum pipe pipe = to_intel_crtc(crtc)->pipe;
if (!vblank->max_vblank_count)
return 0;
return intel_de_read(display, PIPE_FRMCOUNT_G4X(display, pipe));
}
static u32 intel_crtc_scanlines_since_frame_timestamp(struct intel_crtc *crtc)
{
struct intel_display *display = to_intel_display(crtc);
struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(&crtc->base);
const struct drm_display_mode *mode = &vblank->hwmode;
u32 htotal = mode->crtc_htotal;
u32 clock = mode->crtc_clock;
u32 scan_prev_time, scan_curr_time, scan_post_time;
do {
scan_prev_time = intel_de_read_fw(display,
PIPE_FRMTMSTMP(crtc->pipe));
scan_curr_time = intel_de_read_fw(display, IVB_TIMESTAMP_CTR);
scan_post_time = intel_de_read_fw(display,
PIPE_FRMTMSTMP(crtc->pipe));
} while (scan_post_time != scan_prev_time);
return div_u64(mul_u32_u32(scan_curr_time - scan_prev_time,
clock), 1000 * htotal);
}
static u32 __intel_get_crtc_scanline_from_timestamp(struct intel_crtc *crtc)
{
struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(&crtc->base);
const struct drm_display_mode *mode = &vblank->hwmode;
u32 vblank_start = mode->crtc_vblank_start;
u32 vtotal = mode->crtc_vtotal;
u32 scanline;
scanline = intel_crtc_scanlines_since_frame_timestamp(crtc);
scanline = min(scanline, vtotal - 1);
scanline = (scanline + vblank_start) % vtotal;
return scanline;
}
int intel_crtc_scanline_offset(const struct intel_crtc_state *crtc_state)
{
struct intel_display *display = to_intel_display(crtc_state);
if (DISPLAY_VER(display) >= 20 || display->platform.battlemage)
return 1;
else if (DISPLAY_VER(display) >= 9 ||
display->platform.broadwell || display->platform.haswell)
return intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI) ? 2 : 1;
else if (DISPLAY_VER(display) >= 3)
return 1;
else
return -1;
}
static int __intel_get_crtc_scanline(struct intel_crtc *crtc)
{
struct intel_display *display = to_intel_display(crtc);
struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(&crtc->base);
const struct drm_display_mode *mode = &vblank->hwmode;
enum pipe pipe = crtc->pipe;
int position, vtotal;
if (!crtc->active)
return 0;
if (crtc->mode_flags & I915_MODE_FLAG_GET_SCANLINE_FROM_TIMESTAMP)
return __intel_get_crtc_scanline_from_timestamp(crtc);
vtotal = intel_mode_vtotal(mode);
position = intel_de_read_fw(display, PIPEDSL(display, pipe)) & PIPEDSL_LINE_MASK;
if (HAS_DDI(display) && !position) {
int i, temp;
for (i = 0; i < 100; i++) {
udelay(1);
temp = intel_de_read_fw(display,
PIPEDSL(display, pipe)) & PIPEDSL_LINE_MASK;
if (temp != position) {
position = temp;
break;
}
}
}
return (position + vtotal + crtc->scanline_offset) % vtotal;
}
#ifdef I915
static void intel_vblank_section_enter(struct intel_display *display)
__acquires(uncore->lock)
{
struct intel_uncore *uncore = to_intel_uncore(display->drm);
spin_lock(&uncore->lock);
}
static void intel_vblank_section_exit(struct intel_display *display)
__releases(uncore->lock)
{
struct intel_uncore *uncore = to_intel_uncore(display->drm);
spin_unlock(&uncore->lock);
}
#else
static void intel_vblank_section_enter(struct intel_display *display)
{
}
static void intel_vblank_section_exit(struct intel_display *display)
{
}
#endif
static bool i915_get_crtc_scanoutpos(struct drm_crtc *_crtc,
bool in_vblank_irq,
int *vpos, int *hpos,
ktime_t *stime, ktime_t *etime,
const struct drm_display_mode *mode)
{
struct intel_display *display = to_intel_display(_crtc->dev);
struct intel_crtc *crtc = to_intel_crtc(_crtc);
enum pipe pipe = crtc->pipe;
int position;
int vbl_start, vbl_end, hsync_start, htotal, vtotal;
unsigned long irqflags;
bool use_scanline_counter = DISPLAY_VER(display) >= 5 ||
display->platform.g4x || DISPLAY_VER(display) == 2 ||
crtc->mode_flags & I915_MODE_FLAG_USE_SCANLINE_COUNTER;
if (drm_WARN_ON(display->drm, !mode->crtc_clock)) {
drm_dbg(display->drm,
"trying to get scanoutpos for disabled pipe %c\n",
pipe_name(pipe));
return false;
}
htotal = mode->crtc_htotal;
hsync_start = mode->crtc_hsync_start;
vtotal = intel_mode_vtotal(mode);
vbl_start = intel_mode_vblank_start(mode);
vbl_end = intel_mode_vblank_end(mode);
local_irq_save(irqflags);
intel_vblank_section_enter(display);
if (stime)
*stime = ktime_get();
if (crtc->mode_flags & I915_MODE_FLAG_VRR) {
int scanlines = intel_crtc_scanlines_since_frame_timestamp(crtc);
position = __intel_get_crtc_scanline(crtc);
if (position >= vbl_start && scanlines < position)
position = min(crtc->vmax_vblank_start + scanlines, vtotal - 1);
} else if (use_scanline_counter) {
position = __intel_get_crtc_scanline(crtc);
} else {
position = (intel_de_read_fw(display, PIPEFRAMEPIXEL(display, pipe)) & PIPE_PIXEL_MASK) >> PIPE_PIXEL_SHIFT;
vbl_start *= htotal;
vbl_end *= htotal;
vtotal *= htotal;
position = min(position, vtotal - 1);
position = (position + htotal - hsync_start) % vtotal;
}
if (etime)
*etime = ktime_get();
intel_vblank_section_exit(display);
local_irq_restore(irqflags);
if (position >= vbl_start)
position -= vbl_end;
else
position += vtotal - vbl_end;
if (use_scanline_counter) {
*vpos = position;
*hpos = 0;
} else {
*vpos = position / htotal;
*hpos = position - (*vpos * htotal);
}
return true;
}
bool intel_crtc_get_vblank_timestamp(struct drm_crtc *crtc, int *max_error,
ktime_t *vblank_time, bool in_vblank_irq)
{
return drm_crtc_vblank_helper_get_vblank_timestamp_internal(
crtc, max_error, vblank_time, in_vblank_irq,
i915_get_crtc_scanoutpos);
}
int intel_get_crtc_scanline(struct intel_crtc *crtc)
{
struct intel_display *display = to_intel_display(crtc);
unsigned long irqflags;
int position;
local_irq_save(irqflags);
intel_vblank_section_enter(display);
position = __intel_get_crtc_scanline(crtc);
intel_vblank_section_exit(display);
local_irq_restore(irqflags);
return position;
}
static bool pipe_scanline_is_moving(struct intel_display *display,
enum pipe pipe)
{
i915_reg_t reg = PIPEDSL(display, pipe);
u32 line1, line2;
line1 = intel_de_read(display, reg) & PIPEDSL_LINE_MASK;
msleep(5);
line2 = intel_de_read(display, reg) & PIPEDSL_LINE_MASK;
return line1 != line2;
}
static void wait_for_pipe_scanline_moving(struct intel_crtc *crtc, bool state)
{
struct intel_display *display = to_intel_display(crtc);
enum pipe pipe = crtc->pipe;
bool is_moving;
int ret;
ret = poll_timeout_us(is_moving = pipe_scanline_is_moving(display, pipe),
is_moving == state,
500, 100 * 1000, false);
if (ret)
drm_err(display->drm,
"pipe %c scanline %s wait timed out\n",
pipe_name(pipe), str_on_off(state));
}
void intel_wait_for_pipe_scanline_stopped(struct intel_crtc *crtc)
{
wait_for_pipe_scanline_moving(crtc, false);
}
void intel_wait_for_pipe_scanline_moving(struct intel_crtc *crtc)
{
wait_for_pipe_scanline_moving(crtc, true);
}
static void intel_crtc_active_timings(struct drm_display_mode *mode,
int *vmax_vblank_start,
const struct intel_crtc_state *crtc_state,
bool vrr_enable)
{
drm_mode_init(mode, &crtc_state->hw.adjusted_mode);
*vmax_vblank_start = 0;
if (!vrr_enable)
return;
mode->crtc_vtotal = intel_vrr_vmax_vtotal(crtc_state);
mode->crtc_vblank_end = intel_vrr_vmax_vtotal(crtc_state);
mode->crtc_vblank_start = intel_vrr_vmin_vblank_start(crtc_state);
*vmax_vblank_start = intel_vrr_vmax_vblank_start(crtc_state);
}
void intel_crtc_update_active_timings(const struct intel_crtc_state *crtc_state,
bool vrr_enable)
{
struct intel_display *display = to_intel_display(crtc_state);
struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc);
u8 mode_flags = crtc_state->mode_flags;
struct drm_display_mode adjusted_mode;
int vmax_vblank_start = 0;
unsigned long irqflags;
intel_crtc_active_timings(&adjusted_mode, &vmax_vblank_start,
crtc_state, vrr_enable);
if (vrr_enable)
drm_WARN_ON(display->drm, (mode_flags & I915_MODE_FLAG_VRR) == 0);
else
mode_flags &= ~I915_MODE_FLAG_VRR;
spin_lock_irqsave(&display->drm->vblank_time_lock, irqflags);
intel_vblank_section_enter(display);
drm_calc_timestamping_constants(&crtc->base, &adjusted_mode);
crtc->vmax_vblank_start = vmax_vblank_start;
crtc->mode_flags = mode_flags;
crtc->scanline_offset = intel_crtc_scanline_offset(crtc_state);
intel_vblank_section_exit(display);
spin_unlock_irqrestore(&display->drm->vblank_time_lock, irqflags);
}
int intel_mode_vdisplay(const struct drm_display_mode *mode)
{
int vdisplay = mode->crtc_vdisplay;
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
vdisplay = DIV_ROUND_UP(vdisplay, 2);
return vdisplay;
}
int intel_mode_vblank_start(const struct drm_display_mode *mode)
{
int vblank_start = mode->crtc_vblank_start;
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
vblank_start = DIV_ROUND_UP(vblank_start, 2);
return vblank_start;
}
int intel_mode_vblank_end(const struct drm_display_mode *mode)
{
int vblank_end = mode->crtc_vblank_end;
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
vblank_end /= 2;
return vblank_end;
}
int intel_mode_vtotal(const struct drm_display_mode *mode)
{
int vtotal = mode->crtc_vtotal;
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
vtotal /= 2;
return vtotal;
}
int intel_mode_vblank_delay(const struct drm_display_mode *mode)
{
return intel_mode_vblank_start(mode) - intel_mode_vdisplay(mode);
}
static const struct intel_crtc_state *
pre_commit_crtc_state(const struct intel_crtc_state *old_crtc_state,
const struct intel_crtc_state *new_crtc_state)
{
if (intel_crtc_needs_modeset(new_crtc_state))
return new_crtc_state;
else
return old_crtc_state;
}
const struct intel_crtc_state *
intel_pre_commit_crtc_state(struct intel_atomic_state *state,
struct intel_crtc *crtc)
{
const struct intel_crtc_state *old_crtc_state =
intel_atomic_get_old_crtc_state(state, crtc);
const struct intel_crtc_state *new_crtc_state =
intel_atomic_get_new_crtc_state(state, crtc);
return pre_commit_crtc_state(old_crtc_state, new_crtc_state);
}
static int vrr_vblank_start(const struct intel_crtc_state *crtc_state)
{
bool is_push_sent = intel_vrr_is_push_sent(crtc_state);
int vblank_start;
if (!crtc_state->vrr.dc_balance.enable) {
if (is_push_sent)
return intel_vrr_vmin_vblank_start(crtc_state);
else
return intel_vrr_vmax_vblank_start(crtc_state);
}
if (is_push_sent)
vblank_start = intel_vrr_dcb_vmin_vblank_start_next(crtc_state);
else
vblank_start = intel_vrr_dcb_vmax_vblank_start_next(crtc_state);
if (vblank_start >= 0)
return vblank_start;
if (is_push_sent)
vblank_start = intel_vrr_dcb_vmin_vblank_start_final(crtc_state);
else
vblank_start = intel_vrr_dcb_vmax_vblank_start_final(crtc_state);
return vblank_start;
}
void intel_vblank_evade_init(const struct intel_crtc_state *old_crtc_state,
const struct intel_crtc_state *new_crtc_state,
struct intel_vblank_evade_ctx *evade)
{
struct intel_display *display = to_intel_display(new_crtc_state);
struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->uapi.crtc);
const struct intel_crtc_state *crtc_state;
const struct drm_display_mode *adjusted_mode;
int vblank_delay;
evade->crtc = crtc;
evade->need_vlv_dsi_wa = (display->platform.valleyview ||
display->platform.cherryview) &&
intel_crtc_has_type(new_crtc_state, INTEL_OUTPUT_DSI);
crtc_state = pre_commit_crtc_state(old_crtc_state, new_crtc_state);
adjusted_mode = &crtc_state->hw.adjusted_mode;
if (crtc->mode_flags & I915_MODE_FLAG_VRR) {
drm_WARN_ON(crtc->base.dev, intel_crtc_needs_modeset(new_crtc_state) ||
new_crtc_state->update_m_n || new_crtc_state->update_lrr);
evade->vblank_start = vrr_vblank_start(crtc_state);
vblank_delay = crtc_state->set_context_latency;
} else {
evade->vblank_start = intel_mode_vblank_start(adjusted_mode);
vblank_delay = intel_mode_vblank_delay(adjusted_mode);
}
evade->min = evade->vblank_start - intel_usecs_to_scanlines(adjusted_mode,
VBLANK_EVASION_TIME_US);
evade->max = evade->vblank_start - 1;
if (intel_color_uses_dsb(new_crtc_state) ||
new_crtc_state->update_m_n || new_crtc_state->update_lrr)
evade->min -= vblank_delay;
}
int intel_vblank_evade(struct intel_vblank_evade_ctx *evade)
{
struct intel_crtc *crtc = evade->crtc;
struct intel_display *display = to_intel_display(crtc);
long timeout = msecs_to_jiffies_timeout(1);
wait_queue_head_t *wq = drm_crtc_vblank_waitqueue(&crtc->base);
DEFINE_WAIT(wait);
int scanline;
if (evade->min <= 0 || evade->max <= 0)
return 0;
for (;;) {
prepare_to_wait(wq, &wait, TASK_UNINTERRUPTIBLE);
scanline = intel_get_crtc_scanline(crtc);
if (scanline < evade->min || scanline > evade->max)
break;
if (!timeout) {
drm_dbg_kms(display->drm,
"Potential atomic update failure on pipe %c\n",
pipe_name(crtc->pipe));
break;
}
local_irq_enable();
timeout = schedule_timeout(timeout);
local_irq_disable();
}
finish_wait(wq, &wait);
while (evade->need_vlv_dsi_wa && scanline == evade->vblank_start)
scanline = intel_get_crtc_scanline(crtc);
return scanline;
}
int intel_crtc_vblank_length(const struct intel_crtc_state *crtc_state)
{
const struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode;
if (crtc_state->vrr.enable)
return crtc_state->vrr.guardband;
else
return adjusted_mode->crtc_vtotal - adjusted_mode->crtc_vblank_start;
}