2019-06-04 16:11:33 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2015-03-03 05:01:12 +08:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2015 Broadcom
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* DOC: VC4 CRTC module
|
|
|
|
*
|
|
|
|
* In VC4, the Pixel Valve is what most closely corresponds to the
|
|
|
|
* DRM's concept of a CRTC. The PV generates video timings from the
|
2017-02-28 04:11:43 +08:00
|
|
|
* encoder's clock plus its configuration. It pulls scaled pixels from
|
2015-03-03 05:01:12 +08:00
|
|
|
* the HVS at that timing, and feeds it to the encoder.
|
|
|
|
*
|
|
|
|
* However, the DRM CRTC also collects the configuration of all the
|
2017-02-28 04:11:43 +08:00
|
|
|
* DRM planes attached to it. As a result, the CRTC is also
|
|
|
|
* responsible for writing the display list for the HVS channel that
|
|
|
|
* the CRTC will use.
|
2015-03-03 05:01:12 +08:00
|
|
|
*
|
|
|
|
* The 2835 has 3 different pixel valves. pv0 in the audio power
|
|
|
|
* domain feeds DSI0 or DPI, while pv1 feeds DS1 or SMI. pv2 in the
|
|
|
|
* image domain can feed either HDMI or the SDTV controller. The
|
|
|
|
* pixel valve chooses from the CPRMAN clocks (HSM for HDMI, VEC for
|
|
|
|
* SDTV, etc.) according to which output type is chosen in the mux.
|
|
|
|
*
|
|
|
|
* For power management, the pixel valve's registers are all clocked
|
|
|
|
* by the AXI clock, while the timings and FIFOs make use of the
|
|
|
|
* output-specific clock. Since the encoders also directly consume
|
|
|
|
* the CPRMAN clocks, and know what timings they need, they are the
|
|
|
|
* ones that set the clock.
|
|
|
|
*/
|
|
|
|
|
2019-07-16 14:42:07 +08:00
|
|
|
#include <linux/clk.h>
|
|
|
|
#include <linux/component.h>
|
|
|
|
#include <linux/of_device.h>
|
2021-09-24 02:50:13 +08:00
|
|
|
#include <linux/pm_runtime.h>
|
2019-07-16 14:42:07 +08:00
|
|
|
|
2017-05-18 12:29:38 +08:00
|
|
|
#include <drm/drm_atomic.h>
|
|
|
|
#include <drm/drm_atomic_helper.h>
|
2018-09-05 21:57:11 +08:00
|
|
|
#include <drm/drm_atomic_uapi.h>
|
2022-08-02 08:04:02 +08:00
|
|
|
#include <drm/drm_fb_dma_helper.h>
|
2022-06-14 17:54:49 +08:00
|
|
|
#include <drm/drm_framebuffer.h>
|
2022-08-25 00:13:26 +08:00
|
|
|
#include <drm/drm_drv.h>
|
2019-02-21 05:03:38 +08:00
|
|
|
#include <drm/drm_print.h>
|
2019-01-18 05:03:34 +08:00
|
|
|
#include <drm/drm_probe_helper.h>
|
2019-07-16 14:42:07 +08:00
|
|
|
#include <drm/drm_vblank.h>
|
|
|
|
|
2015-03-03 05:01:12 +08:00
|
|
|
#include "vc4_drv.h"
|
2021-09-24 02:50:13 +08:00
|
|
|
#include "vc4_hdmi.h"
|
2015-03-03 05:01:12 +08:00
|
|
|
#include "vc4_regs.h"
|
|
|
|
|
2020-05-27 23:47:57 +08:00
|
|
|
#define HVS_FIFO_LATENCY_PIX 6
|
|
|
|
|
2022-12-01 23:11:49 +08:00
|
|
|
#define CRTC_WRITE(offset, val) \
|
|
|
|
do { \
|
|
|
|
kunit_fail_current_test("Accessing a register in a unit test!\n"); \
|
|
|
|
writel(val, vc4_crtc->regs + (offset)); \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define CRTC_READ(offset) \
|
|
|
|
({ \
|
|
|
|
kunit_fail_current_test("Accessing a register in a unit test!\n"); \
|
|
|
|
readl(vc4_crtc->regs + (offset)); \
|
|
|
|
})
|
2015-03-03 05:01:12 +08:00
|
|
|
|
2019-02-21 05:03:38 +08:00
|
|
|
static const struct debugfs_reg32 crtc_regs[] = {
|
|
|
|
VC4_REG32(PV_CONTROL),
|
|
|
|
VC4_REG32(PV_V_CONTROL),
|
|
|
|
VC4_REG32(PV_VSYNCD_EVEN),
|
|
|
|
VC4_REG32(PV_HORZA),
|
|
|
|
VC4_REG32(PV_HORZB),
|
|
|
|
VC4_REG32(PV_VERTA),
|
|
|
|
VC4_REG32(PV_VERTB),
|
|
|
|
VC4_REG32(PV_VERTA_EVEN),
|
|
|
|
VC4_REG32(PV_VERTB_EVEN),
|
|
|
|
VC4_REG32(PV_INTEN),
|
|
|
|
VC4_REG32(PV_INTSTAT),
|
|
|
|
VC4_REG32(PV_STAT),
|
|
|
|
VC4_REG32(PV_HACT_ACT),
|
2015-03-03 05:01:12 +08:00
|
|
|
};
|
|
|
|
|
2020-09-03 16:00:41 +08:00
|
|
|
static unsigned int
|
|
|
|
vc4_crtc_get_cob_allocation(struct vc4_dev *vc4, unsigned int channel)
|
|
|
|
{
|
2022-03-31 22:37:44 +08:00
|
|
|
struct vc4_hvs *hvs = vc4->hvs;
|
2020-09-03 16:00:41 +08:00
|
|
|
u32 dispbase = HVS_READ(SCALER_DISPBASEX(channel));
|
|
|
|
/* Top/base are supposed to be 4-pixel aligned, but the
|
|
|
|
* Raspberry Pi firmware fills the low bits (which are
|
|
|
|
* presumably ignored).
|
|
|
|
*/
|
|
|
|
u32 top = VC4_GET_FIELD(dispbase, SCALER_DISPBASEX_TOP) & ~3;
|
|
|
|
u32 base = VC4_GET_FIELD(dispbase, SCALER_DISPBASEX_BASE) & ~3;
|
|
|
|
|
|
|
|
return top - base + 4;
|
|
|
|
}
|
|
|
|
|
2020-01-23 21:59:38 +08:00
|
|
|
static bool vc4_crtc_get_scanout_position(struct drm_crtc *crtc,
|
|
|
|
bool in_vblank_irq,
|
|
|
|
int *vpos, int *hpos,
|
|
|
|
ktime_t *stime, ktime_t *etime,
|
|
|
|
const struct drm_display_mode *mode)
|
drm/vc4: Implement precise vblank timestamping.
Precise vblank timestamping is implemented via the
usual scanout position based method. On VC4 the
pixelvalves PV do not have a scanout position
register. Only the hardware video scaler HVS has a
similar register which describes which scanline for
the output is currently composited and stored in the
HVS fifo for later consumption by the PV.
This causes a problem in that the HVS runs at a much
faster clock (system clock / audio gate) than the PV
which runs at video mode dot clock, so the unless the
fifo between HVS and PV is full, the HVS will progress
faster in its observable read line position than video
scan rate, so the HVS position reading can't be directly
translated into a scanout position for timestamp correction.
Additionally when the PV is in vblank, it doesn't consume
from the fifo, so the fifo gets full very quickly and then
the HVS stops compositing until the PV enters active scanout
and starts consuming scanlines from the fifo again, making
new space for the HVS to composite.
Therefore a simple translation of HVS read position into
elapsed time since (or to) start of active scanout does
not work, but for the most interesting cases we can still
get useful and sufficiently accurate results:
1. The PV enters active scanout of a new frame with the
fifo of the HVS completely full, and the HVS can refill
any fifo line which gets consumed and thereby freed up by
the PV during active scanout very quickly. Therefore the
PV and HVS work effectively in lock-step during active
scanout with the fifo never having more than 1 scanline
freed up by the PV before it gets refilled. The PV's
real scanout position is therefore trailing the HVS
compositing position as scanoutpos = hvspos - fifosize
and we can get the true scanoutpos as HVS readpos minus
fifo size, so precise timestamping works while in active
scanout, except for the last few scanlines of the frame,
when the HVS reaches end of frame, stops compositing and
the PV catches up and drains the fifo. This special case
would only introduce minor errors though.
2. If we are in vblank, then we can only guess something
reasonable. If called from vblank irq, we assume the irq is
usually dispatched with minimum delay, so we can take a
timestamp taken at entry into the vblank irq handler as a
baseline and then add a full vblank duration until the
guessed start of active scanout. As irq dispatch is usually
pretty low latency this works with relatively low jitter and
good results.
If we aren't called from vblank then we could be anywhere
within the vblank interval, so we return a neutral result,
simply the current system timestamp, and hope for the best.
Measurement shows the generated timestamps to be rather precise,
and at least never off more than 1 vblank duration worst-case.
Limitations: Doesn't work well yet for interlaced video modes,
therefore disabled in interlaced mode for now.
v2: Use the DISPBASE registers to determine the FIFO size (changes
by anholt)
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-and-tested-by: Mario Kleiner <mario.kleiner.de@gmail.com> (v2)
2016-06-23 14:17:50 +08:00
|
|
|
{
|
2020-01-23 21:59:38 +08:00
|
|
|
struct drm_device *dev = crtc->dev;
|
drm/vc4: Implement precise vblank timestamping.
Precise vblank timestamping is implemented via the
usual scanout position based method. On VC4 the
pixelvalves PV do not have a scanout position
register. Only the hardware video scaler HVS has a
similar register which describes which scanline for
the output is currently composited and stored in the
HVS fifo for later consumption by the PV.
This causes a problem in that the HVS runs at a much
faster clock (system clock / audio gate) than the PV
which runs at video mode dot clock, so the unless the
fifo between HVS and PV is full, the HVS will progress
faster in its observable read line position than video
scan rate, so the HVS position reading can't be directly
translated into a scanout position for timestamp correction.
Additionally when the PV is in vblank, it doesn't consume
from the fifo, so the fifo gets full very quickly and then
the HVS stops compositing until the PV enters active scanout
and starts consuming scanlines from the fifo again, making
new space for the HVS to composite.
Therefore a simple translation of HVS read position into
elapsed time since (or to) start of active scanout does
not work, but for the most interesting cases we can still
get useful and sufficiently accurate results:
1. The PV enters active scanout of a new frame with the
fifo of the HVS completely full, and the HVS can refill
any fifo line which gets consumed and thereby freed up by
the PV during active scanout very quickly. Therefore the
PV and HVS work effectively in lock-step during active
scanout with the fifo never having more than 1 scanline
freed up by the PV before it gets refilled. The PV's
real scanout position is therefore trailing the HVS
compositing position as scanoutpos = hvspos - fifosize
and we can get the true scanoutpos as HVS readpos minus
fifo size, so precise timestamping works while in active
scanout, except for the last few scanlines of the frame,
when the HVS reaches end of frame, stops compositing and
the PV catches up and drains the fifo. This special case
would only introduce minor errors though.
2. If we are in vblank, then we can only guess something
reasonable. If called from vblank irq, we assume the irq is
usually dispatched with minimum delay, so we can take a
timestamp taken at entry into the vblank irq handler as a
baseline and then add a full vblank duration until the
guessed start of active scanout. As irq dispatch is usually
pretty low latency this works with relatively low jitter and
good results.
If we aren't called from vblank then we could be anywhere
within the vblank interval, so we return a neutral result,
simply the current system timestamp, and hope for the best.
Measurement shows the generated timestamps to be rather precise,
and at least never off more than 1 vblank duration worst-case.
Limitations: Doesn't work well yet for interlaced video modes,
therefore disabled in interlaced mode for now.
v2: Use the DISPBASE registers to determine the FIFO size (changes
by anholt)
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-and-tested-by: Mario Kleiner <mario.kleiner.de@gmail.com> (v2)
2016-06-23 14:17:50 +08:00
|
|
|
struct vc4_dev *vc4 = to_vc4_dev(dev);
|
2022-03-31 22:37:44 +08:00
|
|
|
struct vc4_hvs *hvs = vc4->hvs;
|
2017-01-09 19:25:45 +08:00
|
|
|
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
|
2020-09-03 16:00:46 +08:00
|
|
|
struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state);
|
2020-09-03 16:00:41 +08:00
|
|
|
unsigned int cob_size;
|
drm/vc4: Implement precise vblank timestamping.
Precise vblank timestamping is implemented via the
usual scanout position based method. On VC4 the
pixelvalves PV do not have a scanout position
register. Only the hardware video scaler HVS has a
similar register which describes which scanline for
the output is currently composited and stored in the
HVS fifo for later consumption by the PV.
This causes a problem in that the HVS runs at a much
faster clock (system clock / audio gate) than the PV
which runs at video mode dot clock, so the unless the
fifo between HVS and PV is full, the HVS will progress
faster in its observable read line position than video
scan rate, so the HVS position reading can't be directly
translated into a scanout position for timestamp correction.
Additionally when the PV is in vblank, it doesn't consume
from the fifo, so the fifo gets full very quickly and then
the HVS stops compositing until the PV enters active scanout
and starts consuming scanlines from the fifo again, making
new space for the HVS to composite.
Therefore a simple translation of HVS read position into
elapsed time since (or to) start of active scanout does
not work, but for the most interesting cases we can still
get useful and sufficiently accurate results:
1. The PV enters active scanout of a new frame with the
fifo of the HVS completely full, and the HVS can refill
any fifo line which gets consumed and thereby freed up by
the PV during active scanout very quickly. Therefore the
PV and HVS work effectively in lock-step during active
scanout with the fifo never having more than 1 scanline
freed up by the PV before it gets refilled. The PV's
real scanout position is therefore trailing the HVS
compositing position as scanoutpos = hvspos - fifosize
and we can get the true scanoutpos as HVS readpos minus
fifo size, so precise timestamping works while in active
scanout, except for the last few scanlines of the frame,
when the HVS reaches end of frame, stops compositing and
the PV catches up and drains the fifo. This special case
would only introduce minor errors though.
2. If we are in vblank, then we can only guess something
reasonable. If called from vblank irq, we assume the irq is
usually dispatched with minimum delay, so we can take a
timestamp taken at entry into the vblank irq handler as a
baseline and then add a full vblank duration until the
guessed start of active scanout. As irq dispatch is usually
pretty low latency this works with relatively low jitter and
good results.
If we aren't called from vblank then we could be anywhere
within the vblank interval, so we return a neutral result,
simply the current system timestamp, and hope for the best.
Measurement shows the generated timestamps to be rather precise,
and at least never off more than 1 vblank duration worst-case.
Limitations: Doesn't work well yet for interlaced video modes,
therefore disabled in interlaced mode for now.
v2: Use the DISPBASE registers to determine the FIFO size (changes
by anholt)
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-and-tested-by: Mario Kleiner <mario.kleiner.de@gmail.com> (v2)
2016-06-23 14:17:50 +08:00
|
|
|
u32 val;
|
|
|
|
int fifo_lines;
|
|
|
|
int vblank_lines;
|
drm/vblank: drop the mode argument from drm_calc_vbltimestamp_from_scanoutpos
If we restrict this helper to only kms drivers (which is the case) we
can look up the correct mode easily ourselves. But it's a bit tricky:
- All legacy drivers look at crtc->hwmode. But that is updated already
at the beginning of the modeset helper, which means when we disable
a pipe. Hence the final timestamps might be a bit off. But since
this is an existing bug I'm not going to change it, but just try to
be bug-for-bug compatible with the current code. This only applies
to radeon&amdgpu.
- i915 tries to get it perfect by updating crtc->hwmode when the pipe
is off (i.e. vblank->enabled = false).
- All other atomic drivers look at crtc->state->adjusted_mode. Those
that look at state->requested_mode simply don't adjust their mode,
so it's the same. That has two problems: Accessing crtc->state from
interrupt handling code is unsafe, and it's updated before we shut
down the pipe. For nonblocking modesets it's even worse.
For atomic drivers try to implement what i915 does. To do that we add
a new hwmode field to the vblank structure, and update it from
drm_calc_timestamping_constants(). For atomic drivers that's called
from the right spot by the helper library already, so all fine. But
for safety let's enforce that.
For legacy driver this function is only called at the end (oh the
fun), which is broken, so again let's not bother and just stay
bug-for-bug compatible.
The benefit is that we can use drm_calc_vbltimestamp_from_scanoutpos
directly to implement ->get_vblank_timestamp in every driver, deleting
a lot of code.
v2: Completely new approach, trying to mimick the i915 solution.
v3: Fixup kerneldoc.
v4: Drop the WARN_ON to check that the vblank is off, atomic helpers
currently unconditionally call this. Recomputing the same stuff should
be harmless.
v5: Fix typos and move misplaced hunks to the right patches (Neil).
v6: Undo hunk movement (kbuild).
Cc: Mario Kleiner <mario.kleiner@tuebingen.mpg.de>
Cc: Eric Anholt <eric@anholt.net>
Cc: Rob Clark <robdclark@gmail.com>
Cc: linux-arm-msm@vger.kernel.org
Cc: freedreno@lists.freedesktop.org
Cc: Alex Deucher <alexander.deucher@amd.com>
Cc: Christian König <christian.koenig@amd.com>
Cc: Ben Skeggs <bskeggs@redhat.com>
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
Acked-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@intel.com>
Link: http://patchwork.freedesktop.org/patch/msgid/20170509140329.24114-4-daniel.vetter@ffwll.ch
2017-05-09 22:03:28 +08:00
|
|
|
bool ret = false;
|
drm/vc4: Implement precise vblank timestamping.
Precise vblank timestamping is implemented via the
usual scanout position based method. On VC4 the
pixelvalves PV do not have a scanout position
register. Only the hardware video scaler HVS has a
similar register which describes which scanline for
the output is currently composited and stored in the
HVS fifo for later consumption by the PV.
This causes a problem in that the HVS runs at a much
faster clock (system clock / audio gate) than the PV
which runs at video mode dot clock, so the unless the
fifo between HVS and PV is full, the HVS will progress
faster in its observable read line position than video
scan rate, so the HVS position reading can't be directly
translated into a scanout position for timestamp correction.
Additionally when the PV is in vblank, it doesn't consume
from the fifo, so the fifo gets full very quickly and then
the HVS stops compositing until the PV enters active scanout
and starts consuming scanlines from the fifo again, making
new space for the HVS to composite.
Therefore a simple translation of HVS read position into
elapsed time since (or to) start of active scanout does
not work, but for the most interesting cases we can still
get useful and sufficiently accurate results:
1. The PV enters active scanout of a new frame with the
fifo of the HVS completely full, and the HVS can refill
any fifo line which gets consumed and thereby freed up by
the PV during active scanout very quickly. Therefore the
PV and HVS work effectively in lock-step during active
scanout with the fifo never having more than 1 scanline
freed up by the PV before it gets refilled. The PV's
real scanout position is therefore trailing the HVS
compositing position as scanoutpos = hvspos - fifosize
and we can get the true scanoutpos as HVS readpos minus
fifo size, so precise timestamping works while in active
scanout, except for the last few scanlines of the frame,
when the HVS reaches end of frame, stops compositing and
the PV catches up and drains the fifo. This special case
would only introduce minor errors though.
2. If we are in vblank, then we can only guess something
reasonable. If called from vblank irq, we assume the irq is
usually dispatched with minimum delay, so we can take a
timestamp taken at entry into the vblank irq handler as a
baseline and then add a full vblank duration until the
guessed start of active scanout. As irq dispatch is usually
pretty low latency this works with relatively low jitter and
good results.
If we aren't called from vblank then we could be anywhere
within the vblank interval, so we return a neutral result,
simply the current system timestamp, and hope for the best.
Measurement shows the generated timestamps to be rather precise,
and at least never off more than 1 vblank duration worst-case.
Limitations: Doesn't work well yet for interlaced video modes,
therefore disabled in interlaced mode for now.
v2: Use the DISPBASE registers to determine the FIFO size (changes
by anholt)
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-and-tested-by: Mario Kleiner <mario.kleiner.de@gmail.com> (v2)
2016-06-23 14:17:50 +08:00
|
|
|
|
|
|
|
/* preempt_disable_rt() should go right here in PREEMPT_RT patchset. */
|
|
|
|
|
|
|
|
/* Get optional system timestamp before query. */
|
|
|
|
if (stime)
|
|
|
|
*stime = ktime_get();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read vertical scanline which is currently composed for our
|
|
|
|
* pixelvalve by the HVS, and also the scaler status.
|
|
|
|
*/
|
2020-09-03 16:00:46 +08:00
|
|
|
val = HVS_READ(SCALER_DISPSTATX(vc4_crtc_state->assigned_channel));
|
drm/vc4: Implement precise vblank timestamping.
Precise vblank timestamping is implemented via the
usual scanout position based method. On VC4 the
pixelvalves PV do not have a scanout position
register. Only the hardware video scaler HVS has a
similar register which describes which scanline for
the output is currently composited and stored in the
HVS fifo for later consumption by the PV.
This causes a problem in that the HVS runs at a much
faster clock (system clock / audio gate) than the PV
which runs at video mode dot clock, so the unless the
fifo between HVS and PV is full, the HVS will progress
faster in its observable read line position than video
scan rate, so the HVS position reading can't be directly
translated into a scanout position for timestamp correction.
Additionally when the PV is in vblank, it doesn't consume
from the fifo, so the fifo gets full very quickly and then
the HVS stops compositing until the PV enters active scanout
and starts consuming scanlines from the fifo again, making
new space for the HVS to composite.
Therefore a simple translation of HVS read position into
elapsed time since (or to) start of active scanout does
not work, but for the most interesting cases we can still
get useful and sufficiently accurate results:
1. The PV enters active scanout of a new frame with the
fifo of the HVS completely full, and the HVS can refill
any fifo line which gets consumed and thereby freed up by
the PV during active scanout very quickly. Therefore the
PV and HVS work effectively in lock-step during active
scanout with the fifo never having more than 1 scanline
freed up by the PV before it gets refilled. The PV's
real scanout position is therefore trailing the HVS
compositing position as scanoutpos = hvspos - fifosize
and we can get the true scanoutpos as HVS readpos minus
fifo size, so precise timestamping works while in active
scanout, except for the last few scanlines of the frame,
when the HVS reaches end of frame, stops compositing and
the PV catches up and drains the fifo. This special case
would only introduce minor errors though.
2. If we are in vblank, then we can only guess something
reasonable. If called from vblank irq, we assume the irq is
usually dispatched with minimum delay, so we can take a
timestamp taken at entry into the vblank irq handler as a
baseline and then add a full vblank duration until the
guessed start of active scanout. As irq dispatch is usually
pretty low latency this works with relatively low jitter and
good results.
If we aren't called from vblank then we could be anywhere
within the vblank interval, so we return a neutral result,
simply the current system timestamp, and hope for the best.
Measurement shows the generated timestamps to be rather precise,
and at least never off more than 1 vblank duration worst-case.
Limitations: Doesn't work well yet for interlaced video modes,
therefore disabled in interlaced mode for now.
v2: Use the DISPBASE registers to determine the FIFO size (changes
by anholt)
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-and-tested-by: Mario Kleiner <mario.kleiner.de@gmail.com> (v2)
2016-06-23 14:17:50 +08:00
|
|
|
|
|
|
|
/* Get optional system timestamp after query. */
|
|
|
|
if (etime)
|
|
|
|
*etime = ktime_get();
|
|
|
|
|
|
|
|
/* preempt_enable_rt() should go right here in PREEMPT_RT patchset. */
|
|
|
|
|
|
|
|
/* Vertical position of hvs composed scanline. */
|
|
|
|
*vpos = VC4_GET_FIELD(val, SCALER_DISPSTATX_LINE);
|
2016-07-20 02:59:00 +08:00
|
|
|
*hpos = 0;
|
|
|
|
|
|
|
|
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
|
|
|
|
*vpos /= 2;
|
drm/vc4: Implement precise vblank timestamping.
Precise vblank timestamping is implemented via the
usual scanout position based method. On VC4 the
pixelvalves PV do not have a scanout position
register. Only the hardware video scaler HVS has a
similar register which describes which scanline for
the output is currently composited and stored in the
HVS fifo for later consumption by the PV.
This causes a problem in that the HVS runs at a much
faster clock (system clock / audio gate) than the PV
which runs at video mode dot clock, so the unless the
fifo between HVS and PV is full, the HVS will progress
faster in its observable read line position than video
scan rate, so the HVS position reading can't be directly
translated into a scanout position for timestamp correction.
Additionally when the PV is in vblank, it doesn't consume
from the fifo, so the fifo gets full very quickly and then
the HVS stops compositing until the PV enters active scanout
and starts consuming scanlines from the fifo again, making
new space for the HVS to composite.
Therefore a simple translation of HVS read position into
elapsed time since (or to) start of active scanout does
not work, but for the most interesting cases we can still
get useful and sufficiently accurate results:
1. The PV enters active scanout of a new frame with the
fifo of the HVS completely full, and the HVS can refill
any fifo line which gets consumed and thereby freed up by
the PV during active scanout very quickly. Therefore the
PV and HVS work effectively in lock-step during active
scanout with the fifo never having more than 1 scanline
freed up by the PV before it gets refilled. The PV's
real scanout position is therefore trailing the HVS
compositing position as scanoutpos = hvspos - fifosize
and we can get the true scanoutpos as HVS readpos minus
fifo size, so precise timestamping works while in active
scanout, except for the last few scanlines of the frame,
when the HVS reaches end of frame, stops compositing and
the PV catches up and drains the fifo. This special case
would only introduce minor errors though.
2. If we are in vblank, then we can only guess something
reasonable. If called from vblank irq, we assume the irq is
usually dispatched with minimum delay, so we can take a
timestamp taken at entry into the vblank irq handler as a
baseline and then add a full vblank duration until the
guessed start of active scanout. As irq dispatch is usually
pretty low latency this works with relatively low jitter and
good results.
If we aren't called from vblank then we could be anywhere
within the vblank interval, so we return a neutral result,
simply the current system timestamp, and hope for the best.
Measurement shows the generated timestamps to be rather precise,
and at least never off more than 1 vblank duration worst-case.
Limitations: Doesn't work well yet for interlaced video modes,
therefore disabled in interlaced mode for now.
v2: Use the DISPBASE registers to determine the FIFO size (changes
by anholt)
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-and-tested-by: Mario Kleiner <mario.kleiner.de@gmail.com> (v2)
2016-06-23 14:17:50 +08:00
|
|
|
|
2016-07-20 02:59:00 +08:00
|
|
|
/* Use hpos to correct for field offset in interlaced mode. */
|
2022-03-31 22:37:44 +08:00
|
|
|
if (vc4_hvs_get_fifo_frame_count(hvs, vc4_crtc_state->assigned_channel) % 2)
|
2016-07-20 02:59:00 +08:00
|
|
|
*hpos += mode->crtc_htotal / 2;
|
|
|
|
}
|
drm/vc4: Implement precise vblank timestamping.
Precise vblank timestamping is implemented via the
usual scanout position based method. On VC4 the
pixelvalves PV do not have a scanout position
register. Only the hardware video scaler HVS has a
similar register which describes which scanline for
the output is currently composited and stored in the
HVS fifo for later consumption by the PV.
This causes a problem in that the HVS runs at a much
faster clock (system clock / audio gate) than the PV
which runs at video mode dot clock, so the unless the
fifo between HVS and PV is full, the HVS will progress
faster in its observable read line position than video
scan rate, so the HVS position reading can't be directly
translated into a scanout position for timestamp correction.
Additionally when the PV is in vblank, it doesn't consume
from the fifo, so the fifo gets full very quickly and then
the HVS stops compositing until the PV enters active scanout
and starts consuming scanlines from the fifo again, making
new space for the HVS to composite.
Therefore a simple translation of HVS read position into
elapsed time since (or to) start of active scanout does
not work, but for the most interesting cases we can still
get useful and sufficiently accurate results:
1. The PV enters active scanout of a new frame with the
fifo of the HVS completely full, and the HVS can refill
any fifo line which gets consumed and thereby freed up by
the PV during active scanout very quickly. Therefore the
PV and HVS work effectively in lock-step during active
scanout with the fifo never having more than 1 scanline
freed up by the PV before it gets refilled. The PV's
real scanout position is therefore trailing the HVS
compositing position as scanoutpos = hvspos - fifosize
and we can get the true scanoutpos as HVS readpos minus
fifo size, so precise timestamping works while in active
scanout, except for the last few scanlines of the frame,
when the HVS reaches end of frame, stops compositing and
the PV catches up and drains the fifo. This special case
would only introduce minor errors though.
2. If we are in vblank, then we can only guess something
reasonable. If called from vblank irq, we assume the irq is
usually dispatched with minimum delay, so we can take a
timestamp taken at entry into the vblank irq handler as a
baseline and then add a full vblank duration until the
guessed start of active scanout. As irq dispatch is usually
pretty low latency this works with relatively low jitter and
good results.
If we aren't called from vblank then we could be anywhere
within the vblank interval, so we return a neutral result,
simply the current system timestamp, and hope for the best.
Measurement shows the generated timestamps to be rather precise,
and at least never off more than 1 vblank duration worst-case.
Limitations: Doesn't work well yet for interlaced video modes,
therefore disabled in interlaced mode for now.
v2: Use the DISPBASE registers to determine the FIFO size (changes
by anholt)
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-and-tested-by: Mario Kleiner <mario.kleiner.de@gmail.com> (v2)
2016-06-23 14:17:50 +08:00
|
|
|
|
2020-09-03 16:00:46 +08:00
|
|
|
cob_size = vc4_crtc_get_cob_allocation(vc4, vc4_crtc_state->assigned_channel);
|
drm/vc4: Implement precise vblank timestamping.
Precise vblank timestamping is implemented via the
usual scanout position based method. On VC4 the
pixelvalves PV do not have a scanout position
register. Only the hardware video scaler HVS has a
similar register which describes which scanline for
the output is currently composited and stored in the
HVS fifo for later consumption by the PV.
This causes a problem in that the HVS runs at a much
faster clock (system clock / audio gate) than the PV
which runs at video mode dot clock, so the unless the
fifo between HVS and PV is full, the HVS will progress
faster in its observable read line position than video
scan rate, so the HVS position reading can't be directly
translated into a scanout position for timestamp correction.
Additionally when the PV is in vblank, it doesn't consume
from the fifo, so the fifo gets full very quickly and then
the HVS stops compositing until the PV enters active scanout
and starts consuming scanlines from the fifo again, making
new space for the HVS to composite.
Therefore a simple translation of HVS read position into
elapsed time since (or to) start of active scanout does
not work, but for the most interesting cases we can still
get useful and sufficiently accurate results:
1. The PV enters active scanout of a new frame with the
fifo of the HVS completely full, and the HVS can refill
any fifo line which gets consumed and thereby freed up by
the PV during active scanout very quickly. Therefore the
PV and HVS work effectively in lock-step during active
scanout with the fifo never having more than 1 scanline
freed up by the PV before it gets refilled. The PV's
real scanout position is therefore trailing the HVS
compositing position as scanoutpos = hvspos - fifosize
and we can get the true scanoutpos as HVS readpos minus
fifo size, so precise timestamping works while in active
scanout, except for the last few scanlines of the frame,
when the HVS reaches end of frame, stops compositing and
the PV catches up and drains the fifo. This special case
would only introduce minor errors though.
2. If we are in vblank, then we can only guess something
reasonable. If called from vblank irq, we assume the irq is
usually dispatched with minimum delay, so we can take a
timestamp taken at entry into the vblank irq handler as a
baseline and then add a full vblank duration until the
guessed start of active scanout. As irq dispatch is usually
pretty low latency this works with relatively low jitter and
good results.
If we aren't called from vblank then we could be anywhere
within the vblank interval, so we return a neutral result,
simply the current system timestamp, and hope for the best.
Measurement shows the generated timestamps to be rather precise,
and at least never off more than 1 vblank duration worst-case.
Limitations: Doesn't work well yet for interlaced video modes,
therefore disabled in interlaced mode for now.
v2: Use the DISPBASE registers to determine the FIFO size (changes
by anholt)
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-and-tested-by: Mario Kleiner <mario.kleiner.de@gmail.com> (v2)
2016-06-23 14:17:50 +08:00
|
|
|
/* This is the offset we need for translating hvs -> pv scanout pos. */
|
2020-09-03 16:00:41 +08:00
|
|
|
fifo_lines = cob_size / mode->crtc_hdisplay;
|
drm/vc4: Implement precise vblank timestamping.
Precise vblank timestamping is implemented via the
usual scanout position based method. On VC4 the
pixelvalves PV do not have a scanout position
register. Only the hardware video scaler HVS has a
similar register which describes which scanline for
the output is currently composited and stored in the
HVS fifo for later consumption by the PV.
This causes a problem in that the HVS runs at a much
faster clock (system clock / audio gate) than the PV
which runs at video mode dot clock, so the unless the
fifo between HVS and PV is full, the HVS will progress
faster in its observable read line position than video
scan rate, so the HVS position reading can't be directly
translated into a scanout position for timestamp correction.
Additionally when the PV is in vblank, it doesn't consume
from the fifo, so the fifo gets full very quickly and then
the HVS stops compositing until the PV enters active scanout
and starts consuming scanlines from the fifo again, making
new space for the HVS to composite.
Therefore a simple translation of HVS read position into
elapsed time since (or to) start of active scanout does
not work, but for the most interesting cases we can still
get useful and sufficiently accurate results:
1. The PV enters active scanout of a new frame with the
fifo of the HVS completely full, and the HVS can refill
any fifo line which gets consumed and thereby freed up by
the PV during active scanout very quickly. Therefore the
PV and HVS work effectively in lock-step during active
scanout with the fifo never having more than 1 scanline
freed up by the PV before it gets refilled. The PV's
real scanout position is therefore trailing the HVS
compositing position as scanoutpos = hvspos - fifosize
and we can get the true scanoutpos as HVS readpos minus
fifo size, so precise timestamping works while in active
scanout, except for the last few scanlines of the frame,
when the HVS reaches end of frame, stops compositing and
the PV catches up and drains the fifo. This special case
would only introduce minor errors though.
2. If we are in vblank, then we can only guess something
reasonable. If called from vblank irq, we assume the irq is
usually dispatched with minimum delay, so we can take a
timestamp taken at entry into the vblank irq handler as a
baseline and then add a full vblank duration until the
guessed start of active scanout. As irq dispatch is usually
pretty low latency this works with relatively low jitter and
good results.
If we aren't called from vblank then we could be anywhere
within the vblank interval, so we return a neutral result,
simply the current system timestamp, and hope for the best.
Measurement shows the generated timestamps to be rather precise,
and at least never off more than 1 vblank duration worst-case.
Limitations: Doesn't work well yet for interlaced video modes,
therefore disabled in interlaced mode for now.
v2: Use the DISPBASE registers to determine the FIFO size (changes
by anholt)
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-and-tested-by: Mario Kleiner <mario.kleiner.de@gmail.com> (v2)
2016-06-23 14:17:50 +08:00
|
|
|
|
|
|
|
if (fifo_lines > 0)
|
drm/vblank: drop the mode argument from drm_calc_vbltimestamp_from_scanoutpos
If we restrict this helper to only kms drivers (which is the case) we
can look up the correct mode easily ourselves. But it's a bit tricky:
- All legacy drivers look at crtc->hwmode. But that is updated already
at the beginning of the modeset helper, which means when we disable
a pipe. Hence the final timestamps might be a bit off. But since
this is an existing bug I'm not going to change it, but just try to
be bug-for-bug compatible with the current code. This only applies
to radeon&amdgpu.
- i915 tries to get it perfect by updating crtc->hwmode when the pipe
is off (i.e. vblank->enabled = false).
- All other atomic drivers look at crtc->state->adjusted_mode. Those
that look at state->requested_mode simply don't adjust their mode,
so it's the same. That has two problems: Accessing crtc->state from
interrupt handling code is unsafe, and it's updated before we shut
down the pipe. For nonblocking modesets it's even worse.
For atomic drivers try to implement what i915 does. To do that we add
a new hwmode field to the vblank structure, and update it from
drm_calc_timestamping_constants(). For atomic drivers that's called
from the right spot by the helper library already, so all fine. But
for safety let's enforce that.
For legacy driver this function is only called at the end (oh the
fun), which is broken, so again let's not bother and just stay
bug-for-bug compatible.
The benefit is that we can use drm_calc_vbltimestamp_from_scanoutpos
directly to implement ->get_vblank_timestamp in every driver, deleting
a lot of code.
v2: Completely new approach, trying to mimick the i915 solution.
v3: Fixup kerneldoc.
v4: Drop the WARN_ON to check that the vblank is off, atomic helpers
currently unconditionally call this. Recomputing the same stuff should
be harmless.
v5: Fix typos and move misplaced hunks to the right patches (Neil).
v6: Undo hunk movement (kbuild).
Cc: Mario Kleiner <mario.kleiner@tuebingen.mpg.de>
Cc: Eric Anholt <eric@anholt.net>
Cc: Rob Clark <robdclark@gmail.com>
Cc: linux-arm-msm@vger.kernel.org
Cc: freedreno@lists.freedesktop.org
Cc: Alex Deucher <alexander.deucher@amd.com>
Cc: Christian König <christian.koenig@amd.com>
Cc: Ben Skeggs <bskeggs@redhat.com>
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
Acked-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@intel.com>
Link: http://patchwork.freedesktop.org/patch/msgid/20170509140329.24114-4-daniel.vetter@ffwll.ch
2017-05-09 22:03:28 +08:00
|
|
|
ret = true;
|
drm/vc4: Implement precise vblank timestamping.
Precise vblank timestamping is implemented via the
usual scanout position based method. On VC4 the
pixelvalves PV do not have a scanout position
register. Only the hardware video scaler HVS has a
similar register which describes which scanline for
the output is currently composited and stored in the
HVS fifo for later consumption by the PV.
This causes a problem in that the HVS runs at a much
faster clock (system clock / audio gate) than the PV
which runs at video mode dot clock, so the unless the
fifo between HVS and PV is full, the HVS will progress
faster in its observable read line position than video
scan rate, so the HVS position reading can't be directly
translated into a scanout position for timestamp correction.
Additionally when the PV is in vblank, it doesn't consume
from the fifo, so the fifo gets full very quickly and then
the HVS stops compositing until the PV enters active scanout
and starts consuming scanlines from the fifo again, making
new space for the HVS to composite.
Therefore a simple translation of HVS read position into
elapsed time since (or to) start of active scanout does
not work, but for the most interesting cases we can still
get useful and sufficiently accurate results:
1. The PV enters active scanout of a new frame with the
fifo of the HVS completely full, and the HVS can refill
any fifo line which gets consumed and thereby freed up by
the PV during active scanout very quickly. Therefore the
PV and HVS work effectively in lock-step during active
scanout with the fifo never having more than 1 scanline
freed up by the PV before it gets refilled. The PV's
real scanout position is therefore trailing the HVS
compositing position as scanoutpos = hvspos - fifosize
and we can get the true scanoutpos as HVS readpos minus
fifo size, so precise timestamping works while in active
scanout, except for the last few scanlines of the frame,
when the HVS reaches end of frame, stops compositing and
the PV catches up and drains the fifo. This special case
would only introduce minor errors though.
2. If we are in vblank, then we can only guess something
reasonable. If called from vblank irq, we assume the irq is
usually dispatched with minimum delay, so we can take a
timestamp taken at entry into the vblank irq handler as a
baseline and then add a full vblank duration until the
guessed start of active scanout. As irq dispatch is usually
pretty low latency this works with relatively low jitter and
good results.
If we aren't called from vblank then we could be anywhere
within the vblank interval, so we return a neutral result,
simply the current system timestamp, and hope for the best.
Measurement shows the generated timestamps to be rather precise,
and at least never off more than 1 vblank duration worst-case.
Limitations: Doesn't work well yet for interlaced video modes,
therefore disabled in interlaced mode for now.
v2: Use the DISPBASE registers to determine the FIFO size (changes
by anholt)
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-and-tested-by: Mario Kleiner <mario.kleiner.de@gmail.com> (v2)
2016-06-23 14:17:50 +08:00
|
|
|
|
|
|
|
/* HVS more than fifo_lines into frame for compositing? */
|
|
|
|
if (*vpos > fifo_lines) {
|
|
|
|
/*
|
|
|
|
* We are in active scanout and can get some meaningful results
|
|
|
|
* from HVS. The actual PV scanout can not trail behind more
|
|
|
|
* than fifo_lines as that is the fifo's capacity. Assume that
|
|
|
|
* in active scanout the HVS and PV work in lockstep wrt. HVS
|
|
|
|
* refilling the fifo and PV consuming from the fifo, ie.
|
|
|
|
* whenever the PV consumes and frees up a scanline in the
|
|
|
|
* fifo, the HVS will immediately refill it, therefore
|
|
|
|
* incrementing vpos. Therefore we choose HVS read position -
|
|
|
|
* fifo size in scanlines as a estimate of the real scanout
|
|
|
|
* position of the PV.
|
|
|
|
*/
|
|
|
|
*vpos -= fifo_lines + 1;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Less: This happens when we are in vblank and the HVS, after getting
|
|
|
|
* the VSTART restart signal from the PV, just started refilling its
|
|
|
|
* fifo with new lines from the top-most lines of the new framebuffers.
|
|
|
|
* The PV does not scan out in vblank, so does not remove lines from
|
|
|
|
* the fifo, so the fifo will be full quickly and the HVS has to pause.
|
|
|
|
* We can't get meaningful readings wrt. scanline position of the PV
|
|
|
|
* and need to make things up in a approximative but consistent way.
|
|
|
|
*/
|
2016-09-29 08:30:25 +08:00
|
|
|
vblank_lines = mode->vtotal - mode->vdisplay;
|
drm/vc4: Implement precise vblank timestamping.
Precise vblank timestamping is implemented via the
usual scanout position based method. On VC4 the
pixelvalves PV do not have a scanout position
register. Only the hardware video scaler HVS has a
similar register which describes which scanline for
the output is currently composited and stored in the
HVS fifo for later consumption by the PV.
This causes a problem in that the HVS runs at a much
faster clock (system clock / audio gate) than the PV
which runs at video mode dot clock, so the unless the
fifo between HVS and PV is full, the HVS will progress
faster in its observable read line position than video
scan rate, so the HVS position reading can't be directly
translated into a scanout position for timestamp correction.
Additionally when the PV is in vblank, it doesn't consume
from the fifo, so the fifo gets full very quickly and then
the HVS stops compositing until the PV enters active scanout
and starts consuming scanlines from the fifo again, making
new space for the HVS to composite.
Therefore a simple translation of HVS read position into
elapsed time since (or to) start of active scanout does
not work, but for the most interesting cases we can still
get useful and sufficiently accurate results:
1. The PV enters active scanout of a new frame with the
fifo of the HVS completely full, and the HVS can refill
any fifo line which gets consumed and thereby freed up by
the PV during active scanout very quickly. Therefore the
PV and HVS work effectively in lock-step during active
scanout with the fifo never having more than 1 scanline
freed up by the PV before it gets refilled. The PV's
real scanout position is therefore trailing the HVS
compositing position as scanoutpos = hvspos - fifosize
and we can get the true scanoutpos as HVS readpos minus
fifo size, so precise timestamping works while in active
scanout, except for the last few scanlines of the frame,
when the HVS reaches end of frame, stops compositing and
the PV catches up and drains the fifo. This special case
would only introduce minor errors though.
2. If we are in vblank, then we can only guess something
reasonable. If called from vblank irq, we assume the irq is
usually dispatched with minimum delay, so we can take a
timestamp taken at entry into the vblank irq handler as a
baseline and then add a full vblank duration until the
guessed start of active scanout. As irq dispatch is usually
pretty low latency this works with relatively low jitter and
good results.
If we aren't called from vblank then we could be anywhere
within the vblank interval, so we return a neutral result,
simply the current system timestamp, and hope for the best.
Measurement shows the generated timestamps to be rather precise,
and at least never off more than 1 vblank duration worst-case.
Limitations: Doesn't work well yet for interlaced video modes,
therefore disabled in interlaced mode for now.
v2: Use the DISPBASE registers to determine the FIFO size (changes
by anholt)
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-and-tested-by: Mario Kleiner <mario.kleiner.de@gmail.com> (v2)
2016-06-23 14:17:50 +08:00
|
|
|
|
drm/vblank: drop the mode argument from drm_calc_vbltimestamp_from_scanoutpos
If we restrict this helper to only kms drivers (which is the case) we
can look up the correct mode easily ourselves. But it's a bit tricky:
- All legacy drivers look at crtc->hwmode. But that is updated already
at the beginning of the modeset helper, which means when we disable
a pipe. Hence the final timestamps might be a bit off. But since
this is an existing bug I'm not going to change it, but just try to
be bug-for-bug compatible with the current code. This only applies
to radeon&amdgpu.
- i915 tries to get it perfect by updating crtc->hwmode when the pipe
is off (i.e. vblank->enabled = false).
- All other atomic drivers look at crtc->state->adjusted_mode. Those
that look at state->requested_mode simply don't adjust their mode,
so it's the same. That has two problems: Accessing crtc->state from
interrupt handling code is unsafe, and it's updated before we shut
down the pipe. For nonblocking modesets it's even worse.
For atomic drivers try to implement what i915 does. To do that we add
a new hwmode field to the vblank structure, and update it from
drm_calc_timestamping_constants(). For atomic drivers that's called
from the right spot by the helper library already, so all fine. But
for safety let's enforce that.
For legacy driver this function is only called at the end (oh the
fun), which is broken, so again let's not bother and just stay
bug-for-bug compatible.
The benefit is that we can use drm_calc_vbltimestamp_from_scanoutpos
directly to implement ->get_vblank_timestamp in every driver, deleting
a lot of code.
v2: Completely new approach, trying to mimick the i915 solution.
v3: Fixup kerneldoc.
v4: Drop the WARN_ON to check that the vblank is off, atomic helpers
currently unconditionally call this. Recomputing the same stuff should
be harmless.
v5: Fix typos and move misplaced hunks to the right patches (Neil).
v6: Undo hunk movement (kbuild).
Cc: Mario Kleiner <mario.kleiner@tuebingen.mpg.de>
Cc: Eric Anholt <eric@anholt.net>
Cc: Rob Clark <robdclark@gmail.com>
Cc: linux-arm-msm@vger.kernel.org
Cc: freedreno@lists.freedesktop.org
Cc: Alex Deucher <alexander.deucher@amd.com>
Cc: Christian König <christian.koenig@amd.com>
Cc: Ben Skeggs <bskeggs@redhat.com>
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
Acked-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@intel.com>
Link: http://patchwork.freedesktop.org/patch/msgid/20170509140329.24114-4-daniel.vetter@ffwll.ch
2017-05-09 22:03:28 +08:00
|
|
|
if (in_vblank_irq) {
|
drm/vc4: Implement precise vblank timestamping.
Precise vblank timestamping is implemented via the
usual scanout position based method. On VC4 the
pixelvalves PV do not have a scanout position
register. Only the hardware video scaler HVS has a
similar register which describes which scanline for
the output is currently composited and stored in the
HVS fifo for later consumption by the PV.
This causes a problem in that the HVS runs at a much
faster clock (system clock / audio gate) than the PV
which runs at video mode dot clock, so the unless the
fifo between HVS and PV is full, the HVS will progress
faster in its observable read line position than video
scan rate, so the HVS position reading can't be directly
translated into a scanout position for timestamp correction.
Additionally when the PV is in vblank, it doesn't consume
from the fifo, so the fifo gets full very quickly and then
the HVS stops compositing until the PV enters active scanout
and starts consuming scanlines from the fifo again, making
new space for the HVS to composite.
Therefore a simple translation of HVS read position into
elapsed time since (or to) start of active scanout does
not work, but for the most interesting cases we can still
get useful and sufficiently accurate results:
1. The PV enters active scanout of a new frame with the
fifo of the HVS completely full, and the HVS can refill
any fifo line which gets consumed and thereby freed up by
the PV during active scanout very quickly. Therefore the
PV and HVS work effectively in lock-step during active
scanout with the fifo never having more than 1 scanline
freed up by the PV before it gets refilled. The PV's
real scanout position is therefore trailing the HVS
compositing position as scanoutpos = hvspos - fifosize
and we can get the true scanoutpos as HVS readpos minus
fifo size, so precise timestamping works while in active
scanout, except for the last few scanlines of the frame,
when the HVS reaches end of frame, stops compositing and
the PV catches up and drains the fifo. This special case
would only introduce minor errors though.
2. If we are in vblank, then we can only guess something
reasonable. If called from vblank irq, we assume the irq is
usually dispatched with minimum delay, so we can take a
timestamp taken at entry into the vblank irq handler as a
baseline and then add a full vblank duration until the
guessed start of active scanout. As irq dispatch is usually
pretty low latency this works with relatively low jitter and
good results.
If we aren't called from vblank then we could be anywhere
within the vblank interval, so we return a neutral result,
simply the current system timestamp, and hope for the best.
Measurement shows the generated timestamps to be rather precise,
and at least never off more than 1 vblank duration worst-case.
Limitations: Doesn't work well yet for interlaced video modes,
therefore disabled in interlaced mode for now.
v2: Use the DISPBASE registers to determine the FIFO size (changes
by anholt)
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-and-tested-by: Mario Kleiner <mario.kleiner.de@gmail.com> (v2)
2016-06-23 14:17:50 +08:00
|
|
|
/*
|
|
|
|
* Assume the irq handler got called close to first
|
|
|
|
* line of vblank, so PV has about a full vblank
|
|
|
|
* scanlines to go, and as a base timestamp use the
|
|
|
|
* one taken at entry into vblank irq handler, so it
|
|
|
|
* is not affected by random delays due to lock
|
|
|
|
* contention on event_lock or vblank_time lock in
|
|
|
|
* the core.
|
|
|
|
*/
|
|
|
|
*vpos = -vblank_lines;
|
|
|
|
|
|
|
|
if (stime)
|
|
|
|
*stime = vc4_crtc->t_vblank;
|
|
|
|
if (etime)
|
|
|
|
*etime = vc4_crtc->t_vblank;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the HVS fifo is not yet full then we know for certain
|
|
|
|
* we are at the very beginning of vblank, as the hvs just
|
|
|
|
* started refilling, and the stime and etime timestamps
|
|
|
|
* truly correspond to start of vblank.
|
drm/vblank: drop the mode argument from drm_calc_vbltimestamp_from_scanoutpos
If we restrict this helper to only kms drivers (which is the case) we
can look up the correct mode easily ourselves. But it's a bit tricky:
- All legacy drivers look at crtc->hwmode. But that is updated already
at the beginning of the modeset helper, which means when we disable
a pipe. Hence the final timestamps might be a bit off. But since
this is an existing bug I'm not going to change it, but just try to
be bug-for-bug compatible with the current code. This only applies
to radeon&amdgpu.
- i915 tries to get it perfect by updating crtc->hwmode when the pipe
is off (i.e. vblank->enabled = false).
- All other atomic drivers look at crtc->state->adjusted_mode. Those
that look at state->requested_mode simply don't adjust their mode,
so it's the same. That has two problems: Accessing crtc->state from
interrupt handling code is unsafe, and it's updated before we shut
down the pipe. For nonblocking modesets it's even worse.
For atomic drivers try to implement what i915 does. To do that we add
a new hwmode field to the vblank structure, and update it from
drm_calc_timestamping_constants(). For atomic drivers that's called
from the right spot by the helper library already, so all fine. But
for safety let's enforce that.
For legacy driver this function is only called at the end (oh the
fun), which is broken, so again let's not bother and just stay
bug-for-bug compatible.
The benefit is that we can use drm_calc_vbltimestamp_from_scanoutpos
directly to implement ->get_vblank_timestamp in every driver, deleting
a lot of code.
v2: Completely new approach, trying to mimick the i915 solution.
v3: Fixup kerneldoc.
v4: Drop the WARN_ON to check that the vblank is off, atomic helpers
currently unconditionally call this. Recomputing the same stuff should
be harmless.
v5: Fix typos and move misplaced hunks to the right patches (Neil).
v6: Undo hunk movement (kbuild).
Cc: Mario Kleiner <mario.kleiner@tuebingen.mpg.de>
Cc: Eric Anholt <eric@anholt.net>
Cc: Rob Clark <robdclark@gmail.com>
Cc: linux-arm-msm@vger.kernel.org
Cc: freedreno@lists.freedesktop.org
Cc: Alex Deucher <alexander.deucher@amd.com>
Cc: Christian König <christian.koenig@amd.com>
Cc: Ben Skeggs <bskeggs@redhat.com>
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
Acked-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@intel.com>
Link: http://patchwork.freedesktop.org/patch/msgid/20170509140329.24114-4-daniel.vetter@ffwll.ch
2017-05-09 22:03:28 +08:00
|
|
|
*
|
|
|
|
* Unfortunately there's no way to report this to upper levels
|
|
|
|
* and make it more useful.
|
drm/vc4: Implement precise vblank timestamping.
Precise vblank timestamping is implemented via the
usual scanout position based method. On VC4 the
pixelvalves PV do not have a scanout position
register. Only the hardware video scaler HVS has a
similar register which describes which scanline for
the output is currently composited and stored in the
HVS fifo for later consumption by the PV.
This causes a problem in that the HVS runs at a much
faster clock (system clock / audio gate) than the PV
which runs at video mode dot clock, so the unless the
fifo between HVS and PV is full, the HVS will progress
faster in its observable read line position than video
scan rate, so the HVS position reading can't be directly
translated into a scanout position for timestamp correction.
Additionally when the PV is in vblank, it doesn't consume
from the fifo, so the fifo gets full very quickly and then
the HVS stops compositing until the PV enters active scanout
and starts consuming scanlines from the fifo again, making
new space for the HVS to composite.
Therefore a simple translation of HVS read position into
elapsed time since (or to) start of active scanout does
not work, but for the most interesting cases we can still
get useful and sufficiently accurate results:
1. The PV enters active scanout of a new frame with the
fifo of the HVS completely full, and the HVS can refill
any fifo line which gets consumed and thereby freed up by
the PV during active scanout very quickly. Therefore the
PV and HVS work effectively in lock-step during active
scanout with the fifo never having more than 1 scanline
freed up by the PV before it gets refilled. The PV's
real scanout position is therefore trailing the HVS
compositing position as scanoutpos = hvspos - fifosize
and we can get the true scanoutpos as HVS readpos minus
fifo size, so precise timestamping works while in active
scanout, except for the last few scanlines of the frame,
when the HVS reaches end of frame, stops compositing and
the PV catches up and drains the fifo. This special case
would only introduce minor errors though.
2. If we are in vblank, then we can only guess something
reasonable. If called from vblank irq, we assume the irq is
usually dispatched with minimum delay, so we can take a
timestamp taken at entry into the vblank irq handler as a
baseline and then add a full vblank duration until the
guessed start of active scanout. As irq dispatch is usually
pretty low latency this works with relatively low jitter and
good results.
If we aren't called from vblank then we could be anywhere
within the vblank interval, so we return a neutral result,
simply the current system timestamp, and hope for the best.
Measurement shows the generated timestamps to be rather precise,
and at least never off more than 1 vblank duration worst-case.
Limitations: Doesn't work well yet for interlaced video modes,
therefore disabled in interlaced mode for now.
v2: Use the DISPBASE registers to determine the FIFO size (changes
by anholt)
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-and-tested-by: Mario Kleiner <mario.kleiner.de@gmail.com> (v2)
2016-06-23 14:17:50 +08:00
|
|
|
*/
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* No clue where we are inside vblank. Return a vpos of zero,
|
|
|
|
* which will cause calling code to just return the etime
|
|
|
|
* timestamp uncorrected. At least this is no worse than the
|
|
|
|
* standard fallback.
|
|
|
|
*/
|
|
|
|
*vpos = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-09-03 16:00:47 +08:00
|
|
|
static u32 vc4_get_fifo_full_level(struct vc4_crtc *vc4_crtc, u32 format)
|
2015-03-03 05:01:12 +08:00
|
|
|
{
|
2020-09-03 16:01:09 +08:00
|
|
|
const struct vc4_crtc_data *crtc_data = vc4_crtc_to_vc4_crtc_data(vc4_crtc);
|
2020-09-03 16:00:47 +08:00
|
|
|
const struct vc4_pv_data *pv_data = vc4_crtc_to_vc4_pv_data(vc4_crtc);
|
2021-03-19 00:13:28 +08:00
|
|
|
struct vc4_dev *vc4 = to_vc4_dev(vc4_crtc->base.dev);
|
2020-09-03 16:00:47 +08:00
|
|
|
u32 fifo_len_bytes = pv_data->fifo_depth;
|
2015-03-03 05:01:12 +08:00
|
|
|
|
2020-09-03 16:00:47 +08:00
|
|
|
/*
|
|
|
|
* Pixels are pulled from the HVS if the number of bytes is
|
|
|
|
* lower than the FIFO full level.
|
|
|
|
*
|
|
|
|
* The latency of the pixel fetch mechanism is 6 pixels, so we
|
|
|
|
* need to convert those 6 pixels in bytes, depending on the
|
|
|
|
* format, and then subtract that from the length of the FIFO
|
|
|
|
* to make sure we never end up in a situation where the FIFO
|
|
|
|
* is full.
|
|
|
|
*/
|
2015-03-03 05:01:12 +08:00
|
|
|
switch (format) {
|
|
|
|
case PV_CONTROL_FORMAT_DSIV_16:
|
|
|
|
case PV_CONTROL_FORMAT_DSIC_16:
|
2020-05-27 23:47:57 +08:00
|
|
|
return fifo_len_bytes - 2 * HVS_FIFO_LATENCY_PIX;
|
2015-03-03 05:01:12 +08:00
|
|
|
case PV_CONTROL_FORMAT_DSIV_18:
|
|
|
|
return fifo_len_bytes - 14;
|
|
|
|
case PV_CONTROL_FORMAT_24:
|
|
|
|
case PV_CONTROL_FORMAT_DSIV_24:
|
|
|
|
default:
|
2020-09-03 16:01:09 +08:00
|
|
|
/*
|
|
|
|
* For some reason, the pixelvalve4 doesn't work with
|
|
|
|
* the usual formula and will only work with 32.
|
|
|
|
*/
|
|
|
|
if (crtc_data->hvs_output == 5)
|
|
|
|
return 32;
|
|
|
|
|
2021-03-19 00:13:28 +08:00
|
|
|
/*
|
|
|
|
* It looks like in some situations, we will overflow
|
|
|
|
* the PixelValve FIFO (with the bit 10 of PV stat being
|
|
|
|
* set) and stall the HVS / PV, eventually resulting in
|
|
|
|
* a page flip timeout.
|
|
|
|
*
|
|
|
|
* Displaying the video overlay during a playback with
|
|
|
|
* Kodi on an RPi3 seems to be a great solution with a
|
|
|
|
* failure rate around 50%.
|
|
|
|
*
|
|
|
|
* Removing 1 from the FIFO full level however
|
|
|
|
* seems to completely remove that issue.
|
|
|
|
*/
|
2022-06-10 19:51:37 +08:00
|
|
|
if (!vc4->is_vc5)
|
2021-03-19 00:13:28 +08:00
|
|
|
return fifo_len_bytes - 3 * HVS_FIFO_LATENCY_PIX - 1;
|
|
|
|
|
2020-05-27 23:47:57 +08:00
|
|
|
return fifo_len_bytes - 3 * HVS_FIFO_LATENCY_PIX;
|
2015-03-03 05:01:12 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-03 16:00:48 +08:00
|
|
|
static u32 vc4_crtc_get_fifo_full_level_bits(struct vc4_crtc *vc4_crtc,
|
|
|
|
u32 format)
|
|
|
|
{
|
|
|
|
u32 level = vc4_get_fifo_full_level(vc4_crtc, format);
|
2020-09-03 16:01:09 +08:00
|
|
|
u32 ret = 0;
|
|
|
|
|
|
|
|
ret |= VC4_SET_FIELD((level >> 6),
|
|
|
|
PV5_CONTROL_FIFO_LEVEL_HIGH);
|
2020-09-03 16:00:48 +08:00
|
|
|
|
2020-09-03 16:01:09 +08:00
|
|
|
return ret | VC4_SET_FIELD(level & 0x3f,
|
|
|
|
PV_CONTROL_FIFO_LEVEL);
|
2020-09-03 16:00:48 +08:00
|
|
|
}
|
|
|
|
|
2015-03-03 05:01:12 +08:00
|
|
|
/*
|
2016-12-15 03:46:15 +08:00
|
|
|
* Returns the encoder attached to the CRTC.
|
|
|
|
*
|
|
|
|
* VC4 can only scan out to one encoder at a time, while the DRM core
|
|
|
|
* allows drivers to push pixels to more than one encoder from the
|
|
|
|
* same CRTC.
|
2015-03-03 05:01:12 +08:00
|
|
|
*/
|
2021-10-25 23:28:56 +08:00
|
|
|
struct drm_encoder *vc4_get_crtc_encoder(struct drm_crtc *crtc,
|
2021-10-25 23:28:58 +08:00
|
|
|
struct drm_crtc_state *state)
|
2015-03-03 05:01:12 +08:00
|
|
|
{
|
2021-10-25 23:28:58 +08:00
|
|
|
struct drm_encoder *encoder;
|
2021-05-07 23:05:07 +08:00
|
|
|
|
2021-10-25 23:28:58 +08:00
|
|
|
WARN_ON(hweight32(state->encoder_mask) > 1);
|
2021-05-07 23:05:07 +08:00
|
|
|
|
2021-10-25 23:28:58 +08:00
|
|
|
drm_for_each_encoder_mask(encoder, crtc->dev, state->encoder_mask)
|
|
|
|
return encoder;
|
2015-03-03 05:01:12 +08:00
|
|
|
|
2016-12-15 03:46:15 +08:00
|
|
|
return NULL;
|
2015-03-03 05:01:12 +08:00
|
|
|
}
|
|
|
|
|
2020-09-03 16:00:52 +08:00
|
|
|
static void vc4_crtc_pixelvalve_reset(struct drm_crtc *crtc)
|
|
|
|
{
|
|
|
|
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
|
2022-08-25 00:13:26 +08:00
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
int idx;
|
|
|
|
|
|
|
|
if (!drm_dev_enter(dev, &idx))
|
|
|
|
return;
|
2020-09-03 16:00:52 +08:00
|
|
|
|
|
|
|
/* The PV needs to be disabled before it can be flushed */
|
|
|
|
CRTC_WRITE(PV_CONTROL, CRTC_READ(PV_CONTROL) & ~PV_CONTROL_EN);
|
|
|
|
CRTC_WRITE(PV_CONTROL, CRTC_READ(PV_CONTROL) | PV_CONTROL_FIFO_CLR);
|
2022-08-25 00:13:26 +08:00
|
|
|
|
|
|
|
drm_dev_exit(idx);
|
2020-09-03 16:00:52 +08:00
|
|
|
}
|
|
|
|
|
2021-10-25 23:28:57 +08:00
|
|
|
static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_encoder *encoder,
|
|
|
|
struct drm_atomic_state *state)
|
2015-03-03 05:01:12 +08:00
|
|
|
{
|
2020-09-03 16:01:09 +08:00
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct vc4_dev *vc4 = to_vc4_dev(dev);
|
2016-12-15 03:46:15 +08:00
|
|
|
struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);
|
2015-03-03 05:01:12 +08:00
|
|
|
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
|
2020-09-03 16:00:39 +08:00
|
|
|
const struct vc4_pv_data *pv_data = vc4_crtc_to_vc4_pv_data(vc4_crtc);
|
2021-05-07 23:05:06 +08:00
|
|
|
struct drm_crtc_state *crtc_state = crtc->state;
|
|
|
|
struct drm_display_mode *mode = &crtc_state->adjusted_mode;
|
2015-03-03 05:01:12 +08:00
|
|
|
bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE;
|
2022-06-13 22:48:00 +08:00
|
|
|
bool is_hdmi = vc4_encoder->type == VC4_ENCODER_TYPE_HDMI0 ||
|
|
|
|
vc4_encoder->type == VC4_ENCODER_TYPE_HDMI1;
|
|
|
|
u32 pixel_rep = ((mode->flags & DRM_MODE_FLAG_DBLCLK) && !is_hdmi) ? 2 : 1;
|
2016-12-15 03:46:15 +08:00
|
|
|
bool is_dsi = (vc4_encoder->type == VC4_ENCODER_TYPE_DSI0 ||
|
|
|
|
vc4_encoder->type == VC4_ENCODER_TYPE_DSI1);
|
2022-06-13 22:47:40 +08:00
|
|
|
bool is_dsi1 = vc4_encoder->type == VC4_ENCODER_TYPE_DSI1;
|
2022-12-07 19:53:24 +08:00
|
|
|
bool is_vec = vc4_encoder->type == VC4_ENCODER_TYPE_VEC;
|
2022-06-13 22:47:40 +08:00
|
|
|
u32 format = is_dsi1 ? PV_CONTROL_FORMAT_DSIV_24 : PV_CONTROL_FORMAT_24;
|
2020-09-03 16:00:39 +08:00
|
|
|
u8 ppc = pv_data->pixels_per_clock;
|
2022-12-07 19:53:24 +08:00
|
|
|
|
|
|
|
u16 vert_bp = mode->crtc_vtotal - mode->crtc_vsync_end;
|
|
|
|
u16 vert_sync = mode->crtc_vsync_end - mode->crtc_vsync_start;
|
|
|
|
u16 vert_fp = mode->crtc_vsync_start - mode->crtc_vdisplay;
|
|
|
|
|
2020-09-03 16:00:53 +08:00
|
|
|
bool debug_dump_regs = false;
|
2022-08-25 00:13:26 +08:00
|
|
|
int idx;
|
|
|
|
|
|
|
|
if (!drm_dev_enter(dev, &idx))
|
|
|
|
return;
|
2020-09-03 16:00:53 +08:00
|
|
|
|
|
|
|
if (debug_dump_regs) {
|
|
|
|
struct drm_printer p = drm_info_printer(&vc4_crtc->pdev->dev);
|
|
|
|
dev_info(&vc4_crtc->pdev->dev, "CRTC %d regs before:\n",
|
|
|
|
drm_crtc_index(crtc));
|
|
|
|
drm_print_regset32(&p, &vc4_crtc->regset);
|
|
|
|
}
|
2015-03-03 05:01:12 +08:00
|
|
|
|
2020-09-03 16:00:52 +08:00
|
|
|
vc4_crtc_pixelvalve_reset(crtc);
|
2015-03-03 05:01:12 +08:00
|
|
|
|
|
|
|
CRTC_WRITE(PV_HORZA,
|
2020-09-03 16:00:39 +08:00
|
|
|
VC4_SET_FIELD((mode->htotal - mode->hsync_end) * pixel_rep / ppc,
|
2015-03-03 05:01:12 +08:00
|
|
|
PV_HORZA_HBP) |
|
2020-09-03 16:00:39 +08:00
|
|
|
VC4_SET_FIELD((mode->hsync_end - mode->hsync_start) * pixel_rep / ppc,
|
2015-03-03 05:01:12 +08:00
|
|
|
PV_HORZA_HSYNC));
|
2020-09-03 16:00:39 +08:00
|
|
|
|
2015-03-03 05:01:12 +08:00
|
|
|
CRTC_WRITE(PV_HORZB,
|
2020-09-03 16:00:39 +08:00
|
|
|
VC4_SET_FIELD((mode->hsync_start - mode->hdisplay) * pixel_rep / ppc,
|
2015-03-03 05:01:12 +08:00
|
|
|
PV_HORZB_HFP) |
|
2020-09-03 16:00:39 +08:00
|
|
|
VC4_SET_FIELD(mode->hdisplay * pixel_rep / ppc,
|
|
|
|
PV_HORZB_HACTIVE));
|
2015-03-03 05:01:12 +08:00
|
|
|
|
|
|
|
if (interlace) {
|
2022-12-07 19:53:24 +08:00
|
|
|
bool odd_field_first = false;
|
|
|
|
u32 field_delay = mode->htotal * pixel_rep / (2 * ppc);
|
|
|
|
u16 vert_bp_even = vert_bp;
|
|
|
|
u16 vert_fp_even = vert_fp;
|
|
|
|
|
|
|
|
if (is_vec) {
|
|
|
|
/* VEC (composite output) */
|
|
|
|
++field_delay;
|
|
|
|
if (mode->htotal == 858) {
|
|
|
|
/* 525-line mode (NTSC or PAL-M) */
|
|
|
|
odd_field_first = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (odd_field_first)
|
|
|
|
++vert_fp_even;
|
|
|
|
else
|
|
|
|
++vert_bp;
|
|
|
|
|
2015-03-03 05:01:12 +08:00
|
|
|
CRTC_WRITE(PV_VERTA_EVEN,
|
2022-12-07 19:53:24 +08:00
|
|
|
VC4_SET_FIELD(vert_bp_even, PV_VERTA_VBP) |
|
|
|
|
VC4_SET_FIELD(vert_sync, PV_VERTA_VSYNC));
|
2015-03-03 05:01:12 +08:00
|
|
|
CRTC_WRITE(PV_VERTB_EVEN,
|
2022-12-07 19:53:24 +08:00
|
|
|
VC4_SET_FIELD(vert_fp_even, PV_VERTB_VFP) |
|
2016-09-29 08:30:25 +08:00
|
|
|
VC4_SET_FIELD(mode->crtc_vdisplay, PV_VERTB_VACTIVE));
|
|
|
|
|
2022-12-07 19:53:24 +08:00
|
|
|
/* We set up first field even mode for HDMI and VEC's PAL.
|
|
|
|
* For NTSC, we need first field odd.
|
2016-09-29 08:30:25 +08:00
|
|
|
*/
|
|
|
|
CRTC_WRITE(PV_V_CONTROL,
|
|
|
|
PV_VCONTROL_CONTINUOUS |
|
2016-12-15 03:46:15 +08:00
|
|
|
(is_dsi ? PV_VCONTROL_DSI : 0) |
|
2016-09-29 08:30:25 +08:00
|
|
|
PV_VCONTROL_INTERLACE |
|
2022-12-07 19:53:24 +08:00
|
|
|
(odd_field_first
|
|
|
|
? PV_VCONTROL_ODD_FIRST
|
|
|
|
: VC4_SET_FIELD(field_delay,
|
|
|
|
PV_VCONTROL_ODD_DELAY)));
|
|
|
|
CRTC_WRITE(PV_VSYNCD_EVEN,
|
|
|
|
(odd_field_first ? field_delay : 0));
|
2016-09-29 08:30:25 +08:00
|
|
|
} else {
|
2016-12-15 03:46:15 +08:00
|
|
|
CRTC_WRITE(PV_V_CONTROL,
|
|
|
|
PV_VCONTROL_CONTINUOUS |
|
|
|
|
(is_dsi ? PV_VCONTROL_DSI : 0));
|
2022-12-07 19:53:24 +08:00
|
|
|
CRTC_WRITE(PV_VSYNCD_EVEN, 0);
|
2015-03-03 05:01:12 +08:00
|
|
|
}
|
|
|
|
|
2022-12-07 19:53:24 +08:00
|
|
|
CRTC_WRITE(PV_VERTA,
|
|
|
|
VC4_SET_FIELD(vert_bp, PV_VERTA_VBP) |
|
|
|
|
VC4_SET_FIELD(vert_sync, PV_VERTA_VSYNC));
|
|
|
|
CRTC_WRITE(PV_VERTB,
|
|
|
|
VC4_SET_FIELD(vert_fp, PV_VERTB_VFP) |
|
|
|
|
VC4_SET_FIELD(mode->crtc_vdisplay, PV_VERTB_VACTIVE));
|
|
|
|
|
2020-05-27 23:47:58 +08:00
|
|
|
if (is_dsi)
|
|
|
|
CRTC_WRITE(PV_HACT_ACT, mode->hdisplay * pixel_rep);
|
2015-03-03 05:01:12 +08:00
|
|
|
|
2022-06-10 19:51:37 +08:00
|
|
|
if (vc4->is_vc5)
|
2020-09-03 16:01:09 +08:00
|
|
|
CRTC_WRITE(PV_MUX_CFG,
|
|
|
|
VC4_SET_FIELD(PV_MUX_CFG_RGB_PIXEL_MUX_MODE_NO_SWAP,
|
|
|
|
PV_MUX_CFG_RGB_PIXEL_MUX_MODE));
|
|
|
|
|
2020-09-03 16:01:03 +08:00
|
|
|
CRTC_WRITE(PV_CONTROL, PV_CONTROL_FIFO_CLR |
|
2020-09-03 16:00:48 +08:00
|
|
|
vc4_crtc_get_fifo_full_level_bits(vc4_crtc, format) |
|
2015-03-03 05:01:12 +08:00
|
|
|
VC4_SET_FIELD(format, PV_CONTROL_FORMAT) |
|
2016-09-30 06:34:44 +08:00
|
|
|
VC4_SET_FIELD(pixel_rep - 1, PV_CONTROL_PIXEL_REP) |
|
2015-03-03 05:01:12 +08:00
|
|
|
PV_CONTROL_CLR_AT_START |
|
|
|
|
PV_CONTROL_TRIGGER_UNDERFLOW |
|
|
|
|
PV_CONTROL_WAIT_HSTART |
|
2016-12-15 03:46:15 +08:00
|
|
|
VC4_SET_FIELD(vc4_encoder->clock_select,
|
2020-09-03 16:00:44 +08:00
|
|
|
PV_CONTROL_CLK_SELECT));
|
2018-07-03 15:50:22 +08:00
|
|
|
|
|
|
|
if (debug_dump_regs) {
|
2019-02-21 05:03:38 +08:00
|
|
|
struct drm_printer p = drm_info_printer(&vc4_crtc->pdev->dev);
|
2020-09-03 16:00:53 +08:00
|
|
|
dev_info(&vc4_crtc->pdev->dev, "CRTC %d regs after:\n",
|
2019-02-21 05:03:38 +08:00
|
|
|
drm_crtc_index(crtc));
|
|
|
|
drm_print_regset32(&p, &vc4_crtc->regset);
|
2018-07-03 15:50:22 +08:00
|
|
|
}
|
2022-08-25 00:13:26 +08:00
|
|
|
|
|
|
|
drm_dev_exit(idx);
|
2020-09-03 16:00:53 +08:00
|
|
|
}
|
2018-07-03 15:50:22 +08:00
|
|
|
|
2015-03-03 05:01:12 +08:00
|
|
|
static void require_hvs_enabled(struct drm_device *dev)
|
|
|
|
{
|
|
|
|
struct vc4_dev *vc4 = to_vc4_dev(dev);
|
2022-03-31 22:37:44 +08:00
|
|
|
struct vc4_hvs *hvs = vc4->hvs;
|
2015-03-03 05:01:12 +08:00
|
|
|
|
|
|
|
WARN_ON_ONCE((HVS_READ(SCALER_DISPCTRL) & SCALER_DISPCTRL_ENABLE) !=
|
|
|
|
SCALER_DISPCTRL_ENABLE);
|
|
|
|
}
|
|
|
|
|
2020-12-15 23:42:36 +08:00
|
|
|
static int vc4_crtc_disable(struct drm_crtc *crtc,
|
2021-05-07 23:05:08 +08:00
|
|
|
struct drm_encoder *encoder,
|
2020-12-15 23:42:36 +08:00
|
|
|
struct drm_atomic_state *state,
|
|
|
|
unsigned int channel)
|
2015-03-03 05:01:12 +08:00
|
|
|
{
|
2020-09-03 16:01:00 +08:00
|
|
|
struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);
|
2020-09-03 16:01:06 +08:00
|
|
|
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
|
|
|
|
struct drm_device *dev = crtc->dev;
|
2022-03-31 22:37:44 +08:00
|
|
|
struct vc4_dev *vc4 = to_vc4_dev(dev);
|
2022-08-25 00:13:26 +08:00
|
|
|
int idx, ret;
|
|
|
|
|
|
|
|
if (!drm_dev_enter(dev, &idx))
|
|
|
|
return -ENODEV;
|
2020-06-11 21:36:47 +08:00
|
|
|
|
2020-06-11 21:36:54 +08:00
|
|
|
CRTC_WRITE(PV_V_CONTROL,
|
|
|
|
CRTC_READ(PV_V_CONTROL) & ~PV_VCONTROL_VIDEN);
|
|
|
|
ret = wait_for(!(CRTC_READ(PV_V_CONTROL) & PV_VCONTROL_VIDEN), 1);
|
|
|
|
WARN_ONCE(ret, "Timeout waiting for !PV_VCONTROL_VIDEN\n");
|
2015-03-03 05:01:12 +08:00
|
|
|
|
2020-09-03 16:01:01 +08:00
|
|
|
/*
|
|
|
|
* This delay is needed to avoid to get a pixel stuck in an
|
|
|
|
* unflushable FIFO between the pixelvalve and the HDMI
|
|
|
|
* controllers on the BCM2711.
|
|
|
|
*
|
|
|
|
* Timing is fairly sensitive here, so mdelay is the safest
|
|
|
|
* approach.
|
|
|
|
*
|
|
|
|
* If it was to be reworked, the stuck pixel happens on a
|
|
|
|
* BCM2711 when changing mode with a good probability, so a
|
|
|
|
* script that changes mode on a regular basis should trigger
|
|
|
|
* the bug after less than 10 attempts. It manifests itself with
|
|
|
|
* every pixels being shifted by one to the right, and thus the
|
|
|
|
* last pixel of a line actually being displayed as the first
|
|
|
|
* pixel on the next line.
|
|
|
|
*/
|
|
|
|
mdelay(20);
|
|
|
|
|
2020-09-03 16:01:06 +08:00
|
|
|
if (vc4_encoder && vc4_encoder->post_crtc_disable)
|
2020-12-15 23:42:36 +08:00
|
|
|
vc4_encoder->post_crtc_disable(encoder, state);
|
2020-09-03 16:01:00 +08:00
|
|
|
|
2020-09-03 16:01:02 +08:00
|
|
|
vc4_crtc_pixelvalve_reset(crtc);
|
2022-03-31 22:37:44 +08:00
|
|
|
vc4_hvs_stop_channel(vc4->hvs, channel);
|
2017-06-16 16:30:33 +08:00
|
|
|
|
2020-09-03 16:01:06 +08:00
|
|
|
if (vc4_encoder && vc4_encoder->post_crtc_powerdown)
|
2020-12-15 23:42:36 +08:00
|
|
|
vc4_encoder->post_crtc_powerdown(encoder, state);
|
2020-09-03 16:01:00 +08:00
|
|
|
|
2022-08-25 00:13:26 +08:00
|
|
|
drm_dev_exit(idx);
|
|
|
|
|
2020-09-03 16:01:06 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-09-03 16:01:07 +08:00
|
|
|
int vc4_crtc_disable_at_boot(struct drm_crtc *crtc)
|
|
|
|
{
|
|
|
|
struct drm_device *drm = crtc->dev;
|
2022-03-31 22:37:44 +08:00
|
|
|
struct vc4_dev *vc4 = to_vc4_dev(drm);
|
2020-09-03 16:01:07 +08:00
|
|
|
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
|
2021-05-07 23:05:08 +08:00
|
|
|
enum vc4_encoder_type encoder_type;
|
|
|
|
const struct vc4_pv_data *pv_data;
|
|
|
|
struct drm_encoder *encoder;
|
2021-09-24 02:50:13 +08:00
|
|
|
struct vc4_hdmi *vc4_hdmi;
|
2021-05-07 23:05:08 +08:00
|
|
|
unsigned encoder_sel;
|
2020-09-03 16:01:07 +08:00
|
|
|
int channel;
|
2021-09-24 02:50:13 +08:00
|
|
|
int ret;
|
2020-09-03 16:01:07 +08:00
|
|
|
|
|
|
|
if (!(of_device_is_compatible(vc4_crtc->pdev->dev.of_node,
|
|
|
|
"brcm,bcm2711-pixelvalve2") ||
|
|
|
|
of_device_is_compatible(vc4_crtc->pdev->dev.of_node,
|
|
|
|
"brcm,bcm2711-pixelvalve4")))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!(CRTC_READ(PV_CONTROL) & PV_CONTROL_EN))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!(CRTC_READ(PV_V_CONTROL) & PV_VCONTROL_VIDEN))
|
|
|
|
return 0;
|
|
|
|
|
2022-03-31 22:37:44 +08:00
|
|
|
channel = vc4_hvs_get_fifo_from_output(vc4->hvs, vc4_crtc->data->hvs_output);
|
2020-09-03 16:01:07 +08:00
|
|
|
if (channel < 0)
|
|
|
|
return 0;
|
|
|
|
|
2021-05-07 23:05:08 +08:00
|
|
|
encoder_sel = VC4_GET_FIELD(CRTC_READ(PV_CONTROL), PV_CONTROL_CLK_SELECT);
|
|
|
|
if (WARN_ON(encoder_sel != 0))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
pv_data = vc4_crtc_to_vc4_pv_data(vc4_crtc);
|
|
|
|
encoder_type = pv_data->encoder_types[encoder_sel];
|
2022-12-01 23:11:46 +08:00
|
|
|
encoder = vc4_find_encoder_by_type(drm, encoder_type);
|
2021-05-07 23:05:08 +08:00
|
|
|
if (WARN_ON(!encoder))
|
|
|
|
return 0;
|
|
|
|
|
2021-09-24 02:50:13 +08:00
|
|
|
vc4_hdmi = encoder_to_vc4_hdmi(encoder);
|
|
|
|
ret = pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = vc4_crtc_disable(crtc, encoder, NULL, channel);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
drm/vc4: crtc: Fix runtime_pm reference counting
At boot on the BCM2711, if the HDMI controllers are running, the CRTC
driver will disable itself and its associated HDMI controller to work
around a hardware bug that would leave some pixels stuck in a FIFO.
In order to avoid that issue, we need to run some operations in lockstep
between the CRTC and HDMI controller, and we need to make sure the HDMI
controller will be powered properly.
However, since we haven't enabled it through KMS, the runtime_pm state
is off at this point so we need to make sure the device is powered
through pm_runtime_resume_and_get, and once the operations are complete,
we call pm_runtime_put.
However, the HDMI controller will do that itself in its
post_crtc_powerdown, which means we'll end up calling pm_runtime_put for
a single pm_runtime_get, throwing the reference counting off. Let's
remove the pm_runtime_put call in the CRTC code in order to have the
proper counting.
Fixes: bca10db67bda ("drm/vc4: crtc: Make sure the HDMI controller is powered when disabling")
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Reviewed-by: Javier Martinez Canillas <javierm@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20220203102003.1114673-1-maxime@cerno.tech
2022-02-03 18:20:03 +08:00
|
|
|
/*
|
|
|
|
* post_crtc_powerdown will have called pm_runtime_put, so we
|
|
|
|
* don't need it here otherwise we'll get the reference counting
|
|
|
|
* wrong.
|
|
|
|
*/
|
2021-09-24 02:50:13 +08:00
|
|
|
|
|
|
|
return 0;
|
2020-09-03 16:01:07 +08:00
|
|
|
}
|
|
|
|
|
2022-07-12 01:38:44 +08:00
|
|
|
void vc4_crtc_send_vblank(struct drm_crtc *crtc)
|
|
|
|
{
|
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
if (!crtc->state || !crtc->state->event)
|
|
|
|
return;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&dev->event_lock, flags);
|
|
|
|
drm_crtc_send_vblank_event(crtc, crtc->state->event);
|
|
|
|
crtc->state->event = NULL;
|
|
|
|
spin_unlock_irqrestore(&dev->event_lock, flags);
|
|
|
|
}
|
|
|
|
|
2020-09-03 16:01:06 +08:00
|
|
|
static void vc4_crtc_atomic_disable(struct drm_crtc *crtc,
|
drm/atomic: Pass the full state to CRTC atomic enable/disable
If the CRTC driver ever needs to access the full DRM state, it can't do so
at atomic_enable / atomic_disable time since drm_atomic_helper_swap_state
will have cleared the pointer from the struct drm_crtc_state to the struct
drm_atomic_state before calling those hooks.
In order to allow that, let's pass the full DRM state to atomic_enable and
atomic_disable. The conversion was done using the coccinelle script below,
built tested on all the drivers and actually tested on vc4.
virtual report
@@
struct drm_crtc_helper_funcs *FUNCS;
identifier dev, state;
identifier crtc, crtc_state;
@@
disable_outputs(struct drm_device *dev, struct drm_atomic_state *state)
{
<...
- FUNCS->atomic_disable(crtc, crtc_state);
+ FUNCS->atomic_disable(crtc, state);
...>
}
@@
struct drm_crtc_helper_funcs *FUNCS;
identifier dev, state;
identifier crtc, crtc_state;
@@
drm_atomic_helper_commit_modeset_enables(struct drm_device *dev, struct drm_atomic_state *state)
{
<...
- FUNCS->atomic_enable(crtc, crtc_state);
+ FUNCS->atomic_enable(crtc, state);
...>
}
@@
identifier crtc, old_state;
@@
struct drm_crtc_helper_funcs {
...
- void (*atomic_enable)(struct drm_crtc *crtc, struct drm_crtc_state *old_state);
+ void (*atomic_enable)(struct drm_crtc *crtc, struct drm_atomic_state *state);
...
- void (*atomic_disable)(struct drm_crtc *crtc, struct drm_crtc_state *old_state);
+ void (*atomic_disable)(struct drm_crtc *crtc, struct drm_atomic_state *state);
...
}
@ crtc_atomic_func @
identifier helpers;
identifier func;
@@
(
static struct drm_crtc_helper_funcs helpers = {
...,
.atomic_enable = func,
...,
};
|
static struct drm_crtc_helper_funcs helpers = {
...,
.atomic_disable = func,
...,
};
)
@ ignores_old_state @
identifier crtc_atomic_func.func;
identifier crtc, old_state;
@@
void func(struct drm_crtc *crtc,
struct drm_crtc_state *old_state)
{
... when != old_state
}
@ adds_old_state depends on crtc_atomic_func && !ignores_old_state @
identifier crtc_atomic_func.func;
identifier crtc, old_state;
@@
void func(struct drm_crtc *crtc, struct drm_crtc_state *old_state)
{
+ struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, crtc);
...
}
@ depends on crtc_atomic_func @
identifier crtc_atomic_func.func;
expression E;
type T;
@@
void func(...)
{
...
- T state = E;
+ T crtc_state = E;
<+...
- state
+ crtc_state
...+>
}
@ depends on crtc_atomic_func @
identifier crtc_atomic_func.func;
type T;
@@
void func(...)
{
...
- T state;
+ T crtc_state;
<+...
- state
+ crtc_state
...+>
}
@ depends on crtc_atomic_func @
identifier crtc_atomic_func.func;
identifier old_state;
identifier crtc;
@@
void func(struct drm_crtc *crtc,
- struct drm_crtc_state *old_state
+ struct drm_atomic_state *state
)
{ ... }
@ include depends on adds_old_state @
@@
#include <drm/drm_atomic.h>
@ no_include depends on !include && adds_old_state @
@@
+ #include <drm/drm_atomic.h>
#include <drm/...>
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: https://patchwork.freedesktop.org/patch/msgid/845aa10ef171fc0ea060495efef142a0c13f7870.1602161031.git-series.maxime@cerno.tech
2020-10-08 20:44:08 +08:00
|
|
|
struct drm_atomic_state *state)
|
2020-09-03 16:01:06 +08:00
|
|
|
{
|
drm/atomic: Pass the full state to CRTC atomic enable/disable
If the CRTC driver ever needs to access the full DRM state, it can't do so
at atomic_enable / atomic_disable time since drm_atomic_helper_swap_state
will have cleared the pointer from the struct drm_crtc_state to the struct
drm_atomic_state before calling those hooks.
In order to allow that, let's pass the full DRM state to atomic_enable and
atomic_disable. The conversion was done using the coccinelle script below,
built tested on all the drivers and actually tested on vc4.
virtual report
@@
struct drm_crtc_helper_funcs *FUNCS;
identifier dev, state;
identifier crtc, crtc_state;
@@
disable_outputs(struct drm_device *dev, struct drm_atomic_state *state)
{
<...
- FUNCS->atomic_disable(crtc, crtc_state);
+ FUNCS->atomic_disable(crtc, state);
...>
}
@@
struct drm_crtc_helper_funcs *FUNCS;
identifier dev, state;
identifier crtc, crtc_state;
@@
drm_atomic_helper_commit_modeset_enables(struct drm_device *dev, struct drm_atomic_state *state)
{
<...
- FUNCS->atomic_enable(crtc, crtc_state);
+ FUNCS->atomic_enable(crtc, state);
...>
}
@@
identifier crtc, old_state;
@@
struct drm_crtc_helper_funcs {
...
- void (*atomic_enable)(struct drm_crtc *crtc, struct drm_crtc_state *old_state);
+ void (*atomic_enable)(struct drm_crtc *crtc, struct drm_atomic_state *state);
...
- void (*atomic_disable)(struct drm_crtc *crtc, struct drm_crtc_state *old_state);
+ void (*atomic_disable)(struct drm_crtc *crtc, struct drm_atomic_state *state);
...
}
@ crtc_atomic_func @
identifier helpers;
identifier func;
@@
(
static struct drm_crtc_helper_funcs helpers = {
...,
.atomic_enable = func,
...,
};
|
static struct drm_crtc_helper_funcs helpers = {
...,
.atomic_disable = func,
...,
};
)
@ ignores_old_state @
identifier crtc_atomic_func.func;
identifier crtc, old_state;
@@
void func(struct drm_crtc *crtc,
struct drm_crtc_state *old_state)
{
... when != old_state
}
@ adds_old_state depends on crtc_atomic_func && !ignores_old_state @
identifier crtc_atomic_func.func;
identifier crtc, old_state;
@@
void func(struct drm_crtc *crtc, struct drm_crtc_state *old_state)
{
+ struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, crtc);
...
}
@ depends on crtc_atomic_func @
identifier crtc_atomic_func.func;
expression E;
type T;
@@
void func(...)
{
...
- T state = E;
+ T crtc_state = E;
<+...
- state
+ crtc_state
...+>
}
@ depends on crtc_atomic_func @
identifier crtc_atomic_func.func;
type T;
@@
void func(...)
{
...
- T state;
+ T crtc_state;
<+...
- state
+ crtc_state
...+>
}
@ depends on crtc_atomic_func @
identifier crtc_atomic_func.func;
identifier old_state;
identifier crtc;
@@
void func(struct drm_crtc *crtc,
- struct drm_crtc_state *old_state
+ struct drm_atomic_state *state
)
{ ... }
@ include depends on adds_old_state @
@@
#include <drm/drm_atomic.h>
@ no_include depends on !include && adds_old_state @
@@
+ #include <drm/drm_atomic.h>
#include <drm/...>
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: https://patchwork.freedesktop.org/patch/msgid/845aa10ef171fc0ea060495efef142a0c13f7870.1602161031.git-series.maxime@cerno.tech
2020-10-08 20:44:08 +08:00
|
|
|
struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state,
|
|
|
|
crtc);
|
2020-09-03 16:01:06 +08:00
|
|
|
struct vc4_crtc_state *old_vc4_state = to_vc4_crtc_state(old_state);
|
2021-10-25 23:28:58 +08:00
|
|
|
struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc, old_state);
|
2020-09-03 16:01:06 +08:00
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
|
2021-10-25 23:28:59 +08:00
|
|
|
drm_dbg(dev, "Disabling CRTC %s (%u) connected to Encoder %s (%u)",
|
|
|
|
crtc->name, crtc->base.id, encoder->name, encoder->base.id);
|
|
|
|
|
2020-09-03 16:01:06 +08:00
|
|
|
require_hvs_enabled(dev);
|
|
|
|
|
|
|
|
/* Disable vblank irq handling before crtc is disabled. */
|
|
|
|
drm_crtc_vblank_off(crtc);
|
|
|
|
|
2021-05-07 23:05:08 +08:00
|
|
|
vc4_crtc_disable(crtc, encoder, state, old_vc4_state->assigned_channel);
|
2020-09-03 16:01:06 +08:00
|
|
|
|
2017-06-16 16:30:33 +08:00
|
|
|
/*
|
|
|
|
* Make sure we issue a vblank event after disabling the CRTC if
|
|
|
|
* someone was waiting it.
|
|
|
|
*/
|
2022-07-12 01:38:44 +08:00
|
|
|
vc4_crtc_send_vblank(crtc);
|
2015-03-03 05:01:12 +08:00
|
|
|
}
|
|
|
|
|
2017-06-30 17:36:44 +08:00
|
|
|
static void vc4_crtc_atomic_enable(struct drm_crtc *crtc,
|
drm/atomic: Pass the full state to CRTC atomic enable/disable
If the CRTC driver ever needs to access the full DRM state, it can't do so
at atomic_enable / atomic_disable time since drm_atomic_helper_swap_state
will have cleared the pointer from the struct drm_crtc_state to the struct
drm_atomic_state before calling those hooks.
In order to allow that, let's pass the full DRM state to atomic_enable and
atomic_disable. The conversion was done using the coccinelle script below,
built tested on all the drivers and actually tested on vc4.
virtual report
@@
struct drm_crtc_helper_funcs *FUNCS;
identifier dev, state;
identifier crtc, crtc_state;
@@
disable_outputs(struct drm_device *dev, struct drm_atomic_state *state)
{
<...
- FUNCS->atomic_disable(crtc, crtc_state);
+ FUNCS->atomic_disable(crtc, state);
...>
}
@@
struct drm_crtc_helper_funcs *FUNCS;
identifier dev, state;
identifier crtc, crtc_state;
@@
drm_atomic_helper_commit_modeset_enables(struct drm_device *dev, struct drm_atomic_state *state)
{
<...
- FUNCS->atomic_enable(crtc, crtc_state);
+ FUNCS->atomic_enable(crtc, state);
...>
}
@@
identifier crtc, old_state;
@@
struct drm_crtc_helper_funcs {
...
- void (*atomic_enable)(struct drm_crtc *crtc, struct drm_crtc_state *old_state);
+ void (*atomic_enable)(struct drm_crtc *crtc, struct drm_atomic_state *state);
...
- void (*atomic_disable)(struct drm_crtc *crtc, struct drm_crtc_state *old_state);
+ void (*atomic_disable)(struct drm_crtc *crtc, struct drm_atomic_state *state);
...
}
@ crtc_atomic_func @
identifier helpers;
identifier func;
@@
(
static struct drm_crtc_helper_funcs helpers = {
...,
.atomic_enable = func,
...,
};
|
static struct drm_crtc_helper_funcs helpers = {
...,
.atomic_disable = func,
...,
};
)
@ ignores_old_state @
identifier crtc_atomic_func.func;
identifier crtc, old_state;
@@
void func(struct drm_crtc *crtc,
struct drm_crtc_state *old_state)
{
... when != old_state
}
@ adds_old_state depends on crtc_atomic_func && !ignores_old_state @
identifier crtc_atomic_func.func;
identifier crtc, old_state;
@@
void func(struct drm_crtc *crtc, struct drm_crtc_state *old_state)
{
+ struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, crtc);
...
}
@ depends on crtc_atomic_func @
identifier crtc_atomic_func.func;
expression E;
type T;
@@
void func(...)
{
...
- T state = E;
+ T crtc_state = E;
<+...
- state
+ crtc_state
...+>
}
@ depends on crtc_atomic_func @
identifier crtc_atomic_func.func;
type T;
@@
void func(...)
{
...
- T state;
+ T crtc_state;
<+...
- state
+ crtc_state
...+>
}
@ depends on crtc_atomic_func @
identifier crtc_atomic_func.func;
identifier old_state;
identifier crtc;
@@
void func(struct drm_crtc *crtc,
- struct drm_crtc_state *old_state
+ struct drm_atomic_state *state
)
{ ... }
@ include depends on adds_old_state @
@@
#include <drm/drm_atomic.h>
@ no_include depends on !include && adds_old_state @
@@
+ #include <drm/drm_atomic.h>
#include <drm/...>
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: https://patchwork.freedesktop.org/patch/msgid/845aa10ef171fc0ea060495efef142a0c13f7870.1602161031.git-series.maxime@cerno.tech
2020-10-08 20:44:08 +08:00
|
|
|
struct drm_atomic_state *state)
|
2015-03-03 05:01:12 +08:00
|
|
|
{
|
2021-10-25 23:28:58 +08:00
|
|
|
struct drm_crtc_state *new_state = drm_atomic_get_new_crtc_state(state,
|
|
|
|
crtc);
|
2015-03-03 05:01:12 +08:00
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
|
2021-10-25 23:28:58 +08:00
|
|
|
struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc, new_state);
|
2020-09-03 16:01:00 +08:00
|
|
|
struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);
|
2022-08-25 00:13:26 +08:00
|
|
|
int idx;
|
2015-03-03 05:01:12 +08:00
|
|
|
|
2021-10-25 23:28:59 +08:00
|
|
|
drm_dbg(dev, "Enabling CRTC %s (%u) connected to Encoder %s (%u)",
|
|
|
|
crtc->name, crtc->base.id, encoder->name, encoder->base.id);
|
|
|
|
|
2022-08-25 00:13:26 +08:00
|
|
|
if (!drm_dev_enter(dev, &idx))
|
|
|
|
return;
|
|
|
|
|
2015-03-03 05:01:12 +08:00
|
|
|
require_hvs_enabled(dev);
|
|
|
|
|
2017-06-23 04:25:26 +08:00
|
|
|
/* Enable vblank irq handling before crtc is started otherwise
|
|
|
|
* drm_crtc_get_vblank() fails in vc4_crtc_update_dlist().
|
|
|
|
*/
|
|
|
|
drm_crtc_vblank_on(crtc);
|
|
|
|
|
2020-12-15 23:42:35 +08:00
|
|
|
vc4_hvs_atomic_enable(crtc, state);
|
2015-03-03 05:01:12 +08:00
|
|
|
|
2020-09-03 16:01:00 +08:00
|
|
|
if (vc4_encoder->pre_crtc_configure)
|
2020-12-15 23:42:36 +08:00
|
|
|
vc4_encoder->pre_crtc_configure(encoder, state);
|
2020-09-03 16:01:00 +08:00
|
|
|
|
2021-10-25 23:28:57 +08:00
|
|
|
vc4_crtc_config_pv(crtc, encoder, state);
|
2020-09-03 16:00:59 +08:00
|
|
|
|
|
|
|
CRTC_WRITE(PV_CONTROL, CRTC_READ(PV_CONTROL) | PV_CONTROL_EN);
|
|
|
|
|
2020-09-03 16:01:00 +08:00
|
|
|
if (vc4_encoder->pre_crtc_enable)
|
2020-12-15 23:42:36 +08:00
|
|
|
vc4_encoder->pre_crtc_enable(encoder, state);
|
2020-09-03 16:01:00 +08:00
|
|
|
|
2018-07-03 15:50:22 +08:00
|
|
|
/* When feeding the transposer block the pixelvalve is unneeded and
|
|
|
|
* should not be enabled.
|
|
|
|
*/
|
2020-06-11 21:36:54 +08:00
|
|
|
CRTC_WRITE(PV_V_CONTROL,
|
|
|
|
CRTC_READ(PV_V_CONTROL) | PV_VCONTROL_VIDEN);
|
2020-09-03 16:01:00 +08:00
|
|
|
|
|
|
|
if (vc4_encoder->post_crtc_enable)
|
2020-12-15 23:42:36 +08:00
|
|
|
vc4_encoder->post_crtc_enable(encoder, state);
|
2022-08-25 00:13:26 +08:00
|
|
|
|
|
|
|
drm_dev_exit(idx);
|
2015-03-03 05:01:12 +08:00
|
|
|
}
|
|
|
|
|
2017-05-25 22:19:22 +08:00
|
|
|
static enum drm_mode_status vc4_crtc_mode_valid(struct drm_crtc *crtc,
|
|
|
|
const struct drm_display_mode *mode)
|
2016-07-20 02:58:58 +08:00
|
|
|
{
|
2016-07-20 02:58:59 +08:00
|
|
|
/* Do not allow doublescan modes from user space */
|
2017-05-25 22:19:22 +08:00
|
|
|
if (mode->flags & DRM_MODE_FLAG_DBLSCAN) {
|
2016-07-20 02:58:59 +08:00
|
|
|
DRM_DEBUG_KMS("[CRTC:%d] Doublescan mode rejected.\n",
|
|
|
|
crtc->base.id);
|
2017-05-25 22:19:22 +08:00
|
|
|
return MODE_NO_DBLESCAN;
|
2016-07-20 02:58:59 +08:00
|
|
|
}
|
|
|
|
|
2017-05-25 22:19:22 +08:00
|
|
|
return MODE_OK;
|
2016-07-20 02:58:58 +08:00
|
|
|
}
|
|
|
|
|
2018-12-06 22:24:38 +08:00
|
|
|
void vc4_crtc_get_margins(struct drm_crtc_state *state,
|
|
|
|
unsigned int *left, unsigned int *right,
|
|
|
|
unsigned int *top, unsigned int *bottom)
|
|
|
|
{
|
|
|
|
struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state);
|
|
|
|
struct drm_connector_state *conn_state;
|
|
|
|
struct drm_connector *conn;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
*left = vc4_state->margins.left;
|
|
|
|
*right = vc4_state->margins.right;
|
|
|
|
*top = vc4_state->margins.top;
|
|
|
|
*bottom = vc4_state->margins.bottom;
|
|
|
|
|
|
|
|
/* We have to interate over all new connector states because
|
|
|
|
* vc4_crtc_get_margins() might be called before
|
|
|
|
* vc4_crtc_atomic_check() which means margins info in vc4_crtc_state
|
|
|
|
* might be outdated.
|
|
|
|
*/
|
|
|
|
for_each_new_connector_in_state(state->state, conn, conn_state, i) {
|
|
|
|
if (conn_state->crtc != state->crtc)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
*left = conn_state->tv.margins.left;
|
|
|
|
*right = conn_state->tv.margins.right;
|
|
|
|
*top = conn_state->tv.margins.top;
|
|
|
|
*bottom = conn_state->tv.margins.bottom;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-01 23:11:48 +08:00
|
|
|
int vc4_crtc_atomic_check(struct drm_crtc *crtc,
|
|
|
|
struct drm_atomic_state *state)
|
2015-03-03 05:01:12 +08:00
|
|
|
{
|
drm/atomic: Pass the full state to CRTC atomic_check
The current atomic helpers have either their object state being passed as
an argument or the full atomic state.
The former is the pattern that was done at first, before switching to the
latter for new hooks or when it was needed.
Let's start convert all the remaining helpers to provide a consistent
interface, starting with the CRTC's atomic_check.
The conversion was done using the coccinelle script below,
built tested on all the drivers and actually tested on vc4.
virtual report
@@
struct drm_crtc_helper_funcs *FUNCS;
struct drm_crtc *crtc;
struct drm_crtc_state *crtc_state;
identifier dev, state;
identifier ret, f;
@@
f(struct drm_device *dev, struct drm_atomic_state *state)
{
<...
- ret = FUNCS->atomic_check(crtc, crtc_state);
+ ret = FUNCS->atomic_check(crtc, state);
...>
}
@@
identifier crtc, new_state;
@@
struct drm_crtc_helper_funcs {
...
- int (*atomic_check)(struct drm_crtc *crtc, struct drm_crtc_state *new_state);
+ int (*atomic_check)(struct drm_crtc *crtc, struct drm_atomic_state *state);
...
}
@ crtc_atomic_func @
identifier helpers;
identifier func;
@@
static struct drm_crtc_helper_funcs helpers = {
...,
.atomic_check = func,
...,
};
@ ignores_new_state @
identifier crtc_atomic_func.func;
identifier crtc, new_state;
@@
int func(struct drm_crtc *crtc,
struct drm_crtc_state *new_state)
{
... when != new_state
}
@ adds_new_state depends on crtc_atomic_func && !ignores_new_state @
identifier crtc_atomic_func.func;
identifier crtc, new_state;
@@
int func(struct drm_crtc *crtc, struct drm_crtc_state *new_state)
{
+ struct drm_crtc_state *new_state = drm_atomic_get_new_crtc_state(state, crtc);
...
}
@ depends on crtc_atomic_func @
identifier crtc_atomic_func.func;
expression E;
type T;
@@
int func(...)
{
...
- T state = E;
+ T crtc_state = E;
<+...
- state
+ crtc_state
...+>
}
@ depends on crtc_atomic_func @
identifier crtc_atomic_func.func;
type T;
@@
int func(...)
{
...
- T state;
+ T crtc_state;
<+...
- state
+ crtc_state
...+>
}
@ depends on crtc_atomic_func @
identifier crtc_atomic_func.func;
identifier new_state;
identifier crtc;
@@
int func(struct drm_crtc *crtc,
- struct drm_crtc_state *new_state
+ struct drm_atomic_state *state
)
{ ... }
@@
identifier new_state;
identifier crtc;
@@
int vmw_du_crtc_atomic_check(struct drm_crtc *crtc,
- struct drm_crtc_state *new_state
+ struct drm_atomic_state *state
)
{
+ struct drm_crtc_state *new_state = drm_atomic_get_new_crtc_state(state, crtc);
...
}
@@
identifier new_state;
identifier crtc;
@@
int vmw_du_crtc_atomic_check(struct drm_crtc *crtc,
- struct drm_crtc_state *new_state
+ struct drm_atomic_state *state
);
@ include depends on adds_new_state @
@@
#include <drm/drm_atomic.h>
@ no_include depends on !include && adds_new_state @
@@
+ #include <drm/drm_atomic.h>
#include <drm/...>
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Acked-by: Thomas Zimmermann <tzimmermann@suse.de>
Link: https://patchwork.freedesktop.org/patch/msgid/20201028123222.1732139-1-maxime@cerno.tech
2020-10-28 20:32:21 +08:00
|
|
|
struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
|
|
|
|
crtc);
|
|
|
|
struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state);
|
2018-07-03 15:50:22 +08:00
|
|
|
struct drm_connector *conn;
|
|
|
|
struct drm_connector_state *conn_state;
|
2021-10-25 23:29:03 +08:00
|
|
|
struct drm_encoder *encoder;
|
2018-07-03 15:50:22 +08:00
|
|
|
int ret, i;
|
2015-03-03 05:01:12 +08:00
|
|
|
|
2020-12-15 23:42:35 +08:00
|
|
|
ret = vc4_hvs_atomic_check(crtc, state);
|
2015-12-29 05:25:41 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2015-03-03 05:01:12 +08:00
|
|
|
|
2021-10-25 23:29:03 +08:00
|
|
|
encoder = vc4_get_crtc_encoder(crtc, crtc_state);
|
|
|
|
if (encoder) {
|
|
|
|
const struct drm_display_mode *mode = &crtc_state->adjusted_mode;
|
|
|
|
struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);
|
|
|
|
|
|
|
|
if (vc4_encoder->type == VC4_ENCODER_TYPE_HDMI0) {
|
2023-01-27 22:55:58 +08:00
|
|
|
vc4_state->hvs_load = max(mode->clock * mode->hdisplay / mode->htotal + 8000,
|
2021-10-25 23:29:03 +08:00
|
|
|
mode->clock * 9 / 10) * 1000;
|
|
|
|
} else {
|
|
|
|
vc4_state->hvs_load = mode->clock * 1000;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-02 21:38:34 +08:00
|
|
|
for_each_new_connector_in_state(state, conn, conn_state,
|
drm/atomic: Pass the full state to CRTC atomic_check
The current atomic helpers have either their object state being passed as
an argument or the full atomic state.
The former is the pattern that was done at first, before switching to the
latter for new hooks or when it was needed.
Let's start convert all the remaining helpers to provide a consistent
interface, starting with the CRTC's atomic_check.
The conversion was done using the coccinelle script below,
built tested on all the drivers and actually tested on vc4.
virtual report
@@
struct drm_crtc_helper_funcs *FUNCS;
struct drm_crtc *crtc;
struct drm_crtc_state *crtc_state;
identifier dev, state;
identifier ret, f;
@@
f(struct drm_device *dev, struct drm_atomic_state *state)
{
<...
- ret = FUNCS->atomic_check(crtc, crtc_state);
+ ret = FUNCS->atomic_check(crtc, state);
...>
}
@@
identifier crtc, new_state;
@@
struct drm_crtc_helper_funcs {
...
- int (*atomic_check)(struct drm_crtc *crtc, struct drm_crtc_state *new_state);
+ int (*atomic_check)(struct drm_crtc *crtc, struct drm_atomic_state *state);
...
}
@ crtc_atomic_func @
identifier helpers;
identifier func;
@@
static struct drm_crtc_helper_funcs helpers = {
...,
.atomic_check = func,
...,
};
@ ignores_new_state @
identifier crtc_atomic_func.func;
identifier crtc, new_state;
@@
int func(struct drm_crtc *crtc,
struct drm_crtc_state *new_state)
{
... when != new_state
}
@ adds_new_state depends on crtc_atomic_func && !ignores_new_state @
identifier crtc_atomic_func.func;
identifier crtc, new_state;
@@
int func(struct drm_crtc *crtc, struct drm_crtc_state *new_state)
{
+ struct drm_crtc_state *new_state = drm_atomic_get_new_crtc_state(state, crtc);
...
}
@ depends on crtc_atomic_func @
identifier crtc_atomic_func.func;
expression E;
type T;
@@
int func(...)
{
...
- T state = E;
+ T crtc_state = E;
<+...
- state
+ crtc_state
...+>
}
@ depends on crtc_atomic_func @
identifier crtc_atomic_func.func;
type T;
@@
int func(...)
{
...
- T state;
+ T crtc_state;
<+...
- state
+ crtc_state
...+>
}
@ depends on crtc_atomic_func @
identifier crtc_atomic_func.func;
identifier new_state;
identifier crtc;
@@
int func(struct drm_crtc *crtc,
- struct drm_crtc_state *new_state
+ struct drm_atomic_state *state
)
{ ... }
@@
identifier new_state;
identifier crtc;
@@
int vmw_du_crtc_atomic_check(struct drm_crtc *crtc,
- struct drm_crtc_state *new_state
+ struct drm_atomic_state *state
)
{
+ struct drm_crtc_state *new_state = drm_atomic_get_new_crtc_state(state, crtc);
...
}
@@
identifier new_state;
identifier crtc;
@@
int vmw_du_crtc_atomic_check(struct drm_crtc *crtc,
- struct drm_crtc_state *new_state
+ struct drm_atomic_state *state
);
@ include depends on adds_new_state @
@@
#include <drm/drm_atomic.h>
@ no_include depends on !include && adds_new_state @
@@
+ #include <drm/drm_atomic.h>
#include <drm/...>
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Acked-by: Thomas Zimmermann <tzimmermann@suse.de>
Link: https://patchwork.freedesktop.org/patch/msgid/20201028123222.1732139-1-maxime@cerno.tech
2020-10-28 20:32:21 +08:00
|
|
|
i) {
|
2018-07-03 15:50:22 +08:00
|
|
|
if (conn_state->crtc != crtc)
|
|
|
|
continue;
|
|
|
|
|
2018-12-06 22:24:38 +08:00
|
|
|
vc4_state->margins.left = conn_state->tv.margins.left;
|
|
|
|
vc4_state->margins.right = conn_state->tv.margins.right;
|
|
|
|
vc4_state->margins.top = conn_state->tv.margins.top;
|
|
|
|
vc4_state->margins.bottom = conn_state->tv.margins.bottom;
|
2018-07-03 15:50:22 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2015-03-03 05:01:12 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-02-07 17:16:34 +08:00
|
|
|
static int vc4_enable_vblank(struct drm_crtc *crtc)
|
2015-03-03 05:01:12 +08:00
|
|
|
{
|
2017-01-09 19:25:45 +08:00
|
|
|
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
|
2022-08-25 00:13:26 +08:00
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
int idx;
|
|
|
|
|
|
|
|
if (!drm_dev_enter(dev, &idx))
|
|
|
|
return -ENODEV;
|
2015-03-03 05:01:12 +08:00
|
|
|
|
|
|
|
CRTC_WRITE(PV_INTEN, PV_INT_VFP_START);
|
|
|
|
|
2022-08-25 00:13:26 +08:00
|
|
|
drm_dev_exit(idx);
|
|
|
|
|
2015-03-03 05:01:12 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-02-07 17:16:34 +08:00
|
|
|
static void vc4_disable_vblank(struct drm_crtc *crtc)
|
2015-03-03 05:01:12 +08:00
|
|
|
{
|
2017-01-09 19:25:45 +08:00
|
|
|
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
|
2022-08-25 00:13:26 +08:00
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
int idx;
|
|
|
|
|
|
|
|
if (!drm_dev_enter(dev, &idx))
|
|
|
|
return;
|
2015-03-03 05:01:12 +08:00
|
|
|
|
|
|
|
CRTC_WRITE(PV_INTEN, 0);
|
2022-08-25 00:13:26 +08:00
|
|
|
|
|
|
|
drm_dev_exit(idx);
|
2015-03-03 05:01:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void vc4_crtc_handle_page_flip(struct vc4_crtc *vc4_crtc)
|
|
|
|
{
|
|
|
|
struct drm_crtc *crtc = &vc4_crtc->base;
|
|
|
|
struct drm_device *dev = crtc->dev;
|
drm/vc4: Make pageflip completion handling more robust.
Protect both the setup of the pageflip event and the
latching of the new requested displaylist head pointer
by the event lock, so we can't get into a situation
where vc4_atomic_flush latches the new display list via
HVS_WRITE, then immediately gets preempted before queueing
the pageflip event, then the page-flip completes in hw and
the vc4_crtc_handle_page_flip() runs and no-ops due to
lack of a pending pageflip event, then vc4_atomic_flush
continues and only then queues the pageflip event - after
the page flip handling already no-oped. This would cause
flip completion handling only at the next vblank - one
frame too late.
In vc4_crtc_handle_page_flip() check the actual DL head
pointer in SCALER_DISPLACTX against the requested pointer
for page flip to make sure that the flip actually really
completed in the current vblank and doesn't get deferred
to the next one because the DL head pointer was written
a bit too late into SCALER_DISPLISTX, after start of
vblank, and missed the boat. This avoids handling a
pageflip completion too early - one frame too early.
According to Eric, DL head pointer updates which were
written into the HVS DISPLISTX reg get committed to hardware
at the last pixel of active scanout. Our vblank interrupt
handler, as triggered by PV_INT_VFP_START irq, gets to run
earliest at the first pixel of HBLANK at the end of the
last scanline of active scanout, ie. vblank irq handling
runs at least 1 pixel duration after a potential pageflip
completion happened in hardware.
This ordering of events in the hardware, together with the
lock protection and SCALER_DISPLACTX sampling of this patch,
guarantees that pageflip completion handling only runs at
exactly the vblank irq of actual pageflip completion in all
cases.
Background info from Eric about the relative timing of
HVS, PV's and trigger points for interrupts, DL updates:
https://lists.freedesktop.org/archives/dri-devel/2016-May/107510.html
Tested on RPi 2B with hardware timing measurement equipment
and shown to no longer complete flips too early or too late.
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Reviewed-by: Eric Anholt <eric@anholt.net>
2016-05-18 20:02:46 +08:00
|
|
|
struct vc4_dev *vc4 = to_vc4_dev(dev);
|
2022-03-31 22:37:44 +08:00
|
|
|
struct vc4_hvs *hvs = vc4->hvs;
|
2021-10-25 22:11:07 +08:00
|
|
|
u32 chan = vc4_crtc->current_hvs_channel;
|
2015-03-03 05:01:12 +08:00
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&dev->event_lock, flags);
|
drm/vc4: Fix non-blocking commit getting stuck forever
In some situation, we can end up being stuck on a non-blocking that went
through properly.
The situation that seems to trigger it reliably is to first start a
non-blocking commit, and then right after, and before we had any vblank
interrupt), start a blocking commit.
This will lead to the first commit workqueue to be scheduled, setup the
display, while the second commit is waiting for the first one to be
completed.
The vblank interrupt will then be raised, vc4_crtc_handle_vblank() will
run and will compare the active dlist in the HVS channel to the one
associated with the crtc->state.
However, at that point, the second commit is waiting using
drm_atomic_helper_wait_for_dependencies that occurs after
drm_atomic_helper_swap_state has been called, so crtc->state points to
the second commit state. vc4_crtc_handle_vblank() will compare the two
dlist addresses and since they don't match will ignore the interrupt.
The vblank event will never be reported, and the first and second commit
will wait for the first commit completion until they timeout.
The underlying reason is that it was never safe to do so. Indeed,
accessing the ->state pointer access synchronization is based on
ownership guarantees that can only occur within the functions and hooks
defined as part of the KMS framework, and obviously the irq handler
isn't one of them. The rework to move to generic helpers only uncovered
the underlying issue.
However, since the code path between
drm_atomic_helper_wait_for_dependencies() and
drm_atomic_helper_wait_for_vblanks() is serialised and we can't get two
commits in that path at the same time, we can work around this issue by
setting a variable associated to struct drm_crtc to the dlist we expect,
and then using it from the vc4_crtc_handle_vblank() function.
Since that state is shared with the modesetting path, we also need to
introduce a spinlock to protect the code shared between the interrupt
handler and the modesetting path, protecting only our new variable for
now.
Link: https://lore.kernel.org/all/YWgteNaNeaS9uWDe@phenom.ffwll.local/
Link: https://lore.kernel.org/r/20211025141113.702757-3-maxime@cerno.tech
Fixes: 56d1fe0979dc ("drm/vc4: Make pageflip completion handling more robust.")
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
2021-10-25 22:11:06 +08:00
|
|
|
spin_lock(&vc4_crtc->irq_lock);
|
drm/vc4: Make pageflip completion handling more robust.
Protect both the setup of the pageflip event and the
latching of the new requested displaylist head pointer
by the event lock, so we can't get into a situation
where vc4_atomic_flush latches the new display list via
HVS_WRITE, then immediately gets preempted before queueing
the pageflip event, then the page-flip completes in hw and
the vc4_crtc_handle_page_flip() runs and no-ops due to
lack of a pending pageflip event, then vc4_atomic_flush
continues and only then queues the pageflip event - after
the page flip handling already no-oped. This would cause
flip completion handling only at the next vblank - one
frame too late.
In vc4_crtc_handle_page_flip() check the actual DL head
pointer in SCALER_DISPLACTX against the requested pointer
for page flip to make sure that the flip actually really
completed in the current vblank and doesn't get deferred
to the next one because the DL head pointer was written
a bit too late into SCALER_DISPLISTX, after start of
vblank, and missed the boat. This avoids handling a
pageflip completion too early - one frame too early.
According to Eric, DL head pointer updates which were
written into the HVS DISPLISTX reg get committed to hardware
at the last pixel of active scanout. Our vblank interrupt
handler, as triggered by PV_INT_VFP_START irq, gets to run
earliest at the first pixel of HBLANK at the end of the
last scanline of active scanout, ie. vblank irq handling
runs at least 1 pixel duration after a potential pageflip
completion happened in hardware.
This ordering of events in the hardware, together with the
lock protection and SCALER_DISPLACTX sampling of this patch,
guarantees that pageflip completion handling only runs at
exactly the vblank irq of actual pageflip completion in all
cases.
Background info from Eric about the relative timing of
HVS, PV's and trigger points for interrupts, DL updates:
https://lists.freedesktop.org/archives/dri-devel/2016-May/107510.html
Tested on RPi 2B with hardware timing measurement equipment
and shown to no longer complete flips too early or too late.
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Reviewed-by: Eric Anholt <eric@anholt.net>
2016-05-18 20:02:46 +08:00
|
|
|
if (vc4_crtc->event &&
|
drm/vc4: Fix non-blocking commit getting stuck forever
In some situation, we can end up being stuck on a non-blocking that went
through properly.
The situation that seems to trigger it reliably is to first start a
non-blocking commit, and then right after, and before we had any vblank
interrupt), start a blocking commit.
This will lead to the first commit workqueue to be scheduled, setup the
display, while the second commit is waiting for the first one to be
completed.
The vblank interrupt will then be raised, vc4_crtc_handle_vblank() will
run and will compare the active dlist in the HVS channel to the one
associated with the crtc->state.
However, at that point, the second commit is waiting using
drm_atomic_helper_wait_for_dependencies that occurs after
drm_atomic_helper_swap_state has been called, so crtc->state points to
the second commit state. vc4_crtc_handle_vblank() will compare the two
dlist addresses and since they don't match will ignore the interrupt.
The vblank event will never be reported, and the first and second commit
will wait for the first commit completion until they timeout.
The underlying reason is that it was never safe to do so. Indeed,
accessing the ->state pointer access synchronization is based on
ownership guarantees that can only occur within the functions and hooks
defined as part of the KMS framework, and obviously the irq handler
isn't one of them. The rework to move to generic helpers only uncovered
the underlying issue.
However, since the code path between
drm_atomic_helper_wait_for_dependencies() and
drm_atomic_helper_wait_for_vblanks() is serialised and we can't get two
commits in that path at the same time, we can work around this issue by
setting a variable associated to struct drm_crtc to the dlist we expect,
and then using it from the vc4_crtc_handle_vblank() function.
Since that state is shared with the modesetting path, we also need to
introduce a spinlock to protect the code shared between the interrupt
handler and the modesetting path, protecting only our new variable for
now.
Link: https://lore.kernel.org/all/YWgteNaNeaS9uWDe@phenom.ffwll.local/
Link: https://lore.kernel.org/r/20211025141113.702757-3-maxime@cerno.tech
Fixes: 56d1fe0979dc ("drm/vc4: Make pageflip completion handling more robust.")
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
2021-10-25 22:11:06 +08:00
|
|
|
(vc4_crtc->current_dlist == HVS_READ(SCALER_DISPLACTX(chan)) ||
|
2021-10-25 22:11:05 +08:00
|
|
|
vc4_crtc->feeds_txp)) {
|
2015-03-03 05:01:12 +08:00
|
|
|
drm_crtc_send_vblank_event(crtc, vc4_crtc->event);
|
|
|
|
vc4_crtc->event = NULL;
|
2016-05-07 01:26:06 +08:00
|
|
|
drm_crtc_vblank_put(crtc);
|
2019-02-20 23:51:22 +08:00
|
|
|
|
|
|
|
/* Wait for the page flip to unmask the underrun to ensure that
|
|
|
|
* the display list was updated by the hardware. Before that
|
|
|
|
* happens, the HVS will be using the previous display list with
|
|
|
|
* the CRTC and encoder already reconfigured, leading to
|
|
|
|
* underruns. This can be seen when reconfiguring the CRTC.
|
|
|
|
*/
|
2022-03-31 22:37:44 +08:00
|
|
|
vc4_hvs_unmask_underrun(hvs, chan);
|
2015-03-03 05:01:12 +08:00
|
|
|
}
|
drm/vc4: Fix non-blocking commit getting stuck forever
In some situation, we can end up being stuck on a non-blocking that went
through properly.
The situation that seems to trigger it reliably is to first start a
non-blocking commit, and then right after, and before we had any vblank
interrupt), start a blocking commit.
This will lead to the first commit workqueue to be scheduled, setup the
display, while the second commit is waiting for the first one to be
completed.
The vblank interrupt will then be raised, vc4_crtc_handle_vblank() will
run and will compare the active dlist in the HVS channel to the one
associated with the crtc->state.
However, at that point, the second commit is waiting using
drm_atomic_helper_wait_for_dependencies that occurs after
drm_atomic_helper_swap_state has been called, so crtc->state points to
the second commit state. vc4_crtc_handle_vblank() will compare the two
dlist addresses and since they don't match will ignore the interrupt.
The vblank event will never be reported, and the first and second commit
will wait for the first commit completion until they timeout.
The underlying reason is that it was never safe to do so. Indeed,
accessing the ->state pointer access synchronization is based on
ownership guarantees that can only occur within the functions and hooks
defined as part of the KMS framework, and obviously the irq handler
isn't one of them. The rework to move to generic helpers only uncovered
the underlying issue.
However, since the code path between
drm_atomic_helper_wait_for_dependencies() and
drm_atomic_helper_wait_for_vblanks() is serialised and we can't get two
commits in that path at the same time, we can work around this issue by
setting a variable associated to struct drm_crtc to the dlist we expect,
and then using it from the vc4_crtc_handle_vblank() function.
Since that state is shared with the modesetting path, we also need to
introduce a spinlock to protect the code shared between the interrupt
handler and the modesetting path, protecting only our new variable for
now.
Link: https://lore.kernel.org/all/YWgteNaNeaS9uWDe@phenom.ffwll.local/
Link: https://lore.kernel.org/r/20211025141113.702757-3-maxime@cerno.tech
Fixes: 56d1fe0979dc ("drm/vc4: Make pageflip completion handling more robust.")
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
2021-10-25 22:11:06 +08:00
|
|
|
spin_unlock(&vc4_crtc->irq_lock);
|
2015-03-03 05:01:12 +08:00
|
|
|
spin_unlock_irqrestore(&dev->event_lock, flags);
|
|
|
|
}
|
|
|
|
|
2018-07-03 15:50:22 +08:00
|
|
|
void vc4_crtc_handle_vblank(struct vc4_crtc *crtc)
|
|
|
|
{
|
|
|
|
crtc->t_vblank = ktime_get();
|
|
|
|
drm_crtc_handle_vblank(&crtc->base);
|
|
|
|
vc4_crtc_handle_page_flip(crtc);
|
|
|
|
}
|
|
|
|
|
2015-03-03 05:01:12 +08:00
|
|
|
static irqreturn_t vc4_crtc_irq_handler(int irq, void *data)
|
|
|
|
{
|
|
|
|
struct vc4_crtc *vc4_crtc = data;
|
|
|
|
u32 stat = CRTC_READ(PV_INTSTAT);
|
|
|
|
irqreturn_t ret = IRQ_NONE;
|
|
|
|
|
|
|
|
if (stat & PV_INT_VFP_START) {
|
|
|
|
CRTC_WRITE(PV_INTSTAT, PV_INT_VFP_START);
|
2018-07-03 15:50:22 +08:00
|
|
|
vc4_crtc_handle_vblank(vc4_crtc);
|
2015-03-03 05:01:12 +08:00
|
|
|
ret = IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-12-01 04:34:01 +08:00
|
|
|
struct vc4_async_flip_state {
|
|
|
|
struct drm_crtc *crtc;
|
|
|
|
struct drm_framebuffer *fb;
|
2018-04-30 21:32:32 +08:00
|
|
|
struct drm_framebuffer *old_fb;
|
2015-12-01 04:34:01 +08:00
|
|
|
struct drm_pending_vblank_event *event;
|
|
|
|
|
2022-06-10 19:51:44 +08:00
|
|
|
union {
|
drm/vc4: crtc: Fix out of order frames during asynchronous page flips
When doing an asynchronous page flip (PAGE_FLIP ioctl with the
DRM_MODE_PAGE_FLIP_ASYNC flag set), the current code waits for the
possible GPU buffer being rendered through a call to
vc4_queue_seqno_cb().
On the BCM2835-37, the GPU driver is part of the vc4 driver and that
function is defined in vc4_gem.c to wait for the buffer to be rendered,
and once it's done, call a callback.
However, on the BCM2711 used on the RaspberryPi4, the GPU driver is
separate (v3d) and that function won't do anything. This was working
because we were going into a path, due to uninitialized variables, that
was always scheduling the callback.
However, we were never actually waiting for the buffer to be rendered
which was resulting in frames being displayed out of order.
The generic API to signal those kind of completion in the kernel are the
DMA fences, and fortunately the v3d drivers supports them and signal
when its job is done. That API also provides an equivalent function that
allows to have a callback being executed when the fence is signalled as
done.
Let's change our driver a bit to rely on the previous function for the
older SoCs, and on DMA fences for the BCM2711.
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Reviewed-by: Melissa Wen <mwen@igalia.com>
Link: https://lore.kernel.org/r/20220610115149.964394-14-maxime@cerno.tech
2022-06-10 19:51:48 +08:00
|
|
|
struct dma_fence_cb fence;
|
2022-06-10 19:51:44 +08:00
|
|
|
struct vc4_seqno_cb seqno;
|
|
|
|
} cb;
|
2015-12-01 04:34:01 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/* Called when the V3D execution for the BO being flipped to is done, so that
|
|
|
|
* we can actually update the plane's address to point to it.
|
|
|
|
*/
|
|
|
|
static void
|
2022-06-10 19:51:44 +08:00
|
|
|
vc4_async_page_flip_complete(struct vc4_async_flip_state *flip_state)
|
2015-12-01 04:34:01 +08:00
|
|
|
{
|
|
|
|
struct drm_crtc *crtc = flip_state->crtc;
|
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct drm_plane *plane = crtc->primary;
|
|
|
|
|
|
|
|
vc4_plane_async_set_fb(plane, flip_state->fb);
|
|
|
|
if (flip_state->event) {
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&dev->event_lock, flags);
|
|
|
|
drm_crtc_send_vblank_event(crtc, flip_state->event);
|
|
|
|
spin_unlock_irqrestore(&dev->event_lock, flags);
|
|
|
|
}
|
|
|
|
|
2016-05-07 01:26:06 +08:00
|
|
|
drm_crtc_vblank_put(crtc);
|
2017-08-03 19:58:40 +08:00
|
|
|
drm_framebuffer_put(flip_state->fb);
|
2018-04-30 21:32:32 +08:00
|
|
|
|
2022-06-10 19:51:45 +08:00
|
|
|
if (flip_state->old_fb)
|
2018-04-30 21:32:32 +08:00
|
|
|
drm_framebuffer_put(flip_state->old_fb);
|
|
|
|
|
2015-12-01 04:34:01 +08:00
|
|
|
kfree(flip_state);
|
|
|
|
}
|
|
|
|
|
2022-06-10 19:51:44 +08:00
|
|
|
static void vc4_async_page_flip_seqno_complete(struct vc4_seqno_cb *cb)
|
|
|
|
{
|
|
|
|
struct vc4_async_flip_state *flip_state =
|
|
|
|
container_of(cb, struct vc4_async_flip_state, cb.seqno);
|
2022-06-10 19:51:45 +08:00
|
|
|
struct vc4_bo *bo = NULL;
|
2018-04-30 21:32:32 +08:00
|
|
|
|
2022-06-10 19:51:45 +08:00
|
|
|
if (flip_state->old_fb) {
|
2022-08-02 08:04:03 +08:00
|
|
|
struct drm_gem_dma_object *dma_bo =
|
2022-08-02 08:04:02 +08:00
|
|
|
drm_fb_dma_get_gem_obj(flip_state->old_fb, 0);
|
2022-08-02 08:04:03 +08:00
|
|
|
bo = to_vc4_bo(&dma_bo->base);
|
2018-04-30 21:32:32 +08:00
|
|
|
}
|
|
|
|
|
2022-06-10 19:51:44 +08:00
|
|
|
vc4_async_page_flip_complete(flip_state);
|
2022-06-10 19:51:45 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Decrement the BO usecnt in order to keep the inc/dec
|
|
|
|
* calls balanced when the planes are updated through
|
|
|
|
* the async update path.
|
|
|
|
*
|
|
|
|
* FIXME: we should move to generic async-page-flip when
|
|
|
|
* it's available, so that we can get rid of this
|
|
|
|
* hand-made cleanup_fb() logic.
|
|
|
|
*/
|
|
|
|
if (bo)
|
|
|
|
vc4_bo_dec_usecnt(bo);
|
2015-12-01 04:34:01 +08:00
|
|
|
}
|
|
|
|
|
drm/vc4: crtc: Fix out of order frames during asynchronous page flips
When doing an asynchronous page flip (PAGE_FLIP ioctl with the
DRM_MODE_PAGE_FLIP_ASYNC flag set), the current code waits for the
possible GPU buffer being rendered through a call to
vc4_queue_seqno_cb().
On the BCM2835-37, the GPU driver is part of the vc4 driver and that
function is defined in vc4_gem.c to wait for the buffer to be rendered,
and once it's done, call a callback.
However, on the BCM2711 used on the RaspberryPi4, the GPU driver is
separate (v3d) and that function won't do anything. This was working
because we were going into a path, due to uninitialized variables, that
was always scheduling the callback.
However, we were never actually waiting for the buffer to be rendered
which was resulting in frames being displayed out of order.
The generic API to signal those kind of completion in the kernel are the
DMA fences, and fortunately the v3d drivers supports them and signal
when its job is done. That API also provides an equivalent function that
allows to have a callback being executed when the fence is signalled as
done.
Let's change our driver a bit to rely on the previous function for the
older SoCs, and on DMA fences for the BCM2711.
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Reviewed-by: Melissa Wen <mwen@igalia.com>
Link: https://lore.kernel.org/r/20220610115149.964394-14-maxime@cerno.tech
2022-06-10 19:51:48 +08:00
|
|
|
static void vc4_async_page_flip_fence_complete(struct dma_fence *fence,
|
|
|
|
struct dma_fence_cb *cb)
|
2015-12-01 04:34:01 +08:00
|
|
|
{
|
drm/vc4: crtc: Fix out of order frames during asynchronous page flips
When doing an asynchronous page flip (PAGE_FLIP ioctl with the
DRM_MODE_PAGE_FLIP_ASYNC flag set), the current code waits for the
possible GPU buffer being rendered through a call to
vc4_queue_seqno_cb().
On the BCM2835-37, the GPU driver is part of the vc4 driver and that
function is defined in vc4_gem.c to wait for the buffer to be rendered,
and once it's done, call a callback.
However, on the BCM2711 used on the RaspberryPi4, the GPU driver is
separate (v3d) and that function won't do anything. This was working
because we were going into a path, due to uninitialized variables, that
was always scheduling the callback.
However, we were never actually waiting for the buffer to be rendered
which was resulting in frames being displayed out of order.
The generic API to signal those kind of completion in the kernel are the
DMA fences, and fortunately the v3d drivers supports them and signal
when its job is done. That API also provides an equivalent function that
allows to have a callback being executed when the fence is signalled as
done.
Let's change our driver a bit to rely on the previous function for the
older SoCs, and on DMA fences for the BCM2711.
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Reviewed-by: Melissa Wen <mwen@igalia.com>
Link: https://lore.kernel.org/r/20220610115149.964394-14-maxime@cerno.tech
2022-06-10 19:51:48 +08:00
|
|
|
struct vc4_async_flip_state *flip_state =
|
|
|
|
container_of(cb, struct vc4_async_flip_state, cb.fence);
|
|
|
|
|
|
|
|
vc4_async_page_flip_complete(flip_state);
|
|
|
|
dma_fence_put(fence);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vc4_async_set_fence_cb(struct drm_device *dev,
|
|
|
|
struct vc4_async_flip_state *flip_state)
|
|
|
|
{
|
|
|
|
struct drm_framebuffer *fb = flip_state->fb;
|
2022-08-02 08:04:03 +08:00
|
|
|
struct drm_gem_dma_object *dma_bo = drm_fb_dma_get_gem_obj(fb, 0);
|
drm/vc4: crtc: Fix out of order frames during asynchronous page flips
When doing an asynchronous page flip (PAGE_FLIP ioctl with the
DRM_MODE_PAGE_FLIP_ASYNC flag set), the current code waits for the
possible GPU buffer being rendered through a call to
vc4_queue_seqno_cb().
On the BCM2835-37, the GPU driver is part of the vc4 driver and that
function is defined in vc4_gem.c to wait for the buffer to be rendered,
and once it's done, call a callback.
However, on the BCM2711 used on the RaspberryPi4, the GPU driver is
separate (v3d) and that function won't do anything. This was working
because we were going into a path, due to uninitialized variables, that
was always scheduling the callback.
However, we were never actually waiting for the buffer to be rendered
which was resulting in frames being displayed out of order.
The generic API to signal those kind of completion in the kernel are the
DMA fences, and fortunately the v3d drivers supports them and signal
when its job is done. That API also provides an equivalent function that
allows to have a callback being executed when the fence is signalled as
done.
Let's change our driver a bit to rely on the previous function for the
older SoCs, and on DMA fences for the BCM2711.
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Reviewed-by: Melissa Wen <mwen@igalia.com>
Link: https://lore.kernel.org/r/20220610115149.964394-14-maxime@cerno.tech
2022-06-10 19:51:48 +08:00
|
|
|
struct vc4_dev *vc4 = to_vc4_dev(dev);
|
|
|
|
struct dma_fence *fence;
|
|
|
|
int ret;
|
2015-12-01 04:34:01 +08:00
|
|
|
|
drm/vc4: crtc: Fix out of order frames during asynchronous page flips
When doing an asynchronous page flip (PAGE_FLIP ioctl with the
DRM_MODE_PAGE_FLIP_ASYNC flag set), the current code waits for the
possible GPU buffer being rendered through a call to
vc4_queue_seqno_cb().
On the BCM2835-37, the GPU driver is part of the vc4 driver and that
function is defined in vc4_gem.c to wait for the buffer to be rendered,
and once it's done, call a callback.
However, on the BCM2711 used on the RaspberryPi4, the GPU driver is
separate (v3d) and that function won't do anything. This was working
because we were going into a path, due to uninitialized variables, that
was always scheduling the callback.
However, we were never actually waiting for the buffer to be rendered
which was resulting in frames being displayed out of order.
The generic API to signal those kind of completion in the kernel are the
DMA fences, and fortunately the v3d drivers supports them and signal
when its job is done. That API also provides an equivalent function that
allows to have a callback being executed when the fence is signalled as
done.
Let's change our driver a bit to rely on the previous function for the
older SoCs, and on DMA fences for the BCM2711.
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Reviewed-by: Melissa Wen <mwen@igalia.com>
Link: https://lore.kernel.org/r/20220610115149.964394-14-maxime@cerno.tech
2022-06-10 19:51:48 +08:00
|
|
|
if (!vc4->is_vc5) {
|
2022-08-02 08:04:03 +08:00
|
|
|
struct vc4_bo *bo = to_vc4_bo(&dma_bo->base);
|
drm/vc4: crtc: Fix out of order frames during asynchronous page flips
When doing an asynchronous page flip (PAGE_FLIP ioctl with the
DRM_MODE_PAGE_FLIP_ASYNC flag set), the current code waits for the
possible GPU buffer being rendered through a call to
vc4_queue_seqno_cb().
On the BCM2835-37, the GPU driver is part of the vc4 driver and that
function is defined in vc4_gem.c to wait for the buffer to be rendered,
and once it's done, call a callback.
However, on the BCM2711 used on the RaspberryPi4, the GPU driver is
separate (v3d) and that function won't do anything. This was working
because we were going into a path, due to uninitialized variables, that
was always scheduling the callback.
However, we were never actually waiting for the buffer to be rendered
which was resulting in frames being displayed out of order.
The generic API to signal those kind of completion in the kernel are the
DMA fences, and fortunately the v3d drivers supports them and signal
when its job is done. That API also provides an equivalent function that
allows to have a callback being executed when the fence is signalled as
done.
Let's change our driver a bit to rely on the previous function for the
older SoCs, and on DMA fences for the BCM2711.
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Reviewed-by: Melissa Wen <mwen@igalia.com>
Link: https://lore.kernel.org/r/20220610115149.964394-14-maxime@cerno.tech
2022-06-10 19:51:48 +08:00
|
|
|
|
|
|
|
return vc4_queue_seqno_cb(dev, &flip_state->cb.seqno, bo->seqno,
|
|
|
|
vc4_async_page_flip_seqno_complete);
|
|
|
|
}
|
|
|
|
|
2022-08-02 08:04:03 +08:00
|
|
|
ret = dma_resv_get_singleton(dma_bo->base.resv, DMA_RESV_USAGE_READ, &fence);
|
2018-04-30 21:32:32 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
drm/vc4: crtc: Fix out of order frames during asynchronous page flips
When doing an asynchronous page flip (PAGE_FLIP ioctl with the
DRM_MODE_PAGE_FLIP_ASYNC flag set), the current code waits for the
possible GPU buffer being rendered through a call to
vc4_queue_seqno_cb().
On the BCM2835-37, the GPU driver is part of the vc4 driver and that
function is defined in vc4_gem.c to wait for the buffer to be rendered,
and once it's done, call a callback.
However, on the BCM2711 used on the RaspberryPi4, the GPU driver is
separate (v3d) and that function won't do anything. This was working
because we were going into a path, due to uninitialized variables, that
was always scheduling the callback.
However, we were never actually waiting for the buffer to be rendered
which was resulting in frames being displayed out of order.
The generic API to signal those kind of completion in the kernel are the
DMA fences, and fortunately the v3d drivers supports them and signal
when its job is done. That API also provides an equivalent function that
allows to have a callback being executed when the fence is signalled as
done.
Let's change our driver a bit to rely on the previous function for the
older SoCs, and on DMA fences for the BCM2711.
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Reviewed-by: Melissa Wen <mwen@igalia.com>
Link: https://lore.kernel.org/r/20220610115149.964394-14-maxime@cerno.tech
2022-06-10 19:51:48 +08:00
|
|
|
/* If there's no fence, complete the page flip immediately */
|
|
|
|
if (!fence) {
|
|
|
|
vc4_async_page_flip_fence_complete(fence, &flip_state->cb.fence);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the fence has already been completed, complete the page flip */
|
|
|
|
if (dma_fence_add_callback(fence, &flip_state->cb.fence,
|
|
|
|
vc4_async_page_flip_fence_complete))
|
|
|
|
vc4_async_page_flip_fence_complete(fence, &flip_state->cb.fence);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-06-10 19:51:46 +08:00
|
|
|
static int
|
|
|
|
vc4_async_page_flip_common(struct drm_crtc *crtc,
|
|
|
|
struct drm_framebuffer *fb,
|
|
|
|
struct drm_pending_vblank_event *event,
|
|
|
|
uint32_t flags)
|
2015-12-01 04:34:01 +08:00
|
|
|
{
|
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct drm_plane *plane = crtc->primary;
|
|
|
|
struct vc4_async_flip_state *flip_state;
|
|
|
|
|
|
|
|
flip_state = kzalloc(sizeof(*flip_state), GFP_KERNEL);
|
2022-06-10 19:51:46 +08:00
|
|
|
if (!flip_state)
|
2015-12-01 04:34:01 +08:00
|
|
|
return -ENOMEM;
|
|
|
|
|
2017-08-03 19:58:40 +08:00
|
|
|
drm_framebuffer_get(fb);
|
2015-12-01 04:34:01 +08:00
|
|
|
flip_state->fb = fb;
|
|
|
|
flip_state->crtc = crtc;
|
|
|
|
flip_state->event = event;
|
|
|
|
|
2018-04-30 21:32:32 +08:00
|
|
|
/* Save the current FB before it's replaced by the new one in
|
|
|
|
* drm_atomic_set_fb_for_plane(). We'll need the old FB in
|
|
|
|
* vc4_async_page_flip_complete() to decrement the BO usecnt and keep
|
|
|
|
* it consistent.
|
|
|
|
* FIXME: we should move to generic async-page-flip when it's
|
|
|
|
* available, so that we can get rid of this hand-made cleanup_fb()
|
|
|
|
* logic.
|
|
|
|
*/
|
|
|
|
flip_state->old_fb = plane->state->fb;
|
|
|
|
if (flip_state->old_fb)
|
|
|
|
drm_framebuffer_get(flip_state->old_fb);
|
|
|
|
|
2016-05-07 01:26:06 +08:00
|
|
|
WARN_ON(drm_crtc_vblank_get(crtc) != 0);
|
|
|
|
|
2015-12-01 04:34:01 +08:00
|
|
|
/* Immediately update the plane's legacy fb pointer, so that later
|
|
|
|
* modeset prep sees the state that will be present when the semaphore
|
|
|
|
* is released.
|
|
|
|
*/
|
|
|
|
drm_atomic_set_fb_for_plane(plane->state, fb);
|
|
|
|
|
drm/vc4: crtc: Fix out of order frames during asynchronous page flips
When doing an asynchronous page flip (PAGE_FLIP ioctl with the
DRM_MODE_PAGE_FLIP_ASYNC flag set), the current code waits for the
possible GPU buffer being rendered through a call to
vc4_queue_seqno_cb().
On the BCM2835-37, the GPU driver is part of the vc4 driver and that
function is defined in vc4_gem.c to wait for the buffer to be rendered,
and once it's done, call a callback.
However, on the BCM2711 used on the RaspberryPi4, the GPU driver is
separate (v3d) and that function won't do anything. This was working
because we were going into a path, due to uninitialized variables, that
was always scheduling the callback.
However, we were never actually waiting for the buffer to be rendered
which was resulting in frames being displayed out of order.
The generic API to signal those kind of completion in the kernel are the
DMA fences, and fortunately the v3d drivers supports them and signal
when its job is done. That API also provides an equivalent function that
allows to have a callback being executed when the fence is signalled as
done.
Let's change our driver a bit to rely on the previous function for the
older SoCs, and on DMA fences for the BCM2711.
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Reviewed-by: Melissa Wen <mwen@igalia.com>
Link: https://lore.kernel.org/r/20220610115149.964394-14-maxime@cerno.tech
2022-06-10 19:51:48 +08:00
|
|
|
vc4_async_set_fence_cb(dev, flip_state);
|
2015-12-01 04:34:01 +08:00
|
|
|
|
|
|
|
/* Driver takes ownership of state on successful async commit. */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-06-10 19:51:46 +08:00
|
|
|
/* Implements async (non-vblank-synced) page flips.
|
|
|
|
*
|
|
|
|
* The page flip ioctl needs to return immediately, so we grab the
|
|
|
|
* modeset semaphore on the pipe, and queue the address update for
|
|
|
|
* when V3D is done with the BO being flipped to.
|
|
|
|
*/
|
|
|
|
static int vc4_async_page_flip(struct drm_crtc *crtc,
|
|
|
|
struct drm_framebuffer *fb,
|
|
|
|
struct drm_pending_vblank_event *event,
|
|
|
|
uint32_t flags)
|
|
|
|
{
|
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct vc4_dev *vc4 = to_vc4_dev(dev);
|
2022-08-02 08:04:03 +08:00
|
|
|
struct drm_gem_dma_object *dma_bo = drm_fb_dma_get_gem_obj(fb, 0);
|
|
|
|
struct vc4_bo *bo = to_vc4_bo(&dma_bo->base);
|
2022-06-10 19:51:46 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (WARN_ON_ONCE(vc4->is_vc5))
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Increment the BO usecnt here, so that we never end up with an
|
|
|
|
* unbalanced number of vc4_bo_{dec,inc}_usecnt() calls when the
|
|
|
|
* plane is later updated through the non-async path.
|
|
|
|
*
|
|
|
|
* FIXME: we should move to generic async-page-flip when
|
|
|
|
* it's available, so that we can get rid of this
|
|
|
|
* hand-made prepare_fb() logic.
|
|
|
|
*/
|
|
|
|
ret = vc4_bo_inc_usecnt(bo);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = vc4_async_page_flip_common(crtc, fb, event, flags);
|
|
|
|
if (ret) {
|
|
|
|
vc4_bo_dec_usecnt(bo);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-06-10 19:51:47 +08:00
|
|
|
static int vc5_async_page_flip(struct drm_crtc *crtc,
|
|
|
|
struct drm_framebuffer *fb,
|
|
|
|
struct drm_pending_vblank_event *event,
|
|
|
|
uint32_t flags)
|
|
|
|
{
|
|
|
|
return vc4_async_page_flip_common(crtc, fb, event, flags);
|
|
|
|
}
|
|
|
|
|
2020-06-11 21:36:48 +08:00
|
|
|
int vc4_page_flip(struct drm_crtc *crtc,
|
|
|
|
struct drm_framebuffer *fb,
|
|
|
|
struct drm_pending_vblank_event *event,
|
|
|
|
uint32_t flags,
|
|
|
|
struct drm_modeset_acquire_ctx *ctx)
|
2015-12-01 04:34:01 +08:00
|
|
|
{
|
2022-06-10 19:51:47 +08:00
|
|
|
if (flags & DRM_MODE_PAGE_FLIP_ASYNC) {
|
|
|
|
struct drm_device *dev = crtc->dev;
|
|
|
|
struct vc4_dev *vc4 = to_vc4_dev(dev);
|
|
|
|
|
|
|
|
if (vc4->is_vc5)
|
|
|
|
return vc5_async_page_flip(crtc, fb, event, flags);
|
|
|
|
else
|
|
|
|
return vc4_async_page_flip(crtc, fb, event, flags);
|
|
|
|
} else {
|
2017-03-23 05:50:50 +08:00
|
|
|
return drm_atomic_helper_page_flip(crtc, fb, event, flags, ctx);
|
2022-06-10 19:51:47 +08:00
|
|
|
}
|
2015-12-01 04:34:01 +08:00
|
|
|
}
|
|
|
|
|
2020-06-11 21:36:48 +08:00
|
|
|
struct drm_crtc_state *vc4_crtc_duplicate_state(struct drm_crtc *crtc)
|
2015-12-29 05:25:41 +08:00
|
|
|
{
|
2018-07-03 15:50:22 +08:00
|
|
|
struct vc4_crtc_state *vc4_state, *old_vc4_state;
|
2015-12-29 05:25:41 +08:00
|
|
|
|
|
|
|
vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL);
|
|
|
|
if (!vc4_state)
|
|
|
|
return NULL;
|
|
|
|
|
2018-07-03 15:50:22 +08:00
|
|
|
old_vc4_state = to_vc4_crtc_state(crtc->state);
|
2018-12-06 22:24:38 +08:00
|
|
|
vc4_state->margins = old_vc4_state->margins;
|
2020-09-03 16:00:46 +08:00
|
|
|
vc4_state->assigned_channel = old_vc4_state->assigned_channel;
|
2018-07-03 15:50:22 +08:00
|
|
|
|
2015-12-29 05:25:41 +08:00
|
|
|
__drm_atomic_helper_crtc_duplicate_state(crtc, &vc4_state->base);
|
|
|
|
return &vc4_state->base;
|
|
|
|
}
|
|
|
|
|
2020-06-11 21:36:48 +08:00
|
|
|
void vc4_crtc_destroy_state(struct drm_crtc *crtc,
|
|
|
|
struct drm_crtc_state *state)
|
2015-12-29 05:25:41 +08:00
|
|
|
{
|
|
|
|
struct vc4_dev *vc4 = to_vc4_dev(crtc->dev);
|
|
|
|
struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state);
|
|
|
|
|
2019-10-04 05:00:58 +08:00
|
|
|
if (drm_mm_node_allocated(&vc4_state->mm)) {
|
2015-12-29 05:25:41 +08:00
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&vc4->hvs->mm_lock, flags);
|
|
|
|
drm_mm_remove_node(&vc4_state->mm);
|
|
|
|
spin_unlock_irqrestore(&vc4->hvs->mm_lock, flags);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-10-11 00:44:06 +08:00
|
|
|
drm_atomic_helper_crtc_destroy_state(crtc, state);
|
2015-12-29 05:25:41 +08:00
|
|
|
}
|
|
|
|
|
2020-06-11 21:36:48 +08:00
|
|
|
void vc4_crtc_reset(struct drm_crtc *crtc)
|
2017-03-29 04:13:43 +08:00
|
|
|
{
|
2020-09-23 16:40:31 +08:00
|
|
|
struct vc4_crtc_state *vc4_crtc_state;
|
|
|
|
|
2017-03-29 04:13:43 +08:00
|
|
|
if (crtc->state)
|
2019-04-24 23:06:29 +08:00
|
|
|
vc4_crtc_destroy_state(crtc, crtc->state);
|
2020-09-23 16:40:31 +08:00
|
|
|
|
|
|
|
vc4_crtc_state = kzalloc(sizeof(*vc4_crtc_state), GFP_KERNEL);
|
|
|
|
if (!vc4_crtc_state) {
|
|
|
|
crtc->state = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-09-23 16:40:32 +08:00
|
|
|
vc4_crtc_state->assigned_channel = VC4_HVS_CHANNEL_DISABLED;
|
2020-09-23 16:40:31 +08:00
|
|
|
__drm_atomic_helper_crtc_reset(crtc, &vc4_crtc_state->base);
|
2017-03-29 04:13:43 +08:00
|
|
|
}
|
|
|
|
|
2022-07-12 01:39:34 +08:00
|
|
|
int vc4_crtc_late_register(struct drm_crtc *crtc)
|
|
|
|
{
|
|
|
|
struct drm_device *drm = crtc->dev;
|
|
|
|
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
|
|
|
|
const struct vc4_crtc_data *crtc_data = vc4_crtc_to_vc4_crtc_data(vc4_crtc);
|
|
|
|
|
2022-12-19 20:06:18 +08:00
|
|
|
vc4_debugfs_add_regset32(drm, crtc_data->debugfs_name,
|
|
|
|
&vc4_crtc->regset);
|
2022-07-12 01:39:34 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-03-03 05:01:12 +08:00
|
|
|
static const struct drm_crtc_funcs vc4_crtc_funcs = {
|
|
|
|
.set_config = drm_atomic_helper_set_config,
|
2015-12-01 04:34:01 +08:00
|
|
|
.page_flip = vc4_page_flip,
|
2015-03-03 05:01:12 +08:00
|
|
|
.set_property = NULL,
|
|
|
|
.cursor_set = NULL, /* handled by drm_mode_cursor_universal */
|
|
|
|
.cursor_move = NULL, /* handled by drm_mode_cursor_universal */
|
2017-03-29 04:13:43 +08:00
|
|
|
.reset = vc4_crtc_reset,
|
2015-12-29 05:25:41 +08:00
|
|
|
.atomic_duplicate_state = vc4_crtc_duplicate_state,
|
|
|
|
.atomic_destroy_state = vc4_crtc_destroy_state,
|
2017-02-07 17:16:34 +08:00
|
|
|
.enable_vblank = vc4_enable_vblank,
|
|
|
|
.disable_vblank = vc4_disable_vblank,
|
2020-01-23 21:59:39 +08:00
|
|
|
.get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp,
|
2022-07-12 01:39:34 +08:00
|
|
|
.late_register = vc4_crtc_late_register,
|
2015-03-03 05:01:12 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static const struct drm_crtc_helper_funcs vc4_crtc_helper_funcs = {
|
2017-05-25 22:19:22 +08:00
|
|
|
.mode_valid = vc4_crtc_mode_valid,
|
2015-03-03 05:01:12 +08:00
|
|
|
.atomic_check = vc4_crtc_atomic_check,
|
2021-10-25 22:11:07 +08:00
|
|
|
.atomic_begin = vc4_hvs_atomic_begin,
|
2020-06-11 21:36:47 +08:00
|
|
|
.atomic_flush = vc4_hvs_atomic_flush,
|
2017-06-30 17:36:44 +08:00
|
|
|
.atomic_enable = vc4_crtc_atomic_enable,
|
2017-06-30 17:36:45 +08:00
|
|
|
.atomic_disable = vc4_crtc_atomic_disable,
|
2020-01-23 21:59:38 +08:00
|
|
|
.get_scanout_position = vc4_crtc_get_scanout_position,
|
2015-03-03 05:01:12 +08:00
|
|
|
};
|
|
|
|
|
2022-12-01 23:11:48 +08:00
|
|
|
const struct vc4_pv_data bcm2835_pv0_data = {
|
2020-06-11 21:36:49 +08:00
|
|
|
.base = {
|
2022-11-23 23:26:02 +08:00
|
|
|
.name = "pixelvalve-0",
|
2022-07-12 01:38:50 +08:00
|
|
|
.debugfs_name = "crtc0_regs",
|
2020-09-03 16:00:46 +08:00
|
|
|
.hvs_available_channels = BIT(0),
|
2020-09-03 16:00:42 +08:00
|
|
|
.hvs_output = 0,
|
2020-06-11 21:36:49 +08:00
|
|
|
},
|
2020-09-03 16:00:47 +08:00
|
|
|
.fifo_depth = 64,
|
2020-09-03 16:00:39 +08:00
|
|
|
.pixels_per_clock = 1,
|
2016-12-02 21:48:07 +08:00
|
|
|
.encoder_types = {
|
|
|
|
[PV_CONTROL_CLK_SELECT_DSI] = VC4_ENCODER_TYPE_DSI0,
|
|
|
|
[PV_CONTROL_CLK_SELECT_DPI_SMI_HDMI] = VC4_ENCODER_TYPE_DPI,
|
|
|
|
},
|
2015-03-03 05:01:12 +08:00
|
|
|
};
|
|
|
|
|
2022-12-01 23:11:48 +08:00
|
|
|
const struct vc4_pv_data bcm2835_pv1_data = {
|
2020-06-11 21:36:49 +08:00
|
|
|
.base = {
|
2022-11-23 23:26:02 +08:00
|
|
|
.name = "pixelvalve-1",
|
2022-07-12 01:38:50 +08:00
|
|
|
.debugfs_name = "crtc1_regs",
|
2020-09-03 16:00:46 +08:00
|
|
|
.hvs_available_channels = BIT(2),
|
2020-09-03 16:00:42 +08:00
|
|
|
.hvs_output = 2,
|
2020-06-11 21:36:49 +08:00
|
|
|
},
|
2020-09-03 16:00:47 +08:00
|
|
|
.fifo_depth = 64,
|
2020-09-03 16:00:39 +08:00
|
|
|
.pixels_per_clock = 1,
|
2016-12-02 21:48:07 +08:00
|
|
|
.encoder_types = {
|
|
|
|
[PV_CONTROL_CLK_SELECT_DSI] = VC4_ENCODER_TYPE_DSI1,
|
|
|
|
[PV_CONTROL_CLK_SELECT_DPI_SMI_HDMI] = VC4_ENCODER_TYPE_SMI,
|
|
|
|
},
|
2015-03-03 05:01:12 +08:00
|
|
|
};
|
|
|
|
|
2022-12-01 23:11:48 +08:00
|
|
|
const struct vc4_pv_data bcm2835_pv2_data = {
|
2020-06-11 21:36:49 +08:00
|
|
|
.base = {
|
2022-11-23 23:26:02 +08:00
|
|
|
.name = "pixelvalve-2",
|
2022-07-12 01:38:50 +08:00
|
|
|
.debugfs_name = "crtc2_regs",
|
2020-09-03 16:00:46 +08:00
|
|
|
.hvs_available_channels = BIT(1),
|
2020-09-03 16:00:42 +08:00
|
|
|
.hvs_output = 1,
|
2020-06-11 21:36:49 +08:00
|
|
|
},
|
2020-09-03 16:00:47 +08:00
|
|
|
.fifo_depth = 64,
|
2020-09-03 16:00:39 +08:00
|
|
|
.pixels_per_clock = 1,
|
2016-12-02 21:48:07 +08:00
|
|
|
.encoder_types = {
|
2020-09-03 16:00:49 +08:00
|
|
|
[PV_CONTROL_CLK_SELECT_DPI_SMI_HDMI] = VC4_ENCODER_TYPE_HDMI0,
|
2016-12-02 21:48:07 +08:00
|
|
|
[PV_CONTROL_CLK_SELECT_VEC] = VC4_ENCODER_TYPE_VEC,
|
|
|
|
},
|
2015-03-03 05:01:12 +08:00
|
|
|
};
|
|
|
|
|
2022-12-01 23:11:48 +08:00
|
|
|
const struct vc4_pv_data bcm2711_pv0_data = {
|
2020-09-03 16:01:09 +08:00
|
|
|
.base = {
|
2022-11-23 23:26:02 +08:00
|
|
|
.name = "pixelvalve-0",
|
2022-07-12 01:38:50 +08:00
|
|
|
.debugfs_name = "crtc0_regs",
|
2020-09-03 16:01:09 +08:00
|
|
|
.hvs_available_channels = BIT(0),
|
|
|
|
.hvs_output = 0,
|
|
|
|
},
|
|
|
|
.fifo_depth = 64,
|
|
|
|
.pixels_per_clock = 1,
|
|
|
|
.encoder_types = {
|
|
|
|
[0] = VC4_ENCODER_TYPE_DSI0,
|
|
|
|
[1] = VC4_ENCODER_TYPE_DPI,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2022-12-01 23:11:48 +08:00
|
|
|
const struct vc4_pv_data bcm2711_pv1_data = {
|
2020-09-03 16:01:09 +08:00
|
|
|
.base = {
|
2022-11-23 23:26:02 +08:00
|
|
|
.name = "pixelvalve-1",
|
2022-07-12 01:38:50 +08:00
|
|
|
.debugfs_name = "crtc1_regs",
|
2020-09-03 16:01:09 +08:00
|
|
|
.hvs_available_channels = BIT(0) | BIT(1) | BIT(2),
|
|
|
|
.hvs_output = 3,
|
|
|
|
},
|
|
|
|
.fifo_depth = 64,
|
|
|
|
.pixels_per_clock = 1,
|
|
|
|
.encoder_types = {
|
|
|
|
[0] = VC4_ENCODER_TYPE_DSI1,
|
|
|
|
[1] = VC4_ENCODER_TYPE_SMI,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2022-12-01 23:11:48 +08:00
|
|
|
const struct vc4_pv_data bcm2711_pv2_data = {
|
2020-09-03 16:01:09 +08:00
|
|
|
.base = {
|
2022-11-23 23:26:02 +08:00
|
|
|
.name = "pixelvalve-2",
|
2022-07-12 01:38:50 +08:00
|
|
|
.debugfs_name = "crtc2_regs",
|
2020-09-03 16:01:09 +08:00
|
|
|
.hvs_available_channels = BIT(0) | BIT(1) | BIT(2),
|
|
|
|
.hvs_output = 4,
|
|
|
|
},
|
|
|
|
.fifo_depth = 256,
|
|
|
|
.pixels_per_clock = 2,
|
|
|
|
.encoder_types = {
|
|
|
|
[0] = VC4_ENCODER_TYPE_HDMI0,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2022-12-01 23:11:48 +08:00
|
|
|
const struct vc4_pv_data bcm2711_pv3_data = {
|
2020-09-03 16:01:09 +08:00
|
|
|
.base = {
|
2022-11-23 23:26:02 +08:00
|
|
|
.name = "pixelvalve-3",
|
2022-07-12 01:38:50 +08:00
|
|
|
.debugfs_name = "crtc3_regs",
|
2020-09-03 16:01:09 +08:00
|
|
|
.hvs_available_channels = BIT(1),
|
|
|
|
.hvs_output = 1,
|
|
|
|
},
|
|
|
|
.fifo_depth = 64,
|
|
|
|
.pixels_per_clock = 1,
|
|
|
|
.encoder_types = {
|
2021-05-20 23:03:41 +08:00
|
|
|
[PV_CONTROL_CLK_SELECT_VEC] = VC4_ENCODER_TYPE_VEC,
|
2020-09-03 16:01:09 +08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2022-12-01 23:11:48 +08:00
|
|
|
const struct vc4_pv_data bcm2711_pv4_data = {
|
2020-09-03 16:01:09 +08:00
|
|
|
.base = {
|
2022-11-23 23:26:02 +08:00
|
|
|
.name = "pixelvalve-4",
|
2022-07-12 01:38:50 +08:00
|
|
|
.debugfs_name = "crtc4_regs",
|
2020-09-03 16:01:09 +08:00
|
|
|
.hvs_available_channels = BIT(0) | BIT(1) | BIT(2),
|
|
|
|
.hvs_output = 5,
|
|
|
|
},
|
|
|
|
.fifo_depth = 64,
|
|
|
|
.pixels_per_clock = 2,
|
|
|
|
.encoder_types = {
|
|
|
|
[0] = VC4_ENCODER_TYPE_HDMI1,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2015-03-03 05:01:12 +08:00
|
|
|
static const struct of_device_id vc4_crtc_dt_match[] = {
|
2020-05-27 23:47:52 +08:00
|
|
|
{ .compatible = "brcm,bcm2835-pixelvalve0", .data = &bcm2835_pv0_data },
|
|
|
|
{ .compatible = "brcm,bcm2835-pixelvalve1", .data = &bcm2835_pv1_data },
|
|
|
|
{ .compatible = "brcm,bcm2835-pixelvalve2", .data = &bcm2835_pv2_data },
|
2020-09-03 16:01:09 +08:00
|
|
|
{ .compatible = "brcm,bcm2711-pixelvalve0", .data = &bcm2711_pv0_data },
|
|
|
|
{ .compatible = "brcm,bcm2711-pixelvalve1", .data = &bcm2711_pv1_data },
|
|
|
|
{ .compatible = "brcm,bcm2711-pixelvalve2", .data = &bcm2711_pv2_data },
|
|
|
|
{ .compatible = "brcm,bcm2711-pixelvalve3", .data = &bcm2711_pv3_data },
|
|
|
|
{ .compatible = "brcm,bcm2711-pixelvalve4", .data = &bcm2711_pv4_data },
|
2015-03-03 05:01:12 +08:00
|
|
|
{}
|
|
|
|
};
|
|
|
|
|
|
|
|
static void vc4_set_crtc_possible_masks(struct drm_device *drm,
|
|
|
|
struct drm_crtc *crtc)
|
|
|
|
{
|
|
|
|
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
|
2020-06-11 21:36:49 +08:00
|
|
|
const struct vc4_pv_data *pv_data = vc4_crtc_to_vc4_pv_data(vc4_crtc);
|
|
|
|
const enum vc4_encoder_type *encoder_types = pv_data->encoder_types;
|
2015-03-03 05:01:12 +08:00
|
|
|
struct drm_encoder *encoder;
|
|
|
|
|
|
|
|
drm_for_each_encoder(encoder, drm) {
|
2018-07-03 15:50:22 +08:00
|
|
|
struct vc4_encoder *vc4_encoder;
|
2016-12-02 21:48:07 +08:00
|
|
|
int i;
|
|
|
|
|
2021-05-07 23:05:05 +08:00
|
|
|
if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
|
|
|
|
continue;
|
|
|
|
|
2018-07-03 15:50:22 +08:00
|
|
|
vc4_encoder = to_vc4_encoder(encoder);
|
2020-06-11 21:36:49 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(pv_data->encoder_types); i++) {
|
2016-12-02 21:48:07 +08:00
|
|
|
if (vc4_encoder->type == encoder_types[i]) {
|
|
|
|
vc4_encoder->clock_select = i;
|
|
|
|
encoder->possible_crtcs |= drm_crtc_mask(crtc);
|
|
|
|
break;
|
|
|
|
}
|
2015-03-03 05:01:12 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-01 23:11:45 +08:00
|
|
|
/**
|
|
|
|
* __vc4_crtc_init - Initializes a CRTC
|
|
|
|
* @drm: DRM Device
|
|
|
|
* @pdev: CRTC Platform Device
|
|
|
|
* @vc4_crtc: CRTC Object to Initialize
|
|
|
|
* @data: Configuration data associated with this CRTC
|
|
|
|
* @primary_plane: Primary plane for CRTC
|
|
|
|
* @crtc_funcs: Callbacks for the new CRTC
|
|
|
|
* @crtc_helper_funcs: Helper Callbacks for the new CRTC
|
|
|
|
* @feeds_txp: Is this CRTC connected to the TXP?
|
|
|
|
*
|
|
|
|
* Initializes our private CRTC structure. This function is mostly
|
|
|
|
* relevant for KUnit testing, all other users should use
|
|
|
|
* vc4_crtc_init() instead.
|
|
|
|
*
|
|
|
|
* Returns:
|
|
|
|
* 0 on success, a negative error code on failure.
|
|
|
|
*/
|
|
|
|
int __vc4_crtc_init(struct drm_device *drm,
|
|
|
|
struct platform_device *pdev,
|
|
|
|
struct vc4_crtc *vc4_crtc,
|
|
|
|
const struct vc4_crtc_data *data,
|
|
|
|
struct drm_plane *primary_plane,
|
|
|
|
const struct drm_crtc_funcs *crtc_funcs,
|
|
|
|
const struct drm_crtc_helper_funcs *crtc_helper_funcs,
|
|
|
|
bool feeds_txp)
|
2020-06-11 21:36:51 +08:00
|
|
|
{
|
2020-09-03 16:00:51 +08:00
|
|
|
struct vc4_dev *vc4 = to_vc4_dev(drm);
|
2020-06-11 21:36:51 +08:00
|
|
|
struct drm_crtc *crtc = &vc4_crtc->base;
|
|
|
|
unsigned int i;
|
2022-07-12 01:38:52 +08:00
|
|
|
int ret;
|
2020-06-11 21:36:51 +08:00
|
|
|
|
2022-11-23 23:25:59 +08:00
|
|
|
vc4_crtc->data = data;
|
|
|
|
vc4_crtc->pdev = pdev;
|
|
|
|
vc4_crtc->feeds_txp = feeds_txp;
|
drm/vc4: Fix non-blocking commit getting stuck forever
In some situation, we can end up being stuck on a non-blocking that went
through properly.
The situation that seems to trigger it reliably is to first start a
non-blocking commit, and then right after, and before we had any vblank
interrupt), start a blocking commit.
This will lead to the first commit workqueue to be scheduled, setup the
display, while the second commit is waiting for the first one to be
completed.
The vblank interrupt will then be raised, vc4_crtc_handle_vblank() will
run and will compare the active dlist in the HVS channel to the one
associated with the crtc->state.
However, at that point, the second commit is waiting using
drm_atomic_helper_wait_for_dependencies that occurs after
drm_atomic_helper_swap_state has been called, so crtc->state points to
the second commit state. vc4_crtc_handle_vblank() will compare the two
dlist addresses and since they don't match will ignore the interrupt.
The vblank event will never be reported, and the first and second commit
will wait for the first commit completion until they timeout.
The underlying reason is that it was never safe to do so. Indeed,
accessing the ->state pointer access synchronization is based on
ownership guarantees that can only occur within the functions and hooks
defined as part of the KMS framework, and obviously the irq handler
isn't one of them. The rework to move to generic helpers only uncovered
the underlying issue.
However, since the code path between
drm_atomic_helper_wait_for_dependencies() and
drm_atomic_helper_wait_for_vblanks() is serialised and we can't get two
commits in that path at the same time, we can work around this issue by
setting a variable associated to struct drm_crtc to the dlist we expect,
and then using it from the vc4_crtc_handle_vblank() function.
Since that state is shared with the modesetting path, we also need to
introduce a spinlock to protect the code shared between the interrupt
handler and the modesetting path, protecting only our new variable for
now.
Link: https://lore.kernel.org/all/YWgteNaNeaS9uWDe@phenom.ffwll.local/
Link: https://lore.kernel.org/r/20211025141113.702757-3-maxime@cerno.tech
Fixes: 56d1fe0979dc ("drm/vc4: Make pageflip completion handling more robust.")
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
2021-10-25 22:11:06 +08:00
|
|
|
spin_lock_init(&vc4_crtc->irq_lock);
|
2022-07-12 01:38:52 +08:00
|
|
|
ret = drmm_crtc_init_with_planes(drm, crtc, primary_plane, NULL,
|
2022-11-23 23:26:02 +08:00
|
|
|
crtc_funcs, data->name);
|
2022-07-12 01:38:52 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2020-06-11 21:36:51 +08:00
|
|
|
drm_crtc_helper_add(crtc, crtc_helper_funcs);
|
|
|
|
|
2022-06-10 19:51:37 +08:00
|
|
|
if (!vc4->is_vc5) {
|
2020-09-03 16:00:51 +08:00
|
|
|
drm_mode_crtc_set_gamma_size(crtc, ARRAY_SIZE(vc4_crtc->lut_r));
|
|
|
|
|
|
|
|
drm_crtc_enable_color_mgmt(crtc, 0, false, crtc->gamma_size);
|
|
|
|
|
|
|
|
/* We support CTM, but only for one CRTC at a time. It's therefore
|
|
|
|
* implemented as private driver state in vc4_kms, not here.
|
|
|
|
*/
|
|
|
|
drm_crtc_enable_color_mgmt(crtc, 0, true, crtc->gamma_size);
|
|
|
|
}
|
2020-06-11 21:36:51 +08:00
|
|
|
|
|
|
|
for (i = 0; i < crtc->gamma_size; i++) {
|
|
|
|
vc4_crtc->lut_r[i] = i;
|
|
|
|
vc4_crtc->lut_g[i] = i;
|
|
|
|
vc4_crtc->lut_b[i] = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-12-01 23:11:45 +08:00
|
|
|
int vc4_crtc_init(struct drm_device *drm, struct platform_device *pdev,
|
|
|
|
struct vc4_crtc *vc4_crtc,
|
|
|
|
const struct vc4_crtc_data *data,
|
|
|
|
const struct drm_crtc_funcs *crtc_funcs,
|
|
|
|
const struct drm_crtc_helper_funcs *crtc_helper_funcs,
|
|
|
|
bool feeds_txp)
|
|
|
|
{
|
|
|
|
struct drm_plane *primary_plane;
|
|
|
|
|
|
|
|
/* For now, we create just the primary and the legacy cursor
|
|
|
|
* planes. We should be able to stack more planes on easily,
|
|
|
|
* but to do that we would need to compute the bandwidth
|
|
|
|
* requirement of the plane configuration, and reject ones
|
|
|
|
* that will take too much.
|
|
|
|
*/
|
|
|
|
primary_plane = vc4_plane_init(drm, DRM_PLANE_TYPE_PRIMARY, 0);
|
|
|
|
if (IS_ERR(primary_plane)) {
|
|
|
|
dev_err(drm->dev, "failed to construct primary plane\n");
|
|
|
|
return PTR_ERR(primary_plane);
|
|
|
|
}
|
|
|
|
|
|
|
|
return __vc4_crtc_init(drm, pdev, vc4_crtc, data, primary_plane,
|
|
|
|
crtc_funcs, crtc_helper_funcs, feeds_txp);
|
|
|
|
}
|
|
|
|
|
2015-03-03 05:01:12 +08:00
|
|
|
static int vc4_crtc_bind(struct device *dev, struct device *master, void *data)
|
|
|
|
{
|
|
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
|
|
struct drm_device *drm = dev_get_drvdata(master);
|
2020-06-11 21:36:49 +08:00
|
|
|
const struct vc4_pv_data *pv_data;
|
2015-03-03 05:01:12 +08:00
|
|
|
struct vc4_crtc *vc4_crtc;
|
|
|
|
struct drm_crtc *crtc;
|
2020-06-11 21:36:51 +08:00
|
|
|
int ret;
|
2015-03-03 05:01:12 +08:00
|
|
|
|
2022-07-12 01:38:51 +08:00
|
|
|
vc4_crtc = drmm_kzalloc(drm, sizeof(*vc4_crtc), GFP_KERNEL);
|
2015-03-03 05:01:12 +08:00
|
|
|
if (!vc4_crtc)
|
|
|
|
return -ENOMEM;
|
|
|
|
crtc = &vc4_crtc->base;
|
|
|
|
|
2020-05-27 23:47:53 +08:00
|
|
|
pv_data = of_device_get_match_data(dev);
|
|
|
|
if (!pv_data)
|
2015-03-03 05:01:12 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
vc4_crtc->regs = vc4_ioremap_regs(pdev, 0);
|
|
|
|
if (IS_ERR(vc4_crtc->regs))
|
|
|
|
return PTR_ERR(vc4_crtc->regs);
|
|
|
|
|
2019-02-21 05:03:38 +08:00
|
|
|
vc4_crtc->regset.base = vc4_crtc->regs;
|
|
|
|
vc4_crtc->regset.regs = crtc_regs;
|
|
|
|
vc4_crtc->regset.nregs = ARRAY_SIZE(crtc_regs);
|
|
|
|
|
2022-11-23 23:25:59 +08:00
|
|
|
ret = vc4_crtc_init(drm, pdev, vc4_crtc, &pv_data->base,
|
|
|
|
&vc4_crtc_funcs, &vc4_crtc_helper_funcs,
|
|
|
|
false);
|
2020-06-11 21:36:51 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
vc4_set_crtc_possible_masks(drm, crtc);
|
drm/vc4: Implement precise vblank timestamping.
Precise vblank timestamping is implemented via the
usual scanout position based method. On VC4 the
pixelvalves PV do not have a scanout position
register. Only the hardware video scaler HVS has a
similar register which describes which scanline for
the output is currently composited and stored in the
HVS fifo for later consumption by the PV.
This causes a problem in that the HVS runs at a much
faster clock (system clock / audio gate) than the PV
which runs at video mode dot clock, so the unless the
fifo between HVS and PV is full, the HVS will progress
faster in its observable read line position than video
scan rate, so the HVS position reading can't be directly
translated into a scanout position for timestamp correction.
Additionally when the PV is in vblank, it doesn't consume
from the fifo, so the fifo gets full very quickly and then
the HVS stops compositing until the PV enters active scanout
and starts consuming scanlines from the fifo again, making
new space for the HVS to composite.
Therefore a simple translation of HVS read position into
elapsed time since (or to) start of active scanout does
not work, but for the most interesting cases we can still
get useful and sufficiently accurate results:
1. The PV enters active scanout of a new frame with the
fifo of the HVS completely full, and the HVS can refill
any fifo line which gets consumed and thereby freed up by
the PV during active scanout very quickly. Therefore the
PV and HVS work effectively in lock-step during active
scanout with the fifo never having more than 1 scanline
freed up by the PV before it gets refilled. The PV's
real scanout position is therefore trailing the HVS
compositing position as scanoutpos = hvspos - fifosize
and we can get the true scanoutpos as HVS readpos minus
fifo size, so precise timestamping works while in active
scanout, except for the last few scanlines of the frame,
when the HVS reaches end of frame, stops compositing and
the PV catches up and drains the fifo. This special case
would only introduce minor errors though.
2. If we are in vblank, then we can only guess something
reasonable. If called from vblank irq, we assume the irq is
usually dispatched with minimum delay, so we can take a
timestamp taken at entry into the vblank irq handler as a
baseline and then add a full vblank duration until the
guessed start of active scanout. As irq dispatch is usually
pretty low latency this works with relatively low jitter and
good results.
If we aren't called from vblank then we could be anywhere
within the vblank interval, so we return a neutral result,
simply the current system timestamp, and hope for the best.
Measurement shows the generated timestamps to be rather precise,
and at least never off more than 1 vblank duration worst-case.
Limitations: Doesn't work well yet for interlaced video modes,
therefore disabled in interlaced mode for now.
v2: Use the DISPBASE registers to determine the FIFO size (changes
by anholt)
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-and-tested-by: Mario Kleiner <mario.kleiner.de@gmail.com> (v2)
2016-06-23 14:17:50 +08:00
|
|
|
|
2015-03-03 05:01:12 +08:00
|
|
|
CRTC_WRITE(PV_INTEN, 0);
|
|
|
|
CRTC_WRITE(PV_INTSTAT, PV_INT_VFP_START);
|
|
|
|
ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
|
2020-09-03 16:00:40 +08:00
|
|
|
vc4_crtc_irq_handler,
|
|
|
|
IRQF_SHARED,
|
|
|
|
"vc4 crtc", vc4_crtc);
|
2015-03-03 05:01:12 +08:00
|
|
|
if (ret)
|
2022-07-12 01:38:48 +08:00
|
|
|
return ret;
|
2015-03-03 05:01:12 +08:00
|
|
|
|
|
|
|
platform_set_drvdata(pdev, vc4_crtc);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vc4_crtc_unbind(struct device *dev, struct device *master,
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
|
|
struct vc4_crtc *vc4_crtc = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
CRTC_WRITE(PV_INTEN, 0);
|
|
|
|
|
|
|
|
platform_set_drvdata(pdev, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct component_ops vc4_crtc_ops = {
|
|
|
|
.bind = vc4_crtc_bind,
|
|
|
|
.unbind = vc4_crtc_unbind,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int vc4_crtc_dev_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
return component_add(&pdev->dev, &vc4_crtc_ops);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vc4_crtc_dev_remove(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
component_del(&pdev->dev, &vc4_crtc_ops);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct platform_driver vc4_crtc_driver = {
|
|
|
|
.probe = vc4_crtc_dev_probe,
|
|
|
|
.remove = vc4_crtc_dev_remove,
|
|
|
|
.driver = {
|
|
|
|
.name = "vc4_crtc",
|
|
|
|
.of_match_table = vc4_crtc_dt_match,
|
|
|
|
},
|
|
|
|
};
|