[RFC PATCH 04/18] drm/i915: Implementation of Generic DSI DRRS

Ramalingam C
2015-06-26T13:59:58Z

This patch Implements the generic DSI DRRS functions and registers
them with Generic DRRS state machine.

Platform specific DSI DRRS functions will be implemented and
registered with this generic DSI DRRS implementation. Hence
extending the DSI DRRS to new platform is made simple.

Signed-off-by: Ramalingam C <ramalingam.c@intel.com>
---
drivers/gpu/drm/i915/Makefile | 1 +
drivers/gpu/drm/i915/intel_drrs.c | 10 ++
drivers/gpu/drm/i915/intel_dsi.h | 3 +
drivers/gpu/drm/i915/intel_dsi_drrs.c | 320 +++++++++++++++++++++++++++++++++
drivers/gpu/drm/i915/intel_dsi_drrs.h | 61 +++++++
drivers/gpu/drm/i915/intel_dsi_pll.c | 5 -
6 files changed, 395 insertions(+), 5 deletions(-)
create mode 100644 drivers/gpu/drm/i915/intel_dsi_drrs.c
create mode 100644 drivers/gpu/drm/i915/intel_dsi_drrs.h

diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile
index 5fd100b..4f77fb2 100644
--- a/drivers/gpu/drm/i915/Makefile
+++ b/drivers/gpu/drm/i915/Makefile
@@ -55,6 +55,7 @@ i915-y += intel_audio.o \
intel_overlay.o \
intel_psr.o \
intel_drrs.o \
+ intel_dsi_drrs.o \
intel_sideband.o \
intel_sprite.o
i915-$(CONFIG_ACPI) += intel_acpi.o intel_opregion.o
diff --git a/drivers/gpu/drm/i915/intel_drrs.c b/drivers/gpu/drm/i915/intel_drrs.c
index eb67909..2a776d2 100644
--- a/drivers/gpu/drm/i915/intel_drrs.c
+++ b/drivers/gpu/drm/i915/intel_drrs.c
@@ -19,6 +19,7 @@

#include "i915_drv.h"
#include "intel_drv.h"
+#include "intel_dsi.h"
#include "intel_drrs.h"

int get_drrs_struct_index_for_pipe(struct drm_i915_private *dev_priv, int pipe)
@@ -354,6 +355,7 @@ int intel_drrs_init(struct drm_device *dev,
struct drm_display_mode *fixed_mode)
{
struct drm_i915_private *dev_priv = dev->dev_private;
+ struct intel_encoder *intel_encoder = intel_connector->encoder;
struct i915_drrs *drrs;
int ret = 0, index;

@@ -384,6 +386,14 @@ int intel_drrs_init(struct drm_device *dev,
drrs = dev_priv->drrs[index];
drrs->connector = intel_connector;

+ if (intel_encoder->type == INTEL_OUTPUT_DSI) {
+ drrs->encoder_ops = get_intel_dsi_drrs_ops();
+ } else {
+ DRM_ERROR("DRRS: Unsupported Encoder\n");
+ ret = -EINVAL;
+ goto err_out;
+ }
+
if (!drrs->encoder_ops) {
DRM_ERROR("Encoder ops not initialized\n");
ret = -EINVAL;
diff --git a/drivers/gpu/drm/i915/intel_dsi.h b/drivers/gpu/drm/i915/intel_dsi.h
index 2784ac4..129f68e 100644
--- a/drivers/gpu/drm/i915/intel_dsi.h
+++ b/drivers/gpu/drm/i915/intel_dsi.h
@@ -28,6 +28,7 @@
#include <drm/drm_crtc.h>
#include <drm/drm_mipi_dsi.h>
#include "intel_drv.h"
+#include "intel_dsi_drrs.h"

/* Dual Link support */
#define DSI_DUAL_LINK_NONE 0
@@ -47,6 +48,8 @@ struct intel_dsi {
/* bit mask of ports being driven */
u16 ports;

+ struct dsi_drrs dsi_drrs;
+
/* if true, use HS mode, otherwise LP */
bool hs;

diff --git a/drivers/gpu/drm/i915/intel_dsi_drrs.c b/drivers/gpu/drm/i915/intel_dsi_drrs.c
new file mode 100644
index 0000000..6ab0e9e
--- /dev/null
+++ b/drivers/gpu/drm/i915/intel_dsi_drrs.c
@@ -0,0 +1,320 @@
+/*
+ * 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 <ramalingam.c@intel.com>
+ */
+
+#include <drm/i915_drm.h>
+#include <linux/delay.h>
+
+#include "intel_drv.h"
+#include "intel_dsi.h"
+
+/* Work function for DSI deferred work */
+static void intel_mipi_drrs_work_fn(struct work_struct *__work)
+{
+ struct intel_mipi_drrs_work *work =
+ container_of(to_delayed_work(__work),
+ struct intel_mipi_drrs_work, work);
+ struct i915_drrs *drrs = work->drrs;
+ struct intel_encoder *intel_encoder = drrs->connector->encoder;
+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&intel_encoder->base);
+ struct dsi_drrs *dsi_drrs = &intel_dsi->dsi_drrs;
+ struct dsi_mnp *dsi_mnp;
+ struct drm_display_mode *prev_mode = NULL;
+ bool fallback_attempt = false;
+ int ret, retry_cnt = 3;
+
+init:
+ dsi_mnp = &dsi_drrs->mnp[work->target_rr_type];
+ DRM_DEBUG("Refresh rate Type: %d-->%d\n",
+ drrs->drrs_state.current_rr_type,
+ work->target_rr_type);
+ DRM_DEBUG("Target RR: %d\n", work->target_mode->vrefresh);
+
+retry:
+ if (!intel_crtc || !intel_crtc->active)
+ goto out;
+
+ ret = dsi_drrs->ops->configure_dsi_pll(intel_encoder, dsi_mnp);
+ if (ret == 0) {
+
+ /* PLL Programming is successful */
+ mutex_lock(&drrs->drrs_mutex);
+ drrs->drrs_state.current_rr_type = work->target_rr_type;
+ mutex_unlock(&drrs->drrs_mutex);
+ DRM_INFO("Refresh Rate set to : %dHz\n",
+ work->target_mode->vrefresh);
+
+ /* TODO: Update crtc mode and drain ladency with watermark */
+
+ } else if (ret == -ETIMEDOUT && retry_cnt) {
+
+ /* Timed out. But still attempts are allowed */
+ retry_cnt--;
+ DRM_DEBUG("Retry left ... <%d>\n", retry_cnt);
+ goto retry;
+ } else if (ret == -EACCES && !fallback_attempt) {
+
+ /*
+ * PLL Didn't lock for the programmed value
+ * fall back to prev mode
+ */
+ DRM_ERROR("Falling back to the previous DRRS state. %d->%d\n",
+ work->target_rr_type,
+ drrs->drrs_state.current_rr_type);
+
+ mutex_lock(&drrs->drrs_mutex);
+ drrs->drrs_state.target_rr_type =
+ drrs->drrs_state.current_rr_type;
+ mutex_unlock(&drrs->drrs_mutex);
+
+ work->target_rr_type = drrs->drrs_state.target_rr_type;
+ drm_mode_destroy(intel_encoder->base.dev, work->target_mode);
+
+ if (work->target_rr_type == DRRS_HIGH_RR)
+ prev_mode = drrs->connector->panel.fixed_mode;
+ else if (work->target_rr_type == DRRS_LOW_RR)
+ prev_mode = drrs->connector->panel.downclock_mode;
+
+ work->target_mode = drm_mode_duplicate(intel_encoder->base.dev,
+ prev_mode);
+ fallback_attempt = true;
+ goto init;
+ } else {
+
+ /*
+ * All attempts are failed or Fall back
+ * mode all didn't go through.
+ */
+ if (fallback_attempt)
+ DRM_ERROR("DRRS State Fallback attempt failed\n");
+ if (ret == -ETIMEDOUT)
+ DRM_ERROR("TIMEDOUT in all retry attempt\n");
+ }
+
+out:
+ drm_mode_destroy(intel_encoder->base.dev, work->target_mode);
+ work->work_completed = true;
+}
+
+/* Whether DRRS_HR_STATE is pending in the dsi deferred work */
+bool intel_dsi_is_drrs_hr_state_pending(struct i915_drrs *drrs)
+{
+ struct intel_dsi *intel_dsi =
+ enc_to_intel_dsi(&drrs->connector->encoder->base);
+ struct dsi_drrs *dsi_drrs = &intel_dsi->dsi_drrs;
+ struct intel_mipi_drrs_work *work = dsi_drrs->mipi_drrs_work;
+
+ if (work_busy(&work->work.work) && work->target_rr_type == DRRS_HIGH_RR)
+ return true;
+ return false;
+}
+
+void intel_dsi_set_drrs_state(struct i915_drrs *drrs)
+{
+ struct intel_encoder *intel_encoder = drrs->connector->encoder;
+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&intel_encoder->base);
+ struct dsi_drrs *dsi_drrs = &intel_dsi->dsi_drrs;
+ struct drm_display_mode *target_mode =
+ drrs->connector->panel.target_mode;
+ struct intel_mipi_drrs_work *work = dsi_drrs->mipi_drrs_work;
+ unsigned int ret;
+
+ ret = work_busy(&work->work.work);
+ if (ret) {
+ if (work->target_mode)
+ if (work->target_mode->vrefresh ==
+ target_mode->vrefresh) {
+ DRM_INFO("Repeated request for %dHz\n",
+ target_mode->vrefresh);
+ return;
+ }
+ DRM_DEBUG("Cancelling an queued/executing work\n");
+ atomic_set(&work->abort_wait_loop, 1);
+ cancel_delayed_work_sync(&work->work);
+ atomic_set(&work->abort_wait_loop, 0);
+ if ((ret & WORK_BUSY_PENDING) && !work->work_completed)
+ drm_mode_destroy(intel_encoder->base.dev,
+ work->target_mode);
+ }
+
+ work->work_completed = false;
+ work->drrs = drrs;
+ work->target_rr_type = drrs->drrs_state.target_rr_type;
+ work->target_mode = drm_mode_duplicate(intel_encoder->base.dev,
+ target_mode);
+ schedule_delayed_work(&work->work, 0);
+}
+
+/* DSI deferred function init*/
+int intel_dsi_drrs_deferred_work_init(struct i915_drrs *drrs)
+{
+ struct intel_encoder *intel_encoder = drrs->connector->encoder;
+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&intel_encoder->base);
+ struct dsi_drrs *dsi_drrs = &intel_dsi->dsi_drrs;
+ struct intel_mipi_drrs_work *work;
+
+ work = kzalloc(sizeof(struct intel_mipi_drrs_work), GFP_KERNEL);
+ if (!work) {
+ DRM_ERROR("Failed to allocate mipi DRRS work structure\n");
+ return -ENOMEM;
+ }
+
+ atomic_set(&work->abort_wait_loop, 0);
+ INIT_DELAYED_WORK(&work->work, intel_mipi_drrs_work_fn);
+ work->target_mode = NULL;
+ work->work_completed = true;
+
+ dsi_drrs->mipi_drrs_work = work;
+ return 0;
+}
+
+/* Based on the VBT's min supported vrefresh, downclock mode will be created */
+struct drm_display_mode *
+intel_dsi_calc_panel_downclock(struct i915_drrs *drrs,
+ struct drm_display_mode *fixed_mode)
+{
+ struct intel_encoder *intel_encoder = drrs->connector->encoder;
+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&intel_encoder->base);
+ struct dsi_drrs *dsi_drrs = &intel_dsi->dsi_drrs;
+ struct drm_display_mode *downclock_mode = NULL;
+
+ if (dsi_drrs->min_vrefresh == 0 ||
+ dsi_drrs->min_vrefresh >= fixed_mode->vrefresh) {
+ DRM_ERROR("Invalid min Vrefresh. %d\n", dsi_drrs->min_vrefresh);
+ return NULL;
+ }
+
+ /* Allocate */
+ downclock_mode =
+ drm_mode_duplicate(intel_encoder->base.dev, fixed_mode);
+ if (!downclock_mode) {
+ DRM_ERROR("Downclock mode allocation failed. No memory\n");
+ return NULL;
+ }
+
+ downclock_mode->vrefresh = dsi_drrs->min_vrefresh;
+ DRM_DEBUG("drrs_min_vrefresh = %u\n", downclock_mode->vrefresh);
+ downclock_mode->clock = downclock_mode->vrefresh *
+ downclock_mode->vtotal * downclock_mode->htotal / 1000;
+
+ return downclock_mode;
+}
+
+/* Main init Enrty for DSI DRRS module */
+int intel_dsi_drrs_init(struct i915_drrs *drrs,
+ struct drm_display_mode *fixed_mode)
+{
+ struct intel_encoder *intel_encoder = drrs->connector->encoder;
+ struct drm_i915_private *dev_priv =
+ intel_encoder->base.dev->dev_private;
+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&intel_encoder->base);
+ struct dsi_drrs *dsi_drrs = &intel_dsi->dsi_drrs;
+ struct intel_panel *panel = &drrs->connector->panel;
+ struct drm_display_mode *downclock_mode;
+ int ret = 0;
+
+ dsi_drrs->min_vrefresh = dev_priv->vbt.drrs_min_vrefresh;
+
+ if (fixed_mode->vrefresh == 0)
+ fixed_mode->vrefresh = drm_mode_vrefresh(fixed_mode);
+
+ /* Modes Initialization */
+ downclock_mode = intel_dsi_calc_panel_downclock(drrs, fixed_mode);
+ if (!downclock_mode) {
+ DRM_ERROR("Downclock mode not Found\n");
+ ret = -ENOMEM;
+ goto out_err_1;
+ }
+
+ DRM_DEBUG("downclock_mode :\n");
+ drm_mode_debug_printmodeline(downclock_mode);
+
+ panel->target_mode = NULL;
+
+ if (!dsi_drrs->ops || !dsi_drrs->ops->mnp_calculate_for_mode ||
+ !dsi_drrs->ops->configure_dsi_pll) {
+ DRM_ERROR("DSI platform ops not initialized\n");
+ ret = -EINVAL;
+ goto out_err_2;
+ }
+
+ /* Calculate mnp for fixed and downclock modes */
+ ret = dsi_drrs->ops->mnp_calculate_for_mode(
+ intel_encoder, &dsi_drrs->mnp[DRRS_HIGH_RR],
+ fixed_mode);
+ if (ret < 0)
+ goto out_err_2;
+
+ ret = dsi_drrs->ops->mnp_calculate_for_mode(
+ intel_encoder, &dsi_drrs->mnp[DRRS_LOW_RR],
+ downclock_mode);
+ if (ret < 0)
+ goto out_err_2;
+
+ ret = intel_dsi_drrs_deferred_work_init(drrs);
+ if (ret < 0)
+ goto out_err_2;
+
+ /* In DSI SEAMLESS DRRS is a SW driven feature */
+ drrs->drrs_state.type = SEAMLESS_DRRS_SUPPORT_SW;
+ intel_panel_init(panel, fixed_mode, downclock_mode);
+
+ return ret;
+
+out_err_2:
+ drm_mode_destroy(intel_encoder->base.dev, downclock_mode);
+out_err_1:
+ return ret;
+}
+
+void intel_dsi_drrs_exit(struct i915_drrs *drrs)
+{
+ struct intel_encoder *intel_encoder = drrs->connector->encoder;
+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&intel_encoder->base);
+ struct dsi_drrs *dsi_drrs = &intel_dsi->dsi_drrs;
+ struct intel_mipi_drrs_work *work = dsi_drrs->mipi_drrs_work;
+ unsigned int ret;
+
+ ret = work_busy(&work->work.work);
+ if (ret) {
+ DRM_DEBUG("Cancelling an queued/executing work\n");
+ atomic_set(&work->abort_wait_loop, 1);
+ cancel_delayed_work_sync(&work->work);
+ atomic_set(&work->abort_wait_loop, 0);
+ if ((ret & WORK_BUSY_PENDING) && !work->work_completed)
+ drm_mode_destroy(intel_encoder->base.dev,
+ work->target_mode);
+ }
+
+ drm_mode_destroy(intel_encoder->base.dev,
+ drrs->connector->panel.downclock_mode);
+ drrs->connector->panel.downclock_mode = NULL;
+
+ kfree(dsi_drrs->mipi_drrs_work);
+ drrs->drrs_state.type = DRRS_NOT_SUPPORTED;
+}
+
+struct drrs_encoder_ops drrs_dsi_ops = {
+ .init = intel_dsi_drrs_init,
+ .exit = intel_dsi_drrs_exit,
+ .set_drrs_state = intel_dsi_set_drrs_state,
+ .is_drrs_hr_state_pending = intel_dsi_is_drrs_hr_state_pending,
+};
+
+/* Call back Function for Intel_drrs module to get the dsi func ptr */
+inline struct drrs_encoder_ops *get_intel_dsi_drrs_ops(void)
+{
+ return &drrs_dsi_ops;
+}
diff --git a/drivers/gpu/drm/i915/intel_dsi_drrs.h b/drivers/gpu/drm/i915/intel_dsi_drrs.h
new file mode 100644
index 0000000..27736a0
--- /dev/null
+++ b/drivers/gpu/drm/i915/intel_dsi_drrs.h
@@ -0,0 +1,61 @@
+/*
+ * 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 <ramalingam.c@intel.com>
+ */
+
+#ifndef INTEL_DSI_DRRS_H
+#define INTEL_DSI_DRRS_H
+
+struct dsi_mnp {
+ u32 dsi_pll_ctrl;
+ u32 dsi_pll_div;
+};
+
+struct drrs_dsi_platform_ops {
+ int (*configure_dsi_pll)(struct intel_encoder *encoder,
+ struct dsi_mnp *dsi_mnp);
+ int (*mnp_calculate_for_mode)(struct intel_encoder *encoder,
+ struct dsi_mnp *dsi_mnp,
+ struct drm_display_mode *mode);
+};
+
+/**
+ * MIPI PLL register dont have a option to perform a seamless
+ * PLL divider change. To simulate that operation in SW we are using
+ * this deferred work
+ */
+struct intel_mipi_drrs_work {
+ struct delayed_work work;
+ struct i915_drrs *drrs;
+
+ /* Target Refresh rate type and the target mode */
+ enum drrs_refresh_rate_type target_rr_type;
+ struct drm_display_mode *target_mode;
+
+ /* Atomic variable to terminate any executing deferred work */
+ atomic_t abort_wait_loop;
+
+ /* To indicate the scheduled work completion */
+ bool work_completed;
+};
+
+struct dsi_drrs {
+ struct intel_mipi_drrs_work *mipi_drrs_work;
+ struct dsi_mnp mnp[DRRS_MAX_RR];
+ int min_vrefresh;
+ struct drrs_dsi_platform_ops *ops;
+};
+
+extern inline struct drrs_encoder_ops *get_intel_dsi_drrs_ops(void);
+#endif /* INTEL_DSI_DRRS_H */
diff --git a/drivers/gpu/drm/i915/intel_dsi_pll.c b/drivers/gpu/drm/i915/intel_dsi_pll.c
index d20cf37..c58eb02 100644
--- a/drivers/gpu/drm/i915/intel_dsi_pll.c
+++ b/drivers/gpu/drm/i915/intel_dsi_pll.c
@@ -38,11 +38,6 @@
#define DSI_HFP_PACKET_EXTRA_SIZE 6
#define DSI_EOTP_PACKET_SIZE 4

-struct dsi_mnp {
- u32 dsi_pll_ctrl;
- u32 dsi_pll_div;
-};
-
static const u32 lfsr_converts[] = {
426, 469, 234, 373, 442, 221, 110, 311, 411, /* 62 - 70 */
461, 486, 243, 377, 188, 350, 175, 343, 427, 213, /* 71 - 80 */