drm/vblank: add dynamic per-crtc vblank configuration support

We would like to be able to enable vblank_disable_immediate
unconditionally, however there are a handful of cases where a small off
delay is necessary (e.g. with PSR enabled). So, we would like to be able
to adjust the vblank off delay and disable imminent values dynamically
for a given CRTC. Since, it will allow drivers to apply static screen
optimizations more quickly and consequently allow users to benefit more
so from the power savings afforded by the aforementioned optimizations,
while avoiding issues in cases where an off delay is still warranted.
In particular, the PSR case requires a small off delay of 2 frames,
otherwise display firmware isn't able to keep up with all of the
requests made to amdgpu. So, introduce drm_crtc_vblank_on_config() which
is like drm_crtc_vblank_on(), but it allows drivers to specify the
vblank CRTC configuration before enabling vblanking support for a given
CRTC.

Signed-off-by: Hamza Mahfooz <hamza.mahfooz@amd.com>
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: https://patchwork.freedesktop.org/patch/msgid/20240725205109.209743-1-hamza.mahfooz@amd.com
This commit is contained in:
Hamza Mahfooz 2024-07-25 16:51:08 -04:00
parent 9d8e91439f
commit 0d5040e406
No known key found for this signature in database
GPG Key ID: 3D6797D6EE6FCAE2
3 changed files with 97 additions and 26 deletions

View File

@ -131,7 +131,7 @@
* guaranteed to be enabled. * guaranteed to be enabled.
* *
* On many hardware disabling the vblank interrupt cannot be done in a race-free * On many hardware disabling the vblank interrupt cannot be done in a race-free
* manner, see &drm_driver.vblank_disable_immediate and * manner, see &drm_vblank_crtc_config.disable_immediate and
* &drm_driver.max_vblank_count. In that case the vblank core only disables the * &drm_driver.max_vblank_count. In that case the vblank core only disables the
* vblanks after a timer has expired, which can be configured through the * vblanks after a timer has expired, which can be configured through the
* ``vblankoffdelay`` module parameter. * ``vblankoffdelay`` module parameter.
@ -1241,6 +1241,7 @@ EXPORT_SYMBOL(drm_crtc_vblank_get);
void drm_vblank_put(struct drm_device *dev, unsigned int pipe) void drm_vblank_put(struct drm_device *dev, unsigned int pipe)
{ {
struct drm_vblank_crtc *vblank = drm_vblank_crtc(dev, pipe); struct drm_vblank_crtc *vblank = drm_vblank_crtc(dev, pipe);
int vblank_offdelay = vblank->config.offdelay_ms;
if (drm_WARN_ON(dev, pipe >= dev->num_crtcs)) if (drm_WARN_ON(dev, pipe >= dev->num_crtcs))
return; return;
@ -1250,13 +1251,13 @@ void drm_vblank_put(struct drm_device *dev, unsigned int pipe)
/* Last user schedules interrupt disable */ /* Last user schedules interrupt disable */
if (atomic_dec_and_test(&vblank->refcount)) { if (atomic_dec_and_test(&vblank->refcount)) {
if (drm_vblank_offdelay == 0) if (!vblank_offdelay)
return; return;
else if (drm_vblank_offdelay < 0) else if (vblank_offdelay < 0)
vblank_disable_fn(&vblank->disable_timer); vblank_disable_fn(&vblank->disable_timer);
else if (!dev->vblank_disable_immediate) else if (!vblank->config.disable_immediate)
mod_timer(&vblank->disable_timer, mod_timer(&vblank->disable_timer,
jiffies + ((drm_vblank_offdelay * HZ)/1000)); jiffies + ((vblank_offdelay * HZ) / 1000));
} }
} }
@ -1265,7 +1266,8 @@ void drm_vblank_put(struct drm_device *dev, unsigned int pipe)
* @crtc: which counter to give up * @crtc: which counter to give up
* *
* Release ownership of a given vblank counter, turning off interrupts * Release ownership of a given vblank counter, turning off interrupts
* if possible. Disable interrupts after drm_vblank_offdelay milliseconds. * if possible. Disable interrupts after &drm_vblank_crtc_config.offdelay_ms
* milliseconds.
*/ */
void drm_crtc_vblank_put(struct drm_crtc *crtc) void drm_crtc_vblank_put(struct drm_crtc *crtc)
{ {
@ -1466,16 +1468,20 @@ void drm_crtc_set_max_vblank_count(struct drm_crtc *crtc,
EXPORT_SYMBOL(drm_crtc_set_max_vblank_count); EXPORT_SYMBOL(drm_crtc_set_max_vblank_count);
/** /**
* drm_crtc_vblank_on - enable vblank events on a CRTC * drm_crtc_vblank_on_config - enable vblank events on a CRTC with custom
* configuration options
* @crtc: CRTC in question * @crtc: CRTC in question
* @config: Vblank configuration value
* *
* This functions restores the vblank interrupt state captured with * See drm_crtc_vblank_on(). In addition, this function allows you to provide a
* drm_crtc_vblank_off() again and is generally called when enabling @crtc. Note * custom vblank configuration for a given CRTC.
* that calls to drm_crtc_vblank_on() and drm_crtc_vblank_off() can be *
* unbalanced and so can also be unconditionally called in driver load code to * Note that @config is copied, the pointer does not need to stay valid beyond
* reflect the current hardware state of the crtc. * this function call. For details of the parameters see
* struct drm_vblank_crtc_config.
*/ */
void drm_crtc_vblank_on(struct drm_crtc *crtc) void drm_crtc_vblank_on_config(struct drm_crtc *crtc,
const struct drm_vblank_crtc_config *config)
{ {
struct drm_device *dev = crtc->dev; struct drm_device *dev = crtc->dev;
unsigned int pipe = drm_crtc_index(crtc); unsigned int pipe = drm_crtc_index(crtc);
@ -1488,6 +1494,8 @@ void drm_crtc_vblank_on(struct drm_crtc *crtc)
drm_dbg_vbl(dev, "crtc %d, vblank enabled %d, inmodeset %d\n", drm_dbg_vbl(dev, "crtc %d, vblank enabled %d, inmodeset %d\n",
pipe, vblank->enabled, vblank->inmodeset); pipe, vblank->enabled, vblank->inmodeset);
vblank->config = *config;
/* Drop our private "prevent drm_vblank_get" refcount */ /* Drop our private "prevent drm_vblank_get" refcount */
if (vblank->inmodeset) { if (vblank->inmodeset) {
atomic_dec(&vblank->refcount); atomic_dec(&vblank->refcount);
@ -1500,10 +1508,33 @@ void drm_crtc_vblank_on(struct drm_crtc *crtc)
* re-enable interrupts if there are users left, or the * re-enable interrupts if there are users left, or the
* user wishes vblank interrupts to be enabled all the time. * user wishes vblank interrupts to be enabled all the time.
*/ */
if (atomic_read(&vblank->refcount) != 0 || drm_vblank_offdelay == 0) if (atomic_read(&vblank->refcount) != 0 || !vblank->config.offdelay_ms)
drm_WARN_ON(dev, drm_vblank_enable(dev, pipe)); drm_WARN_ON(dev, drm_vblank_enable(dev, pipe));
spin_unlock_irq(&dev->vbl_lock); spin_unlock_irq(&dev->vbl_lock);
} }
EXPORT_SYMBOL(drm_crtc_vblank_on_config);
/**
* drm_crtc_vblank_on - enable vblank events on a CRTC
* @crtc: CRTC in question
*
* This functions restores the vblank interrupt state captured with
* drm_crtc_vblank_off() again and is generally called when enabling @crtc. Note
* that calls to drm_crtc_vblank_on() and drm_crtc_vblank_off() can be
* unbalanced and so can also be unconditionally called in driver load code to
* reflect the current hardware state of the crtc.
*
* Note that unlike in drm_crtc_vblank_on_config(), default values are used.
*/
void drm_crtc_vblank_on(struct drm_crtc *crtc)
{
const struct drm_vblank_crtc_config config = {
.offdelay_ms = drm_vblank_offdelay,
.disable_immediate = crtc->dev->vblank_disable_immediate
};
drm_crtc_vblank_on_config(crtc, &config);
}
EXPORT_SYMBOL(drm_crtc_vblank_on); EXPORT_SYMBOL(drm_crtc_vblank_on);
static void drm_vblank_restore(struct drm_device *dev, unsigned int pipe) static void drm_vblank_restore(struct drm_device *dev, unsigned int pipe)
@ -1556,16 +1587,21 @@ static void drm_vblank_restore(struct drm_device *dev, unsigned int pipe)
* *
* Note that drivers must have race-free high-precision timestamping support, * Note that drivers must have race-free high-precision timestamping support,
* i.e. &drm_crtc_funcs.get_vblank_timestamp must be hooked up and * i.e. &drm_crtc_funcs.get_vblank_timestamp must be hooked up and
* &drm_driver.vblank_disable_immediate must be set to indicate the * &drm_vblank_crtc_config.disable_immediate must be set to indicate the
* time-stamping functions are race-free against vblank hardware counter * time-stamping functions are race-free against vblank hardware counter
* increments. * increments.
*/ */
void drm_crtc_vblank_restore(struct drm_crtc *crtc) void drm_crtc_vblank_restore(struct drm_crtc *crtc)
{ {
WARN_ON_ONCE(!crtc->funcs->get_vblank_timestamp); struct drm_device *dev = crtc->dev;
WARN_ON_ONCE(!crtc->dev->vblank_disable_immediate); unsigned int pipe = drm_crtc_index(crtc);
struct drm_vblank_crtc *vblank = drm_vblank_crtc(dev, pipe);
drm_vblank_restore(crtc->dev, drm_crtc_index(crtc)); drm_WARN_ON_ONCE(dev, !crtc->funcs->get_vblank_timestamp);
drm_WARN_ON_ONCE(dev, vblank->inmodeset);
drm_WARN_ON_ONCE(dev, !vblank->config.disable_immediate);
drm_vblank_restore(dev, pipe);
} }
EXPORT_SYMBOL(drm_crtc_vblank_restore); EXPORT_SYMBOL(drm_crtc_vblank_restore);
@ -1754,7 +1790,7 @@ int drm_wait_vblank_ioctl(struct drm_device *dev, void *data,
/* If the counter is currently enabled and accurate, short-circuit /* If the counter is currently enabled and accurate, short-circuit
* queries to return the cached timestamp of the last vblank. * queries to return the cached timestamp of the last vblank.
*/ */
if (dev->vblank_disable_immediate && if (vblank->config.disable_immediate &&
drm_wait_vblank_is_query(vblwait) && drm_wait_vblank_is_query(vblwait) &&
READ_ONCE(vblank->enabled)) { READ_ONCE(vblank->enabled)) {
drm_wait_vblank_reply(dev, pipe, &vblwait->reply); drm_wait_vblank_reply(dev, pipe, &vblwait->reply);
@ -1918,8 +1954,8 @@ bool drm_handle_vblank(struct drm_device *dev, unsigned int pipe)
* been signaled. The disable has to be last (after * been signaled. The disable has to be last (after
* drm_handle_vblank_events) so that the timestamp is always accurate. * drm_handle_vblank_events) so that the timestamp is always accurate.
*/ */
disable_irq = (dev->vblank_disable_immediate && disable_irq = (vblank->config.disable_immediate &&
drm_vblank_offdelay > 0 && vblank->config.offdelay_ms > 0 &&
!atomic_read(&vblank->refcount)); !atomic_read(&vblank->refcount));
drm_handle_vblank_events(dev, pipe); drm_handle_vblank_events(dev, pipe);
@ -1992,7 +2028,8 @@ int drm_crtc_get_sequence_ioctl(struct drm_device *dev, void *data,
pipe = drm_crtc_index(crtc); pipe = drm_crtc_index(crtc);
vblank = drm_crtc_vblank_crtc(crtc); vblank = drm_crtc_vblank_crtc(crtc);
vblank_enabled = dev->vblank_disable_immediate && READ_ONCE(vblank->enabled); vblank_enabled = READ_ONCE(vblank->config.disable_immediate) &&
READ_ONCE(vblank->enabled);
if (!vblank_enabled) { if (!vblank_enabled) {
ret = drm_crtc_vblank_get(crtc); ret = drm_crtc_vblank_get(crtc);

View File

@ -213,8 +213,9 @@ struct drm_device {
* This can be set to true it the hardware has a working vblank counter * This can be set to true it the hardware has a working vblank counter
* with high-precision timestamping (otherwise there are races) and the * with high-precision timestamping (otherwise there are races) and the
* driver uses drm_crtc_vblank_on() and drm_crtc_vblank_off() * driver uses drm_crtc_vblank_on() and drm_crtc_vblank_off()
* appropriately. See also @max_vblank_count and * appropriately. Also, see @max_vblank_count,
* &drm_crtc_funcs.get_vblank_counter. * &drm_crtc_funcs.get_vblank_counter and
* &drm_vblank_crtc_config.disable_immediate.
*/ */
bool vblank_disable_immediate; bool vblank_disable_immediate;

View File

@ -78,6 +78,31 @@ struct drm_pending_vblank_event {
} event; } event;
}; };
/**
* struct drm_vblank_crtc_config - vblank configuration for a CRTC
*/
struct drm_vblank_crtc_config {
/**
* @offdelay_ms: Vblank off delay in ms, used to determine how long
* &drm_vblank_crtc.disable_timer waits before disabling.
*
* Defaults to the value of drm_vblank_offdelay in drm_crtc_vblank_on().
*/
int offdelay_ms;
/**
* @disable_immediate: See &drm_device.vblank_disable_immediate
* for the exact semantics of immediate vblank disabling.
*
* Additionally, this tracks the disable immediate value per crtc, just
* in case it needs to differ from the default value for a given device.
*
* Defaults to the value of &drm_device.vblank_disable_immediate in
* drm_crtc_vblank_on().
*/
bool disable_immediate;
};
/** /**
* struct drm_vblank_crtc - vblank tracking for a CRTC * struct drm_vblank_crtc - vblank tracking for a CRTC
* *
@ -99,8 +124,8 @@ struct drm_vblank_crtc {
wait_queue_head_t queue; wait_queue_head_t queue;
/** /**
* @disable_timer: Disable timer for the delayed vblank disabling * @disable_timer: Disable timer for the delayed vblank disabling
* hysteresis logic. Vblank disabling is controlled through the * hysteresis logic. Vblank disabling is controlled through
* drm_vblank_offdelay module option and the setting of the * &drm_vblank_crtc_config.offdelay_ms and the setting of the
* &drm_device.max_vblank_count value. * &drm_device.max_vblank_count value.
*/ */
struct timer_list disable_timer; struct timer_list disable_timer;
@ -198,6 +223,12 @@ struct drm_vblank_crtc {
*/ */
struct drm_display_mode hwmode; struct drm_display_mode hwmode;
/**
* @config: Stores vblank configuration values for a given CRTC.
* Also, see drm_crtc_vblank_on_config().
*/
struct drm_vblank_crtc_config config;
/** /**
* @enabled: Tracks the enabling state of the corresponding &drm_crtc to * @enabled: Tracks the enabling state of the corresponding &drm_crtc to
* avoid double-disabling and hence corrupting saved state. Needed by * avoid double-disabling and hence corrupting saved state. Needed by
@ -247,6 +278,8 @@ void drm_wait_one_vblank(struct drm_device *dev, unsigned int pipe);
void drm_crtc_wait_one_vblank(struct drm_crtc *crtc); void drm_crtc_wait_one_vblank(struct drm_crtc *crtc);
void drm_crtc_vblank_off(struct drm_crtc *crtc); void drm_crtc_vblank_off(struct drm_crtc *crtc);
void drm_crtc_vblank_reset(struct drm_crtc *crtc); void drm_crtc_vblank_reset(struct drm_crtc *crtc);
void drm_crtc_vblank_on_config(struct drm_crtc *crtc,
const struct drm_vblank_crtc_config *config);
void drm_crtc_vblank_on(struct drm_crtc *crtc); void drm_crtc_vblank_on(struct drm_crtc *crtc);
u64 drm_crtc_accurate_vblank_count(struct drm_crtc *crtc); u64 drm_crtc_accurate_vblank_count(struct drm_crtc *crtc);
void drm_crtc_vblank_restore(struct drm_crtc *crtc); void drm_crtc_vblank_restore(struct drm_crtc *crtc);