drm/i915/skl: Deinit/init the display at suspend/resume
authorDamien Lespiau <damien.lespiau@intel.com>
Thu, 21 May 2015 15:37:48 +0000 (16:37 +0100)
committerDaniel Vetter <daniel.vetter@ffwll.ch>
Thu, 21 May 2015 20:50:15 +0000 (22:50 +0200)
We need to re-init the display hardware when going out of suspend. This
includes:

  - Hooking the PCH to the reset logic
  - Restoring CDCDLK
  - Enabling the DDB power

Among those, only the CDCDLK one is a bit tricky. There's some
complexity in that:

  - DPLL0 (which is the source for CDCLK) has two VCOs, each with a set
    of supported frequencies. As eDP also uses DPLL0 for its link rate,
    once DPLL0 is on, we restrict the possible eDP link rates the chosen
    VCO.
  - CDCLK also limits the bandwidth available to push pixels.

So, as a first step, this commit restore what the BIOS set, until I can
do more testing.

In case that's of interest for the reviewer, I've unit tested the
function that derives the decimal frequency field:

  #include <stdio.h>
  #include <stdint.h>
  #include <assert.h>

  #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))

  static const struct dpll_freq {
          unsigned int freq;
          unsigned int decimal;
  } freqs[] = {
          { .freq = 308570, .decimal = 0b01001100111},
          { .freq = 337500, .decimal = 0b01010100001},
          { .freq = 432000, .decimal = 0b01101011110},
          { .freq = 450000, .decimal = 0b01110000010},
          { .freq = 540000, .decimal = 0b10000110110},
          { .freq = 617140, .decimal = 0b10011010000},
          { .freq = 675000, .decimal = 0b10101000100},
  };

  static void intbits(unsigned int v)
  {
          int i;

          for(i = 10; i >= 0; i--)
                  putchar('0' + ((v >> i) & 1));
  }

  static unsigned int freq_decimal(unsigned int freq /* in kHz */)
  {
          return (freq - 1000) / 500;
  }

  static void test_freq(const struct dpll_freq *entry)
  {
          unsigned int decimal = freq_decimal(entry->freq);

          printf("freq: %d, expected: ", entry->freq);
          intbits(entry->decimal);
          printf(", got: ");
          intbits(decimal);
          putchar('\n');

          assert(decimal == entry->decimal);
  }

  int main(int argc, char **argv)
  {
          int i;

          for (i = 0; i < ARRAY_SIZE(freqs); i++)
                  test_freq(&freqs[i]);

          return 0;
  }

v2:
  - Rebase on top of -nightly
  - Use (freq - 1000) / 500 for the decimal frequency (Ville)
  - Fix setting the enable bit of HSW_NDE_RSTWRN_OPT (Ville)
  - Rename skl_display_{resume,suspend} to skl_{init,uninit}_cdclk to
    be consistent with the BXT code (Ville)
  - Store boot CDCLK in ddi_pll_init (Ville)
  - Merge dev_priv's skl_boot_cdclk into cdclk_freq
  - Use LCPLL_PLL_LOCK instead of (1 << 30) (Ville)
  - Replace various '0' by SKL_DPLL0 to be a bit more explicit that
    we're programming DPLL0
  - Busy poll the PCU before doing the frequency change. It takes about
    3/4 cycles, each separated by 10us, to get the ACK from the CPU
    (Ville)

v3:
  - Restore dev_priv->skl_boot_cdclk, leaving unification with
    dev_priv->cdclk_freq for a later patch (Daniel, Ville)

Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Signed-off-by: Damien Lespiau <damien.lespiau@intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
drivers/gpu/drm/i915/i915_drv.c
drivers/gpu/drm/i915/i915_drv.h
drivers/gpu/drm/i915/i915_reg.h
drivers/gpu/drm/i915/intel_ddi.c
drivers/gpu/drm/i915/intel_display.c
drivers/gpu/drm/i915/intel_drv.h

index 93191c1fd18e4958973d42f8fd5a6df86f76e109..884b4f9b81c4abb163ec181deebb44c3cfc6abce 100644 (file)
@@ -1047,6 +1047,8 @@ static int skl_suspend_complete(struct drm_i915_private *dev_priv)
         */
        intel_csr_load_status_set(dev_priv, FW_UNINITIALIZED);
 
+       skl_uninit_cdclk(dev_priv);
+
        return 0;
 }
 
@@ -1093,6 +1095,7 @@ static int skl_resume_prepare(struct drm_i915_private *dev_priv)
 {
        struct drm_device *dev = dev_priv->dev;
 
+       skl_init_cdclk(dev_priv);
        intel_csr_load_program(dev);
 
        return 0;
index b4eebd04565c9782291486f625d93f205d4d9175..44d592f3b67c54ac408ea45b717712e3f9739e48 100644 (file)
@@ -1705,6 +1705,7 @@ struct drm_i915_private {
        int num_fence_regs; /* 8 on pre-965, 16 otherwise */
 
        unsigned int fsb_freq, mem_freq, is_ddr3;
+       unsigned int skl_boot_cdclk;
        unsigned int cdclk_freq;
        unsigned int hpll_freq;
 
index 3f94f38eec1f1ab0e2b31720b96a29b514f6e2f8..c799b993c15d4ddc1d610f874dc103749c7c2f99 100644 (file)
@@ -6699,6 +6699,9 @@ enum skl_disp_power_wells {
 #define     GEN9_MEM_LATENCY_LEVEL_1_5_SHIFT   8
 #define     GEN9_MEM_LATENCY_LEVEL_2_6_SHIFT   16
 #define     GEN9_MEM_LATENCY_LEVEL_3_7_SHIFT   24
+#define   SKL_PCODE_CDCLK_CONTROL              0x7
+#define     SKL_CDCLK_PREPARE_FOR_CHANGE       0x3
+#define     SKL_CDCLK_READY_FOR_CHANGE         0x1
 #define   GEN6_PCODE_WRITE_MIN_FREQ_TABLE      0x8
 #define   GEN6_PCODE_READ_MIN_FREQ_TABLE       0x9
 #define   GEN6_READ_OC_PARAMS                  0xc
index d602db25eb33438b00e524328ecceb3486e6f657..cacb07b7a8f108a84a209223c4d7604d1c462a13 100644 (file)
@@ -2510,6 +2510,7 @@ void intel_ddi_pll_init(struct drm_device *dev)
 {
        struct drm_i915_private *dev_priv = dev->dev_private;
        uint32_t val = I915_READ(LCPLL_CTL);
+       int cdclk_freq;
 
        if (IS_SKYLAKE(dev))
                skl_shared_dplls_init(dev_priv);
@@ -2518,12 +2519,15 @@ void intel_ddi_pll_init(struct drm_device *dev)
        else
                hsw_shared_dplls_init(dev_priv);
 
-       DRM_DEBUG_KMS("CDCLK running at %dKHz\n",
-                     dev_priv->display.get_display_clock_speed(dev));
+       cdclk_freq = dev_priv->display.get_display_clock_speed(dev);
+       DRM_DEBUG_KMS("CDCLK running at %dKHz\n", cdclk_freq);
 
        if (IS_SKYLAKE(dev)) {
+               dev_priv->skl_boot_cdclk = cdclk_freq;
                if (!(I915_READ(LCPLL1_CTL) & LCPLL_PLL_ENABLE))
                        DRM_ERROR("LCPLL1 is disabled\n");
+               else
+                       intel_display_power_get(dev_priv, POWER_DOMAIN_PLLS);
        } else if (IS_BROXTON(dev)) {
                broxton_init_cdclk(dev);
                broxton_ddi_phy_init(dev);
index 048565572764472a7fbf2c99fc5805ed759f8a3b..268043f65dd8af19252096ea4c97f63b162547f6 100644 (file)
@@ -5527,6 +5527,214 @@ void broxton_uninit_cdclk(struct drm_device *dev)
        intel_display_power_put(dev_priv, POWER_DOMAIN_PLLS);
 }
 
+static const struct skl_cdclk_entry {
+       unsigned int freq;
+       unsigned int vco;
+} skl_cdclk_frequencies[] = {
+       { .freq = 308570, .vco = 8640 },
+       { .freq = 337500, .vco = 8100 },
+       { .freq = 432000, .vco = 8640 },
+       { .freq = 450000, .vco = 8100 },
+       { .freq = 540000, .vco = 8100 },
+       { .freq = 617140, .vco = 8640 },
+       { .freq = 675000, .vco = 8100 },
+};
+
+static unsigned int skl_cdclk_decimal(unsigned int freq)
+{
+       return (freq - 1000) / 500;
+}
+
+static unsigned int skl_cdclk_get_vco(unsigned int freq)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(skl_cdclk_frequencies); i++) {
+               const struct skl_cdclk_entry *e = &skl_cdclk_frequencies[i];
+
+               if (e->freq == freq)
+                       return e->vco;
+       }
+
+       return 8100;
+}
+
+static void
+skl_dpll0_enable(struct drm_i915_private *dev_priv, unsigned int required_vco)
+{
+       unsigned int min_freq;
+       u32 val;
+
+       /* select the minimum CDCLK before enabling DPLL 0 */
+       val = I915_READ(CDCLK_CTL);
+       val &= ~CDCLK_FREQ_SEL_MASK | ~CDCLK_FREQ_DECIMAL_MASK;
+       val |= CDCLK_FREQ_337_308;
+
+       if (required_vco == 8640)
+               min_freq = 308570;
+       else
+               min_freq = 337500;
+
+       val = CDCLK_FREQ_337_308 | skl_cdclk_decimal(min_freq);
+
+       I915_WRITE(CDCLK_CTL, val);
+       POSTING_READ(CDCLK_CTL);
+
+       /*
+        * We always enable DPLL0 with the lowest link rate possible, but still
+        * taking into account the VCO required to operate the eDP panel at the
+        * desired frequency. The usual DP link rates operate with a VCO of
+        * 8100 while the eDP 1.4 alternate link rates need a VCO of 8640.
+        * The modeset code is responsible for the selection of the exact link
+        * rate later on, with the constraint of choosing a frequency that
+        * works with required_vco.
+        */
+       val = I915_READ(DPLL_CTRL1);
+
+       val &= ~(DPLL_CTRL1_HDMI_MODE(SKL_DPLL0) | DPLL_CTRL1_SSC(SKL_DPLL0) |
+                DPLL_CTRL1_LINK_RATE_MASK(SKL_DPLL0));
+       val |= DPLL_CTRL1_OVERRIDE(SKL_DPLL0);
+       if (required_vco == 8640)
+               val |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1080,
+                                           SKL_DPLL0);
+       else
+               val |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_810,
+                                           SKL_DPLL0);
+
+       I915_WRITE(DPLL_CTRL1, val);
+       POSTING_READ(DPLL_CTRL1);
+
+       I915_WRITE(LCPLL1_CTL, I915_READ(LCPLL1_CTL) | LCPLL_PLL_ENABLE);
+
+       if (wait_for(I915_READ(LCPLL1_CTL) & LCPLL_PLL_LOCK, 5))
+               DRM_ERROR("DPLL0 not locked\n");
+}
+
+static bool skl_cdclk_pcu_ready(struct drm_i915_private *dev_priv)
+{
+       int ret;
+       u32 val;
+
+       /* inform PCU we want to change CDCLK */
+       val = SKL_CDCLK_PREPARE_FOR_CHANGE;
+       mutex_lock(&dev_priv->rps.hw_lock);
+       ret = sandybridge_pcode_read(dev_priv, SKL_PCODE_CDCLK_CONTROL, &val);
+       mutex_unlock(&dev_priv->rps.hw_lock);
+
+       return ret == 0 && (val & SKL_CDCLK_READY_FOR_CHANGE);
+}
+
+static bool skl_cdclk_wait_for_pcu_ready(struct drm_i915_private *dev_priv)
+{
+       unsigned int i;
+
+       for (i = 0; i < 15; i++) {
+               if (skl_cdclk_pcu_ready(dev_priv))
+                       return true;
+               udelay(10);
+       }
+
+       return false;
+}
+
+static void skl_set_cdclk(struct drm_i915_private *dev_priv, unsigned int freq)
+{
+       u32 freq_select, pcu_ack;
+
+       DRM_DEBUG_DRIVER("Changing CDCLK to %dKHz\n", freq);
+
+       if (!skl_cdclk_wait_for_pcu_ready(dev_priv)) {
+               DRM_ERROR("failed to inform PCU about cdclk change\n");
+               return;
+       }
+
+       /* set CDCLK_CTL */
+       switch(freq) {
+       case 450000:
+       case 432000:
+               freq_select = CDCLK_FREQ_450_432;
+               pcu_ack = 1;
+               break;
+       case 540000:
+               freq_select = CDCLK_FREQ_540;
+               pcu_ack = 2;
+               break;
+       case 308570:
+       case 337500:
+       default:
+               freq_select = CDCLK_FREQ_337_308;
+               pcu_ack = 0;
+               break;
+       case 617140:
+       case 675000:
+               freq_select = CDCLK_FREQ_675_617;
+               pcu_ack = 3;
+               break;
+       }
+
+       I915_WRITE(CDCLK_CTL, freq_select | skl_cdclk_decimal(freq));
+       POSTING_READ(CDCLK_CTL);
+
+       /* inform PCU of the change */
+       mutex_lock(&dev_priv->rps.hw_lock);
+       sandybridge_pcode_write(dev_priv, SKL_PCODE_CDCLK_CONTROL, pcu_ack);
+       mutex_unlock(&dev_priv->rps.hw_lock);
+}
+
+void skl_uninit_cdclk(struct drm_i915_private *dev_priv)
+{
+       /* disable DBUF power */
+       I915_WRITE(DBUF_CTL, I915_READ(DBUF_CTL) & ~DBUF_POWER_REQUEST);
+       POSTING_READ(DBUF_CTL);
+
+       udelay(10);
+
+       if (I915_READ(DBUF_CTL) & DBUF_POWER_STATE)
+               DRM_ERROR("DBuf power disable timeout\n");
+
+       /* disable DPLL0 */
+       I915_WRITE(LCPLL1_CTL, I915_READ(LCPLL1_CTL) & ~LCPLL_PLL_ENABLE);
+       if (wait_for(!(I915_READ(LCPLL1_CTL) & LCPLL_PLL_LOCK), 1))
+               DRM_ERROR("Couldn't disable DPLL0\n");
+
+       intel_display_power_put(dev_priv, POWER_DOMAIN_PLLS);
+}
+
+void skl_init_cdclk(struct drm_i915_private *dev_priv)
+{
+       u32 val;
+       unsigned int required_vco;
+
+       /* enable PCH reset handshake */
+       val = I915_READ(HSW_NDE_RSTWRN_OPT);
+       I915_WRITE(HSW_NDE_RSTWRN_OPT, val | RESET_PCH_HANDSHAKE_ENABLE);
+
+       /* enable PG1 and Misc I/O */
+       intel_display_power_get(dev_priv, POWER_DOMAIN_PLLS);
+
+       /* DPLL0 already enabed !? */
+       if (I915_READ(LCPLL1_CTL) & LCPLL_PLL_ENABLE) {
+               DRM_DEBUG_DRIVER("DPLL0 already running\n");
+               return;
+       }
+
+       /* enable DPLL0 */
+       required_vco = skl_cdclk_get_vco(dev_priv->skl_boot_cdclk);
+       skl_dpll0_enable(dev_priv, required_vco);
+
+       /* set CDCLK to the frequency the BIOS chose */
+       skl_set_cdclk(dev_priv, dev_priv->skl_boot_cdclk);
+
+       /* enable DBUF power */
+       I915_WRITE(DBUF_CTL, I915_READ(DBUF_CTL) | DBUF_POWER_REQUEST);
+       POSTING_READ(DBUF_CTL);
+
+       udelay(10);
+
+       if (!(I915_READ(DBUF_CTL) & DBUF_POWER_STATE))
+               DRM_ERROR("DBuf power enable timeout\n");
+}
+
 /* returns HPLL frequency in kHz */
 static int valleyview_get_vco(struct drm_i915_private *dev_priv)
 {
index e94cb6e1645606e2ae868fa14aded4aa23a6932d..02d83178bd0318710ece29a140c12acb1fa58942 100644 (file)
@@ -1118,6 +1118,8 @@ void broxton_ddi_phy_init(struct drm_device *dev);
 void broxton_ddi_phy_uninit(struct drm_device *dev);
 void bxt_enable_dc9(struct drm_i915_private *dev_priv);
 void bxt_disable_dc9(struct drm_i915_private *dev_priv);
+void skl_init_cdclk(struct drm_i915_private *dev_priv);
+void skl_uninit_cdclk(struct drm_i915_private *dev_priv);
 void intel_dp_get_m_n(struct intel_crtc *crtc,
                      struct intel_crtc_state *pipe_config);
 void intel_dp_set_m_n(struct intel_crtc *crtc, enum link_m_n_set m_n);