From b680c37a4d145cf4d8f2b24e46b1163e5ceb1d35 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Fri, 19 Sep 2014 18:27:27 +0200 Subject: drm/i915: DocBook integration for frontbuffer tracking I shouldn't ask everyone to do this and fail myself ... This extracts all the frontbuffer tracking functions into intel_frontbuffer.c, adds a DOC overview section and also adds the missing kerneldoc for i915_gem_track_fb and also pulls it into the same section for convenience. v2: Don't forget about the header files. v3: Oops, might check compilation next time around. To make my life easier drop the increase_pllclock from set_base_atomic since really, it doesn't matter if you see your Oops or kgdb with a tiny bit of lag. v4: Try to better explain how to actually use this, requested by Paulo on irc. v5: Explain invalidate/flush a bit clearer. v6: s/business/busyness/ Acked-by: Paulo Zanoni Cc: Paulo Zanoni Cc: Vandana Kannan Signed-off-by: Daniel Vetter --- drivers/gpu/drm/i915/intel_frontbuffer.c | 277 +++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 drivers/gpu/drm/i915/intel_frontbuffer.c (limited to 'drivers/gpu/drm/i915/intel_frontbuffer.c') diff --git a/drivers/gpu/drm/i915/intel_frontbuffer.c b/drivers/gpu/drm/i915/intel_frontbuffer.c new file mode 100644 index 000000000000..f74744c091cb --- /dev/null +++ b/drivers/gpu/drm/i915/intel_frontbuffer.c @@ -0,0 +1,277 @@ +/* + * Copyright © 2014 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Daniel Vetter + */ + +/** + * DOC: frontbuffer tracking + * + * Many features require us to track changes to the currently active + * frontbuffer, especially rendering targetted at the frontbuffer. + * + * To be able to do so GEM tracks frontbuffers using a bitmask for all possible + * frontbuffer slots through i915_gem_track_fb(). The function in this file are + * then called when the contents of the frontbuffer are invalidated, when + * frontbuffer rendering has stopped again to flush out all the changes and when + * the frontbuffer is exchanged with a flip. Subsystems interested in + * frontbuffer changes (e.g. PSR, FBC, DRRS) should directly put their callbacks + * into the relevant places and filter for the frontbuffer slots that they are + * interested int. + * + * On a high level there are two types of powersaving features. The first one + * work like a special cache (FBC and PSR) and are interested when they should + * stop caching and when to restart caching. This is done by placing callbacks + * into the invalidate and the flush functions: At invalidate the caching must + * be stopped and at flush time it can be restarted. And maybe they need to know + * when the frontbuffer changes (e.g. when the hw doesn't initiate an invalidate + * and flush on its own) which can be achieved with placing callbacks into the + * flip functions. + * + * The other type of display power saving feature only cares about busyness + * (e.g. DRRS). In that case all three (invalidate, flush and flip) indicate + * busyness. There is no direct way to detect idleness. Instead an idle timer + * work delayed work should be started from the flush and flip functions and + * cancelled as soon as busyness is detected. + * + * Note that there's also an older frontbuffer activity tracking scheme which + * just trackings general activity. This is done by the various mark_busy and + * mark_idle functions. For display power management features using these + * functions is deprecated and should be avoided. + */ + +#include + +#include "intel_drv.h" +#include "i915_drv.h" + +static void intel_increase_pllclock(struct drm_device *dev, + enum pipe pipe) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + int dpll_reg = DPLL(pipe); + int dpll; + + if (!HAS_GMCH_DISPLAY(dev)) + return; + + if (!dev_priv->lvds_downclock_avail) + return; + + dpll = I915_READ(dpll_reg); + if (!HAS_PIPE_CXSR(dev) && (dpll & DISPLAY_RATE_SELECT_FPA1)) { + DRM_DEBUG_DRIVER("upclocking LVDS\n"); + + assert_panel_unlocked(dev_priv, pipe); + + dpll &= ~DISPLAY_RATE_SELECT_FPA1; + I915_WRITE(dpll_reg, dpll); + intel_wait_for_vblank(dev, pipe); + + dpll = I915_READ(dpll_reg); + if (dpll & DISPLAY_RATE_SELECT_FPA1) + DRM_DEBUG_DRIVER("failed to upclock LVDS!\n"); + } +} + +/** + * intel_mark_fb_busy - mark given planes as busy + * @dev: DRM device + * @frontbuffer_bits: bits for the affected planes + * @ring: optional ring for asynchronous commands + * + * This function gets called every time the screen contents change. It can be + * used to keep e.g. the update rate at the nominal refresh rate with DRRS. + */ +static void intel_mark_fb_busy(struct drm_device *dev, + unsigned frontbuffer_bits, + struct intel_engine_cs *ring) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + enum pipe pipe; + + if (!i915.powersave) + return; + + for_each_pipe(dev_priv, pipe) { + if (!(frontbuffer_bits & INTEL_FRONTBUFFER_ALL_MASK(pipe))) + continue; + + intel_increase_pllclock(dev, pipe); + if (ring && intel_fbc_enabled(dev)) + ring->fbc_dirty = true; + } +} + +/** + * intel_fb_obj_invalidate - invalidate frontbuffer object + * @obj: GEM object to invalidate + * @ring: set for asynchronous rendering + * + * This function gets called every time rendering on the given object starts and + * frontbuffer caching (fbc, low refresh rate for DRRS, panel self refresh) must + * be invalidated. If @ring is non-NULL any subsequent invalidation will be delayed + * until the rendering completes or a flip on this frontbuffer plane is + * scheduled. + */ +void intel_fb_obj_invalidate(struct drm_i915_gem_object *obj, + struct intel_engine_cs *ring) +{ + struct drm_device *dev = obj->base.dev; + struct drm_i915_private *dev_priv = dev->dev_private; + + WARN_ON(!mutex_is_locked(&dev->struct_mutex)); + + if (!obj->frontbuffer_bits) + return; + + if (ring) { + mutex_lock(&dev_priv->fb_tracking.lock); + dev_priv->fb_tracking.busy_bits + |= obj->frontbuffer_bits; + dev_priv->fb_tracking.flip_bits + &= ~obj->frontbuffer_bits; + mutex_unlock(&dev_priv->fb_tracking.lock); + } + + intel_mark_fb_busy(dev, obj->frontbuffer_bits, ring); + + intel_edp_psr_invalidate(dev, obj->frontbuffer_bits); +} + +/** + * intel_frontbuffer_flush - flush frontbuffer + * @dev: DRM device + * @frontbuffer_bits: frontbuffer plane tracking bits + * + * This function gets called every time rendering on the given planes has + * completed and frontbuffer caching can be started again. Flushes will get + * delayed if they're blocked by some oustanding asynchronous rendering. + * + * Can be called without any locks held. + */ +void intel_frontbuffer_flush(struct drm_device *dev, + unsigned frontbuffer_bits) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + + /* Delay flushing when rings are still busy.*/ + mutex_lock(&dev_priv->fb_tracking.lock); + frontbuffer_bits &= ~dev_priv->fb_tracking.busy_bits; + mutex_unlock(&dev_priv->fb_tracking.lock); + + intel_mark_fb_busy(dev, frontbuffer_bits, NULL); + + intel_edp_psr_flush(dev, frontbuffer_bits); + + /* + * FIXME: Unconditional fbc flushing here is a rather gross hack and + * needs to be reworked into a proper frontbuffer tracking scheme like + * psr employs. + */ + if (IS_BROADWELL(dev)) + gen8_fbc_sw_flush(dev, FBC_REND_CACHE_CLEAN); +} + +/** + * intel_fb_obj_flush - flush frontbuffer object + * @obj: GEM object to flush + * @retire: set when retiring asynchronous rendering + * + * This function gets called every time rendering on the given object has + * completed and frontbuffer caching can be started again. If @retire is true + * then any delayed flushes will be unblocked. + */ +void intel_fb_obj_flush(struct drm_i915_gem_object *obj, + bool retire) +{ + struct drm_device *dev = obj->base.dev; + struct drm_i915_private *dev_priv = dev->dev_private; + unsigned frontbuffer_bits; + + WARN_ON(!mutex_is_locked(&dev->struct_mutex)); + + if (!obj->frontbuffer_bits) + return; + + frontbuffer_bits = obj->frontbuffer_bits; + + if (retire) { + mutex_lock(&dev_priv->fb_tracking.lock); + /* Filter out new bits since rendering started. */ + frontbuffer_bits &= dev_priv->fb_tracking.busy_bits; + + dev_priv->fb_tracking.busy_bits &= ~frontbuffer_bits; + mutex_unlock(&dev_priv->fb_tracking.lock); + } + + intel_frontbuffer_flush(dev, frontbuffer_bits); +} + +/** + * intel_frontbuffer_flip_prepare - prepare asnychronous frontbuffer flip + * @dev: DRM device + * @frontbuffer_bits: frontbuffer plane tracking bits + * + * This function gets called after scheduling a flip on @obj. The actual + * frontbuffer flushing will be delayed until completion is signalled with + * intel_frontbuffer_flip_complete. If an invalidate happens in between this + * flush will be cancelled. + * + * Can be called without any locks held. + */ +void intel_frontbuffer_flip_prepare(struct drm_device *dev, + unsigned frontbuffer_bits) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + + mutex_lock(&dev_priv->fb_tracking.lock); + dev_priv->fb_tracking.flip_bits + |= frontbuffer_bits; + mutex_unlock(&dev_priv->fb_tracking.lock); +} + +/** + * intel_frontbuffer_flip_complete - complete asynchronous frontbuffer flush + * @dev: DRM device + * @frontbuffer_bits: frontbuffer plane tracking bits + * + * This function gets called after the flip has been latched and will complete + * on the next vblank. It will execute the fush if it hasn't been cancalled yet. + * + * Can be called without any locks held. + */ +void intel_frontbuffer_flip_complete(struct drm_device *dev, + unsigned frontbuffer_bits) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + + mutex_lock(&dev_priv->fb_tracking.lock); + /* Mask any cancelled flips. */ + frontbuffer_bits &= dev_priv->fb_tracking.flip_bits; + dev_priv->fb_tracking.flip_bits &= ~frontbuffer_bits; + mutex_unlock(&dev_priv->fb_tracking.lock); + + intel_frontbuffer_flush(dev, frontbuffer_bits); +} + -- cgit v1.2.3 From 1d73c2a8f218be3e8b6aa884740fc67110660b54 Mon Sep 17 00:00:00 2001 From: Rodrigo Vivi Date: Wed, 24 Sep 2014 19:50:59 -0400 Subject: drm/i915: Minimize the huge amount of unecessary fbc sw cache clean. The sw cache clean on BDW is a tempoorary workaround because we cannot set cache clean on blt ring with risk of hungs. So we are doing the cache clean on sw. However we are doing much more than needed. Not only when using blt ring. So, with this extra w/a we minimize the ammount of cache cleans and call it only on same cases that it was being called on gen7. The traditional FBC Cache clean happens over LRI on BLT ring when there is a frontbuffer touch happening. frontbuffer tracking set fbc_dirty variable to let BLT flush that it must clean FBC cache. fbc.need_sw_cache_clean works in the opposite information direction of ring->fbc_dirty telling software on frontbuffer tracking to perform the cache clean on sw side. v2: Clean it a little bit and fully check for Broadwell instead of gen8. v3: Rebase after frontbuffer organization. v4: Wiggle confused me. So fixing v3! Cc: Daniel Vetter Cc: Paulo Zanoni Reviewed-by: Paulo Zanoni Signed-off-by: Rodrigo Vivi Signed-off-by: Daniel Vetter --- drivers/gpu/drm/i915/i915_drv.h | 10 +++++++++- drivers/gpu/drm/i915/intel_frontbuffer.c | 6 ++++-- drivers/gpu/drm/i915/intel_pm.c | 2 +- drivers/gpu/drm/i915/intel_ringbuffer.c | 9 +++++++-- 4 files changed, 21 insertions(+), 6 deletions(-) (limited to 'drivers/gpu/drm/i915/intel_frontbuffer.c') diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index 999bd57cab65..cccb7767e837 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -666,6 +666,14 @@ struct i915_fbc { * possible. */ bool enabled; + /* On gen8 some rings cannont perform fbc clean operation so for now + * we are doing this on SW with mmio. + * This variable works in the opposite information direction + * of ring->fbc_dirty telling software on frontbuffer tracking + * to perform the cache clean on sw side. + */ + bool need_sw_cache_clean; + struct intel_fbc_work { struct delayed_work work; struct drm_crtc *crtc; @@ -2825,7 +2833,7 @@ extern void intel_modeset_setup_hw_state(struct drm_device *dev, extern void i915_redisable_vga(struct drm_device *dev); extern void i915_redisable_vga_power_on(struct drm_device *dev); extern bool intel_fbc_enabled(struct drm_device *dev); -extern void gen8_fbc_sw_flush(struct drm_device *dev, u32 value); +extern void bdw_fbc_sw_flush(struct drm_device *dev, u32 value); extern void intel_disable_fbc(struct drm_device *dev); extern bool ironlake_set_drps(struct drm_device *dev, u8 val); extern void intel_init_pch_refclk(struct drm_device *dev); diff --git a/drivers/gpu/drm/i915/intel_frontbuffer.c b/drivers/gpu/drm/i915/intel_frontbuffer.c index f74744c091cb..7eb74a62117f 100644 --- a/drivers/gpu/drm/i915/intel_frontbuffer.c +++ b/drivers/gpu/drm/i915/intel_frontbuffer.c @@ -189,8 +189,10 @@ void intel_frontbuffer_flush(struct drm_device *dev, * needs to be reworked into a proper frontbuffer tracking scheme like * psr employs. */ - if (IS_BROADWELL(dev)) - gen8_fbc_sw_flush(dev, FBC_REND_CACHE_CLEAN); + if (dev_priv->fbc.need_sw_cache_clean) { + dev_priv->fbc.need_sw_cache_clean = false; + bdw_fbc_sw_flush(dev, FBC_REND_CACHE_CLEAN); + } } /** diff --git a/drivers/gpu/drm/i915/intel_pm.c b/drivers/gpu/drm/i915/intel_pm.c index 6b416201240f..011892d5356e 100644 --- a/drivers/gpu/drm/i915/intel_pm.c +++ b/drivers/gpu/drm/i915/intel_pm.c @@ -356,7 +356,7 @@ bool intel_fbc_enabled(struct drm_device *dev) return dev_priv->fbc.enabled; } -void gen8_fbc_sw_flush(struct drm_device *dev, u32 value) +void bdw_fbc_sw_flush(struct drm_device *dev, u32 value) { struct drm_i915_private *dev_priv = dev->dev_private; diff --git a/drivers/gpu/drm/i915/intel_ringbuffer.c b/drivers/gpu/drm/i915/intel_ringbuffer.c index 922d6bc1a1b3..620a89dc868b 100644 --- a/drivers/gpu/drm/i915/intel_ringbuffer.c +++ b/drivers/gpu/drm/i915/intel_ringbuffer.c @@ -2237,6 +2237,7 @@ static int gen6_ring_flush(struct intel_engine_cs *ring, u32 invalidate, u32 flush) { struct drm_device *dev = ring->dev; + struct drm_i915_private *dev_priv = dev->dev_private; uint32_t cmd; int ret; @@ -2267,8 +2268,12 @@ static int gen6_ring_flush(struct intel_engine_cs *ring, } intel_ring_advance(ring); - if (IS_GEN7(dev) && !invalidate && flush) - return gen7_ring_fbc_flush(ring, FBC_REND_CACHE_CLEAN); + if (!invalidate && flush) { + if (IS_GEN7(dev)) + return gen7_ring_fbc_flush(ring, FBC_REND_CACHE_CLEAN); + else if (IS_BROADWELL(dev)) + dev_priv->fbc.need_sw_cache_clean = true; + } return 0; } -- cgit v1.2.3 From 11c9b6c628c646894e6ef53f92cfd33a814ee553 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Tue, 30 Sep 2014 22:10:52 +0200 Subject: drm/i915: Tighting frontbuffer tracking around flips So I think I've spotted a small gap in the frontbuffer tracking while discussing the logic with Paulo on irc: 1. Userspace schedules gpu rendering to the current frontbuffer. This gets tracked in dev_priv->fb_tracking.busy_bits. 2. We pageflip a fully rendered buffer before the frontbuffer rendering completes. 3. The request retiring will never clear busy_bits (since at retire time the old frontbuffer won't have obj->frontbuffer_bits set), so these bits now are stuck until someone again does a bit of frontbuffer tracking. If we clear stale busy_bits in flip_prepare this gap is closed. Reviewed-by: Paulo Zanoni Signed-off-by: Daniel Vetter --- drivers/gpu/drm/i915/intel_frontbuffer.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers/gpu/drm/i915/intel_frontbuffer.c') diff --git a/drivers/gpu/drm/i915/intel_frontbuffer.c b/drivers/gpu/drm/i915/intel_frontbuffer.c index 7eb74a62117f..c5a312d218f7 100644 --- a/drivers/gpu/drm/i915/intel_frontbuffer.c +++ b/drivers/gpu/drm/i915/intel_frontbuffer.c @@ -248,8 +248,9 @@ void intel_frontbuffer_flip_prepare(struct drm_device *dev, struct drm_i915_private *dev_priv = dev->dev_private; mutex_lock(&dev_priv->fb_tracking.lock); - dev_priv->fb_tracking.flip_bits - |= frontbuffer_bits; + dev_priv->fb_tracking.flip_bits |= frontbuffer_bits; + /* Remove stale busy bits due to the old buffer. */ + dev_priv->fb_tracking.busy_bits &= ~frontbuffer_bits; mutex_unlock(&dev_priv->fb_tracking.lock); } -- cgit v1.2.3 From 5c323b2a78fade2df08a51497fc943a6fd1b868b Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Tue, 30 Sep 2014 22:10:53 +0200 Subject: drm/i915: spelling fixes for frontbuffer tracking kerneldoc Oh well. v2: Fix one more spelling fail Paulo spotted. Reviewed-by: Paulo Zanoni Signed-off-by: Daniel Vetter --- drivers/gpu/drm/i915/intel_drv.h | 2 +- drivers/gpu/drm/i915/intel_frontbuffer.c | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) (limited to 'drivers/gpu/drm/i915/intel_frontbuffer.c') diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index 072e69f4080e..f14f456e08f9 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -819,7 +819,7 @@ void intel_frontbuffer_flip_complete(struct drm_device *dev, void intel_frontbuffer_flush(struct drm_device *dev, unsigned frontbuffer_bits); /** - * intel_frontbuffer_flip - prepare frontbuffer flip + * intel_frontbuffer_flip - synchronous frontbuffer flip * @dev: DRM device * @frontbuffer_bits: frontbuffer plane tracking bits * diff --git a/drivers/gpu/drm/i915/intel_frontbuffer.c b/drivers/gpu/drm/i915/intel_frontbuffer.c index c5a312d218f7..58cf2e6b78f4 100644 --- a/drivers/gpu/drm/i915/intel_frontbuffer.c +++ b/drivers/gpu/drm/i915/intel_frontbuffer.c @@ -28,7 +28,7 @@ * DOC: frontbuffer tracking * * Many features require us to track changes to the currently active - * frontbuffer, especially rendering targetted at the frontbuffer. + * frontbuffer, especially rendering targeted at the frontbuffer. * * To be able to do so GEM tracks frontbuffers using a bitmask for all possible * frontbuffer slots through i915_gem_track_fb(). The function in this file are @@ -55,7 +55,7 @@ * cancelled as soon as busyness is detected. * * Note that there's also an older frontbuffer activity tracking scheme which - * just trackings general activity. This is done by the various mark_busy and + * just tracks general activity. This is done by the various mark_busy and * mark_idle functions. For display power management features using these * functions is deprecated and should be avoided. */ @@ -166,7 +166,7 @@ void intel_fb_obj_invalidate(struct drm_i915_gem_object *obj, * * This function gets called every time rendering on the given planes has * completed and frontbuffer caching can be started again. Flushes will get - * delayed if they're blocked by some oustanding asynchronous rendering. + * delayed if they're blocked by some outstanding asynchronous rendering. * * Can be called without any locks held. */ @@ -231,7 +231,7 @@ void intel_fb_obj_flush(struct drm_i915_gem_object *obj, } /** - * intel_frontbuffer_flip_prepare - prepare asnychronous frontbuffer flip + * intel_frontbuffer_flip_prepare - prepare asynchronous frontbuffer flip * @dev: DRM device * @frontbuffer_bits: frontbuffer plane tracking bits * @@ -255,12 +255,12 @@ void intel_frontbuffer_flip_prepare(struct drm_device *dev, } /** - * intel_frontbuffer_flip_complete - complete asynchronous frontbuffer flush + * intel_frontbuffer_flip_complete - complete asynchronous frontbuffer flip * @dev: DRM device * @frontbuffer_bits: frontbuffer plane tracking bits * * This function gets called after the flip has been latched and will complete - * on the next vblank. It will execute the fush if it hasn't been cancalled yet. + * on the next vblank. It will execute the flush if it hasn't been cancelled yet. * * Can be called without any locks held. */ @@ -277,4 +277,3 @@ void intel_frontbuffer_flip_complete(struct drm_device *dev, intel_frontbuffer_flush(dev, frontbuffer_bits); } - -- cgit v1.2.3 From 0bc12bcb1b9686d7011f16410ba17ed0740167c3 Mon Sep 17 00:00:00 2001 From: Rodrigo Vivi Date: Fri, 14 Nov 2014 08:52:28 -0800 Subject: drm/i915: Introduce intel_psr.c No functional changes. Just cleaning and reorganizing it. v2: Rebase it puting it to begin of psr rework. This helps to blame easily at least latest changes. Signed-off-by: Rodrigo Vivi Signed-off-by: Daniel Vetter --- drivers/gpu/drm/i915/Makefile | 1 + drivers/gpu/drm/i915/intel_ddi.c | 4 +- drivers/gpu/drm/i915/intel_display.c | 2 +- drivers/gpu/drm/i915/intel_dp.c | 381 +---------------------------- drivers/gpu/drm/i915/intel_drv.h | 21 +- drivers/gpu/drm/i915/intel_frontbuffer.c | 4 +- drivers/gpu/drm/i915/intel_psr.c | 408 +++++++++++++++++++++++++++++++ 7 files changed, 428 insertions(+), 393 deletions(-) create mode 100644 drivers/gpu/drm/i915/intel_psr.c (limited to 'drivers/gpu/drm/i915/intel_frontbuffer.c') diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile index 891e584e97ea..e4083e41a600 100644 --- a/drivers/gpu/drm/i915/Makefile +++ b/drivers/gpu/drm/i915/Makefile @@ -51,6 +51,7 @@ i915-y += intel_audio.o \ intel_frontbuffer.o \ intel_modes.o \ intel_overlay.o \ + intel_psr.o \ intel_sideband.o \ intel_sprite.o i915-$(CONFIG_ACPI) += intel_acpi.o intel_opregion.o diff --git a/drivers/gpu/drm/i915/intel_ddi.c b/drivers/gpu/drm/i915/intel_ddi.c index ca33ee9477f1..3e4c63cfa415 100644 --- a/drivers/gpu/drm/i915/intel_ddi.c +++ b/drivers/gpu/drm/i915/intel_ddi.c @@ -1583,7 +1583,7 @@ static void intel_enable_ddi(struct intel_encoder *intel_encoder) intel_dp_stop_link_train(intel_dp); intel_edp_backlight_on(intel_dp); - intel_edp_psr_enable(intel_dp); + intel_psr_enable(intel_dp); } if (intel_crtc->config.has_audio) { @@ -1609,7 +1609,7 @@ static void intel_disable_ddi(struct intel_encoder *intel_encoder) if (type == INTEL_OUTPUT_EDP) { struct intel_dp *intel_dp = enc_to_intel_dp(encoder); - intel_edp_psr_disable(intel_dp); + intel_psr_disable(intel_dp); intel_edp_backlight_off(intel_dp); } } diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index 7d58a64195d6..2632f2adc319 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -12449,7 +12449,7 @@ static void intel_setup_outputs(struct drm_device *dev) if (SUPPORTS_TV(dev)) intel_tv_init(dev); - intel_edp_psr_init(dev); + intel_psr_init(dev); for_each_intel_encoder(dev, encoder) { encoder->base.possible_crtcs = encoder->crtc_mask; diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c index a89585d809be..7369e6f10117 100644 --- a/drivers/gpu/drm/i915/intel_dp.c +++ b/drivers/gpu/drm/i915/intel_dp.c @@ -2066,385 +2066,6 @@ static void intel_dp_get_config(struct intel_encoder *encoder, } } -static bool is_edp_psr(struct intel_dp *intel_dp) -{ - return intel_dp->psr_dpcd[0] & DP_PSR_IS_SUPPORTED; -} - -static bool intel_edp_is_psr_enabled(struct drm_device *dev) -{ - struct drm_i915_private *dev_priv = dev->dev_private; - - if (!HAS_PSR(dev)) - return false; - - return I915_READ(EDP_PSR_CTL(dev)) & EDP_PSR_ENABLE; -} - -static void intel_edp_psr_write_vsc(struct intel_dp *intel_dp, - struct edp_vsc_psr *vsc_psr) -{ - struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); - struct drm_device *dev = dig_port->base.base.dev; - struct drm_i915_private *dev_priv = dev->dev_private; - struct intel_crtc *crtc = to_intel_crtc(dig_port->base.base.crtc); - u32 ctl_reg = HSW_TVIDEO_DIP_CTL(crtc->config.cpu_transcoder); - u32 data_reg = HSW_TVIDEO_DIP_VSC_DATA(crtc->config.cpu_transcoder); - uint32_t *data = (uint32_t *) vsc_psr; - unsigned int i; - - /* As per BSPec (Pipe Video Data Island Packet), we need to disable - the video DIP being updated before program video DIP data buffer - registers for DIP being updated. */ - I915_WRITE(ctl_reg, 0); - POSTING_READ(ctl_reg); - - for (i = 0; i < VIDEO_DIP_VSC_DATA_SIZE; i += 4) { - if (i < sizeof(struct edp_vsc_psr)) - I915_WRITE(data_reg + i, *data++); - else - I915_WRITE(data_reg + i, 0); - } - - I915_WRITE(ctl_reg, VIDEO_DIP_ENABLE_VSC_HSW); - POSTING_READ(ctl_reg); -} - -static void intel_edp_psr_setup_vsc(struct intel_dp *intel_dp) -{ - struct edp_vsc_psr psr_vsc; - - /* Prepare VSC packet as per EDP 1.3 spec, Table 3.10 */ - memset(&psr_vsc, 0, sizeof(psr_vsc)); - psr_vsc.sdp_header.HB0 = 0; - psr_vsc.sdp_header.HB1 = 0x7; - psr_vsc.sdp_header.HB2 = 0x2; - psr_vsc.sdp_header.HB3 = 0x8; - intel_edp_psr_write_vsc(intel_dp, &psr_vsc); -} - -static void intel_edp_psr_enable_sink(struct intel_dp *intel_dp) -{ - struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); - struct drm_device *dev = dig_port->base.base.dev; - struct drm_i915_private *dev_priv = dev->dev_private; - uint32_t aux_clock_divider; - int precharge = 0x3; - bool only_standby = false; - static const uint8_t aux_msg[] = { - [0] = DP_AUX_NATIVE_WRITE << 4, - [1] = DP_SET_POWER >> 8, - [2] = DP_SET_POWER & 0xff, - [3] = 1 - 1, - [4] = DP_SET_POWER_D0, - }; - int i; - - BUILD_BUG_ON(sizeof(aux_msg) > 20); - - aux_clock_divider = intel_dp->get_aux_clock_divider(intel_dp, 0); - - if (IS_BROADWELL(dev) && dig_port->port != PORT_A) - only_standby = true; - - /* Enable PSR in sink */ - if (intel_dp->psr_dpcd[1] & DP_PSR_NO_TRAIN_ON_EXIT || only_standby) - drm_dp_dpcd_writeb(&intel_dp->aux, DP_PSR_EN_CFG, - DP_PSR_ENABLE & ~DP_PSR_MAIN_LINK_ACTIVE); - else - drm_dp_dpcd_writeb(&intel_dp->aux, DP_PSR_EN_CFG, - DP_PSR_ENABLE | DP_PSR_MAIN_LINK_ACTIVE); - - /* Setup AUX registers */ - for (i = 0; i < sizeof(aux_msg); i += 4) - I915_WRITE(EDP_PSR_AUX_DATA1(dev) + i, - intel_dp_pack_aux(&aux_msg[i], sizeof(aux_msg) - i)); - - I915_WRITE(EDP_PSR_AUX_CTL(dev), - DP_AUX_CH_CTL_TIME_OUT_400us | - (sizeof(aux_msg) << DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT) | - (precharge << DP_AUX_CH_CTL_PRECHARGE_2US_SHIFT) | - (aux_clock_divider << DP_AUX_CH_CTL_BIT_CLOCK_2X_SHIFT)); -} - -static void intel_edp_psr_enable_source(struct intel_dp *intel_dp) -{ - struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); - struct drm_device *dev = dig_port->base.base.dev; - struct drm_i915_private *dev_priv = dev->dev_private; - uint32_t max_sleep_time = 0x1f; - uint32_t idle_frames = 1; - uint32_t val = 0x0; - const uint32_t link_entry_time = EDP_PSR_MIN_LINK_ENTRY_TIME_8_LINES; - bool only_standby = false; - - if (IS_BROADWELL(dev) && dig_port->port != PORT_A) - only_standby = true; - - if (intel_dp->psr_dpcd[1] & DP_PSR_NO_TRAIN_ON_EXIT || only_standby) { - val |= EDP_PSR_LINK_STANDBY; - val |= EDP_PSR_TP2_TP3_TIME_0us; - val |= EDP_PSR_TP1_TIME_0us; - val |= EDP_PSR_SKIP_AUX_EXIT; - val |= IS_BROADWELL(dev) ? BDW_PSR_SINGLE_FRAME : 0; - } else - val |= EDP_PSR_LINK_DISABLE; - - I915_WRITE(EDP_PSR_CTL(dev), val | - (IS_BROADWELL(dev) ? 0 : link_entry_time) | - max_sleep_time << EDP_PSR_MAX_SLEEP_TIME_SHIFT | - idle_frames << EDP_PSR_IDLE_FRAME_SHIFT | - EDP_PSR_ENABLE); -} - -static bool intel_edp_psr_match_conditions(struct intel_dp *intel_dp) -{ - struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); - struct drm_device *dev = dig_port->base.base.dev; - struct drm_i915_private *dev_priv = dev->dev_private; - struct drm_crtc *crtc = dig_port->base.base.crtc; - struct intel_crtc *intel_crtc = to_intel_crtc(crtc); - - lockdep_assert_held(&dev_priv->psr.lock); - WARN_ON(!drm_modeset_is_locked(&dev->mode_config.connection_mutex)); - WARN_ON(!drm_modeset_is_locked(&crtc->mutex)); - - dev_priv->psr.source_ok = false; - - if (IS_HASWELL(dev) && dig_port->port != PORT_A) { - DRM_DEBUG_KMS("HSW ties PSR to DDI A (eDP)\n"); - return false; - } - - if (!i915.enable_psr) { - DRM_DEBUG_KMS("PSR disable by flag\n"); - return false; - } - - /* Below limitations aren't valid for Broadwell */ - if (IS_BROADWELL(dev)) - goto out; - - if (I915_READ(HSW_STEREO_3D_CTL(intel_crtc->config.cpu_transcoder)) & - S3D_ENABLE) { - DRM_DEBUG_KMS("PSR condition failed: Stereo 3D is Enabled\n"); - return false; - } - - if (intel_crtc->config.adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE) { - DRM_DEBUG_KMS("PSR condition failed: Interlaced is Enabled\n"); - return false; - } - - out: - dev_priv->psr.source_ok = true; - return true; -} - -static void intel_edp_psr_do_enable(struct intel_dp *intel_dp) -{ - struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); - struct drm_device *dev = intel_dig_port->base.base.dev; - struct drm_i915_private *dev_priv = dev->dev_private; - - WARN_ON(I915_READ(EDP_PSR_CTL(dev)) & EDP_PSR_ENABLE); - WARN_ON(dev_priv->psr.active); - lockdep_assert_held(&dev_priv->psr.lock); - - /* Enable/Re-enable PSR on the host */ - intel_edp_psr_enable_source(intel_dp); - - dev_priv->psr.active = true; -} - -void intel_edp_psr_enable(struct intel_dp *intel_dp) -{ - struct drm_device *dev = intel_dp_to_dev(intel_dp); - struct drm_i915_private *dev_priv = dev->dev_private; - - if (!HAS_PSR(dev)) { - DRM_DEBUG_KMS("PSR not supported on this platform\n"); - return; - } - - if (!is_edp_psr(intel_dp)) { - DRM_DEBUG_KMS("PSR not supported by this panel\n"); - return; - } - - mutex_lock(&dev_priv->psr.lock); - if (dev_priv->psr.enabled) { - DRM_DEBUG_KMS("PSR already in use\n"); - goto unlock; - } - - if (!intel_edp_psr_match_conditions(intel_dp)) - goto unlock; - - dev_priv->psr.busy_frontbuffer_bits = 0; - - intel_edp_psr_setup_vsc(intel_dp); - - /* Avoid continuous PSR exit by masking memup and hpd */ - I915_WRITE(EDP_PSR_DEBUG_CTL(dev), EDP_PSR_DEBUG_MASK_MEMUP | - EDP_PSR_DEBUG_MASK_HPD | EDP_PSR_DEBUG_MASK_LPSP); - - /* Enable PSR on the panel */ - intel_edp_psr_enable_sink(intel_dp); - - dev_priv->psr.enabled = intel_dp; -unlock: - mutex_unlock(&dev_priv->psr.lock); -} - -void intel_edp_psr_disable(struct intel_dp *intel_dp) -{ - struct drm_device *dev = intel_dp_to_dev(intel_dp); - struct drm_i915_private *dev_priv = dev->dev_private; - - mutex_lock(&dev_priv->psr.lock); - if (!dev_priv->psr.enabled) { - mutex_unlock(&dev_priv->psr.lock); - return; - } - - if (dev_priv->psr.active) { - I915_WRITE(EDP_PSR_CTL(dev), - I915_READ(EDP_PSR_CTL(dev)) & ~EDP_PSR_ENABLE); - - /* Wait till PSR is idle */ - if (_wait_for((I915_READ(EDP_PSR_STATUS_CTL(dev)) & - EDP_PSR_STATUS_STATE_MASK) == 0, 2000, 10)) - DRM_ERROR("Timed out waiting for PSR Idle State\n"); - - dev_priv->psr.active = false; - } else { - WARN_ON(I915_READ(EDP_PSR_CTL(dev)) & EDP_PSR_ENABLE); - } - - dev_priv->psr.enabled = NULL; - mutex_unlock(&dev_priv->psr.lock); - - cancel_delayed_work_sync(&dev_priv->psr.work); -} - -static void intel_edp_psr_work(struct work_struct *work) -{ - struct drm_i915_private *dev_priv = - container_of(work, typeof(*dev_priv), psr.work.work); - struct intel_dp *intel_dp = dev_priv->psr.enabled; - - /* We have to make sure PSR is ready for re-enable - * otherwise it keeps disabled until next full enable/disable cycle. - * PSR might take some time to get fully disabled - * and be ready for re-enable. - */ - if (wait_for((I915_READ(EDP_PSR_STATUS_CTL(dev_priv->dev)) & - EDP_PSR_STATUS_STATE_MASK) == 0, 50)) { - DRM_ERROR("Timed out waiting for PSR Idle for re-enable\n"); - return; - } - - mutex_lock(&dev_priv->psr.lock); - intel_dp = dev_priv->psr.enabled; - - if (!intel_dp) - goto unlock; - - /* - * The delayed work can race with an invalidate hence we need to - * recheck. Since psr_flush first clears this and then reschedules we - * won't ever miss a flush when bailing out here. - */ - if (dev_priv->psr.busy_frontbuffer_bits) - goto unlock; - - intel_edp_psr_do_enable(intel_dp); -unlock: - mutex_unlock(&dev_priv->psr.lock); -} - -static void intel_edp_psr_do_exit(struct drm_device *dev) -{ - struct drm_i915_private *dev_priv = dev->dev_private; - - if (dev_priv->psr.active) { - u32 val = I915_READ(EDP_PSR_CTL(dev)); - - WARN_ON(!(val & EDP_PSR_ENABLE)); - - I915_WRITE(EDP_PSR_CTL(dev), val & ~EDP_PSR_ENABLE); - - dev_priv->psr.active = false; - } - -} - -void intel_edp_psr_invalidate(struct drm_device *dev, - unsigned frontbuffer_bits) -{ - struct drm_i915_private *dev_priv = dev->dev_private; - struct drm_crtc *crtc; - enum pipe pipe; - - mutex_lock(&dev_priv->psr.lock); - if (!dev_priv->psr.enabled) { - mutex_unlock(&dev_priv->psr.lock); - return; - } - - crtc = dp_to_dig_port(dev_priv->psr.enabled)->base.base.crtc; - pipe = to_intel_crtc(crtc)->pipe; - - intel_edp_psr_do_exit(dev); - - frontbuffer_bits &= INTEL_FRONTBUFFER_ALL_MASK(pipe); - - dev_priv->psr.busy_frontbuffer_bits |= frontbuffer_bits; - mutex_unlock(&dev_priv->psr.lock); -} - -void intel_edp_psr_flush(struct drm_device *dev, - unsigned frontbuffer_bits) -{ - struct drm_i915_private *dev_priv = dev->dev_private; - struct drm_crtc *crtc; - enum pipe pipe; - - mutex_lock(&dev_priv->psr.lock); - if (!dev_priv->psr.enabled) { - mutex_unlock(&dev_priv->psr.lock); - return; - } - - crtc = dp_to_dig_port(dev_priv->psr.enabled)->base.base.crtc; - pipe = to_intel_crtc(crtc)->pipe; - dev_priv->psr.busy_frontbuffer_bits &= ~frontbuffer_bits; - - /* - * On Haswell sprite plane updates don't result in a psr invalidating - * signal in the hardware. Which means we need to manually fake this in - * software for all flushes, not just when we've seen a preceding - * invalidation through frontbuffer rendering. - */ - if (IS_HASWELL(dev) && - (frontbuffer_bits & INTEL_FRONTBUFFER_SPRITE(pipe))) - intel_edp_psr_do_exit(dev); - - if (!dev_priv->psr.active && !dev_priv->psr.busy_frontbuffer_bits) - schedule_delayed_work(&dev_priv->psr.work, - msecs_to_jiffies(100)); - mutex_unlock(&dev_priv->psr.lock); -} - -void intel_edp_psr_init(struct drm_device *dev) -{ - struct drm_i915_private *dev_priv = dev->dev_private; - - INIT_DELAYED_WORK(&dev_priv->psr.work, intel_edp_psr_work); - mutex_init(&dev_priv->psr.lock); -} - static void intel_disable_dp(struct intel_encoder *encoder) { struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); @@ -5095,7 +4716,7 @@ void intel_dp_set_drrs_state(struct drm_device *dev, int refresh_rate) * hard to tell without seeing the user of this function of this code. * Check locking and ordering once that lands. */ - if (INTEL_INFO(dev)->gen < 8 && intel_edp_is_psr_enabled(dev)) { + if (INTEL_INFO(dev)->gen < 8 && intel_psr_is_enabled(dev)) { DRM_DEBUG_KMS("DRRS is disabled as PSR is enabled\n"); return; } diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index d67c59b15128..d1f9b639f0d1 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -999,21 +999,16 @@ void intel_edp_backlight_off(struct intel_dp *intel_dp); void intel_edp_panel_vdd_on(struct intel_dp *intel_dp); void intel_edp_panel_on(struct intel_dp *intel_dp); void intel_edp_panel_off(struct intel_dp *intel_dp); -void intel_edp_psr_enable(struct intel_dp *intel_dp); -void intel_edp_psr_disable(struct intel_dp *intel_dp); void intel_dp_set_drrs_state(struct drm_device *dev, int refresh_rate); -void intel_edp_psr_invalidate(struct drm_device *dev, - unsigned frontbuffer_bits); -void intel_edp_psr_flush(struct drm_device *dev, - unsigned frontbuffer_bits); -void intel_edp_psr_init(struct drm_device *dev); - void intel_dp_add_properties(struct intel_dp *intel_dp, struct drm_connector *connector); void intel_dp_mst_suspend(struct drm_device *dev); void intel_dp_mst_resume(struct drm_device *dev); int intel_dp_max_link_bw(struct intel_dp *intel_dp); void intel_dp_hot_plug(struct intel_encoder *intel_encoder); void vlv_power_sequencer_reset(struct drm_i915_private *dev_priv); +uint32_t intel_dp_pack_aux(const uint8_t *src, int src_bytes); +void intel_dp_unpack_aux(uint32_t src, uint8_t *dst, int dst_bytes); + /* intel_dp_mst.c */ int intel_dp_mst_encoder_init(struct intel_digital_port *intel_dig_port, int conn_id); void intel_dp_mst_encoder_cleanup(struct intel_digital_port *intel_dig_port); @@ -1117,6 +1112,16 @@ void intel_backlight_register(struct drm_device *dev); void intel_backlight_unregister(struct drm_device *dev); +/* intel_psr.c */ +bool intel_psr_is_enabled(struct drm_device *dev); +void intel_psr_enable(struct intel_dp *intel_dp); +void intel_psr_disable(struct intel_dp *intel_dp); +void intel_psr_invalidate(struct drm_device *dev, + unsigned frontbuffer_bits); +void intel_psr_flush(struct drm_device *dev, + unsigned frontbuffer_bits); +void intel_psr_init(struct drm_device *dev); + /* intel_runtime_pm.c */ int intel_power_domains_init(struct drm_i915_private *); void intel_power_domains_fini(struct drm_i915_private *); diff --git a/drivers/gpu/drm/i915/intel_frontbuffer.c b/drivers/gpu/drm/i915/intel_frontbuffer.c index 58cf2e6b78f4..79f6d72179c5 100644 --- a/drivers/gpu/drm/i915/intel_frontbuffer.c +++ b/drivers/gpu/drm/i915/intel_frontbuffer.c @@ -156,7 +156,7 @@ void intel_fb_obj_invalidate(struct drm_i915_gem_object *obj, intel_mark_fb_busy(dev, obj->frontbuffer_bits, ring); - intel_edp_psr_invalidate(dev, obj->frontbuffer_bits); + intel_psr_invalidate(dev, obj->frontbuffer_bits); } /** @@ -182,7 +182,7 @@ void intel_frontbuffer_flush(struct drm_device *dev, intel_mark_fb_busy(dev, frontbuffer_bits, NULL); - intel_edp_psr_flush(dev, frontbuffer_bits); + intel_psr_flush(dev, frontbuffer_bits); /* * FIXME: Unconditional fbc flushing here is a rather gross hack and diff --git a/drivers/gpu/drm/i915/intel_psr.c b/drivers/gpu/drm/i915/intel_psr.c new file mode 100644 index 000000000000..7b3ed910bc48 --- /dev/null +++ b/drivers/gpu/drm/i915/intel_psr.c @@ -0,0 +1,408 @@ +/* + * Copyright © 2014 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include + +#include "intel_drv.h" +#include "i915_drv.h" + +static bool is_edp_psr(struct intel_dp *intel_dp) +{ + return intel_dp->psr_dpcd[0] & DP_PSR_IS_SUPPORTED; +} + +bool intel_psr_is_enabled(struct drm_device *dev) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + + if (!HAS_PSR(dev)) + return false; + + return I915_READ(EDP_PSR_CTL(dev)) & EDP_PSR_ENABLE; +} + +static void intel_psr_write_vsc(struct intel_dp *intel_dp, + struct edp_vsc_psr *vsc_psr) +{ + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct drm_device *dev = dig_port->base.base.dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_crtc *crtc = to_intel_crtc(dig_port->base.base.crtc); + u32 ctl_reg = HSW_TVIDEO_DIP_CTL(crtc->config.cpu_transcoder); + u32 data_reg = HSW_TVIDEO_DIP_VSC_DATA(crtc->config.cpu_transcoder); + uint32_t *data = (uint32_t *) vsc_psr; + unsigned int i; + + /* As per BSPec (Pipe Video Data Island Packet), we need to disable + the video DIP being updated before program video DIP data buffer + registers for DIP being updated. */ + I915_WRITE(ctl_reg, 0); + POSTING_READ(ctl_reg); + + for (i = 0; i < VIDEO_DIP_VSC_DATA_SIZE; i += 4) { + if (i < sizeof(struct edp_vsc_psr)) + I915_WRITE(data_reg + i, *data++); + else + I915_WRITE(data_reg + i, 0); + } + + I915_WRITE(ctl_reg, VIDEO_DIP_ENABLE_VSC_HSW); + POSTING_READ(ctl_reg); +} + +static void intel_psr_setup_vsc(struct intel_dp *intel_dp) +{ + struct edp_vsc_psr psr_vsc; + + /* Prepare VSC packet as per EDP 1.3 spec, Table 3.10 */ + memset(&psr_vsc, 0, sizeof(psr_vsc)); + psr_vsc.sdp_header.HB0 = 0; + psr_vsc.sdp_header.HB1 = 0x7; + psr_vsc.sdp_header.HB2 = 0x2; + psr_vsc.sdp_header.HB3 = 0x8; + intel_psr_write_vsc(intel_dp, &psr_vsc); +} + +static void intel_psr_enable_sink(struct intel_dp *intel_dp) +{ + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct drm_device *dev = dig_port->base.base.dev; + struct drm_i915_private *dev_priv = dev->dev_private; + uint32_t aux_clock_divider; + int precharge = 0x3; + bool only_standby = false; + static const uint8_t aux_msg[] = { + [0] = DP_AUX_NATIVE_WRITE << 4, + [1] = DP_SET_POWER >> 8, + [2] = DP_SET_POWER & 0xff, + [3] = 1 - 1, + [4] = DP_SET_POWER_D0, + }; + int i; + + BUILD_BUG_ON(sizeof(aux_msg) > 20); + + aux_clock_divider = intel_dp->get_aux_clock_divider(intel_dp, 0); + + if (IS_BROADWELL(dev) && dig_port->port != PORT_A) + only_standby = true; + + /* Enable PSR in sink */ + if (intel_dp->psr_dpcd[1] & DP_PSR_NO_TRAIN_ON_EXIT || only_standby) + drm_dp_dpcd_writeb(&intel_dp->aux, DP_PSR_EN_CFG, + DP_PSR_ENABLE & ~DP_PSR_MAIN_LINK_ACTIVE); + else + drm_dp_dpcd_writeb(&intel_dp->aux, DP_PSR_EN_CFG, + DP_PSR_ENABLE | DP_PSR_MAIN_LINK_ACTIVE); + + /* Setup AUX registers */ + for (i = 0; i < sizeof(aux_msg); i += 4) + I915_WRITE(EDP_PSR_AUX_DATA1(dev) + i, + intel_dp_pack_aux(&aux_msg[i], sizeof(aux_msg) - i)); + + I915_WRITE(EDP_PSR_AUX_CTL(dev), + DP_AUX_CH_CTL_TIME_OUT_400us | + (sizeof(aux_msg) << DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT) | + (precharge << DP_AUX_CH_CTL_PRECHARGE_2US_SHIFT) | + (aux_clock_divider << DP_AUX_CH_CTL_BIT_CLOCK_2X_SHIFT)); +} + +static void intel_psr_enable_source(struct intel_dp *intel_dp) +{ + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct drm_device *dev = dig_port->base.base.dev; + struct drm_i915_private *dev_priv = dev->dev_private; + uint32_t max_sleep_time = 0x1f; + uint32_t idle_frames = 1; + uint32_t val = 0x0; + const uint32_t link_entry_time = EDP_PSR_MIN_LINK_ENTRY_TIME_8_LINES; + bool only_standby = false; + + if (IS_BROADWELL(dev) && dig_port->port != PORT_A) + only_standby = true; + + if (intel_dp->psr_dpcd[1] & DP_PSR_NO_TRAIN_ON_EXIT || only_standby) { + val |= EDP_PSR_LINK_STANDBY; + val |= EDP_PSR_TP2_TP3_TIME_0us; + val |= EDP_PSR_TP1_TIME_0us; + val |= EDP_PSR_SKIP_AUX_EXIT; + val |= IS_BROADWELL(dev) ? BDW_PSR_SINGLE_FRAME : 0; + } else + val |= EDP_PSR_LINK_DISABLE; + + I915_WRITE(EDP_PSR_CTL(dev), val | + (IS_BROADWELL(dev) ? 0 : link_entry_time) | + max_sleep_time << EDP_PSR_MAX_SLEEP_TIME_SHIFT | + idle_frames << EDP_PSR_IDLE_FRAME_SHIFT | + EDP_PSR_ENABLE); +} + +static bool intel_psr_match_conditions(struct intel_dp *intel_dp) +{ + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct drm_device *dev = dig_port->base.base.dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct drm_crtc *crtc = dig_port->base.base.crtc; + struct intel_crtc *intel_crtc = to_intel_crtc(crtc); + + lockdep_assert_held(&dev_priv->psr.lock); + WARN_ON(!drm_modeset_is_locked(&dev->mode_config.connection_mutex)); + WARN_ON(!drm_modeset_is_locked(&crtc->mutex)); + + dev_priv->psr.source_ok = false; + + if (IS_HASWELL(dev) && dig_port->port != PORT_A) { + DRM_DEBUG_KMS("HSW ties PSR to DDI A (eDP)\n"); + return false; + } + + if (!i915.enable_psr) { + DRM_DEBUG_KMS("PSR disable by flag\n"); + return false; + } + + /* Below limitations aren't valid for Broadwell */ + if (IS_BROADWELL(dev)) + goto out; + + if (I915_READ(HSW_STEREO_3D_CTL(intel_crtc->config.cpu_transcoder)) & + S3D_ENABLE) { + DRM_DEBUG_KMS("PSR condition failed: Stereo 3D is Enabled\n"); + return false; + } + + if (intel_crtc->config.adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE) { + DRM_DEBUG_KMS("PSR condition failed: Interlaced is Enabled\n"); + return false; + } + + out: + dev_priv->psr.source_ok = true; + return true; +} + +static void intel_psr_do_enable(struct intel_dp *intel_dp) +{ + struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); + struct drm_device *dev = intel_dig_port->base.base.dev; + struct drm_i915_private *dev_priv = dev->dev_private; + + WARN_ON(I915_READ(EDP_PSR_CTL(dev)) & EDP_PSR_ENABLE); + WARN_ON(dev_priv->psr.active); + lockdep_assert_held(&dev_priv->psr.lock); + + /* Enable/Re-enable PSR on the host */ + intel_psr_enable_source(intel_dp); + + dev_priv->psr.active = true; +} + +void intel_psr_enable(struct intel_dp *intel_dp) +{ + struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); + struct drm_device *dev = intel_dig_port->base.base.dev; + struct drm_i915_private *dev_priv = dev->dev_private; + + if (!HAS_PSR(dev)) { + DRM_DEBUG_KMS("PSR not supported on this platform\n"); + return; + } + + if (!is_edp_psr(intel_dp)) { + DRM_DEBUG_KMS("PSR not supported by this panel\n"); + return; + } + + mutex_lock(&dev_priv->psr.lock); + if (dev_priv->psr.enabled) { + DRM_DEBUG_KMS("PSR already in use\n"); + goto unlock; + } + + if (!intel_psr_match_conditions(intel_dp)) + goto unlock; + + dev_priv->psr.busy_frontbuffer_bits = 0; + + intel_psr_setup_vsc(intel_dp); + + /* Avoid continuous PSR exit by masking memup and hpd */ + I915_WRITE(EDP_PSR_DEBUG_CTL(dev), EDP_PSR_DEBUG_MASK_MEMUP | + EDP_PSR_DEBUG_MASK_HPD | EDP_PSR_DEBUG_MASK_LPSP); + + /* Enable PSR on the panel */ + intel_psr_enable_sink(intel_dp); + + dev_priv->psr.enabled = intel_dp; +unlock: + mutex_unlock(&dev_priv->psr.lock); +} + +void intel_psr_disable(struct intel_dp *intel_dp) +{ + struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); + struct drm_device *dev = intel_dig_port->base.base.dev; + struct drm_i915_private *dev_priv = dev->dev_private; + + mutex_lock(&dev_priv->psr.lock); + if (!dev_priv->psr.enabled) { + mutex_unlock(&dev_priv->psr.lock); + return; + } + + if (dev_priv->psr.active) { + I915_WRITE(EDP_PSR_CTL(dev), + I915_READ(EDP_PSR_CTL(dev)) & ~EDP_PSR_ENABLE); + + /* Wait till PSR is idle */ + if (_wait_for((I915_READ(EDP_PSR_STATUS_CTL(dev)) & + EDP_PSR_STATUS_STATE_MASK) == 0, 2000, 10)) + DRM_ERROR("Timed out waiting for PSR Idle State\n"); + + dev_priv->psr.active = false; + } else { + WARN_ON(I915_READ(EDP_PSR_CTL(dev)) & EDP_PSR_ENABLE); + } + + dev_priv->psr.enabled = NULL; + mutex_unlock(&dev_priv->psr.lock); + + cancel_delayed_work_sync(&dev_priv->psr.work); +} + +static void intel_psr_work(struct work_struct *work) +{ + struct drm_i915_private *dev_priv = + container_of(work, typeof(*dev_priv), psr.work.work); + struct intel_dp *intel_dp = dev_priv->psr.enabled; + + /* We have to make sure PSR is ready for re-enable + * otherwise it keeps disabled until next full enable/disable cycle. + * PSR might take some time to get fully disabled + * and be ready for re-enable. + */ + if (wait_for((I915_READ(EDP_PSR_STATUS_CTL(dev_priv->dev)) & + EDP_PSR_STATUS_STATE_MASK) == 0, 50)) { + DRM_ERROR("Timed out waiting for PSR Idle for re-enable\n"); + return; + } + + mutex_lock(&dev_priv->psr.lock); + intel_dp = dev_priv->psr.enabled; + + if (!intel_dp) + goto unlock; + + /* + * The delayed work can race with an invalidate hence we need to + * recheck. Since psr_flush first clears this and then reschedules we + * won't ever miss a flush when bailing out here. + */ + if (dev_priv->psr.busy_frontbuffer_bits) + goto unlock; + + intel_psr_do_enable(intel_dp); +unlock: + mutex_unlock(&dev_priv->psr.lock); +} + +static void intel_psr_exit(struct drm_device *dev) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + + if (dev_priv->psr.active) { + u32 val = I915_READ(EDP_PSR_CTL(dev)); + + WARN_ON(!(val & EDP_PSR_ENABLE)); + + I915_WRITE(EDP_PSR_CTL(dev), val & ~EDP_PSR_ENABLE); + + dev_priv->psr.active = false; + } + +} + +void intel_psr_invalidate(struct drm_device *dev, + unsigned frontbuffer_bits) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + struct drm_crtc *crtc; + enum pipe pipe; + + mutex_lock(&dev_priv->psr.lock); + if (!dev_priv->psr.enabled) { + mutex_unlock(&dev_priv->psr.lock); + return; + } + + crtc = dp_to_dig_port(dev_priv->psr.enabled)->base.base.crtc; + pipe = to_intel_crtc(crtc)->pipe; + + intel_psr_exit(dev); + + frontbuffer_bits &= INTEL_FRONTBUFFER_ALL_MASK(pipe); + + dev_priv->psr.busy_frontbuffer_bits |= frontbuffer_bits; + mutex_unlock(&dev_priv->psr.lock); +} + +void intel_psr_flush(struct drm_device *dev, + unsigned frontbuffer_bits) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + struct drm_crtc *crtc; + enum pipe pipe; + + mutex_lock(&dev_priv->psr.lock); + if (!dev_priv->psr.enabled) { + mutex_unlock(&dev_priv->psr.lock); + return; + } + + crtc = dp_to_dig_port(dev_priv->psr.enabled)->base.base.crtc; + pipe = to_intel_crtc(crtc)->pipe; + dev_priv->psr.busy_frontbuffer_bits &= ~frontbuffer_bits; + + /* + * On Haswell sprite plane updates don't result in a psr invalidating + * signal in the hardware. Which means we need to manually fake this in + * software for all flushes, not just when we've seen a preceding + * invalidation through frontbuffer rendering. + */ + if (IS_HASWELL(dev) && + (frontbuffer_bits & INTEL_FRONTBUFFER_SPRITE(pipe))) + intel_psr_exit(dev); + + if (!dev_priv->psr.active && !dev_priv->psr.busy_frontbuffer_bits) + schedule_delayed_work(&dev_priv->psr.work, + msecs_to_jiffies(100)); + mutex_unlock(&dev_priv->psr.lock); +} + +void intel_psr_init(struct drm_device *dev) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + + INIT_DELAYED_WORK(&dev_priv->psr.work, intel_psr_work); + mutex_init(&dev_priv->psr.lock); +} -- cgit v1.2.3