[RFC PATCH 06/18] drm/i915: VLV dsi drrs support

Ramalingam C
2015-06-26T14:00:05Z

VLV related dsi drrs functions are implemented and registered
with generic dsi drrs.

This will provide the service to generic dsi drrs stack to calculate
the pll divider values and program the pll registers for DRRS
functionality.

Signed-off-by: Ramalingam C <ramalingam.c@intel.com>
---
drivers/gpu/drm/i915/i915_reg.h | 4 +
drivers/gpu/drm/i915/intel_dsi_drrs.c | 27 ++++
drivers/gpu/drm/i915/intel_dsi_drrs.h | 5 +
drivers/gpu/drm/i915/intel_dsi_pll.c | 225 ++++++++++++++++++++++++++++++---
4 files changed, 243 insertions(+), 18 deletions(-)

diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h
index 0b979ad..9f1332b 100644
--- a/drivers/gpu/drm/i915/i915_reg.h
+++ b/drivers/gpu/drm/i915/i915_reg.h
@@ -2964,6 +2964,10 @@ enum skl_disp_power_wells {
#define VLV_EDP_PSR_IN_TRANS (1<<7)
#define VLV_PSRSTAT(pipe) _PIPE(pipe, _PSRSTATA, _PSRSTATB)

+#define VERTICAL_ACTIVE_DISPLAY_MASK (0xfff)
+#define VERTICAL_TOTAL_DISPLAY_OFFSET 16
+#define VERTICAL_TOTAL_DISPLAY_MASK (0xfff<<16)
+
/* HSW+ eDP PSR registers */
#define EDP_PSR_BASE(dev) (IS_HASWELL(dev) ? 0x64800 : 0x6f800)
#define EDP_PSR_CTL(dev) (EDP_PSR_BASE(dev) + 0)
diff --git a/drivers/gpu/drm/i915/intel_dsi_drrs.c b/drivers/gpu/drm/i915/intel_dsi_drrs.c
index 6ab0e9e..eb0758a 100644
--- a/drivers/gpu/drm/i915/intel_dsi_drrs.c
+++ b/drivers/gpu/drm/i915/intel_dsi_drrs.c
@@ -20,6 +20,23 @@
#include "intel_drv.h"
#include "intel_dsi.h"

+/*
+ * VLV and CHV platform code
+ */
+struct drrs_dsi_platform_ops vlv_dsi_drrs_ops = {
+ .configure_dsi_pll = vlv_drrs_configure_dsi_pll,
+ .mnp_calculate_for_mode = vlv_dsi_mnp_calculate_for_mode,
+};
+
+inline struct drrs_dsi_platform_ops *get_vlv_dsi_drrs_ops(void)
+{
+ return &vlv_dsi_drrs_ops;
+}
+
+/*
+ * Generic DSI DRRS code
+ */
+
/* Work function for DSI deferred work */
static void intel_mipi_drrs_work_fn(struct work_struct *__work)
{
@@ -243,6 +260,16 @@ int intel_dsi_drrs_init(struct i915_drrs *drrs,

panel->target_mode = NULL;

+ if (IS_VALLEYVIEW(intel_encoder->base.dev)) {
+
+ /* VLV and CHV */
+ dsi_drrs->ops = get_vlv_dsi_drrs_ops();
+ } else {
+ DRM_ERROR("DRRS: Unsupported platform\n");
+ ret = -EINVAL;
+ goto out_err_2;
+ }
+
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");
diff --git a/drivers/gpu/drm/i915/intel_dsi_drrs.h b/drivers/gpu/drm/i915/intel_dsi_drrs.h
index 27736a0..02f76f0 100644
--- a/drivers/gpu/drm/i915/intel_dsi_drrs.h
+++ b/drivers/gpu/drm/i915/intel_dsi_drrs.h
@@ -58,4 +58,9 @@ struct dsi_drrs {
};

extern inline struct drrs_encoder_ops *get_intel_dsi_drrs_ops(void);
+int vlv_dsi_mnp_calculate_for_mode(struct intel_encoder *encoder,
+ struct dsi_mnp *dsi_mnp,
+ struct drm_display_mode *mode);
+int vlv_drrs_configure_dsi_pll(struct intel_encoder *encoder,
+ struct dsi_mnp *dsi_mnp);
#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 ce5949f..46346b3 100644
--- a/drivers/gpu/drm/i915/intel_dsi_pll.c
+++ b/drivers/gpu/drm/i915/intel_dsi_pll.c
@@ -29,6 +29,7 @@
#include "intel_drv.h"
#include "i915_drv.h"
#include "intel_dsi.h"
+#include "intel_drrs.h"

#define DSI_HSS_PACKET_SIZE 4
#define DSI_HSE_PACKET_SIZE 4
@@ -38,6 +39,8 @@
#define DSI_HFP_PACKET_EXTRA_SIZE 6
#define DSI_EOTP_PACKET_SIZE 4

+#define DSI_DRRS_PLL_CONFIG_TIMEOUT_MS 100
+
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 */
@@ -256,39 +259,225 @@ static int dsi_calc_mnp(int target_dsi_clk, struct dsi_mnp *dsi_mnp)
}

/*
+ * vlv_dsi_pll_reg_configure:
+ * Function to configure the CCK registers for PLL control and dividers
+ *
+ * pll : Pll that is getting configure
+ * dsi_mnp : Struct with divider values
+ * pll_enable : Flag to indicate whether it is a fresh pll enable call or
+ * call on DRRS purpose
+ */
+static void vlv_dsi_pll_reg_configure(struct intel_encoder *encoder,
+ struct dsi_mnp *dsi_mnp, bool pll_enable)
+{
+ struct drm_i915_private *dev_priv = encoder->base.dev->dev_private;
+ struct intel_crtc *intel_crtc = to_intel_crtc(encoder->base.crtc);
+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base);
+
+ if (!intel_crtc)
+ return;
+
+ if (pll_enable) {
+ vlv_cck_write(dev_priv, CCK_REG_DSI_PLL_CONTROL, 0);
+
+ if (intel_dsi->ports & (1 << PORT_A))
+ dsi_mnp->dsi_pll_ctrl |= DSI_PLL_CLK_GATE_DSI0_DSIPLL;
+
+ if (intel_dsi->ports & (1 << PORT_C))
+ dsi_mnp->dsi_pll_ctrl |= DSI_PLL_CLK_GATE_DSI1_DSIPLL;
+
+ } else {
+
+ /*
+ * Updating the M1, N1, P1 div values alone on the
+ * CCK registers. these new values are abstracted from
+ * the dsi_mnp struction
+ */
+ dsi_mnp->dsi_pll_ctrl =
+ (dsi_mnp->dsi_pll_ctrl & DSI_PLL_P1_POST_DIV_MASK) |
+ (vlv_cck_read(dev_priv, CCK_REG_DSI_PLL_CONTROL) &
+ ~DSI_PLL_P1_POST_DIV_MASK);
+ dsi_mnp->dsi_pll_div = (dsi_mnp->dsi_pll_div &
+ (DSI_PLL_M1_DIV_MASK | DSI_PLL_N1_DIV_MASK)) |
+ (vlv_cck_read(dev_priv, CCK_REG_DSI_PLL_DIVIDER)
+ & ~(DSI_PLL_M1_DIV_MASK | DSI_PLL_N1_DIV_MASK));
+ }
+
+ DRM_DEBUG("dsi_pll: div %08x, ctrl %08x\n",
+ dsi_mnp->dsi_pll_div, dsi_mnp->dsi_pll_ctrl);
+
+ vlv_cck_write(dev_priv, CCK_REG_DSI_PLL_DIVIDER, dsi_mnp->dsi_pll_div);
+ vlv_cck_write(dev_priv, CCK_REG_DSI_PLL_CONTROL, dsi_mnp->dsi_pll_ctrl);
+}
+
+/*
+ * vlv_drrs_configure_dsi_pll:
+ * Function to configure the PLL dividers and bring the new values
+ * into effect by power cycling the VCO. This power cycle is supposed
+ * to be completed within the vblank period. This is software implementation
+ * and depends on the CCK register access. Needs to be tested thoroughly.
+ *
+ * encoder : target encoder
+ * dsi_mnp : struct with pll divider values
+ */
+int vlv_drrs_configure_dsi_pll(struct intel_encoder *encoder,
+ struct dsi_mnp *dsi_mnp)
+{
+ struct drm_i915_private *dev_priv = encoder->base.dev->dev_private;
+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base);
+ struct intel_crtc *intel_crtc = to_intel_crtc(encoder->base.crtc);
+ struct dsi_drrs *dsi_drrs = &intel_dsi->dsi_drrs;
+ struct intel_mipi_drrs_work *work = dsi_drrs->mipi_drrs_work;
+ enum pipe pipe;
+ u32 dsl_offset, dsl1, dsl2;
+ u32 vactive, vtotal, vblank, vblank_30_percent, vblank_70_percent;
+ unsigned long timeout;
+
+ if (!intel_crtc)
+ return -EPERM;
+
+ pipe = intel_crtc->pipe;
+ dsl_offset = PIPEDSL(pipe);
+
+ mutex_lock(&dev_priv->sb_lock);
+ vlv_dsi_pll_reg_configure(encoder, dsi_mnp, false);
+ mutex_unlock(&dev_priv->sb_lock);
+
+ DRM_DEBUG("dsi_mnp:: ctrl: 0x%X, div: 0x%X\n", dsi_mnp->dsi_pll_ctrl,
+ dsi_mnp->dsi_pll_div);
+
+ dsi_mnp->dsi_pll_ctrl &= (~DSI_PLL_VCO_EN);
+
+ vtotal = I915_READ(VTOTAL(pipe));
+ vactive = (vtotal & VERTICAL_ACTIVE_DISPLAY_MASK);
+ vtotal = (vtotal & VERTICAL_TOTAL_DISPLAY_MASK) >>
+ VERTICAL_TOTAL_DISPLAY_OFFSET;
+ vblank = vtotal - vactive;
+ vblank_30_percent = vactive + DIV_ROUND_UP((vblank * 3), 10);
+ vblank_70_percent = vactive + DIV_ROUND_UP((vblank * 7), 10);
+
+ timeout = jiffies + msecs_to_jiffies(DSI_DRRS_PLL_CONFIG_TIMEOUT_MS);
+
+tap_vblank_start:
+ do {
+ if (atomic_read(&work->abort_wait_loop) == 1) {
+ DRM_DEBUG_KMS("Aborting the pll update\n");
+ return -EPERM;
+ }
+
+ if (time_after(jiffies, timeout)) {
+ DRM_DEBUG("Timeout at waiting for Vblank\n");
+ return -ETIMEDOUT;
+ }
+
+ dsl1 = (I915_READ(dsl_offset) & DSL_LINEMASK_GEN3);
+
+ } while (dsl1 <= vactive || dsl1 > vblank_30_percent);
+
+ mutex_lock(&dev_priv->sb_lock);
+
+ dsl2 = I915_READ(dsl_offset) & DSL_LINEMASK_GEN3;
+
+ /*
+ * Did we cross Vblank due to delay in mutex acquirement?
+ * Keeping two scanlines in vblank as buffer for ops.
+ */
+ if (dsl2 < vactive || dsl2 > vblank_70_percent) {
+ mutex_unlock(&dev_priv->sb_lock);
+ goto tap_vblank_start;
+ }
+
+ /* Toggle the VCO_EN to bring in the new dividers values */
+ vlv_cck_write(dev_priv, CCK_REG_DSI_PLL_CONTROL, dsi_mnp->dsi_pll_ctrl);
+ dsi_mnp->dsi_pll_ctrl |= DSI_PLL_VCO_EN;
+ vlv_cck_write(dev_priv, CCK_REG_DSI_PLL_CONTROL, dsi_mnp->dsi_pll_ctrl);
+
+ dsl2 = I915_READ(dsl_offset) & DSL_LINEMASK_GEN3;
+
+ mutex_unlock(&dev_priv->sb_lock);
+
+ if (wait_for(I915_READ(PIPECONF(pipe)) &
+ PIPECONF_DSI_PLL_LOCKED, 20)) {
+ DRM_ERROR("DSI PLL lock failed\n");
+ return -1;
+ }
+
+ DRM_DEBUG("PLL Changed between DSL: %u, %u\n", dsl1, dsl2);
+ DRM_DEBUG("DSI PLL locked\n");
+ return 0;
+}
+
+/*
+ * vlv_dsi_mnp_calculate_for_mode:
+ * calculates the dsi_mnp values for a given mode
+ *
+ * encoder : Target encoder
+ * dsi_mnp : output struct to store divider values
+ * mode : Input mode for which mnp is calculated
+ */
+int vlv_dsi_mnp_calculate_for_mode(struct intel_encoder *encoder,
+ struct dsi_mnp *dsi_mnp,
+ struct drm_display_mode *mode)
+{
+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base);
+ u32 dsi_clk, ret;
+
+ dsi_clk = dsi_clk_from_pclk(intel_dsi, mode);
+
+ DRM_DEBUG("Mode->clk: %u, dsi_clk: %u\n", mode->clock, dsi_clk);
+
+ ret = dsi_calc_mnp(dsi_clk, dsi_mnp);
+ if (ret)
+ DRM_DEBUG("dsi_calc_mnp failed\n");
+ else
+ DRM_DEBUG("dsi_mnp: ctrl : 0x%X, div : 0x%X\n",
+ dsi_mnp->dsi_pll_ctrl,
+ dsi_mnp->dsi_pll_div);
+ return ret;
+}
+
+/*
* XXX: The muxing and gating is hard coded for now. Need to add support for
* sharing PLLs with two DSI outputs.
*/
static void vlv_configure_dsi_pll(struct intel_encoder *encoder)
{
struct drm_i915_private *dev_priv = encoder->base.dev->dev_private;
+ struct intel_crtc *intel_crtc = to_intel_crtc(encoder->base.crtc);
struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base);
+ struct dsi_drrs *dsi_drrs = &intel_dsi->dsi_drrs;
+ struct i915_drrs *drrs;
struct intel_connector *intel_connector = intel_dsi->attached_connector;
- int ret;
- struct dsi_mnp dsi_mnp;
- u32 dsi_clk;
-
- dsi_clk = dsi_clk_from_pclk(intel_dsi,
- intel_connector->panel.fixed_mode);
+ int ret, index;

- ret = dsi_calc_mnp(dsi_clk, &dsi_mnp);
- if (ret) {
- DRM_DEBUG_KMS("dsi_calc_mnp failed\n");
+ ret = vlv_dsi_mnp_calculate_for_mode(encoder,
+ &dsi_drrs->mnp[DRRS_HIGH_RR],
+ intel_connector->panel.fixed_mode);
+ if (ret < 0) {
+ DRM_ERROR("dsi_mnp calculations failed\n");
return;
}

- if (intel_dsi->ports & (1 << PORT_A))
- dsi_mnp.dsi_pll_ctrl |= DSI_PLL_CLK_GATE_DSI0_DSIPLL;
+ index = get_drrs_struct_index_for_crtc(dev_priv, intel_crtc);
+ if (index < 0)
+ return;

- if (intel_dsi->ports & (1 << PORT_C))
- dsi_mnp.dsi_pll_ctrl |= DSI_PLL_CLK_GATE_DSI1_DSIPLL;
+ drrs = dev_priv->drrs[index];
+ if (!drrs || !drrs->has_drrs)
+ return;

- DRM_DEBUG_KMS("dsi pll div %08x, ctrl %08x\n",
- dsi_mnp.dsi_pll_div, dsi_mnp.dsi_pll_ctrl);
+ if (drrs && drrs->has_drrs &&
+ intel_connector->panel.downclock_mode) {
+ ret = vlv_dsi_mnp_calculate_for_mode(encoder,
+ &dsi_drrs->mnp[DRRS_LOW_RR],
+ intel_connector->panel.downclock_mode);
+ if (ret < 0) {
+ DRM_ERROR("dsi_mnp calculations failed\n");
+ return;
+ }
+ }

- vlv_cck_write(dev_priv, CCK_REG_DSI_PLL_CONTROL, 0);
- vlv_cck_write(dev_priv, CCK_REG_DSI_PLL_DIVIDER, dsi_mnp.dsi_pll_div);
- vlv_cck_write(dev_priv, CCK_REG_DSI_PLL_CONTROL, dsi_mnp.dsi_pll_ctrl);
+ vlv_dsi_pll_reg_configure(encoder, &dsi_drrs->mnp[DRRS_HIGH_RR], true);
}

void vlv_enable_dsi_pll(struct intel_encoder *encoder)