mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

Add a function for dumping the entries of a specific flip queue. Reviewed-by: Uma Shankar <uma.shankar@intel.com> Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20250624170049.27284-9-ville.syrjala@linux.intel.com
472 lines
14 KiB
C
472 lines
14 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright © 2025 Intel Corporation
|
|
*/
|
|
|
|
#include <linux/pci.h>
|
|
|
|
#include <drm/drm_print.h>
|
|
|
|
#include "i915_utils.h"
|
|
#include "intel_step.h"
|
|
#include "intel_crtc.h"
|
|
#include "intel_de.h"
|
|
#include "intel_display_core.h"
|
|
#include "intel_display_types.h"
|
|
#include "intel_flipq.h"
|
|
#include "intel_dmc.h"
|
|
#include "intel_dmc_regs.h"
|
|
#include "intel_dsb.h"
|
|
#include "intel_vblank.h"
|
|
#include "intel_vrr.h"
|
|
|
|
/**
|
|
* DOC: DMC Flip Queue
|
|
*
|
|
* A flip queue is a ring buffer implemented by the pipe DMC firmware.
|
|
* The driver inserts entries into the queues to be executed by the
|
|
* pipe DMC at a specified presentation timestamp (PTS).
|
|
*
|
|
* Each pipe DMC provides several queues:
|
|
*
|
|
* - 1 general queue (two DSB buffers executed per entry)
|
|
* - 3 plane queues (one DSB buffer executed per entry)
|
|
* - 1 fast queue (deprecated)
|
|
*/
|
|
|
|
#define for_each_flipq(flipq_id) \
|
|
for ((flipq_id) = INTEL_FLIPQ_PLANE_1; (flipq_id) < MAX_INTEL_FLIPQ; (flipq_id)++)
|
|
|
|
static int intel_flipq_offset(enum intel_flipq_id flipq_id)
|
|
{
|
|
switch (flipq_id) {
|
|
case INTEL_FLIPQ_PLANE_1:
|
|
return 0x008;
|
|
case INTEL_FLIPQ_PLANE_2:
|
|
return 0x108;
|
|
case INTEL_FLIPQ_PLANE_3:
|
|
return 0x208;
|
|
case INTEL_FLIPQ_GENERAL:
|
|
return 0x308;
|
|
case INTEL_FLIPQ_FAST:
|
|
return 0x3c8;
|
|
default:
|
|
MISSING_CASE(flipq_id);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int intel_flipq_size_dw(enum intel_flipq_id flipq_id)
|
|
{
|
|
switch (flipq_id) {
|
|
case INTEL_FLIPQ_PLANE_1:
|
|
case INTEL_FLIPQ_PLANE_2:
|
|
case INTEL_FLIPQ_PLANE_3:
|
|
return 64;
|
|
case INTEL_FLIPQ_GENERAL:
|
|
case INTEL_FLIPQ_FAST:
|
|
return 48;
|
|
default:
|
|
MISSING_CASE(flipq_id);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int intel_flipq_elem_size_dw(enum intel_flipq_id flipq_id)
|
|
{
|
|
switch (flipq_id) {
|
|
case INTEL_FLIPQ_PLANE_1:
|
|
case INTEL_FLIPQ_PLANE_2:
|
|
case INTEL_FLIPQ_PLANE_3:
|
|
return 4;
|
|
case INTEL_FLIPQ_GENERAL:
|
|
case INTEL_FLIPQ_FAST:
|
|
return 6;
|
|
default:
|
|
MISSING_CASE(flipq_id);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int intel_flipq_size_entries(enum intel_flipq_id flipq_id)
|
|
{
|
|
return intel_flipq_size_dw(flipq_id) / intel_flipq_elem_size_dw(flipq_id);
|
|
}
|
|
|
|
static void intel_flipq_crtc_init(struct intel_crtc *crtc)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc);
|
|
enum intel_flipq_id flipq_id;
|
|
|
|
for_each_flipq(flipq_id) {
|
|
struct intel_flipq *flipq = &crtc->flipq[flipq_id];
|
|
|
|
flipq->start_mmioaddr = intel_pipedmc_start_mmioaddr(crtc) + intel_flipq_offset(flipq_id);
|
|
flipq->flipq_id = flipq_id;
|
|
|
|
drm_dbg_kms(display->drm, "[CRTC:%d:%s] FQ %d: start 0x%x\n",
|
|
crtc->base.base.id, crtc->base.name,
|
|
flipq_id, flipq->start_mmioaddr);
|
|
}
|
|
}
|
|
|
|
bool intel_flipq_supported(struct intel_display *display)
|
|
{
|
|
if (!display->params.enable_flipq)
|
|
return false;
|
|
|
|
if (!display->dmc.dmc)
|
|
return false;
|
|
|
|
if (DISPLAY_VER(display) == 20)
|
|
return true;
|
|
|
|
/* DMC firmware expects VRR timing generator to be used */
|
|
return DISPLAY_VER(display) >= 30 && intel_vrr_always_use_vrr_tg(display);
|
|
}
|
|
|
|
void intel_flipq_init(struct intel_display *display)
|
|
{
|
|
struct intel_crtc *crtc;
|
|
|
|
intel_dmc_wait_fw_load(display);
|
|
|
|
for_each_intel_crtc(display->drm, crtc)
|
|
intel_flipq_crtc_init(crtc);
|
|
}
|
|
|
|
static int cdclk_factor(struct intel_display *display)
|
|
{
|
|
if (DISPLAY_VER(display) >= 30)
|
|
return 120;
|
|
else
|
|
return 280;
|
|
}
|
|
|
|
int intel_flipq_exec_time_us(struct intel_display *display)
|
|
{
|
|
return intel_dsb_exec_time_us() +
|
|
DIV_ROUND_UP(display->cdclk.hw.cdclk * cdclk_factor(display), 540000) +
|
|
display->sagv.block_time_us;
|
|
}
|
|
|
|
static int intel_flipq_preempt_timeout_ms(struct intel_display *display)
|
|
{
|
|
return DIV_ROUND_UP(intel_flipq_exec_time_us(display), 1000);
|
|
}
|
|
|
|
static void intel_flipq_preempt(struct intel_crtc *crtc, bool preempt)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc);
|
|
|
|
intel_de_rmw(display, PIPEDMC_FQ_CTRL(crtc->pipe),
|
|
PIPEDMC_FQ_CTRL_PREEMPT, preempt ? PIPEDMC_FQ_CTRL_PREEMPT : 0);
|
|
|
|
if (preempt &&
|
|
intel_de_wait_for_clear(display,
|
|
PIPEDMC_FQ_STATUS(crtc->pipe),
|
|
PIPEDMC_FQ_STATUS_BUSY,
|
|
intel_flipq_preempt_timeout_ms(display)))
|
|
drm_err(display->drm, "[CRTC:%d:%s] flip queue preempt timeout\n",
|
|
crtc->base.base.id, crtc->base.name);
|
|
}
|
|
|
|
static int intel_flipq_current_head(struct intel_crtc *crtc, enum intel_flipq_id flipq_id)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc);
|
|
|
|
return intel_de_read(display, PIPEDMC_FPQ_CHP(crtc->pipe, flipq_id));
|
|
}
|
|
|
|
static void intel_flipq_write_tail(struct intel_crtc *crtc)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc);
|
|
|
|
intel_de_write(display, PIPEDMC_FPQ_ATOMIC_TP(crtc->pipe),
|
|
PIPEDMC_FPQ_PLANEQ_3_TP(crtc->flipq[INTEL_FLIPQ_PLANE_3].tail) |
|
|
PIPEDMC_FPQ_PLANEQ_2_TP(crtc->flipq[INTEL_FLIPQ_PLANE_2].tail) |
|
|
PIPEDMC_FPQ_PLANEQ_1_TP(crtc->flipq[INTEL_FLIPQ_PLANE_1].tail) |
|
|
PIPEDMC_FPQ_FASTQ_TP(crtc->flipq[INTEL_FLIPQ_FAST].tail) |
|
|
PIPEDMC_FPQ_GENERALQ_TP(crtc->flipq[INTEL_FLIPQ_GENERAL].tail));
|
|
}
|
|
|
|
static void intel_flipq_sw_dmc_wake(struct intel_crtc *crtc)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc);
|
|
|
|
intel_de_write(display, PIPEDMC_FPQ_CTL1(crtc->pipe), PIPEDMC_SW_DMC_WAKE);
|
|
}
|
|
|
|
static int intel_flipq_exec_time_lines(const struct intel_crtc_state *crtc_state)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc_state);
|
|
|
|
return intel_usecs_to_scanlines(&crtc_state->hw.adjusted_mode,
|
|
intel_flipq_exec_time_us(display));
|
|
}
|
|
|
|
void intel_flipq_dump(struct intel_crtc *crtc,
|
|
enum intel_flipq_id flipq_id)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc);
|
|
struct intel_flipq *flipq = &crtc->flipq[flipq_id];
|
|
u32 tmp;
|
|
|
|
drm_dbg_kms(display->drm,
|
|
"[CRTC:%d:%s] FQ %d @ 0x%x: ",
|
|
crtc->base.base.id, crtc->base.name, flipq_id,
|
|
flipq->start_mmioaddr);
|
|
for (int i = 0 ; i < intel_flipq_size_dw(flipq_id); i++) {
|
|
printk(KERN_CONT " 0x%08x",
|
|
intel_de_read(display, PIPEDMC_FQ_RAM(flipq->start_mmioaddr, i)));
|
|
if (i % intel_flipq_elem_size_dw(flipq_id) == intel_flipq_elem_size_dw(flipq_id) - 1)
|
|
printk(KERN_CONT "\n");
|
|
}
|
|
|
|
drm_dbg_kms(display->drm,
|
|
"[CRTC:%d:%s] FQ %d: chp=0x%x, hp=0x%x\n",
|
|
crtc->base.base.id, crtc->base.name, flipq_id,
|
|
intel_de_read(display, PIPEDMC_FPQ_CHP(crtc->pipe, flipq_id)),
|
|
intel_de_read(display, PIPEDMC_FPQ_HP(crtc->pipe, flipq_id)));
|
|
|
|
drm_dbg_kms(display->drm,
|
|
"[CRTC:%d:%s] FQ %d: current head %d\n",
|
|
crtc->base.base.id, crtc->base.name, flipq_id,
|
|
intel_flipq_current_head(crtc, flipq_id));
|
|
|
|
drm_dbg_kms(display->drm,
|
|
"[CRTC:%d:%s] flip queue timestamp: 0x%x\n",
|
|
crtc->base.base.id, crtc->base.name,
|
|
intel_de_read(display, PIPEDMC_FPQ_TS(crtc->pipe)));
|
|
|
|
tmp = intel_de_read(display, PIPEDMC_FPQ_ATOMIC_TP(crtc->pipe));
|
|
|
|
drm_dbg_kms(display->drm,
|
|
"[CRTC:%d:%s] flip queue atomic tails: P3 %d, P2 %d, P1 %d, G %d, F %d\n",
|
|
crtc->base.base.id, crtc->base.name,
|
|
REG_FIELD_GET(PIPEDMC_FPQ_PLANEQ_3_TP_MASK, tmp),
|
|
REG_FIELD_GET(PIPEDMC_FPQ_PLANEQ_2_TP_MASK, tmp),
|
|
REG_FIELD_GET(PIPEDMC_FPQ_PLANEQ_1_TP_MASK, tmp),
|
|
REG_FIELD_GET(PIPEDMC_FPQ_GENERALQ_TP_MASK, tmp),
|
|
REG_FIELD_GET(PIPEDMC_FPQ_FASTQ_TP_MASK, tmp));
|
|
}
|
|
|
|
void intel_flipq_reset(struct intel_display *display, enum pipe pipe)
|
|
{
|
|
struct intel_crtc *crtc = intel_crtc_for_pipe(display, pipe);
|
|
enum intel_flipq_id flipq_id;
|
|
|
|
intel_de_write(display, PIPEDMC_FQ_CTRL(pipe), 0);
|
|
|
|
intel_de_write(display, PIPEDMC_SCANLINECMPLOWER(pipe), 0);
|
|
intel_de_write(display, PIPEDMC_SCANLINECMPUPPER(pipe), 0);
|
|
|
|
for_each_flipq(flipq_id) {
|
|
struct intel_flipq *flipq = &crtc->flipq[flipq_id];
|
|
|
|
intel_de_write(display, PIPEDMC_FPQ_HP(pipe, flipq_id), 0);
|
|
intel_de_write(display, PIPEDMC_FPQ_CHP(pipe, flipq_id), 0);
|
|
|
|
flipq->tail = 0;
|
|
}
|
|
|
|
intel_de_write(display, PIPEDMC_FPQ_ATOMIC_TP(pipe), 0);
|
|
}
|
|
|
|
static enum pipedmc_event_id flipq_event_id(struct intel_display *display)
|
|
{
|
|
if (DISPLAY_VER(display) >= 30)
|
|
return PIPEDMC_EVENT_FULL_FQ_WAKE_TRIGGER;
|
|
else
|
|
return PIPEDMC_EVENT_SCANLINE_INRANGE_FQ_TRIGGER;
|
|
}
|
|
|
|
void intel_flipq_enable(const struct intel_crtc_state *crtc_state)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc_state);
|
|
struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc);
|
|
/* FIXME what to do with VRR? */
|
|
int scanline = intel_mode_vblank_start(&crtc_state->hw.adjusted_mode) -
|
|
intel_flipq_exec_time_lines(crtc_state);
|
|
|
|
if (DISPLAY_VER(display) >= 30) {
|
|
u32 start_mmioaddr = intel_pipedmc_start_mmioaddr(crtc);
|
|
|
|
/* undocumented magic DMC variables */
|
|
intel_de_write(display, PTL_PIPEDMC_EXEC_TIME_LINES(start_mmioaddr),
|
|
intel_flipq_exec_time_lines(crtc_state));
|
|
intel_de_write(display, PTL_PIPEDMC_END_OF_EXEC_GB(start_mmioaddr),
|
|
100);
|
|
}
|
|
|
|
intel_de_write(display, PIPEDMC_SCANLINECMPUPPER(crtc->pipe),
|
|
PIPEDMC_SCANLINE_UPPER(scanline));
|
|
intel_de_write(display, PIPEDMC_SCANLINECMPLOWER(crtc->pipe),
|
|
PIPEDMC_SCANLINEINRANGECMP_EN |
|
|
PIPEDMC_SCANLINE_LOWER(scanline - 2));
|
|
|
|
intel_pipedmc_enable_event(crtc, flipq_event_id(display));
|
|
|
|
intel_de_write(display, PIPEDMC_FQ_CTRL(crtc->pipe), PIPEDMC_FQ_CTRL_ENABLE);
|
|
}
|
|
|
|
void intel_flipq_disable(const struct intel_crtc_state *crtc_state)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc_state);
|
|
struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc);
|
|
|
|
intel_flipq_preempt(crtc, true);
|
|
|
|
intel_de_write(display, PIPEDMC_FQ_CTRL(crtc->pipe), 0);
|
|
|
|
intel_pipedmc_disable_event(crtc, flipq_event_id(display));
|
|
|
|
intel_de_write(display, PIPEDMC_SCANLINECMPLOWER(crtc->pipe), 0);
|
|
intel_de_write(display, PIPEDMC_SCANLINECMPUPPER(crtc->pipe), 0);
|
|
}
|
|
|
|
static bool assert_flipq_has_room(struct intel_crtc *crtc,
|
|
enum intel_flipq_id flipq_id)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc);
|
|
struct intel_flipq *flipq = &crtc->flipq[flipq_id];
|
|
int head, size = intel_flipq_size_entries(flipq_id);
|
|
|
|
head = intel_flipq_current_head(crtc, flipq_id);
|
|
|
|
return !drm_WARN(display->drm,
|
|
(flipq->tail + size - head) % size >= size - 1,
|
|
"[CRTC:%d:%s] FQ %d overflow (head %d, tail %d, size %d)\n",
|
|
crtc->base.base.id, crtc->base.name, flipq_id,
|
|
head, flipq->tail, size);
|
|
}
|
|
|
|
static void intel_flipq_write(struct intel_display *display,
|
|
struct intel_flipq *flipq, u32 data, int i)
|
|
{
|
|
intel_de_write(display, PIPEDMC_FQ_RAM(flipq->start_mmioaddr, flipq->tail *
|
|
intel_flipq_elem_size_dw(flipq->flipq_id) + i), data);
|
|
}
|
|
|
|
static void lnl_flipq_add(struct intel_display *display,
|
|
struct intel_flipq *flipq,
|
|
unsigned int pts,
|
|
enum intel_dsb_id dsb_id,
|
|
struct intel_dsb *dsb)
|
|
{
|
|
int i = 0;
|
|
|
|
switch (flipq->flipq_id) {
|
|
case INTEL_FLIPQ_GENERAL:
|
|
intel_flipq_write(display, flipq, pts, i++);
|
|
intel_flipq_write(display, flipq, intel_dsb_head(dsb), i++);
|
|
intel_flipq_write(display, flipq, LNL_FQ_INTERRUPT |
|
|
LNL_FQ_DSB_ID(dsb_id) |
|
|
LNL_FQ_DSB_SIZE(intel_dsb_size(dsb) / 64), i++);
|
|
intel_flipq_write(display, flipq, 0, i++);
|
|
intel_flipq_write(display, flipq, 0, i++); /* head for second DSB */
|
|
intel_flipq_write(display, flipq, 0, i++); /* DSB engine + size for second DSB */
|
|
break;
|
|
case INTEL_FLIPQ_PLANE_1:
|
|
case INTEL_FLIPQ_PLANE_2:
|
|
case INTEL_FLIPQ_PLANE_3:
|
|
intel_flipq_write(display, flipq, pts, i++);
|
|
intel_flipq_write(display, flipq, intel_dsb_head(dsb), i++);
|
|
intel_flipq_write(display, flipq, LNL_FQ_INTERRUPT |
|
|
LNL_FQ_DSB_ID(dsb_id) |
|
|
LNL_FQ_DSB_SIZE(intel_dsb_size(dsb) / 64), i++);
|
|
intel_flipq_write(display, flipq, 0, i++);
|
|
break;
|
|
default:
|
|
MISSING_CASE(flipq->flipq_id);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void ptl_flipq_add(struct intel_display *display,
|
|
struct intel_flipq *flipq,
|
|
unsigned int pts,
|
|
enum intel_dsb_id dsb_id,
|
|
struct intel_dsb *dsb)
|
|
{
|
|
int i = 0;
|
|
|
|
switch (flipq->flipq_id) {
|
|
case INTEL_FLIPQ_GENERAL:
|
|
intel_flipq_write(display, flipq, pts, i++);
|
|
intel_flipq_write(display, flipq, 0, i++);
|
|
intel_flipq_write(display, flipq, PTL_FQ_INTERRUPT |
|
|
PTL_FQ_DSB_ID(dsb_id) |
|
|
PTL_FQ_DSB_SIZE(intel_dsb_size(dsb) / 64), i++);
|
|
intel_flipq_write(display, flipq, intel_dsb_head(dsb), i++);
|
|
intel_flipq_write(display, flipq, 0, i++); /* DSB engine + size for second DSB */
|
|
intel_flipq_write(display, flipq, 0, i++); /* head for second DSB */
|
|
break;
|
|
case INTEL_FLIPQ_PLANE_1:
|
|
case INTEL_FLIPQ_PLANE_2:
|
|
case INTEL_FLIPQ_PLANE_3:
|
|
intel_flipq_write(display, flipq, pts, i++);
|
|
intel_flipq_write(display, flipq, 0, i++);
|
|
intel_flipq_write(display, flipq, PTL_FQ_INTERRUPT |
|
|
PTL_FQ_DSB_ID(dsb_id) |
|
|
PTL_FQ_DSB_SIZE(intel_dsb_size(dsb) / 64), i++);
|
|
intel_flipq_write(display, flipq, intel_dsb_head(dsb), i++);
|
|
break;
|
|
default:
|
|
MISSING_CASE(flipq->flipq_id);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void intel_flipq_add(struct intel_crtc *crtc,
|
|
enum intel_flipq_id flipq_id,
|
|
unsigned int pts,
|
|
enum intel_dsb_id dsb_id,
|
|
struct intel_dsb *dsb)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc);
|
|
struct intel_flipq *flipq = &crtc->flipq[flipq_id];
|
|
|
|
if (!assert_flipq_has_room(crtc, flipq_id))
|
|
return;
|
|
|
|
pts += intel_de_read(display, PIPEDMC_FPQ_TS(crtc->pipe));
|
|
|
|
intel_flipq_preempt(crtc, true);
|
|
|
|
if (DISPLAY_VER(display) >= 30)
|
|
ptl_flipq_add(display, flipq, pts, dsb_id, dsb);
|
|
else
|
|
lnl_flipq_add(display, flipq, pts, dsb_id, dsb);
|
|
|
|
flipq->tail = (flipq->tail + 1) % intel_flipq_size_entries(flipq->flipq_id);
|
|
intel_flipq_write_tail(crtc);
|
|
|
|
intel_flipq_preempt(crtc, false);
|
|
|
|
intel_flipq_sw_dmc_wake(crtc);
|
|
}
|
|
|
|
/* Wa_18034343758 */
|
|
static bool need_dmc_halt_wa(struct intel_display *display)
|
|
{
|
|
return DISPLAY_VER(display) == 20 ||
|
|
(display->platform.pantherlake &&
|
|
IS_DISPLAY_STEP(display, STEP_A0, STEP_B0));
|
|
}
|
|
|
|
void intel_flipq_wait_dmc_halt(struct intel_dsb *dsb, struct intel_crtc *crtc)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc);
|
|
|
|
if (need_dmc_halt_wa(display))
|
|
intel_dsb_wait_usec(dsb, 2);
|
|
}
|
|
|
|
void intel_flipq_unhalt_dmc(struct intel_dsb *dsb, struct intel_crtc *crtc)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc);
|
|
|
|
if (need_dmc_halt_wa(display))
|
|
intel_dsb_reg_write(dsb, PIPEDMC_CTL(crtc->pipe), 0);
|
|
}
|