DRRS is a power saving feature, which will refresh the display at the lowest supported refresh rate, based on the rate of change of display content to be rendered.
This patch implements the Generic state machine for the Idleness DRRS. Idleness DRRS is nothing but, when the content of the Display is not changed for a certain duration, refresh rate will be set to the minimum vrefresh supported by the panel. This will be extended for media playback DRRS. i.e based on the display content's Frames Per Second, required lowest vrefresh to support the FPS will be used to render the data. This involves the Userspace and Kernel handshaking. DRRS Support for the encoders like DSI and eDP can be implemented as a separate module and get registered with this generic DRRS stack. Hence extending to new encoder and platform is made easy. Signed-off-by: Ramalingam C <ramalinga...@intel.com> --- drivers/gpu/drm/i915/Makefile | 1 + drivers/gpu/drm/i915/i915_drv.h | 77 ++++- drivers/gpu/drm/i915/intel_drrs.c | 460 ++++++++++++++++++++++++++++++ drivers/gpu/drm/i915/intel_drrs.h | 34 +++ drivers/gpu/drm/i915/intel_drv.h | 5 + drivers/gpu/drm/i915/intel_frontbuffer.c | 2 + 6 files changed, 578 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/i915/intel_drrs.c create mode 100644 drivers/gpu/drm/i915/intel_drrs.h diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile index b7ddf48..5fd100b 100644 --- a/drivers/gpu/drm/i915/Makefile +++ b/drivers/gpu/drm/i915/Makefile @@ -54,6 +54,7 @@ i915-y += intel_audio.o \ intel_modes.o \ intel_overlay.o \ intel_psr.o \ + intel_drrs.o \ intel_sideband.o \ intel_sprite.o i915-$(CONFIG_ACPI) += intel_acpi.o intel_opregion.o diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index 922dd68..00982bb 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -930,10 +930,82 @@ struct i915_fbc { } no_fbc_reason; }; +/** + * DRRS Support Type: + * DRRS_NOT_SUPPORTED : DRRS not supported + * STATIC_DRRS_SUPPORT : Need a complete modeset for DRRS + * SEAMLESS_DRRS_SUPPORT : Seamless vrefresh switch is supported on HW + * SEAMLESS_DRRS_SUPPORT_SW : Seamless vrefresh switch is supported on SW + */ enum drrs_support_type { DRRS_NOT_SUPPORTED = 0, STATIC_DRRS_SUPPORT = 1, - SEAMLESS_DRRS_SUPPORT = 2 + SEAMLESS_DRRS_SUPPORT = 2, + SEAMLESS_DRRS_SUPPORT_SW = 3, +}; + +/** + * Different DRRS States: + * DRRS_HIGH_RR : Refreshrate of Fixed mode. [Maximum Vrefresh] + * DRRS_LOW_RR : Refreshrate of Downclock mode. [Minimum vrefresh] + */ +enum drrs_refresh_rate_type { + DRRS_HIGH_RR, + DRRS_LOW_RR, + DRRS_MAX_RR, +}; + +struct drrs_info { + enum drrs_support_type type; + enum drrs_refresh_rate_type current_rr_type; + enum drrs_refresh_rate_type target_rr_type; +}; + +struct i915_drrs; + +/** + * intel_idleness_drrs_work: + * work : Deferred work to declare the Idleness, if not disturbed. + * crtc : Target drm_crtc + * interval : Time to defer the deferred work + */ +struct intel_idleness_drrs_work { + struct delayed_work work; + struct i915_drrs *drrs; + + /* Idleness interval in mSec*/ + int interval; +}; + +/* Encoder related function pointers */ +struct drrs_encoder_ops { + int (*init)(struct i915_drrs *, struct drm_display_mode *); + void (*exit)(struct i915_drrs *); + void (*set_drrs_state)(struct i915_drrs *); + bool (*is_drrs_hr_state_pending)(struct i915_drrs *); +}; + +struct i915_drrs { + /* Whether another pipe is enabled in parallel */ + bool is_clone; + + /* Whether DRRS is supported on this Panel */ + bool has_drrs; + + /* Holds the DRRS state machine states */ + struct drrs_info drrs_state; + + /* Pointer to the relevant connector */ + struct intel_connector *connector; + + /* Front buffer tracking bits */ + unsigned busy_frontbuffer_bits; + + struct intel_idleness_drrs_work *idleness_drrs_work; + + /* Functions to hold encoder specific DRRS functions */ + struct drrs_encoder_ops *encoder_ops; + struct mutex drrs_mutex; }; struct i915_psr { @@ -1697,6 +1769,7 @@ struct drm_i915_private { struct i915_hotplug hotplug; struct i915_fbc fbc; + struct i915_drrs *drrs[I915_MAX_PIPES]; struct intel_opregion opregion; struct intel_vbt_data vbt; @@ -2394,6 +2467,8 @@ struct drm_i915_cmd_table { #define IS_HSW_ULX(dev) (INTEL_DEVID(dev) == 0x0A0E || \ INTEL_DEVID(dev) == 0x0A1E) #define IS_PRELIMINARY_HW(intel_info) ((intel_info)->is_preliminary) +#define IS_PLATFORM_HAS_DRRS(dev) (INTEL_INFO(dev)->gen >= 7 || \ + INTEL_INFO(dev)->gen <= 9) #define SKL_REVID_A0 (0x0) #define SKL_REVID_B0 (0x1) diff --git a/drivers/gpu/drm/i915/intel_drrs.c b/drivers/gpu/drm/i915/intel_drrs.c new file mode 100644 index 0000000..eb67909 --- /dev/null +++ b/drivers/gpu/drm/i915/intel_drrs.c @@ -0,0 +1,460 @@ +/* + * Copyright (C) 2015, Intel Corporation. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Author: + * Ramalingam C <ramalinga...@intel.com> + */ + +#include <drm/i915_drm.h> +#include <linux/delay.h> + +#include "i915_drv.h" +#include "intel_drv.h" +#include "intel_drrs.h" + +int get_drrs_struct_index_for_pipe(struct drm_i915_private *dev_priv, int pipe) +{ + struct intel_crtc *crtc; + struct i915_drrs *drrs; + int i; + + for (i = 0; i < I915_MAX_PIPES; i++) { + drrs = dev_priv->drrs[i]; + if (drrs) { + crtc = to_intel_crtc( + drrs->connector->encoder->base.crtc); + if (crtc && crtc->pipe == pipe) + return i; + } + } + return -ENXIO; +} + +int get_drrs_struct_index_for_crtc(struct drm_i915_private *dev_priv, + struct intel_crtc *intel_crtc) +{ + int i; + + for (i = 0; i < I915_MAX_PIPES; i++) { + if (dev_priv->drrs[i] && + (dev_priv->drrs[i]->connector->encoder->base.crtc == + &intel_crtc->base)) + return i; + } + + /* No drrs struct exist for this crtc */ + return -ENXIO; + +} + +int get_drrs_struct_index_for_connector(struct drm_i915_private *dev_priv, + struct intel_connector *intel_connector) +{ + int i; + + for (i = 0; i < I915_MAX_PIPES; i++) { + if (dev_priv->drrs[i] && + (dev_priv->drrs[i]->connector == intel_connector)) + return i; + } + + /* No drrs struct exist for this connector */ + return -ENXIO; +} + +int get_free_drrs_struct_index(struct drm_i915_private *dev_priv) +{ + int i; + + for (i = 0; i < I915_MAX_PIPES; i++) { + if (!dev_priv->drrs[i]) + return i; + } + + /* All drrs index are busy */ + return -EBUSY; +} + +void intel_set_drrs_state(struct i915_drrs *drrs) +{ + struct drrs_info *drrs_state; + struct drm_display_mode *target_mode; + struct intel_crtc *intel_crtc; + struct intel_panel *panel; + int refresh_rate; + + if (!drrs || !drrs->has_drrs) { + DRM_ERROR("DRRS is not supported on this pipe\n"); + return; + } + + panel = &drrs->connector->panel; + drrs_state = &drrs->drrs_state; + + intel_crtc = to_intel_crtc(drrs->connector->encoder->base.crtc); + + if (!intel_crtc) { + DRM_DEBUG_KMS("DRRS: intel_crtc not initialized\n"); + return; + } + + if (!intel_crtc->active) { + DRM_INFO("Encoder has been disabled. CRTC not Active\n"); + return; + } + + target_mode = panel->target_mode; + if (target_mode == NULL) { + DRM_ERROR("target_mode cannot be NULL\n"); + return; + } + refresh_rate = target_mode->vrefresh; + + if (refresh_rate <= 0) { + DRM_ERROR("Refresh rate should be positive non-zero.<%d>\n", + refresh_rate); + return; + } + + if (drrs_state->target_rr_type >= DRRS_MAX_RR) { + DRM_ERROR("Unknown refresh_rate_type\n"); + return; + } + + if (drrs_state->target_rr_type == drrs_state->current_rr_type) { + DRM_INFO("Requested for previously set RR. Ignoring\n"); + return; + } + + drrs->encoder_ops->set_drrs_state(drrs); + if (drrs_state->type != SEAMLESS_DRRS_SUPPORT_SW) { + + /* + * If it is non-DSI encoders. + * As only DSI has SEAMLESS_DRRS_SUPPORT_SW. + */ + drrs_state->current_rr_type = drrs_state->target_rr_type; + DRM_INFO("Refresh Rate set to : %dHz\n", refresh_rate); + } +} + +static void intel_idleness_drrs_work_fn(struct work_struct *__work) +{ + struct intel_idleness_drrs_work *work = + container_of(to_delayed_work(__work), + struct intel_idleness_drrs_work, work); + struct intel_panel *panel; + struct i915_drrs *drrs = work->drrs; + + panel = &drrs->connector->panel; + + /* TODO: If DRRS is not supported on clone mode act here */ + mutex_lock(&drrs->drrs_mutex); + if (panel->target_mode != NULL) + DRM_ERROR("FIXME: We shouldn't be here\n"); + + panel->target_mode = panel->downclock_mode; + drrs->drrs_state.target_rr_type = DRRS_LOW_RR; + + intel_set_drrs_state(drrs); + + panel->target_mode = NULL; + mutex_unlock(&drrs->drrs_mutex); +} + +static void intel_cancel_idleness_drrs_work(struct i915_drrs *drrs, bool sync) +{ + if (!drrs || !drrs->has_drrs || !drrs->idleness_drrs_work) + return; + if (sync) { + mutex_unlock(&drrs->drrs_mutex); + cancel_delayed_work_sync(&drrs->idleness_drrs_work->work); + mutex_lock(&drrs->drrs_mutex); + } else { + cancel_delayed_work(&drrs->idleness_drrs_work->work); + } + drrs->connector->panel.target_mode = NULL; +} + +static void intel_enable_idleness_drrs(struct i915_drrs *drrs) +{ + bool force_enable_drrs = false; + + mutex_lock(&drrs->drrs_mutex); + + /* Capturing the deferred request for disable_drrs */ + if (drrs->drrs_state.type == SEAMLESS_DRRS_SUPPORT_SW && + drrs->encoder_ops->is_drrs_hr_state_pending) { + if (drrs->encoder_ops->is_drrs_hr_state_pending(drrs)) + force_enable_drrs = true; + } + + if (drrs->drrs_state.current_rr_type != DRRS_LOW_RR || + force_enable_drrs) { + drrs->idleness_drrs_work->drrs = drrs; + + /* + * Delay the actual enabling to let pageflipping cease and the + * display to settle before starting DRRS + */ + schedule_delayed_work(&drrs->idleness_drrs_work->work, + msecs_to_jiffies(drrs->idleness_drrs_work->interval)); + } + mutex_unlock(&drrs->drrs_mutex); +} + +/* Needs to be called with drrs_mutex acquired */ +void intel_disable_idleness_drrs(struct i915_drrs *drrs) +{ + struct intel_panel *panel; + + mutex_lock(&drrs->drrs_mutex); + panel = &drrs->connector->panel; + + /* as part of disable DRRS, reset refresh rate to HIGH_RR */ + if (drrs->drrs_state.current_rr_type == DRRS_LOW_RR) { + if (panel->target_mode != NULL) + DRM_ERROR("FIXME: We shouldn't be here\n"); + + panel->target_mode = panel->fixed_mode; + drrs->drrs_state.target_rr_type = DRRS_HIGH_RR; + intel_set_drrs_state(drrs); + panel->target_mode = NULL; + } + mutex_unlock(&drrs->drrs_mutex); +} + +/** + * intel_drrs_invalidate - Disable Idleness DRRS + * @dev: DRM device + * @frontbuffer_bits: frontbuffer plane tracking bits + * + * This function gets called everytime rendering on the given planes start. + * Hence DRRS needs to be Upclocked, i.e. (LOW_RR -> HIGH_RR). + * + * Dirty frontbuffers relevant to DRRS are tracked in busy_frontbuffer_bits. + */ +void intel_drrs_invalidate(struct drm_device *dev, + unsigned frontbuffer_bits) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + struct i915_drrs *drrs; + struct intel_crtc *crtc; + int pipe; + int index; + + + for (pipe = 0; pipe < 4; pipe++) { + if (frontbuffer_bits & (0xf << (16 * pipe))) { + index = get_drrs_struct_index_for_pipe(dev_priv, pipe); + if (index < 0) + return; + + drrs = dev_priv->drrs[index]; + if (!drrs || !drrs->has_drrs) + return; + + intel_cancel_idleness_drrs_work(drrs, false); + + crtc = to_intel_crtc( + drrs->connector->encoder->base.crtc); + if (!crtc) + return; + + intel_disable_idleness_drrs(drrs); + + frontbuffer_bits &= + INTEL_FRONTBUFFER_ALL_MASK(crtc->pipe); + + mutex_lock(&drrs->drrs_mutex); + drrs->busy_frontbuffer_bits |= frontbuffer_bits; + mutex_unlock(&drrs->drrs_mutex); + } + } +} + +/** + * intel_drrs_flush - Restart Idleness DRRS + * @dev: DRM device + * @frontbuffer_bits: frontbuffer plane tracking bits + * + * This function gets called every time rendering on the given planes has + * completed or flip on a crtc is completed. So DRRS should be upclocked + * (LOW_RR -> HIGH_RR). And also Idleness detection should be started again, + * if no other planes are dirty. + * + * Dirty frontbuffers relevant to DRRS are tracked in busy_frontbuffer_bits. + */ +void intel_drrs_flush(struct drm_device *dev, + unsigned frontbuffer_bits) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + struct i915_drrs *drrs; + int pipe; + int index; + + for (pipe = 0; pipe < 4; pipe++) { + if (frontbuffer_bits & (0xf << (16 * pipe))) { + index = get_drrs_struct_index_for_pipe(dev_priv, pipe); + if (index < 0) + return; + + drrs = dev_priv->drrs[index]; + if (!drrs || !drrs->has_drrs) + return; + + intel_cancel_idleness_drrs_work(drrs, false); + + mutex_lock(&drrs->drrs_mutex); + drrs->busy_frontbuffer_bits &= ~frontbuffer_bits; + mutex_unlock(&drrs->drrs_mutex); + + intel_disable_idleness_drrs(drrs); + + if (!drrs->busy_frontbuffer_bits) + intel_enable_idleness_drrs(drrs); + } + } +} + +/* Idleness detection logic is initialized */ +int intel_drrs_idleness_detection_init(struct i915_drrs *drrs) +{ + struct intel_idleness_drrs_work *work; + + work = kzalloc(sizeof(struct intel_idleness_drrs_work), GFP_KERNEL); + if (!work) { + DRM_ERROR("Failed to allocate DRRS work structure\n"); + return -ENOMEM; + } + + drrs->is_clone = false; + work->interval = DRRS_IDLENESS_INTERVAL_MS; + work->drrs = drrs; + INIT_DELAYED_WORK(&work->work, intel_idleness_drrs_work_fn); + + drrs->idleness_drrs_work = work; + return 0; +} + +/* + * intel_drrs_init : General entry for DRRS Unit. Called for each PIPE. + */ +int intel_drrs_init(struct drm_device *dev, + struct intel_connector *intel_connector, + struct drm_display_mode *fixed_mode) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + struct i915_drrs *drrs; + int ret = 0, index; + + if (!IS_PLATFORM_HAS_DRRS(dev)) { + DRM_ERROR("DRRS is not enabled on this platform\n"); + return -EPERM; + } + + if (get_drrs_struct_index_for_connector(dev_priv, intel_connector) + >= 0) { + DRM_ERROR("DRRS is already initialized for this connector\n"); + return -EINVAL; + } + + index = get_free_drrs_struct_index(dev_priv); + if (index < 0) { + DRM_ERROR("DRRS is initialized for all pipes\n"); + return -EBUSY; + } + + /* FIXME: Linkedlist can be used */ + dev_priv->drrs[index] = kzalloc(sizeof(*drrs), GFP_KERNEL); + if (!dev_priv->drrs[index]) { + DRM_ERROR("DRRS memory allocation failed\n"); + return -ENOMEM; + } + + drrs = dev_priv->drrs[index]; + drrs->connector = intel_connector; + + if (!drrs->encoder_ops) { + DRM_ERROR("Encoder ops not initialized\n"); + ret = -EINVAL; + goto err_out; + } + + /* First check if DRRS is enabled from VBT struct */ + if (dev_priv->vbt.drrs_type != SEAMLESS_DRRS_SUPPORT) { + DRM_INFO("Panel doesn't support SEAMLESS DRRS\n"); + ret = -EPERM; + goto err_out; + } + + if (!drrs->encoder_ops->init || !drrs->encoder_ops->exit || + !drrs->encoder_ops->set_drrs_state) { + DRM_ERROR("Essential func ptrs are NULL\n"); + ret = -EINVAL; + goto err_out; + } + + ret = drrs->encoder_ops->init(drrs, fixed_mode); + if (ret < 0) { + DRM_ERROR("Encoder DRRS init failed\n"); + goto err_out; + } + + ret = intel_drrs_idleness_detection_init(drrs); + if (ret < 0) { + drrs->encoder_ops->exit(drrs); + goto err_out; + } + + /* SEAMLESS DRRS is supported and downclock mode also exist */ + drrs->has_drrs = true; + to_intel_crtc(intel_encoder->base.crtc)->config->has_drrs = true; + mutex_init(&drrs->drrs_mutex); + drrs->drrs_state.current_rr_type = DRRS_HIGH_RR; + DRM_INFO("SEAMLESS DRRS supported on this panel.\n"); + + return 0; + +err_out: + drrs->drrs_state.type = DRRS_NOT_SUPPORTED; + kfree(dev_priv->drrs[index]); + dev_priv->drrs[index] = NULL; + return ret; +} + +void intel_drrs_exit(struct drm_device *dev, + struct intel_connector *intel_connector) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + struct i915_drrs *drrs; + int index; + + index = get_drrs_struct_index_for_connector(dev_priv, intel_connector); + if (index < 0) + return; + + drrs = dev_priv->drrs[index]; + intel_cancel_idleness_drrs_work(drrs, true); + + if (drrs->encoder_ops->exit) + drrs->encoder_ops->exit(drrs); + + drrs->has_drrs = false; + to_intel_crtc(intel_connector->encoder->base.crtc)->config->has_drrs = + false; + mutex_destroy(&drrs->drrs_mutex); + kfree(drrs->idleness_drrs_work); + + kfree(drrs); + dev_priv->drrs[index] = NULL; +} diff --git a/drivers/gpu/drm/i915/intel_drrs.h b/drivers/gpu/drm/i915/intel_drrs.h new file mode 100644 index 0000000..a07f023 --- /dev/null +++ b/drivers/gpu/drm/i915/intel_drrs.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2015, Intel Corporation. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Author: + * Ramalingam C <ramalinga...@intel.com> + */ + +#ifndef INTEL_DRRS_H__ +#define INTEL_DRRS_H__ + +#define DRRS_IDLENESS_INTERVAL_MS 1000 + +struct intel_encoder; + +int get_drrs_struct_index_for_crtc(struct drm_i915_private *dev_priv, + struct intel_crtc *intel_crtc); +int get_drrs_struct_index_for_connector(struct drm_i915_private *dev_priv, + struct intel_connector *intel_connector); +int get_free_drrs_struct_index(struct drm_i915_private *dev_priv); + +int intel_drrs_init(struct drm_device *dev, + struct intel_connector *intel_connector, + struct drm_display_mode *fixed_mode); + +#endif /* INTEL_DRRS_H__ */ diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index e2f534a..726c31d 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -37,6 +37,8 @@ #include <drm/drm_rect.h> #include <drm/drm_atomic.h> +#include "intel_drrs.h" + /** * _wait_for - magic (register) wait macro * @@ -171,6 +173,7 @@ struct intel_encoder { struct intel_panel { struct drm_display_mode *fixed_mode; struct drm_display_mode *downclock_mode; + struct drm_display_mode *target_mode; int fitting_mode; /* backlight */ @@ -1198,6 +1201,8 @@ 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_plane_destroy(struct drm_plane *plane); +void intel_drrs_invalidate(struct drm_device *dev, unsigned frontbuffer_bits); +void intel_drrs_flush(struct drm_device *dev, unsigned frontbuffer_bits); /* intel_dp_mst.c */ int intel_dp_mst_encoder_init(struct intel_digital_port *intel_dig_port, int conn_id); diff --git a/drivers/gpu/drm/i915/intel_frontbuffer.c b/drivers/gpu/drm/i915/intel_frontbuffer.c index 249d0b3..62d18eb 100644 --- a/drivers/gpu/drm/i915/intel_frontbuffer.c +++ b/drivers/gpu/drm/i915/intel_frontbuffer.c @@ -153,6 +153,7 @@ void intel_fb_obj_invalidate(struct drm_i915_gem_object *obj, intel_mark_fb_busy(dev, obj->frontbuffer_bits, ring); + intel_drrs_invalidate(dev, obj->frontbuffer_bits); intel_psr_invalidate(dev, obj->frontbuffer_bits); intel_fbc_invalidate(dev_priv, obj->frontbuffer_bits, origin); } @@ -180,6 +181,7 @@ void intel_frontbuffer_flush(struct drm_device *dev, intel_mark_fb_busy(dev, frontbuffer_bits, NULL); + intel_drrs_flush(dev, frontbuffer_bits); intel_psr_flush(dev, frontbuffer_bits); intel_fbc_flush(dev_priv, frontbuffer_bits); } -- 1.7.9.5 _______________________________________________ Intel-gfx mailing list Intel-gfx@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/intel-gfx