diff options
author | Damien Lespiau | 2015-05-21 17:37:48 +0200 |
---|---|---|
committer | Daniel Vetter | 2015-05-21 22:50:15 +0200 |
commit | 5d96d8afcfbb1500272756747e0bfcdbebf3b070 (patch) | |
tree | 4d1141ab6de255d787d175dcb27ddc9e97a118ea /drivers/gpu/drm/i915/intel_display.c | |
parent | drm/i915: Free RPS boosts for all laggards (diff) | |
download | kernel-qcow2-linux-5d96d8afcfbb1500272756747e0bfcdbebf3b070.tar.gz kernel-qcow2-linux-5d96d8afcfbb1500272756747e0bfcdbebf3b070.tar.xz kernel-qcow2-linux-5d96d8afcfbb1500272756747e0bfcdbebf3b070.zip |
drm/i915/skl: Deinit/init the display at suspend/resume
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>
Diffstat (limited to 'drivers/gpu/drm/i915/intel_display.c')
-rw-r--r-- | drivers/gpu/drm/i915/intel_display.c | 208 |
1 files changed, 208 insertions, 0 deletions
diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index 048565572764..268043f65dd8 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -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) { |