> -----Original Message-----
> From: Coelho, Luciano <luciano.coe...@intel.com>
> Sent: Thursday, April 4, 2024 5:12 PM
> To: intel-gfx@lists.freedesktop.org
> Cc: intel...@lists.freedesktop.org; Shankar, Uma <uma.shan...@intel.com>;
> ville.syrj...@linux.intel.com; Nikula, Jani <jani.nik...@intel.com>
> Subject: [PATCH v4 1/4] drm/i915/display: add support for DMC wakelocks
> 
> In order to reduce the DC5->DC2 restore time, wakelocks have been introduced
> in DMC so the driver can tell it when registers and other memory areas are 
> going
> to be accessed and keep their respective blocks awake.
> 
> Implement this in the driver by adding the concept of DMC wakelocks.
> When the driver needs to access memory which lies inside pre-defined ranges, 
> it
> will tell DMC to set the wakelock, access the memory, then wait for a while 
> and
> clear the wakelock.
> 
> The wakelock state is protected in the driver with spinlocks to prevent
> concurrency issues.
> 
> BSpec: 71583
> Signed-off-by: Luca Coelho <luciano.coe...@intel.com>
> ---
>  drivers/gpu/drm/i915/Makefile                 |   1 +
>  drivers/gpu/drm/i915/display/intel_de.h       |  97 ++++++-
>  .../gpu/drm/i915/display/intel_display_core.h |   2 +
>  drivers/gpu/drm/i915/display/intel_dmc_regs.h |   6 +
>  drivers/gpu/drm/i915/display/intel_dmc_wl.c   | 238 ++++++++++++++++++
>  drivers/gpu/drm/i915/display/intel_dmc_wl.h   |  31 +++
>  drivers/gpu/drm/xe/Makefile                   |   1 +
>  7 files changed, 368 insertions(+), 8 deletions(-)  create mode 100644
> drivers/gpu/drm/i915/display/intel_dmc_wl.c
>  create mode 100644 drivers/gpu/drm/i915/display/intel_dmc_wl.h
> 
> diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile
> index af9e871daf1d..7cad944b825c 100644
> --- a/drivers/gpu/drm/i915/Makefile
> +++ b/drivers/gpu/drm/i915/Makefile
> @@ -266,6 +266,7 @@ i915-y += \
>       display/intel_display_rps.o \
>       display/intel_display_wa.o \
>       display/intel_dmc.o \
> +     display/intel_dmc_wl.o \
>       display/intel_dpio_phy.o \
>       display/intel_dpll.o \
>       display/intel_dpll_mgr.o \
> diff --git a/drivers/gpu/drm/i915/display/intel_de.h
> b/drivers/gpu/drm/i915/display/intel_de.h
> index ba7a1c6ebc2a..0a0fba81e7af 100644
> --- a/drivers/gpu/drm/i915/display/intel_de.h
> +++ b/drivers/gpu/drm/i915/display/intel_de.h
> @@ -13,52 +13,125 @@
>  static inline u32
>  intel_de_read(struct drm_i915_private *i915, i915_reg_t reg)  {
> -     return intel_uncore_read(&i915->uncore, reg);
> +     u32 val;
> +
> +     intel_dmc_wl_get(i915, reg);
> +
> +     val = intel_uncore_read(&i915->uncore, reg);
> +
> +     intel_dmc_wl_put(i915, reg);
> +
> +     return val;
>  }
> 
>  static inline u8
>  intel_de_read8(struct drm_i915_private *i915, i915_reg_t reg)  {
> -     return intel_uncore_read8(&i915->uncore, reg);
> +     u8 val;
> +
> +     intel_dmc_wl_get(i915, reg);
> +
> +     val = intel_uncore_read8(&i915->uncore, reg);
> +
> +     intel_dmc_wl_put(i915, reg);
> +
> +     return val;
>  }
> 
>  static inline u64
>  intel_de_read64_2x32(struct drm_i915_private *i915,
>                    i915_reg_t lower_reg, i915_reg_t upper_reg)  {
> -     return intel_uncore_read64_2x32(&i915->uncore, lower_reg,
> upper_reg);
> +     u64 val;
> +
> +     intel_dmc_wl_get(i915, lower_reg);
> +     intel_dmc_wl_get(i915, upper_reg);
> +
> +     val = intel_uncore_read64_2x32(&i915->uncore, lower_reg, upper_reg);
> +
> +     intel_dmc_wl_put(i915, upper_reg);
> +     intel_dmc_wl_put(i915, lower_reg);
> +
> +     return val;
>  }
> 
>  static inline void
>  intel_de_posting_read(struct drm_i915_private *i915, i915_reg_t reg)  {
> +     intel_dmc_wl_get(i915, reg);
> +
>       intel_uncore_posting_read(&i915->uncore, reg);
> +
> +     intel_dmc_wl_put(i915, reg);
>  }
> 
>  static inline void
>  intel_de_write(struct drm_i915_private *i915, i915_reg_t reg, u32 val)  {
> +     intel_dmc_wl_get(i915, reg);
> +
>       intel_uncore_write(&i915->uncore, reg, val);
> +
> +     intel_dmc_wl_put(i915, reg);
>  }
> 
>  static inline u32
> -intel_de_rmw(struct drm_i915_private *i915, i915_reg_t reg, u32 clear, u32 
> set)
> +__intel_de_rmw_nowl(struct drm_i915_private *i915, i915_reg_t reg,
> +                 u32 clear, u32 set)
>  {
>       return intel_uncore_rmw(&i915->uncore, reg, clear, set);  }
> 
> +static inline u32
> +intel_de_rmw(struct drm_i915_private *i915, i915_reg_t reg, u32 clear,
> +u32 set) {
> +     u32 val;
> +
> +     intel_dmc_wl_get(i915, reg);
> +
> +     val = __intel_de_rmw_nowl(i915, reg, clear, set);
> +
> +     intel_dmc_wl_put(i915, reg);
> +
> +     return val;
> +}
> +
> +static inline int
> +__intel_wait_for_register_nowl(struct drm_i915_private *i915, i915_reg_t reg,
> +                            u32 mask, u32 value, unsigned int timeout) {
> +     return intel_wait_for_register(&i915->uncore, reg, mask,
> +                                    value, timeout);
> +}
> +
>  static inline int
>  intel_de_wait(struct drm_i915_private *i915, i915_reg_t reg,
>             u32 mask, u32 value, unsigned int timeout)  {
> -     return intel_wait_for_register(&i915->uncore, reg, mask, value, 
> timeout);
> +     int ret;
> +
> +     intel_dmc_wl_get(i915, reg);
> +
> +     ret = __intel_wait_for_register_nowl(i915, reg, mask, value, timeout);
> +
> +     intel_dmc_wl_put(i915, reg);
> +
> +     return ret;
>  }
> 
>  static inline int
>  intel_de_wait_fw(struct drm_i915_private *i915, i915_reg_t reg,
>                u32 mask, u32 value, unsigned int timeout)  {
> -     return intel_wait_for_register_fw(&i915->uncore, reg, mask, value,
> timeout);
> +     int ret;
> +
> +     intel_dmc_wl_get(i915, reg);
> +
> +     ret = intel_wait_for_register_fw(&i915->uncore, reg, mask, value,
> +timeout);
> +
> +     intel_dmc_wl_put(i915, reg);
> +
> +     return ret;
>  }
> 
>  static inline int
> @@ -67,8 +140,16 @@ intel_de_wait_custom(struct drm_i915_private *i915,
> i915_reg_t reg,
>                    unsigned int fast_timeout_us,
>                    unsigned int slow_timeout_ms, u32 *out_value)  {
> -     return __intel_wait_for_register(&i915->uncore, reg, mask, value,
> -                                      fast_timeout_us, slow_timeout_ms,
> out_value);
> +     int ret;
> +
> +     intel_dmc_wl_get(i915, reg);
> +
> +     ret = __intel_wait_for_register(&i915->uncore, reg, mask, value,
> +                                     fast_timeout_us, slow_timeout_ms,
> out_value);
> +
> +     intel_dmc_wl_put(i915, reg);
> +
> +     return ret;
>  }
> 
>  static inline int
> diff --git a/drivers/gpu/drm/i915/display/intel_display_core.h
> b/drivers/gpu/drm/i915/display/intel_display_core.h
> index 2167dbee5eea..c40719073510 100644
> --- a/drivers/gpu/drm/i915/display/intel_display_core.h
> +++ b/drivers/gpu/drm/i915/display/intel_display_core.h
> @@ -26,6 +26,7 @@
>  #include "intel_global_state.h"
>  #include "intel_gmbus.h"
>  #include "intel_opregion.h"
> +#include "intel_dmc_wl.h"
>  #include "intel_wm_types.h"
> 
>  struct task_struct;
> @@ -534,6 +535,7 @@ struct intel_display {
>       struct intel_overlay *overlay;
>       struct intel_display_params params;
>       struct intel_vbt_data vbt;
> +     struct intel_dmc_wl wl;
>       struct intel_wm wm;
>  };
> 
> diff --git a/drivers/gpu/drm/i915/display/intel_dmc_regs.h
> b/drivers/gpu/drm/i915/display/intel_dmc_regs.h
> index 90d0dbb41cfe..1bf446f96a10 100644
> --- a/drivers/gpu/drm/i915/display/intel_dmc_regs.h
> +++ b/drivers/gpu/drm/i915/display/intel_dmc_regs.h
> @@ -97,4 +97,10 @@
>  #define TGL_DMC_DEBUG3               _MMIO(0x101090)
>  #define DG1_DMC_DEBUG3               _MMIO(0x13415c)
> 
> +#define DMC_WAKELOCK_CFG     _MMIO(0x8F1B0)
> +#define  DMC_WAKELOCK_CFG_ENABLE REG_BIT(31)
> +#define DMC_WAKELOCK1_CTL    _MMIO(0x8F140)
> +#define  DMC_WAKELOCK_CTL_REQ         REG_BIT(31)
> +#define  DMC_WAKELOCK_CTL_ACK         REG_BIT(15)
> +
>  #endif /* __INTEL_DMC_REGS_H__ */
> diff --git a/drivers/gpu/drm/i915/display/intel_dmc_wl.c
> b/drivers/gpu/drm/i915/display/intel_dmc_wl.c
> new file mode 100644
> index 000000000000..3d7cf47321c2
> --- /dev/null
> +++ b/drivers/gpu/drm/i915/display/intel_dmc_wl.c
> @@ -0,0 +1,238 @@
> +// SPDX-License-Identifier: MIT
> +/*
> + * Copyright (C) 2024 Intel Corporation  */
> +
> +#include <linux/kernel.h>
> +
> +#include "intel_de.h"
> +#include "intel_dmc_regs.h"
> +#include "intel_dmc_wl.h"
> +
> +/**
> + * DOC: DMC wakelock support

To get the documentation, we may have to include this file in
i915.rst in Documentation section in kernel.

> + *
> + * Wake lock is the mechanism to cause display engine to exit DC
> + * states to allow programming to registers that are powered down in
> + * those states. Previous projects exited DC states automatically when
> + * detecting programming. Now software controls the exit by
> + * programming the wake lock. This improves system performance and
> + * system interactions and better fits the flip queue style of
> + * programming. Wake lock is only required when DC5, DC6, or DC6v have
> + * been enabled in DC_STATE_EN and the wake lock mode of operation has
> + * been enabled.
> + *
> + * The wakelock mechanism in DMC allows the display engine to exit DC
> + * states explicitly before programming registers that may be powered
> + * down.  In earlier hardware, this was done automatically and
> + * implicitly when the display engine accessed a register.  With the
> + * wakelock implementation, the driver asserts a wakelock in DMC,
> + * which forces it to exit the DC state until the wakelock is
> + * deasserted.
> + *
> + * This improves system performance and system interactions and better
> + * fits the flip queue style of programming.  Wakelock is only
> + * required when DC5, DC6, or DC6v have been enabled in DC_STATE_EN
> + * and the wakelock feature enabled in the driver.

Seems some repeat from 1st para, can be dropped.

> + *
> + * The mechanism can be enabled and disabled by writing to the
> + * DMC_WAKELOCK_CFG register.  There are also 13 control registers
> + * that can be used to hold and release different wakelocks.  In the
> + * current implementation, we only need one wakelock, so only
> + * DMC_WAKELOCK1_CTL is used.  The other definitions are here for
> + * potential future use.
> + */
> +
> +#define DMC_WAKELOCK_CTL_TIMEOUT 5
> +#define DMC_WAKELOCK_HOLD_TIME 50
> +
> +struct intel_dmc_wl_range {
> +     u32 start;
> +     u32 end;
> +};
> +
> +static struct intel_dmc_wl_range lnl_wl_range[] = {
> +     { .start = 0x60000, .end = 0x7ffff },
> +};
> +
> +static void __intel_dmc_wl_release(struct drm_i915_private *i915) {
> +     struct intel_dmc_wl *wl = &i915->display.wl;
> +
> +     WARN_ON(refcount_read(&wl->refcount));
> +
> +     queue_delayed_work(i915->unordered_wq, &wl->work,
> +                        msecs_to_jiffies(DMC_WAKELOCK_HOLD_TIME));
> +}
> +
> +static void intel_dmc_wl_work(struct work_struct *work) {
> +     struct intel_dmc_wl *wl =
> +             container_of(work, struct intel_dmc_wl, work.work);
> +     struct drm_i915_private *i915 =
> +             container_of(wl, struct drm_i915_private, display.wl);
> +     unsigned long flags;
> +
> +     spin_lock_irqsave(&wl->lock, flags);
> +
> +     /* Bail out if refcount reached zero while waiting for the spinlock */
> +     if (!refcount_read(&wl->refcount))
> +             goto out_unlock;
> +
> +     __intel_de_rmw_nowl(i915, DMC_WAKELOCK1_CTL,
> DMC_WAKELOCK_CTL_REQ, 0);
> +
> +     if (__intel_wait_for_register_nowl(i915,  DMC_WAKELOCK1_CTL,
> +                                        DMC_WAKELOCK_CTL_ACK, 0,
> +                                        DMC_WAKELOCK_CTL_TIMEOUT)) {
> +             WARN_RATELIMIT(1, "DMC wakelock release timed out");
> +             goto out_unlock;
> +     }
> +
> +     wl->taken = false;
> +
> +out_unlock:
> +     spin_unlock_irqrestore(&wl->lock, flags); }
> +
> +static bool intel_dmc_wl_check_range(u32 address) {
> +     int i;
> +     bool wl_needed = false;
> +
> +     for (i = 0; i < ARRAY_SIZE(lnl_wl_range); i++) {
> +             if (address >= lnl_wl_range[i].start &&
> +                 address <= lnl_wl_range[i].end) {
> +                     wl_needed = true;
> +                     break;
> +             }
> +     }
> +
> +     return wl_needed;
> +}
> +
> +void intel_dmc_wl_init(struct drm_i915_private *i915) {
> +     struct intel_dmc_wl *wl = &i915->display.wl;
> +
> +     INIT_DELAYED_WORK(&wl->work, intel_dmc_wl_work);
> +     spin_lock_init(&wl->lock);
> +     refcount_set(&wl->refcount, 0);
> +}
> +
> +void intel_dmc_wl_enable(struct drm_i915_private *i915) {
> +     struct intel_dmc_wl *wl = &i915->display.wl;
> +     unsigned long flags;
> +
> +     spin_lock_irqsave(&wl->lock, flags);
> +
> +     if (wl->enabled)
> +             goto out_unlock;
> +
> +     /*
> +      * Enable wakelock in DMC.  We shouldn't try to take the
> +      * wakelock, because we're just enabling it, so call the
> +      * non-locking version directly here.
> +      */
> +     __intel_de_rmw_nowl(i915, DMC_WAKELOCK_CFG, 0,
> +DMC_WAKELOCK_CFG_ENABLE);
> +
> +     wl->enabled = true;
> +     wl->taken = false;
> +
> +out_unlock:
> +     spin_unlock_irqrestore(&wl->lock, flags); }
> +
> +void intel_dmc_wl_disable(struct drm_i915_private *i915) {
> +     struct intel_dmc_wl *wl = &i915->display.wl;
> +     unsigned long flags;
> +
> +     flush_delayed_work(&wl->work);
> +
> +     spin_lock_irqsave(&wl->lock, flags);
> +
> +     if (!wl->enabled)
> +             goto out_unlock;
> +
> +     /* Disable wakelock in DMC */
> +     __intel_de_rmw_nowl(i915, DMC_WAKELOCK_CFG,
> DMC_WAKELOCK_CFG_ENABLE,
> +0);
> +
> +     refcount_set(&wl->refcount, 0);
> +     wl->enabled = false;
> +     wl->taken = false;
> +
> +out_unlock:
> +     spin_unlock_irqrestore(&wl->lock, flags); }
> +
> +void intel_dmc_wl_get(struct drm_i915_private *i915, i915_reg_t reg) {
> +     struct intel_dmc_wl *wl = &i915->display.wl;
> +     unsigned long flags;
> +
> +     if (!intel_dmc_wl_check_range(reg.reg))
> +             return;
> +
> +     spin_lock_irqsave(&wl->lock, flags);
> +
> +     if (!wl->enabled)
> +             goto out_unlock;
> +
> +     cancel_delayed_work(&wl->work);
> +
> +     if (refcount_inc_not_zero(&wl->refcount))
> +             goto out_unlock;
> +
> +     refcount_set(&wl->refcount, 1);
> +
> +     /* Only try to take the wakelock if it's not marked as taken

Fix the comment style

> +      * yet.  It may be already taken at this point if we have
> +      * already released the last reference, but the work has not
> +      * run yet.
> +      */
> +     if (!wl->taken) {
> +             __intel_de_rmw_nowl(i915, DMC_WAKELOCK1_CTL, 0,
> +                                 DMC_WAKELOCK_CTL_REQ);
> +
> +             if (__intel_wait_for_register_nowl(i915,
> DMC_WAKELOCK1_CTL,
> +                                                DMC_WAKELOCK_CTL_ACK,
> +                                                DMC_WAKELOCK_CTL_ACK,
> +
> DMC_WAKELOCK_CTL_TIMEOUT)) {
> +                     WARN_RATELIMIT(1, "DMC wakelock ack timed out");
> +                     goto out_unlock;
> +             }
> +
> +             wl->taken = true;
> +     }
> +
> +out_unlock:
> +     spin_unlock_irqrestore(&wl->lock, flags); }
> +
> +void intel_dmc_wl_put(struct drm_i915_private *i915, i915_reg_t reg) {
> +     struct intel_dmc_wl *wl = &i915->display.wl;
> +     unsigned long flags;
> +
> +     if (!intel_dmc_wl_check_range(reg.reg))
> +             return;
> +
> +     spin_lock_irqsave(&wl->lock, flags);
> +
> +     if (!wl->enabled)
> +             goto out_unlock;
> +
> +     if (WARN_RATELIMIT(!refcount_read(&wl->refcount),
> +                        "Tried to put wakelock with refcount zero\n"))
> +             goto out_unlock;
> +
> +     if (refcount_dec_and_test(&wl->refcount)) {
> +             __intel_dmc_wl_release(i915);
> +
> +             goto out_unlock;
> +     }
> +
> +out_unlock:
> +     spin_unlock_irqrestore(&wl->lock, flags); }
> diff --git a/drivers/gpu/drm/i915/display/intel_dmc_wl.h
> b/drivers/gpu/drm/i915/display/intel_dmc_wl.h
> new file mode 100644
> index 000000000000..6fb86b05b437
> --- /dev/null
> +++ b/drivers/gpu/drm/i915/display/intel_dmc_wl.h
> @@ -0,0 +1,31 @@
> +/* SPDX-License-Identifier: MIT */
> +/*
> + * Copyright (C) 2024 Intel Corporation  */
> +
> +#ifndef __INTEL_WAKELOCK_H__
> +#define __INTEL_WAKELOCK_H__
> +
> +#include <linux/types.h>
> +#include <linux/workqueue.h>
> +#include <linux/refcount.h>
> +
> +#include "i915_reg_defs.h"
> +
> +struct drm_i915_private;
> +
> +struct intel_dmc_wl {
> +     spinlock_t lock; /* protects enabled, taken  and refcount */
> +     bool enabled;
> +     bool taken;
> +     refcount_t refcount;
> +     struct delayed_work work;
> +};
> +
> +void intel_dmc_wl_init(struct drm_i915_private *i915); void
> +intel_dmc_wl_enable(struct drm_i915_private *i915); void
> +intel_dmc_wl_disable(struct drm_i915_private *i915); void
> +intel_dmc_wl_get(struct drm_i915_private *i915, i915_reg_t reg); void
> +intel_dmc_wl_put(struct drm_i915_private *i915, i915_reg_t reg);
> +
> +#endif /* __INTEL_WAKELOCK_H__ */
> diff --git a/drivers/gpu/drm/xe/Makefile b/drivers/gpu/drm/xe/Makefile index
> e5b1715f721e..7e2b418a6f16 100644
> --- a/drivers/gpu/drm/xe/Makefile
> +++ b/drivers/gpu/drm/xe/Makefile
> @@ -278,6 +278,7 @@ xe-$(CONFIG_DRM_XE_DISPLAY) += \
>       i915-display/intel_vdsc.o \
>       i915-display/intel_vga.o \
>       i915-display/intel_vrr.o \
> +     i915-display/intel_dmc_wl.o \
>       i915-display/intel_wm.o \
>       i915-display/skl_scaler.o \
>       i915-display/skl_universal_plane.o \
> --
> 2.39.2

Reply via email to