mirror of
https://github.com/torvalds/linux.git
synced 2026-04-21 08:13:56 -04:00
This was done entirely with mindless brute force, using
git grep -l '\<k[vmz]*alloc_objs*(.*, GFP_KERNEL)' |
xargs sed -i 's/\(alloc_objs*(.*\), GFP_KERNEL)/\1)/'
to convert the new alloc_obj() users that had a simple GFP_KERNEL
argument to just drop that argument.
Note that due to the extreme simplicity of the scripting, any slightly
more complex cases spread over multiple lines would not be triggered:
they definitely exist, but this covers the vast bulk of the cases, and
the resulting diff is also then easier to check automatically.
For the same reason the 'flex' versions will be done as a separate
conversion.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
4209 lines
122 KiB
C
4209 lines
122 KiB
C
/*
|
|
* Copyright © 2006-2017 Intel Corporation
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the next
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
* Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <linux/debugfs.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/time.h>
|
|
|
|
#include <drm/drm_fixed.h>
|
|
#include <drm/drm_print.h>
|
|
|
|
#include "hsw_ips.h"
|
|
#include "i915_reg.h"
|
|
#include "intel_atomic.h"
|
|
#include "intel_audio.h"
|
|
#include "intel_cdclk.h"
|
|
#include "intel_crtc.h"
|
|
#include "intel_dbuf_bw.h"
|
|
#include "intel_de.h"
|
|
#include "intel_display_regs.h"
|
|
#include "intel_display_types.h"
|
|
#include "intel_display_utils.h"
|
|
#include "intel_display_wa.h"
|
|
#include "intel_dram.h"
|
|
#include "intel_mchbar_regs.h"
|
|
#include "intel_pci_config.h"
|
|
#include "intel_pcode.h"
|
|
#include "intel_plane.h"
|
|
#include "intel_psr.h"
|
|
#include "intel_step.h"
|
|
#include "intel_vdsc.h"
|
|
#include "skl_watermark.h"
|
|
#include "skl_watermark_regs.h"
|
|
#include "vlv_clock.h"
|
|
#include "vlv_dsi.h"
|
|
#include "vlv_sideband.h"
|
|
|
|
/**
|
|
* DOC: CDCLK / RAWCLK
|
|
*
|
|
* The display engine uses several different clocks to do its work. There
|
|
* are two main clocks involved that aren't directly related to the actual
|
|
* pixel clock or any symbol/bit clock of the actual output port. These
|
|
* are the core display clock (CDCLK) and RAWCLK.
|
|
*
|
|
* CDCLK clocks most of the display pipe logic, and thus its frequency
|
|
* must be high enough to support the rate at which pixels are flowing
|
|
* through the pipes. Downscaling must also be accounted as that increases
|
|
* the effective pixel rate.
|
|
*
|
|
* On several platforms the CDCLK frequency can be changed dynamically
|
|
* to minimize power consumption for a given display configuration.
|
|
* Typically changes to the CDCLK frequency require all the display pipes
|
|
* to be shut down while the frequency is being changed.
|
|
*
|
|
* On SKL+ the DMC will toggle the CDCLK off/on during DC5/6 entry/exit.
|
|
* DMC will not change the active CDCLK frequency however, so that part
|
|
* will still be performed by the driver directly.
|
|
*
|
|
* There are multiple components involved in the generation of the CDCLK
|
|
* frequency:
|
|
*
|
|
* - We have the CDCLK PLL, which generates an output clock based on a
|
|
* reference clock and a ratio parameter.
|
|
* - The CD2X Divider, which divides the output of the PLL based on a
|
|
* divisor selected from a set of pre-defined choices.
|
|
* - The CD2X Squasher, which further divides the output based on a
|
|
* waveform represented as a sequence of bits where each zero
|
|
* "squashes out" a clock cycle.
|
|
* - And, finally, a fixed divider that divides the output frequency by 2.
|
|
*
|
|
* As such, the resulting CDCLK frequency can be calculated with the
|
|
* following formula:
|
|
*
|
|
* cdclk = vco / cd2x_div / (sq_len / sq_div) / 2
|
|
*
|
|
* , where vco is the frequency generated by the PLL; cd2x_div
|
|
* represents the CD2X Divider; sq_len and sq_div are the bit length
|
|
* and the number of high bits for the CD2X Squasher waveform, respectively;
|
|
* and 2 represents the fixed divider.
|
|
*
|
|
* Note that some older platforms do not contain the CD2X Divider
|
|
* and/or CD2X Squasher, in which case we can ignore their respective
|
|
* factors in the formula above.
|
|
*
|
|
* Several methods exist to change the CDCLK frequency, which ones are
|
|
* supported depends on the platform:
|
|
*
|
|
* - Full PLL disable + re-enable with new VCO frequency. Pipes must be inactive.
|
|
* - CD2X divider update. Single pipe can be active as the divider update
|
|
* can be synchronized with the pipe's start of vblank.
|
|
* - Crawl the PLL smoothly to the new VCO frequency. Pipes can be active.
|
|
* - Squash waveform update. Pipes can be active.
|
|
* - Crawl and squash can also be done back to back. Pipes can be active.
|
|
*
|
|
* RAWCLK is a fixed frequency clock, often used by various auxiliary
|
|
* blocks such as AUX CH or backlight PWM. Hence the only thing we
|
|
* really need to know about RAWCLK is its frequency so that various
|
|
* dividers can be programmed correctly.
|
|
*/
|
|
|
|
struct intel_cdclk_state {
|
|
struct intel_global_state base;
|
|
|
|
/*
|
|
* Logical configuration of cdclk (used for all scaling,
|
|
* watermark, etc. calculations and checks). This is
|
|
* computed as if all enabled crtcs were active.
|
|
*/
|
|
struct intel_cdclk_config logical;
|
|
|
|
/*
|
|
* Actual configuration of cdclk, can be different from the
|
|
* logical configuration only when all crtc's are DPMS off.
|
|
*/
|
|
struct intel_cdclk_config actual;
|
|
|
|
/* minimum acceptable cdclk to satisfy DBUF bandwidth requirements */
|
|
int dbuf_bw_min_cdclk;
|
|
/* minimum acceptable cdclk for each pipe */
|
|
int min_cdclk[I915_MAX_PIPES];
|
|
/* minimum acceptable voltage level for each pipe */
|
|
u8 min_voltage_level[I915_MAX_PIPES];
|
|
|
|
/* pipe to which cd2x update is synchronized */
|
|
enum pipe pipe;
|
|
|
|
/* forced minimum cdclk for glk+ audio w/a */
|
|
int force_min_cdclk;
|
|
|
|
/* bitmask of enabled pipes */
|
|
u8 enabled_pipes;
|
|
|
|
/* bitmask of active pipes */
|
|
u8 active_pipes;
|
|
|
|
/* update cdclk with pipes disabled */
|
|
bool disable_pipes;
|
|
};
|
|
|
|
struct intel_cdclk_funcs {
|
|
void (*get_cdclk)(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config);
|
|
void (*set_cdclk)(struct intel_display *display,
|
|
const struct intel_cdclk_config *cdclk_config,
|
|
enum pipe pipe);
|
|
int (*modeset_calc_cdclk)(struct intel_atomic_state *state);
|
|
u8 (*calc_voltage_level)(int cdclk);
|
|
};
|
|
|
|
void intel_cdclk_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
display->funcs.cdclk->get_cdclk(display, cdclk_config);
|
|
}
|
|
|
|
static void intel_cdclk_set_cdclk(struct intel_display *display,
|
|
const struct intel_cdclk_config *cdclk_config,
|
|
enum pipe pipe)
|
|
{
|
|
display->funcs.cdclk->set_cdclk(display, cdclk_config, pipe);
|
|
}
|
|
|
|
static int intel_cdclk_modeset_calc_cdclk(struct intel_atomic_state *state)
|
|
{
|
|
struct intel_display *display = to_intel_display(state);
|
|
|
|
return display->funcs.cdclk->modeset_calc_cdclk(state);
|
|
}
|
|
|
|
static u8 intel_cdclk_calc_voltage_level(struct intel_display *display,
|
|
int cdclk)
|
|
{
|
|
return display->funcs.cdclk->calc_voltage_level(cdclk);
|
|
}
|
|
|
|
static void fixed_133mhz_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
cdclk_config->cdclk = 133333;
|
|
}
|
|
|
|
static void fixed_200mhz_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
cdclk_config->cdclk = 200000;
|
|
}
|
|
|
|
static void fixed_266mhz_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
cdclk_config->cdclk = 266667;
|
|
}
|
|
|
|
static void fixed_333mhz_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
cdclk_config->cdclk = 333333;
|
|
}
|
|
|
|
static void fixed_400mhz_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
cdclk_config->cdclk = 400000;
|
|
}
|
|
|
|
static void fixed_450mhz_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
cdclk_config->cdclk = 450000;
|
|
}
|
|
|
|
static void i85x_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(display->drm->dev);
|
|
u16 hpllcc = 0;
|
|
|
|
/*
|
|
* 852GM/852GMV only supports 133 MHz and the HPLLCC
|
|
* encoding is different :(
|
|
* FIXME is this the right way to detect 852GM/852GMV?
|
|
*/
|
|
if (pdev->revision == 0x1) {
|
|
cdclk_config->cdclk = 133333;
|
|
return;
|
|
}
|
|
|
|
pci_bus_read_config_word(pdev->bus,
|
|
PCI_DEVFN(0, 3), HPLLCC, &hpllcc);
|
|
|
|
/* Assume that the hardware is in the high speed state. This
|
|
* should be the default.
|
|
*/
|
|
switch (hpllcc & GC_CLOCK_CONTROL_MASK) {
|
|
case GC_CLOCK_133_200:
|
|
case GC_CLOCK_133_200_2:
|
|
case GC_CLOCK_100_200:
|
|
cdclk_config->cdclk = 200000;
|
|
break;
|
|
case GC_CLOCK_166_250:
|
|
cdclk_config->cdclk = 250000;
|
|
break;
|
|
case GC_CLOCK_100_133:
|
|
cdclk_config->cdclk = 133333;
|
|
break;
|
|
case GC_CLOCK_133_266:
|
|
case GC_CLOCK_133_266_2:
|
|
case GC_CLOCK_166_266:
|
|
cdclk_config->cdclk = 266667;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void i915gm_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(display->drm->dev);
|
|
u16 gcfgc = 0;
|
|
|
|
pci_read_config_word(pdev, GCFGC, &gcfgc);
|
|
|
|
if (gcfgc & GC_LOW_FREQUENCY_ENABLE) {
|
|
cdclk_config->cdclk = 133333;
|
|
return;
|
|
}
|
|
|
|
switch (gcfgc & GC_DISPLAY_CLOCK_MASK) {
|
|
case GC_DISPLAY_CLOCK_333_320_MHZ:
|
|
cdclk_config->cdclk = 333333;
|
|
break;
|
|
default:
|
|
case GC_DISPLAY_CLOCK_190_200_MHZ:
|
|
cdclk_config->cdclk = 190000;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void i945gm_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(display->drm->dev);
|
|
u16 gcfgc = 0;
|
|
|
|
pci_read_config_word(pdev, GCFGC, &gcfgc);
|
|
|
|
if (gcfgc & GC_LOW_FREQUENCY_ENABLE) {
|
|
cdclk_config->cdclk = 133333;
|
|
return;
|
|
}
|
|
|
|
switch (gcfgc & GC_DISPLAY_CLOCK_MASK) {
|
|
case GC_DISPLAY_CLOCK_333_320_MHZ:
|
|
cdclk_config->cdclk = 320000;
|
|
break;
|
|
default:
|
|
case GC_DISPLAY_CLOCK_190_200_MHZ:
|
|
cdclk_config->cdclk = 200000;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static unsigned int intel_hpll_vco(struct intel_display *display)
|
|
{
|
|
static const unsigned int blb_vco[8] = {
|
|
[0] = 3200000,
|
|
[1] = 4000000,
|
|
[2] = 5333333,
|
|
[3] = 4800000,
|
|
[4] = 6400000,
|
|
};
|
|
static const unsigned int pnv_vco[8] = {
|
|
[0] = 3200000,
|
|
[1] = 4000000,
|
|
[2] = 5333333,
|
|
[3] = 4800000,
|
|
[4] = 2666667,
|
|
};
|
|
static const unsigned int cl_vco[8] = {
|
|
[0] = 3200000,
|
|
[1] = 4000000,
|
|
[2] = 5333333,
|
|
[3] = 6400000,
|
|
[4] = 3333333,
|
|
[5] = 3566667,
|
|
[6] = 4266667,
|
|
};
|
|
static const unsigned int elk_vco[8] = {
|
|
[0] = 3200000,
|
|
[1] = 4000000,
|
|
[2] = 5333333,
|
|
[3] = 4800000,
|
|
};
|
|
static const unsigned int ctg_vco[8] = {
|
|
[0] = 3200000,
|
|
[1] = 4000000,
|
|
[2] = 5333333,
|
|
[3] = 6400000,
|
|
[4] = 2666667,
|
|
[5] = 4266667,
|
|
};
|
|
const unsigned int *vco_table;
|
|
unsigned int vco;
|
|
u8 tmp = 0;
|
|
|
|
/* FIXME other chipsets? */
|
|
if (display->platform.gm45)
|
|
vco_table = ctg_vco;
|
|
else if (display->platform.g45)
|
|
vco_table = elk_vco;
|
|
else if (display->platform.i965gm)
|
|
vco_table = cl_vco;
|
|
else if (display->platform.pineview)
|
|
vco_table = pnv_vco;
|
|
else if (display->platform.g33)
|
|
vco_table = blb_vco;
|
|
else
|
|
return 0;
|
|
|
|
tmp = intel_de_read(display, display->platform.pineview ||
|
|
display->platform.mobile ? HPLLVCO_MOBILE : HPLLVCO);
|
|
|
|
vco = vco_table[tmp & 0x7];
|
|
if (vco == 0)
|
|
drm_err(display->drm, "Bad HPLL VCO (HPLLVCO=0x%02x)\n",
|
|
tmp);
|
|
else
|
|
drm_dbg_kms(display->drm, "HPLL VCO %u kHz\n", vco);
|
|
|
|
return vco;
|
|
}
|
|
|
|
static void g33_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(display->drm->dev);
|
|
static const u8 div_3200[] = { 12, 10, 8, 7, 5, 16 };
|
|
static const u8 div_4000[] = { 14, 12, 10, 8, 6, 20 };
|
|
static const u8 div_4800[] = { 20, 14, 12, 10, 8, 24 };
|
|
static const u8 div_5333[] = { 20, 16, 12, 12, 8, 28 };
|
|
const u8 *div_table;
|
|
unsigned int cdclk_sel;
|
|
u16 tmp = 0;
|
|
|
|
cdclk_config->vco = intel_hpll_vco(display);
|
|
|
|
pci_read_config_word(pdev, GCFGC, &tmp);
|
|
|
|
cdclk_sel = (tmp >> 4) & 0x7;
|
|
|
|
if (cdclk_sel >= ARRAY_SIZE(div_3200))
|
|
goto fail;
|
|
|
|
switch (cdclk_config->vco) {
|
|
case 3200000:
|
|
div_table = div_3200;
|
|
break;
|
|
case 4000000:
|
|
div_table = div_4000;
|
|
break;
|
|
case 4800000:
|
|
div_table = div_4800;
|
|
break;
|
|
case 5333333:
|
|
div_table = div_5333;
|
|
break;
|
|
default:
|
|
goto fail;
|
|
}
|
|
|
|
cdclk_config->cdclk = DIV_ROUND_CLOSEST(cdclk_config->vco,
|
|
div_table[cdclk_sel]);
|
|
return;
|
|
|
|
fail:
|
|
drm_err(display->drm,
|
|
"Unable to determine CDCLK. HPLL VCO=%u kHz, CFGC=0x%08x\n",
|
|
cdclk_config->vco, tmp);
|
|
cdclk_config->cdclk = 190476;
|
|
}
|
|
|
|
static void pnv_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(display->drm->dev);
|
|
u16 gcfgc = 0;
|
|
|
|
pci_read_config_word(pdev, GCFGC, &gcfgc);
|
|
|
|
switch (gcfgc & GC_DISPLAY_CLOCK_MASK) {
|
|
case GC_DISPLAY_CLOCK_267_MHZ_PNV:
|
|
cdclk_config->cdclk = 266667;
|
|
break;
|
|
case GC_DISPLAY_CLOCK_333_MHZ_PNV:
|
|
cdclk_config->cdclk = 333333;
|
|
break;
|
|
case GC_DISPLAY_CLOCK_444_MHZ_PNV:
|
|
cdclk_config->cdclk = 444444;
|
|
break;
|
|
case GC_DISPLAY_CLOCK_200_MHZ_PNV:
|
|
cdclk_config->cdclk = 200000;
|
|
break;
|
|
default:
|
|
drm_err(display->drm,
|
|
"Unknown pnv display core clock 0x%04x\n", gcfgc);
|
|
fallthrough;
|
|
case GC_DISPLAY_CLOCK_133_MHZ_PNV:
|
|
cdclk_config->cdclk = 133333;
|
|
break;
|
|
case GC_DISPLAY_CLOCK_167_MHZ_PNV:
|
|
cdclk_config->cdclk = 166667;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void i965gm_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(display->drm->dev);
|
|
static const u8 div_3200[] = { 16, 10, 8 };
|
|
static const u8 div_4000[] = { 20, 12, 10 };
|
|
static const u8 div_5333[] = { 24, 16, 14 };
|
|
const u8 *div_table;
|
|
unsigned int cdclk_sel;
|
|
u16 tmp = 0;
|
|
|
|
cdclk_config->vco = intel_hpll_vco(display);
|
|
|
|
pci_read_config_word(pdev, GCFGC, &tmp);
|
|
|
|
cdclk_sel = ((tmp >> 8) & 0x1f) - 1;
|
|
|
|
if (cdclk_sel >= ARRAY_SIZE(div_3200))
|
|
goto fail;
|
|
|
|
switch (cdclk_config->vco) {
|
|
case 3200000:
|
|
div_table = div_3200;
|
|
break;
|
|
case 4000000:
|
|
div_table = div_4000;
|
|
break;
|
|
case 5333333:
|
|
div_table = div_5333;
|
|
break;
|
|
default:
|
|
goto fail;
|
|
}
|
|
|
|
cdclk_config->cdclk = DIV_ROUND_CLOSEST(cdclk_config->vco,
|
|
div_table[cdclk_sel]);
|
|
return;
|
|
|
|
fail:
|
|
drm_err(display->drm,
|
|
"Unable to determine CDCLK. HPLL VCO=%u kHz, CFGC=0x%04x\n",
|
|
cdclk_config->vco, tmp);
|
|
cdclk_config->cdclk = 200000;
|
|
}
|
|
|
|
static void gm45_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(display->drm->dev);
|
|
unsigned int cdclk_sel;
|
|
u16 tmp = 0;
|
|
|
|
cdclk_config->vco = intel_hpll_vco(display);
|
|
|
|
pci_read_config_word(pdev, GCFGC, &tmp);
|
|
|
|
cdclk_sel = (tmp >> 12) & 0x1;
|
|
|
|
switch (cdclk_config->vco) {
|
|
case 2666667:
|
|
case 4000000:
|
|
case 5333333:
|
|
cdclk_config->cdclk = cdclk_sel ? 333333 : 222222;
|
|
break;
|
|
case 3200000:
|
|
cdclk_config->cdclk = cdclk_sel ? 320000 : 228571;
|
|
break;
|
|
default:
|
|
drm_err(display->drm,
|
|
"Unable to determine CDCLK. HPLL VCO=%u, CFGC=0x%04x\n",
|
|
cdclk_config->vco, tmp);
|
|
cdclk_config->cdclk = 222222;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void hsw_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
u32 lcpll = intel_de_read(display, LCPLL_CTL);
|
|
u32 freq = lcpll & LCPLL_CLK_FREQ_MASK;
|
|
|
|
if (lcpll & LCPLL_CD_SOURCE_FCLK)
|
|
cdclk_config->cdclk = 800000;
|
|
else if (intel_de_read(display, FUSE_STRAP) & HSW_CDCLK_LIMIT)
|
|
cdclk_config->cdclk = 450000;
|
|
else if (freq == LCPLL_CLK_FREQ_450)
|
|
cdclk_config->cdclk = 450000;
|
|
else if (display->platform.haswell_ult)
|
|
cdclk_config->cdclk = 337500;
|
|
else
|
|
cdclk_config->cdclk = 540000;
|
|
}
|
|
|
|
static int vlv_calc_cdclk(struct intel_display *display, int min_cdclk)
|
|
{
|
|
int freq_320 = (vlv_clock_get_hpll_vco(display->drm) << 1) % 320000 != 0 ?
|
|
333333 : 320000;
|
|
|
|
/*
|
|
* We seem to get an unstable or solid color picture at 200MHz.
|
|
* Not sure what's wrong. For now use 200MHz only when all pipes
|
|
* are off.
|
|
*/
|
|
if (display->platform.valleyview && min_cdclk > freq_320)
|
|
return 400000;
|
|
else if (min_cdclk > 266667)
|
|
return freq_320;
|
|
else if (min_cdclk > 0)
|
|
return 266667;
|
|
else
|
|
return 200000;
|
|
}
|
|
|
|
static u8 vlv_calc_voltage_level(struct intel_display *display, int cdclk)
|
|
{
|
|
if (display->platform.valleyview) {
|
|
if (cdclk >= 320000) /* jump to highest voltage for 400MHz too */
|
|
return 2;
|
|
else if (cdclk >= 266667)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
} else {
|
|
/*
|
|
* Specs are full of misinformation, but testing on actual
|
|
* hardware has shown that we just need to write the desired
|
|
* CCK divider into the Punit register.
|
|
*/
|
|
return DIV_ROUND_CLOSEST(vlv_clock_get_hpll_vco(display->drm) << 1, cdclk) - 1;
|
|
}
|
|
}
|
|
|
|
static void vlv_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
u32 val;
|
|
|
|
cdclk_config->vco = vlv_clock_get_hpll_vco(display->drm);
|
|
cdclk_config->cdclk = vlv_clock_get_cdclk(display->drm);
|
|
|
|
vlv_punit_get(display->drm);
|
|
val = vlv_punit_read(display->drm, PUNIT_REG_DSPSSPM);
|
|
vlv_punit_put(display->drm);
|
|
|
|
if (display->platform.valleyview)
|
|
cdclk_config->voltage_level = (val & DSPFREQGUAR_MASK) >>
|
|
DSPFREQGUAR_SHIFT;
|
|
else
|
|
cdclk_config->voltage_level = (val & DSPFREQGUAR_MASK_CHV) >>
|
|
DSPFREQGUAR_SHIFT_CHV;
|
|
}
|
|
|
|
static void vlv_program_pfi_credits(struct intel_display *display)
|
|
{
|
|
unsigned int credits, default_credits;
|
|
|
|
if (display->platform.cherryview)
|
|
default_credits = PFI_CREDIT(12);
|
|
else
|
|
default_credits = PFI_CREDIT(8);
|
|
|
|
if (display->cdclk.hw.cdclk >= vlv_clock_get_czclk(display->drm)) {
|
|
/* CHV suggested value is 31 or 63 */
|
|
if (display->platform.cherryview)
|
|
credits = PFI_CREDIT_63;
|
|
else
|
|
credits = PFI_CREDIT(15);
|
|
} else {
|
|
credits = default_credits;
|
|
}
|
|
|
|
/*
|
|
* WA - write default credits before re-programming
|
|
* FIXME: should we also set the resend bit here?
|
|
*/
|
|
intel_de_write(display, GCI_CONTROL,
|
|
VGA_FAST_MODE_DISABLE | default_credits);
|
|
|
|
intel_de_write(display, GCI_CONTROL,
|
|
VGA_FAST_MODE_DISABLE | credits | PFI_CREDIT_RESEND);
|
|
|
|
/*
|
|
* FIXME is this guaranteed to clear
|
|
* immediately or should we poll for it?
|
|
*/
|
|
drm_WARN_ON(display->drm,
|
|
intel_de_read(display, GCI_CONTROL) & PFI_CREDIT_RESEND);
|
|
}
|
|
|
|
static void vlv_set_cdclk(struct intel_display *display,
|
|
const struct intel_cdclk_config *cdclk_config,
|
|
enum pipe pipe)
|
|
{
|
|
int cdclk = cdclk_config->cdclk;
|
|
u32 val, cmd = cdclk_config->voltage_level;
|
|
struct ref_tracker *wakeref;
|
|
int ret;
|
|
|
|
switch (cdclk) {
|
|
case 400000:
|
|
case 333333:
|
|
case 320000:
|
|
case 266667:
|
|
case 200000:
|
|
break;
|
|
default:
|
|
MISSING_CASE(cdclk);
|
|
return;
|
|
}
|
|
|
|
/* There are cases where we can end up here with power domains
|
|
* off and a CDCLK frequency other than the minimum, like when
|
|
* issuing a modeset without actually changing any display after
|
|
* a system suspend. So grab the display core domain, which covers
|
|
* the HW blocks needed for the following programming.
|
|
*/
|
|
wakeref = intel_display_power_get(display, POWER_DOMAIN_DISPLAY_CORE);
|
|
|
|
vlv_iosf_sb_get(display->drm,
|
|
BIT(VLV_IOSF_SB_CCK) |
|
|
BIT(VLV_IOSF_SB_BUNIT) |
|
|
BIT(VLV_IOSF_SB_PUNIT));
|
|
|
|
val = vlv_punit_read(display->drm, PUNIT_REG_DSPSSPM);
|
|
val &= ~DSPFREQGUAR_MASK;
|
|
val |= (cmd << DSPFREQGUAR_SHIFT);
|
|
vlv_punit_write(display->drm, PUNIT_REG_DSPSSPM, val);
|
|
|
|
ret = poll_timeout_us(val = vlv_punit_read(display->drm, PUNIT_REG_DSPSSPM),
|
|
(val & DSPFREQSTAT_MASK) == (cmd << DSPFREQSTAT_SHIFT),
|
|
500, 50 * 1000, false);
|
|
if (ret)
|
|
drm_err(display->drm, "timed out waiting for CDCLK change\n");
|
|
|
|
if (cdclk == 400000) {
|
|
u32 divider;
|
|
|
|
divider = DIV_ROUND_CLOSEST(vlv_clock_get_hpll_vco(display->drm) << 1,
|
|
cdclk) - 1;
|
|
|
|
/* adjust cdclk divider */
|
|
val = vlv_cck_read(display->drm, CCK_DISPLAY_CLOCK_CONTROL);
|
|
val &= ~CCK_FREQUENCY_VALUES;
|
|
val |= divider;
|
|
vlv_cck_write(display->drm, CCK_DISPLAY_CLOCK_CONTROL, val);
|
|
|
|
ret = poll_timeout_us(val = vlv_cck_read(display->drm, CCK_DISPLAY_CLOCK_CONTROL),
|
|
(val & CCK_FREQUENCY_STATUS) == (divider << CCK_FREQUENCY_STATUS_SHIFT),
|
|
500, 50 * 1000, false);
|
|
if (ret)
|
|
drm_err(display->drm, "timed out waiting for CDCLK change\n");
|
|
}
|
|
|
|
/* adjust self-refresh exit latency value */
|
|
val = vlv_bunit_read(display->drm, BUNIT_REG_BISOC);
|
|
val &= ~0x7f;
|
|
|
|
/*
|
|
* For high bandwidth configs, we set a higher latency in the bunit
|
|
* so that the core display fetch happens in time to avoid underruns.
|
|
*/
|
|
if (cdclk == 400000)
|
|
val |= 4500 / 250; /* 4.5 usec */
|
|
else
|
|
val |= 3000 / 250; /* 3.0 usec */
|
|
vlv_bunit_write(display->drm, BUNIT_REG_BISOC, val);
|
|
|
|
vlv_iosf_sb_put(display->drm,
|
|
BIT(VLV_IOSF_SB_CCK) |
|
|
BIT(VLV_IOSF_SB_BUNIT) |
|
|
BIT(VLV_IOSF_SB_PUNIT));
|
|
|
|
intel_update_cdclk(display);
|
|
|
|
vlv_program_pfi_credits(display);
|
|
|
|
intel_display_power_put(display, POWER_DOMAIN_DISPLAY_CORE, wakeref);
|
|
}
|
|
|
|
static void chv_set_cdclk(struct intel_display *display,
|
|
const struct intel_cdclk_config *cdclk_config,
|
|
enum pipe pipe)
|
|
{
|
|
int cdclk = cdclk_config->cdclk;
|
|
u32 val, cmd = cdclk_config->voltage_level;
|
|
struct ref_tracker *wakeref;
|
|
int ret;
|
|
|
|
switch (cdclk) {
|
|
case 333333:
|
|
case 320000:
|
|
case 266667:
|
|
case 200000:
|
|
break;
|
|
default:
|
|
MISSING_CASE(cdclk);
|
|
return;
|
|
}
|
|
|
|
/* There are cases where we can end up here with power domains
|
|
* off and a CDCLK frequency other than the minimum, like when
|
|
* issuing a modeset without actually changing any display after
|
|
* a system suspend. So grab the display core domain, which covers
|
|
* the HW blocks needed for the following programming.
|
|
*/
|
|
wakeref = intel_display_power_get(display, POWER_DOMAIN_DISPLAY_CORE);
|
|
|
|
vlv_punit_get(display->drm);
|
|
val = vlv_punit_read(display->drm, PUNIT_REG_DSPSSPM);
|
|
val &= ~DSPFREQGUAR_MASK_CHV;
|
|
val |= (cmd << DSPFREQGUAR_SHIFT_CHV);
|
|
vlv_punit_write(display->drm, PUNIT_REG_DSPSSPM, val);
|
|
|
|
ret = poll_timeout_us(val = vlv_punit_read(display->drm, PUNIT_REG_DSPSSPM),
|
|
(val & DSPFREQSTAT_MASK_CHV) == (cmd << DSPFREQSTAT_SHIFT_CHV),
|
|
500, 50 * 1000, false);
|
|
if (ret)
|
|
drm_err(display->drm, "timed out waiting for CDCLK change\n");
|
|
|
|
vlv_punit_put(display->drm);
|
|
|
|
intel_update_cdclk(display);
|
|
|
|
vlv_program_pfi_credits(display);
|
|
|
|
intel_display_power_put(display, POWER_DOMAIN_DISPLAY_CORE, wakeref);
|
|
}
|
|
|
|
static int bdw_calc_cdclk(int min_cdclk)
|
|
{
|
|
if (min_cdclk > 540000)
|
|
return 675000;
|
|
else if (min_cdclk > 450000)
|
|
return 540000;
|
|
else if (min_cdclk > 337500)
|
|
return 450000;
|
|
else
|
|
return 337500;
|
|
}
|
|
|
|
static u8 bdw_calc_voltage_level(int cdclk)
|
|
{
|
|
switch (cdclk) {
|
|
default:
|
|
case 337500:
|
|
return 2;
|
|
case 450000:
|
|
return 0;
|
|
case 540000:
|
|
return 1;
|
|
case 675000:
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
static void bdw_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
u32 lcpll = intel_de_read(display, LCPLL_CTL);
|
|
u32 freq = lcpll & LCPLL_CLK_FREQ_MASK;
|
|
|
|
if (lcpll & LCPLL_CD_SOURCE_FCLK)
|
|
cdclk_config->cdclk = 800000;
|
|
else if (intel_de_read(display, FUSE_STRAP) & HSW_CDCLK_LIMIT)
|
|
cdclk_config->cdclk = 450000;
|
|
else if (freq == LCPLL_CLK_FREQ_450)
|
|
cdclk_config->cdclk = 450000;
|
|
else if (freq == LCPLL_CLK_FREQ_54O_BDW)
|
|
cdclk_config->cdclk = 540000;
|
|
else if (freq == LCPLL_CLK_FREQ_337_5_BDW)
|
|
cdclk_config->cdclk = 337500;
|
|
else
|
|
cdclk_config->cdclk = 675000;
|
|
|
|
/*
|
|
* Can't read this out :( Let's assume it's
|
|
* at least what the CDCLK frequency requires.
|
|
*/
|
|
cdclk_config->voltage_level =
|
|
bdw_calc_voltage_level(cdclk_config->cdclk);
|
|
}
|
|
|
|
static u32 bdw_cdclk_freq_sel(int cdclk)
|
|
{
|
|
switch (cdclk) {
|
|
default:
|
|
MISSING_CASE(cdclk);
|
|
fallthrough;
|
|
case 337500:
|
|
return LCPLL_CLK_FREQ_337_5_BDW;
|
|
case 450000:
|
|
return LCPLL_CLK_FREQ_450;
|
|
case 540000:
|
|
return LCPLL_CLK_FREQ_54O_BDW;
|
|
case 675000:
|
|
return LCPLL_CLK_FREQ_675_BDW;
|
|
}
|
|
}
|
|
|
|
static void bdw_set_cdclk(struct intel_display *display,
|
|
const struct intel_cdclk_config *cdclk_config,
|
|
enum pipe pipe)
|
|
{
|
|
int cdclk = cdclk_config->cdclk;
|
|
int ret;
|
|
|
|
if (drm_WARN(display->drm,
|
|
(intel_de_read(display, LCPLL_CTL) &
|
|
(LCPLL_PLL_DISABLE | LCPLL_PLL_LOCK |
|
|
LCPLL_CD_CLOCK_DISABLE | LCPLL_ROOT_CD_CLOCK_DISABLE |
|
|
LCPLL_CD2X_CLOCK_DISABLE | LCPLL_POWER_DOWN_ALLOW |
|
|
LCPLL_CD_SOURCE_FCLK)) != LCPLL_PLL_LOCK,
|
|
"trying to change cdclk frequency with cdclk not enabled\n"))
|
|
return;
|
|
|
|
ret = intel_pcode_write(display->drm, BDW_PCODE_DISPLAY_FREQ_CHANGE_REQ, 0x0);
|
|
if (ret) {
|
|
drm_err(display->drm,
|
|
"failed to inform pcode about cdclk change\n");
|
|
return;
|
|
}
|
|
|
|
intel_de_rmw(display, LCPLL_CTL,
|
|
0, LCPLL_CD_SOURCE_FCLK);
|
|
|
|
/*
|
|
* According to the spec, it should be enough to poll for this 1 us.
|
|
* However, extensive testing shows that this can take longer.
|
|
*/
|
|
ret = intel_de_wait_for_set_us(display, LCPLL_CTL,
|
|
LCPLL_CD_SOURCE_FCLK_DONE, 100);
|
|
if (ret)
|
|
drm_err(display->drm, "Switching to FCLK failed\n");
|
|
|
|
intel_de_rmw(display, LCPLL_CTL,
|
|
LCPLL_CLK_FREQ_MASK, bdw_cdclk_freq_sel(cdclk));
|
|
|
|
intel_de_rmw(display, LCPLL_CTL,
|
|
LCPLL_CD_SOURCE_FCLK, 0);
|
|
|
|
ret = intel_de_wait_for_clear_us(display, LCPLL_CTL,
|
|
LCPLL_CD_SOURCE_FCLK_DONE, 1);
|
|
if (ret)
|
|
drm_err(display->drm, "Switching back to LCPLL failed\n");
|
|
|
|
intel_pcode_write(display->drm, HSW_PCODE_DE_WRITE_FREQ_REQ,
|
|
cdclk_config->voltage_level);
|
|
|
|
intel_de_write(display, CDCLK_FREQ,
|
|
DIV_ROUND_CLOSEST(cdclk, 1000) - 1);
|
|
|
|
intel_update_cdclk(display);
|
|
}
|
|
|
|
static int skl_calc_cdclk(int min_cdclk, int vco)
|
|
{
|
|
if (vco == 8640000) {
|
|
if (min_cdclk > 540000)
|
|
return 617143;
|
|
else if (min_cdclk > 432000)
|
|
return 540000;
|
|
else if (min_cdclk > 308571)
|
|
return 432000;
|
|
else
|
|
return 308571;
|
|
} else {
|
|
if (min_cdclk > 540000)
|
|
return 675000;
|
|
else if (min_cdclk > 450000)
|
|
return 540000;
|
|
else if (min_cdclk > 337500)
|
|
return 450000;
|
|
else
|
|
return 337500;
|
|
}
|
|
}
|
|
|
|
static u8 skl_calc_voltage_level(int cdclk)
|
|
{
|
|
if (cdclk > 540000)
|
|
return 3;
|
|
else if (cdclk > 450000)
|
|
return 2;
|
|
else if (cdclk > 337500)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static void skl_dpll0_update(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
u32 val;
|
|
|
|
cdclk_config->ref = 24000;
|
|
cdclk_config->vco = 0;
|
|
|
|
val = intel_de_read(display, LCPLL1_CTL);
|
|
if ((val & LCPLL_PLL_ENABLE) == 0)
|
|
return;
|
|
|
|
if (drm_WARN_ON(display->drm, (val & LCPLL_PLL_LOCK) == 0))
|
|
return;
|
|
|
|
val = intel_de_read(display, DPLL_CTRL1);
|
|
|
|
if (drm_WARN_ON(display->drm,
|
|
(val & (DPLL_CTRL1_HDMI_MODE(SKL_DPLL0) |
|
|
DPLL_CTRL1_SSC(SKL_DPLL0) |
|
|
DPLL_CTRL1_OVERRIDE(SKL_DPLL0))) !=
|
|
DPLL_CTRL1_OVERRIDE(SKL_DPLL0)))
|
|
return;
|
|
|
|
switch (val & DPLL_CTRL1_LINK_RATE_MASK(SKL_DPLL0)) {
|
|
case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_810, SKL_DPLL0):
|
|
case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1350, SKL_DPLL0):
|
|
case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1620, SKL_DPLL0):
|
|
case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_2700, SKL_DPLL0):
|
|
cdclk_config->vco = 8100000;
|
|
break;
|
|
case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1080, SKL_DPLL0):
|
|
case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_2160, SKL_DPLL0):
|
|
cdclk_config->vco = 8640000;
|
|
break;
|
|
default:
|
|
MISSING_CASE(val & DPLL_CTRL1_LINK_RATE_MASK(SKL_DPLL0));
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void skl_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
u32 cdctl;
|
|
|
|
skl_dpll0_update(display, cdclk_config);
|
|
|
|
cdclk_config->cdclk = cdclk_config->bypass = cdclk_config->ref;
|
|
|
|
if (cdclk_config->vco == 0)
|
|
goto out;
|
|
|
|
cdctl = intel_de_read(display, CDCLK_CTL);
|
|
|
|
if (cdclk_config->vco == 8640000) {
|
|
switch (cdctl & CDCLK_FREQ_SEL_MASK) {
|
|
case CDCLK_FREQ_450_432:
|
|
cdclk_config->cdclk = 432000;
|
|
break;
|
|
case CDCLK_FREQ_337_308:
|
|
cdclk_config->cdclk = 308571;
|
|
break;
|
|
case CDCLK_FREQ_540:
|
|
cdclk_config->cdclk = 540000;
|
|
break;
|
|
case CDCLK_FREQ_675_617:
|
|
cdclk_config->cdclk = 617143;
|
|
break;
|
|
default:
|
|
MISSING_CASE(cdctl & CDCLK_FREQ_SEL_MASK);
|
|
break;
|
|
}
|
|
} else {
|
|
switch (cdctl & CDCLK_FREQ_SEL_MASK) {
|
|
case CDCLK_FREQ_450_432:
|
|
cdclk_config->cdclk = 450000;
|
|
break;
|
|
case CDCLK_FREQ_337_308:
|
|
cdclk_config->cdclk = 337500;
|
|
break;
|
|
case CDCLK_FREQ_540:
|
|
cdclk_config->cdclk = 540000;
|
|
break;
|
|
case CDCLK_FREQ_675_617:
|
|
cdclk_config->cdclk = 675000;
|
|
break;
|
|
default:
|
|
MISSING_CASE(cdctl & CDCLK_FREQ_SEL_MASK);
|
|
break;
|
|
}
|
|
}
|
|
|
|
out:
|
|
/*
|
|
* Can't read this out :( Let's assume it's
|
|
* at least what the CDCLK frequency requires.
|
|
*/
|
|
cdclk_config->voltage_level =
|
|
skl_calc_voltage_level(cdclk_config->cdclk);
|
|
}
|
|
|
|
/* convert from kHz to .1 fixpoint MHz with -1MHz offset */
|
|
static int skl_cdclk_decimal(int cdclk)
|
|
{
|
|
return DIV_ROUND_CLOSEST(cdclk - 1000, 500);
|
|
}
|
|
|
|
static void skl_set_preferred_cdclk_vco(struct intel_display *display, int vco)
|
|
{
|
|
bool changed = display->cdclk.skl_preferred_vco_freq != vco;
|
|
|
|
display->cdclk.skl_preferred_vco_freq = vco;
|
|
|
|
if (changed)
|
|
intel_update_max_cdclk(display);
|
|
}
|
|
|
|
static u32 skl_dpll0_link_rate(struct intel_display *display, int vco)
|
|
{
|
|
drm_WARN_ON(display->drm, vco != 8100000 && vco != 8640000);
|
|
|
|
/*
|
|
* 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 vco.
|
|
*/
|
|
if (vco == 8640000)
|
|
return DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1080, SKL_DPLL0);
|
|
else
|
|
return DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_810, SKL_DPLL0);
|
|
}
|
|
|
|
static void skl_dpll0_enable(struct intel_display *display, int vco)
|
|
{
|
|
intel_de_rmw(display, DPLL_CTRL1,
|
|
DPLL_CTRL1_HDMI_MODE(SKL_DPLL0) |
|
|
DPLL_CTRL1_SSC(SKL_DPLL0) |
|
|
DPLL_CTRL1_LINK_RATE_MASK(SKL_DPLL0),
|
|
DPLL_CTRL1_OVERRIDE(SKL_DPLL0) |
|
|
skl_dpll0_link_rate(display, vco));
|
|
intel_de_posting_read(display, DPLL_CTRL1);
|
|
|
|
intel_de_rmw(display, LCPLL1_CTL,
|
|
0, LCPLL_PLL_ENABLE);
|
|
|
|
if (intel_de_wait_for_set_ms(display, LCPLL1_CTL, LCPLL_PLL_LOCK, 5))
|
|
drm_err(display->drm, "DPLL0 not locked\n");
|
|
|
|
display->cdclk.hw.vco = vco;
|
|
|
|
/* We'll want to keep using the current vco from now on. */
|
|
skl_set_preferred_cdclk_vco(display, vco);
|
|
}
|
|
|
|
static void skl_dpll0_disable(struct intel_display *display)
|
|
{
|
|
intel_de_rmw(display, LCPLL1_CTL,
|
|
LCPLL_PLL_ENABLE, 0);
|
|
|
|
if (intel_de_wait_for_clear_ms(display, LCPLL1_CTL, LCPLL_PLL_LOCK, 1))
|
|
drm_err(display->drm, "Couldn't disable DPLL0\n");
|
|
|
|
display->cdclk.hw.vco = 0;
|
|
}
|
|
|
|
static u32 skl_cdclk_freq_sel(struct intel_display *display,
|
|
int cdclk, int vco)
|
|
{
|
|
switch (cdclk) {
|
|
default:
|
|
drm_WARN_ON(display->drm,
|
|
cdclk != display->cdclk.hw.bypass);
|
|
drm_WARN_ON(display->drm, vco != 0);
|
|
fallthrough;
|
|
case 308571:
|
|
case 337500:
|
|
return CDCLK_FREQ_337_308;
|
|
case 450000:
|
|
case 432000:
|
|
return CDCLK_FREQ_450_432;
|
|
case 540000:
|
|
return CDCLK_FREQ_540;
|
|
case 617143:
|
|
case 675000:
|
|
return CDCLK_FREQ_675_617;
|
|
}
|
|
}
|
|
|
|
static void skl_set_cdclk(struct intel_display *display,
|
|
const struct intel_cdclk_config *cdclk_config,
|
|
enum pipe pipe)
|
|
{
|
|
int cdclk = cdclk_config->cdclk;
|
|
int vco = cdclk_config->vco;
|
|
u32 freq_select, cdclk_ctl;
|
|
int ret;
|
|
|
|
/*
|
|
* Based on WA#1183 CDCLK rates 308 and 617MHz CDCLK rates are
|
|
* unsupported on SKL. In theory this should never happen since only
|
|
* the eDP1.4 2.16 and 4.32Gbps rates require it, but eDP1.4 is not
|
|
* supported on SKL either, see the above WA. WARN whenever trying to
|
|
* use the corresponding VCO freq as that always leads to using the
|
|
* minimum 308MHz CDCLK.
|
|
*/
|
|
drm_WARN_ON_ONCE(display->drm,
|
|
display->platform.skylake && vco == 8640000);
|
|
|
|
ret = intel_pcode_request(display->drm, SKL_PCODE_CDCLK_CONTROL,
|
|
SKL_CDCLK_PREPARE_FOR_CHANGE,
|
|
SKL_CDCLK_READY_FOR_CHANGE,
|
|
SKL_CDCLK_READY_FOR_CHANGE, 3);
|
|
if (ret) {
|
|
drm_err(display->drm,
|
|
"Failed to inform PCU about cdclk change (%d)\n", ret);
|
|
return;
|
|
}
|
|
|
|
freq_select = skl_cdclk_freq_sel(display, cdclk, vco);
|
|
|
|
if (display->cdclk.hw.vco != 0 &&
|
|
display->cdclk.hw.vco != vco)
|
|
skl_dpll0_disable(display);
|
|
|
|
cdclk_ctl = intel_de_read(display, CDCLK_CTL);
|
|
|
|
if (display->cdclk.hw.vco != vco) {
|
|
/* Wa Display #1183: skl,kbl,cfl */
|
|
cdclk_ctl &= ~(CDCLK_FREQ_SEL_MASK | CDCLK_FREQ_DECIMAL_MASK);
|
|
cdclk_ctl |= freq_select | skl_cdclk_decimal(cdclk);
|
|
intel_de_write(display, CDCLK_CTL, cdclk_ctl);
|
|
}
|
|
|
|
/* Wa Display #1183: skl,kbl,cfl */
|
|
cdclk_ctl |= CDCLK_DIVMUX_CD_OVERRIDE;
|
|
intel_de_write(display, CDCLK_CTL, cdclk_ctl);
|
|
intel_de_posting_read(display, CDCLK_CTL);
|
|
|
|
if (display->cdclk.hw.vco != vco)
|
|
skl_dpll0_enable(display, vco);
|
|
|
|
/* Wa Display #1183: skl,kbl,cfl */
|
|
cdclk_ctl &= ~(CDCLK_FREQ_SEL_MASK | CDCLK_FREQ_DECIMAL_MASK);
|
|
intel_de_write(display, CDCLK_CTL, cdclk_ctl);
|
|
|
|
cdclk_ctl |= freq_select | skl_cdclk_decimal(cdclk);
|
|
intel_de_write(display, CDCLK_CTL, cdclk_ctl);
|
|
|
|
/* Wa Display #1183: skl,kbl,cfl */
|
|
cdclk_ctl &= ~CDCLK_DIVMUX_CD_OVERRIDE;
|
|
intel_de_write(display, CDCLK_CTL, cdclk_ctl);
|
|
intel_de_posting_read(display, CDCLK_CTL);
|
|
|
|
/* inform PCU of the change */
|
|
intel_pcode_write(display->drm, SKL_PCODE_CDCLK_CONTROL,
|
|
cdclk_config->voltage_level);
|
|
|
|
intel_update_cdclk(display);
|
|
}
|
|
|
|
static void skl_sanitize_cdclk(struct intel_display *display)
|
|
{
|
|
u32 cdctl, expected;
|
|
|
|
/*
|
|
* check if the pre-os initialized the display
|
|
* There is SWF18 scratchpad register defined which is set by the
|
|
* pre-os which can be used by the OS drivers to check the status
|
|
*/
|
|
if ((intel_de_read(display, SWF_ILK(0x18)) & 0x00FFFFFF) == 0)
|
|
goto sanitize;
|
|
|
|
intel_update_cdclk(display);
|
|
intel_cdclk_dump_config(display, &display->cdclk.hw, "Current CDCLK");
|
|
|
|
/* Is PLL enabled and locked ? */
|
|
if (display->cdclk.hw.vco == 0 ||
|
|
display->cdclk.hw.cdclk == display->cdclk.hw.bypass)
|
|
goto sanitize;
|
|
|
|
/* DPLL okay; verify the cdclock
|
|
*
|
|
* Noticed in some instances that the freq selection is correct but
|
|
* decimal part is programmed wrong from BIOS where pre-os does not
|
|
* enable display. Verify the same as well.
|
|
*/
|
|
cdctl = intel_de_read(display, CDCLK_CTL);
|
|
expected = (cdctl & CDCLK_FREQ_SEL_MASK) |
|
|
skl_cdclk_decimal(display->cdclk.hw.cdclk);
|
|
if (cdctl == expected)
|
|
/* All well; nothing to sanitize */
|
|
return;
|
|
|
|
sanitize:
|
|
drm_dbg_kms(display->drm, "Sanitizing cdclk programmed by pre-os\n");
|
|
|
|
/* force cdclk programming */
|
|
display->cdclk.hw.cdclk = 0;
|
|
/* force full PLL disable + enable */
|
|
display->cdclk.hw.vco = ~0;
|
|
}
|
|
|
|
static void skl_cdclk_init_hw(struct intel_display *display)
|
|
{
|
|
struct intel_cdclk_config cdclk_config;
|
|
|
|
skl_sanitize_cdclk(display);
|
|
|
|
if (display->cdclk.hw.cdclk != 0 &&
|
|
display->cdclk.hw.vco != 0) {
|
|
/*
|
|
* Use the current vco as our initial
|
|
* guess as to what the preferred vco is.
|
|
*/
|
|
if (display->cdclk.skl_preferred_vco_freq == 0)
|
|
skl_set_preferred_cdclk_vco(display,
|
|
display->cdclk.hw.vco);
|
|
return;
|
|
}
|
|
|
|
cdclk_config = display->cdclk.hw;
|
|
|
|
cdclk_config.vco = display->cdclk.skl_preferred_vco_freq;
|
|
if (cdclk_config.vco == 0)
|
|
cdclk_config.vco = 8100000;
|
|
cdclk_config.cdclk = skl_calc_cdclk(0, cdclk_config.vco);
|
|
cdclk_config.voltage_level = skl_calc_voltage_level(cdclk_config.cdclk);
|
|
|
|
skl_set_cdclk(display, &cdclk_config, INVALID_PIPE);
|
|
}
|
|
|
|
static void skl_cdclk_uninit_hw(struct intel_display *display)
|
|
{
|
|
struct intel_cdclk_config cdclk_config = display->cdclk.hw;
|
|
|
|
cdclk_config.cdclk = cdclk_config.bypass;
|
|
cdclk_config.vco = 0;
|
|
cdclk_config.voltage_level = skl_calc_voltage_level(cdclk_config.cdclk);
|
|
|
|
skl_set_cdclk(display, &cdclk_config, INVALID_PIPE);
|
|
}
|
|
|
|
struct intel_cdclk_vals {
|
|
u32 cdclk;
|
|
u16 refclk;
|
|
u16 waveform;
|
|
u8 ratio;
|
|
};
|
|
|
|
static const struct intel_cdclk_vals bxt_cdclk_table[] = {
|
|
{ .refclk = 19200, .cdclk = 144000, .ratio = 60 },
|
|
{ .refclk = 19200, .cdclk = 288000, .ratio = 60 },
|
|
{ .refclk = 19200, .cdclk = 384000, .ratio = 60 },
|
|
{ .refclk = 19200, .cdclk = 576000, .ratio = 60 },
|
|
{ .refclk = 19200, .cdclk = 624000, .ratio = 65 },
|
|
{}
|
|
};
|
|
|
|
static const struct intel_cdclk_vals glk_cdclk_table[] = {
|
|
{ .refclk = 19200, .cdclk = 79200, .ratio = 33 },
|
|
{ .refclk = 19200, .cdclk = 158400, .ratio = 33 },
|
|
{ .refclk = 19200, .cdclk = 316800, .ratio = 33 },
|
|
{}
|
|
};
|
|
|
|
static const struct intel_cdclk_vals icl_cdclk_table[] = {
|
|
{ .refclk = 19200, .cdclk = 172800, .ratio = 18 },
|
|
{ .refclk = 19200, .cdclk = 192000, .ratio = 20 },
|
|
{ .refclk = 19200, .cdclk = 307200, .ratio = 32 },
|
|
{ .refclk = 19200, .cdclk = 326400, .ratio = 68 },
|
|
{ .refclk = 19200, .cdclk = 556800, .ratio = 58 },
|
|
{ .refclk = 19200, .cdclk = 652800, .ratio = 68 },
|
|
|
|
{ .refclk = 24000, .cdclk = 180000, .ratio = 15 },
|
|
{ .refclk = 24000, .cdclk = 192000, .ratio = 16 },
|
|
{ .refclk = 24000, .cdclk = 312000, .ratio = 26 },
|
|
{ .refclk = 24000, .cdclk = 324000, .ratio = 54 },
|
|
{ .refclk = 24000, .cdclk = 552000, .ratio = 46 },
|
|
{ .refclk = 24000, .cdclk = 648000, .ratio = 54 },
|
|
|
|
{ .refclk = 38400, .cdclk = 172800, .ratio = 9 },
|
|
{ .refclk = 38400, .cdclk = 192000, .ratio = 10 },
|
|
{ .refclk = 38400, .cdclk = 307200, .ratio = 16 },
|
|
{ .refclk = 38400, .cdclk = 326400, .ratio = 34 },
|
|
{ .refclk = 38400, .cdclk = 556800, .ratio = 29 },
|
|
{ .refclk = 38400, .cdclk = 652800, .ratio = 34 },
|
|
{}
|
|
};
|
|
|
|
static const struct intel_cdclk_vals rkl_cdclk_table[] = {
|
|
{ .refclk = 19200, .cdclk = 172800, .ratio = 36 },
|
|
{ .refclk = 19200, .cdclk = 192000, .ratio = 40 },
|
|
{ .refclk = 19200, .cdclk = 307200, .ratio = 64 },
|
|
{ .refclk = 19200, .cdclk = 326400, .ratio = 136 },
|
|
{ .refclk = 19200, .cdclk = 556800, .ratio = 116 },
|
|
{ .refclk = 19200, .cdclk = 652800, .ratio = 136 },
|
|
|
|
{ .refclk = 24000, .cdclk = 180000, .ratio = 30 },
|
|
{ .refclk = 24000, .cdclk = 192000, .ratio = 32 },
|
|
{ .refclk = 24000, .cdclk = 312000, .ratio = 52 },
|
|
{ .refclk = 24000, .cdclk = 324000, .ratio = 108 },
|
|
{ .refclk = 24000, .cdclk = 552000, .ratio = 92 },
|
|
{ .refclk = 24000, .cdclk = 648000, .ratio = 108 },
|
|
|
|
{ .refclk = 38400, .cdclk = 172800, .ratio = 18 },
|
|
{ .refclk = 38400, .cdclk = 192000, .ratio = 20 },
|
|
{ .refclk = 38400, .cdclk = 307200, .ratio = 32 },
|
|
{ .refclk = 38400, .cdclk = 326400, .ratio = 68 },
|
|
{ .refclk = 38400, .cdclk = 556800, .ratio = 58 },
|
|
{ .refclk = 38400, .cdclk = 652800, .ratio = 68 },
|
|
{}
|
|
};
|
|
|
|
static const struct intel_cdclk_vals adlp_a_step_cdclk_table[] = {
|
|
{ .refclk = 19200, .cdclk = 307200, .ratio = 32 },
|
|
{ .refclk = 19200, .cdclk = 556800, .ratio = 58 },
|
|
{ .refclk = 19200, .cdclk = 652800, .ratio = 68 },
|
|
|
|
{ .refclk = 24000, .cdclk = 312000, .ratio = 26 },
|
|
{ .refclk = 24000, .cdclk = 552000, .ratio = 46 },
|
|
{ .refclk = 24400, .cdclk = 648000, .ratio = 54 },
|
|
|
|
{ .refclk = 38400, .cdclk = 307200, .ratio = 16 },
|
|
{ .refclk = 38400, .cdclk = 556800, .ratio = 29 },
|
|
{ .refclk = 38400, .cdclk = 652800, .ratio = 34 },
|
|
{}
|
|
};
|
|
|
|
static const struct intel_cdclk_vals adlp_cdclk_table[] = {
|
|
{ .refclk = 19200, .cdclk = 172800, .ratio = 27 },
|
|
{ .refclk = 19200, .cdclk = 192000, .ratio = 20 },
|
|
{ .refclk = 19200, .cdclk = 307200, .ratio = 32 },
|
|
{ .refclk = 19200, .cdclk = 556800, .ratio = 58 },
|
|
{ .refclk = 19200, .cdclk = 652800, .ratio = 68 },
|
|
|
|
{ .refclk = 24000, .cdclk = 176000, .ratio = 22 },
|
|
{ .refclk = 24000, .cdclk = 192000, .ratio = 16 },
|
|
{ .refclk = 24000, .cdclk = 312000, .ratio = 26 },
|
|
{ .refclk = 24000, .cdclk = 552000, .ratio = 46 },
|
|
{ .refclk = 24000, .cdclk = 648000, .ratio = 54 },
|
|
|
|
{ .refclk = 38400, .cdclk = 179200, .ratio = 14 },
|
|
{ .refclk = 38400, .cdclk = 192000, .ratio = 10 },
|
|
{ .refclk = 38400, .cdclk = 307200, .ratio = 16 },
|
|
{ .refclk = 38400, .cdclk = 556800, .ratio = 29 },
|
|
{ .refclk = 38400, .cdclk = 652800, .ratio = 34 },
|
|
{}
|
|
};
|
|
|
|
static const struct intel_cdclk_vals rplu_cdclk_table[] = {
|
|
{ .refclk = 19200, .cdclk = 172800, .ratio = 27 },
|
|
{ .refclk = 19200, .cdclk = 192000, .ratio = 20 },
|
|
{ .refclk = 19200, .cdclk = 307200, .ratio = 32 },
|
|
{ .refclk = 19200, .cdclk = 480000, .ratio = 50 },
|
|
{ .refclk = 19200, .cdclk = 556800, .ratio = 58 },
|
|
{ .refclk = 19200, .cdclk = 652800, .ratio = 68 },
|
|
|
|
{ .refclk = 24000, .cdclk = 176000, .ratio = 22 },
|
|
{ .refclk = 24000, .cdclk = 192000, .ratio = 16 },
|
|
{ .refclk = 24000, .cdclk = 312000, .ratio = 26 },
|
|
{ .refclk = 24000, .cdclk = 480000, .ratio = 40 },
|
|
{ .refclk = 24000, .cdclk = 552000, .ratio = 46 },
|
|
{ .refclk = 24000, .cdclk = 648000, .ratio = 54 },
|
|
|
|
{ .refclk = 38400, .cdclk = 179200, .ratio = 14 },
|
|
{ .refclk = 38400, .cdclk = 192000, .ratio = 10 },
|
|
{ .refclk = 38400, .cdclk = 307200, .ratio = 16 },
|
|
{ .refclk = 38400, .cdclk = 480000, .ratio = 25 },
|
|
{ .refclk = 38400, .cdclk = 556800, .ratio = 29 },
|
|
{ .refclk = 38400, .cdclk = 652800, .ratio = 34 },
|
|
{}
|
|
};
|
|
|
|
static const struct intel_cdclk_vals dg2_cdclk_table[] = {
|
|
{ .refclk = 38400, .cdclk = 163200, .ratio = 34, .waveform = 0x8888 },
|
|
{ .refclk = 38400, .cdclk = 204000, .ratio = 34, .waveform = 0x9248 },
|
|
{ .refclk = 38400, .cdclk = 244800, .ratio = 34, .waveform = 0xa4a4 },
|
|
{ .refclk = 38400, .cdclk = 285600, .ratio = 34, .waveform = 0xa54a },
|
|
{ .refclk = 38400, .cdclk = 326400, .ratio = 34, .waveform = 0xaaaa },
|
|
{ .refclk = 38400, .cdclk = 367200, .ratio = 34, .waveform = 0xad5a },
|
|
{ .refclk = 38400, .cdclk = 408000, .ratio = 34, .waveform = 0xb6b6 },
|
|
{ .refclk = 38400, .cdclk = 448800, .ratio = 34, .waveform = 0xdbb6 },
|
|
{ .refclk = 38400, .cdclk = 489600, .ratio = 34, .waveform = 0xeeee },
|
|
{ .refclk = 38400, .cdclk = 530400, .ratio = 34, .waveform = 0xf7de },
|
|
{ .refclk = 38400, .cdclk = 571200, .ratio = 34, .waveform = 0xfefe },
|
|
{ .refclk = 38400, .cdclk = 612000, .ratio = 34, .waveform = 0xfffe },
|
|
{ .refclk = 38400, .cdclk = 652800, .ratio = 34, .waveform = 0xffff },
|
|
{}
|
|
};
|
|
|
|
static const struct intel_cdclk_vals mtl_cdclk_table[] = {
|
|
{ .refclk = 38400, .cdclk = 172800, .ratio = 16, .waveform = 0xad5a },
|
|
{ .refclk = 38400, .cdclk = 192000, .ratio = 16, .waveform = 0xb6b6 },
|
|
{ .refclk = 38400, .cdclk = 307200, .ratio = 16, .waveform = 0x0000 },
|
|
{ .refclk = 38400, .cdclk = 480000, .ratio = 25, .waveform = 0x0000 },
|
|
{ .refclk = 38400, .cdclk = 556800, .ratio = 29, .waveform = 0x0000 },
|
|
{ .refclk = 38400, .cdclk = 652800, .ratio = 34, .waveform = 0x0000 },
|
|
{}
|
|
};
|
|
|
|
static const struct intel_cdclk_vals xe2lpd_cdclk_table[] = {
|
|
{ .refclk = 38400, .cdclk = 153600, .ratio = 16, .waveform = 0xaaaa },
|
|
{ .refclk = 38400, .cdclk = 172800, .ratio = 16, .waveform = 0xad5a },
|
|
{ .refclk = 38400, .cdclk = 192000, .ratio = 16, .waveform = 0xb6b6 },
|
|
{ .refclk = 38400, .cdclk = 211200, .ratio = 16, .waveform = 0xdbb6 },
|
|
{ .refclk = 38400, .cdclk = 230400, .ratio = 16, .waveform = 0xeeee },
|
|
{ .refclk = 38400, .cdclk = 249600, .ratio = 16, .waveform = 0xf7de },
|
|
{ .refclk = 38400, .cdclk = 268800, .ratio = 16, .waveform = 0xfefe },
|
|
{ .refclk = 38400, .cdclk = 288000, .ratio = 16, .waveform = 0xfffe },
|
|
{ .refclk = 38400, .cdclk = 307200, .ratio = 16, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 330000, .ratio = 25, .waveform = 0xdbb6 },
|
|
{ .refclk = 38400, .cdclk = 360000, .ratio = 25, .waveform = 0xeeee },
|
|
{ .refclk = 38400, .cdclk = 390000, .ratio = 25, .waveform = 0xf7de },
|
|
{ .refclk = 38400, .cdclk = 420000, .ratio = 25, .waveform = 0xfefe },
|
|
{ .refclk = 38400, .cdclk = 450000, .ratio = 25, .waveform = 0xfffe },
|
|
{ .refclk = 38400, .cdclk = 480000, .ratio = 25, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 487200, .ratio = 29, .waveform = 0xfefe },
|
|
{ .refclk = 38400, .cdclk = 522000, .ratio = 29, .waveform = 0xfffe },
|
|
{ .refclk = 38400, .cdclk = 556800, .ratio = 29, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 571200, .ratio = 34, .waveform = 0xfefe },
|
|
{ .refclk = 38400, .cdclk = 612000, .ratio = 34, .waveform = 0xfffe },
|
|
{ .refclk = 38400, .cdclk = 652800, .ratio = 34, .waveform = 0xffff },
|
|
{}
|
|
};
|
|
|
|
/*
|
|
* Xe2_HPD always uses the minimal cdclk table from Wa_15015413771
|
|
*/
|
|
static const struct intel_cdclk_vals xe2hpd_cdclk_table[] = {
|
|
{ .refclk = 38400, .cdclk = 652800, .ratio = 34, .waveform = 0xffff },
|
|
{}
|
|
};
|
|
|
|
static const struct intel_cdclk_vals xe3lpd_cdclk_table[] = {
|
|
{ .refclk = 38400, .cdclk = 153600, .ratio = 16, .waveform = 0xaaaa },
|
|
{ .refclk = 38400, .cdclk = 172800, .ratio = 16, .waveform = 0xad5a },
|
|
{ .refclk = 38400, .cdclk = 192000, .ratio = 16, .waveform = 0xb6b6 },
|
|
{ .refclk = 38400, .cdclk = 211200, .ratio = 16, .waveform = 0xdbb6 },
|
|
{ .refclk = 38400, .cdclk = 230400, .ratio = 16, .waveform = 0xeeee },
|
|
{ .refclk = 38400, .cdclk = 249600, .ratio = 16, .waveform = 0xf7de },
|
|
{ .refclk = 38400, .cdclk = 268800, .ratio = 16, .waveform = 0xfefe },
|
|
{ .refclk = 38400, .cdclk = 288000, .ratio = 16, .waveform = 0xfffe },
|
|
{ .refclk = 38400, .cdclk = 307200, .ratio = 16, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 326400, .ratio = 17, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 345600, .ratio = 18, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 364800, .ratio = 19, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 384000, .ratio = 20, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 403200, .ratio = 21, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 422400, .ratio = 22, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 441600, .ratio = 23, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 460800, .ratio = 24, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 480000, .ratio = 25, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 499200, .ratio = 26, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 518400, .ratio = 27, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 537600, .ratio = 28, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 556800, .ratio = 29, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 576000, .ratio = 30, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 595200, .ratio = 31, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 614400, .ratio = 32, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 633600, .ratio = 33, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 652800, .ratio = 34, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 672000, .ratio = 35, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 691200, .ratio = 36, .waveform = 0xffff },
|
|
{}
|
|
};
|
|
|
|
static const struct intel_cdclk_vals xe3p_lpd_cdclk_table[] = {
|
|
{ .refclk = 38400, .cdclk = 151200, .ratio = 21, .waveform = 0xa4a4 },
|
|
{ .refclk = 38400, .cdclk = 176400, .ratio = 21, .waveform = 0xaa54 },
|
|
{ .refclk = 38400, .cdclk = 201600, .ratio = 21, .waveform = 0xaaaa },
|
|
{ .refclk = 38400, .cdclk = 226800, .ratio = 21, .waveform = 0xad5a },
|
|
{ .refclk = 38400, .cdclk = 252000, .ratio = 21, .waveform = 0xb6b6 },
|
|
{ .refclk = 38400, .cdclk = 277200, .ratio = 21, .waveform = 0xdbb6 },
|
|
{ .refclk = 38400, .cdclk = 302400, .ratio = 21, .waveform = 0xeeee },
|
|
{ .refclk = 38400, .cdclk = 327600, .ratio = 21, .waveform = 0xf7de },
|
|
{ .refclk = 38400, .cdclk = 352800, .ratio = 21, .waveform = 0xfefe },
|
|
{ .refclk = 38400, .cdclk = 378000, .ratio = 21, .waveform = 0xfffe },
|
|
{ .refclk = 38400, .cdclk = 403200, .ratio = 21, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 422400, .ratio = 22, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 441600, .ratio = 23, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 460800, .ratio = 24, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 480000, .ratio = 25, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 499200, .ratio = 26, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 518400, .ratio = 27, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 537600, .ratio = 28, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 556800, .ratio = 29, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 576000, .ratio = 30, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 595200, .ratio = 31, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 614400, .ratio = 32, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 633600, .ratio = 33, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 652800, .ratio = 34, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 672000, .ratio = 35, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 691200, .ratio = 36, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 710400, .ratio = 37, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 729600, .ratio = 38, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 748800, .ratio = 39, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 768000, .ratio = 40, .waveform = 0xffff },
|
|
{ .refclk = 38400, .cdclk = 787200, .ratio = 41, .waveform = 0xffff },
|
|
{}
|
|
};
|
|
|
|
static const int cdclk_squash_len = 16;
|
|
|
|
static int cdclk_squash_divider(u16 waveform)
|
|
{
|
|
return hweight16(waveform ?: 0xffff);
|
|
}
|
|
|
|
static int cdclk_divider(int cdclk, int vco, u16 waveform)
|
|
{
|
|
/* 2 * cd2x divider */
|
|
return DIV_ROUND_CLOSEST(vco * cdclk_squash_divider(waveform),
|
|
cdclk * cdclk_squash_len);
|
|
}
|
|
|
|
static int bxt_calc_cdclk(struct intel_display *display, int min_cdclk)
|
|
{
|
|
const struct intel_cdclk_vals *table = display->cdclk.table;
|
|
int i;
|
|
|
|
for (i = 0; table[i].refclk; i++)
|
|
if (table[i].refclk == display->cdclk.hw.ref &&
|
|
table[i].cdclk >= min_cdclk)
|
|
return table[i].cdclk;
|
|
|
|
drm_WARN(display->drm, 1,
|
|
"Cannot satisfy minimum cdclk %d with refclk %u\n",
|
|
min_cdclk, display->cdclk.hw.ref);
|
|
return display->cdclk.max_cdclk_freq;
|
|
}
|
|
|
|
static int bxt_calc_cdclk_pll_vco(struct intel_display *display, int cdclk)
|
|
{
|
|
const struct intel_cdclk_vals *table = display->cdclk.table;
|
|
int i;
|
|
|
|
if (cdclk == display->cdclk.hw.bypass)
|
|
return 0;
|
|
|
|
for (i = 0; table[i].refclk; i++)
|
|
if (table[i].refclk == display->cdclk.hw.ref &&
|
|
table[i].cdclk == cdclk)
|
|
return display->cdclk.hw.ref * table[i].ratio;
|
|
|
|
drm_WARN(display->drm, 1, "cdclk %d not valid for refclk %u\n",
|
|
cdclk, display->cdclk.hw.ref);
|
|
return 0;
|
|
}
|
|
|
|
static u8 bxt_calc_voltage_level(int cdclk)
|
|
{
|
|
return DIV_ROUND_UP(cdclk, 25000);
|
|
}
|
|
|
|
static u8 calc_voltage_level(int cdclk, int num_voltage_levels,
|
|
const int voltage_level_max_cdclk[])
|
|
{
|
|
int voltage_level;
|
|
|
|
for (voltage_level = 0; voltage_level < num_voltage_levels; voltage_level++) {
|
|
if (cdclk <= voltage_level_max_cdclk[voltage_level])
|
|
return voltage_level;
|
|
}
|
|
|
|
MISSING_CASE(cdclk);
|
|
return num_voltage_levels - 1;
|
|
}
|
|
|
|
static u8 icl_calc_voltage_level(int cdclk)
|
|
{
|
|
static const int icl_voltage_level_max_cdclk[] = {
|
|
[0] = 312000,
|
|
[1] = 556800,
|
|
[2] = 652800,
|
|
};
|
|
|
|
return calc_voltage_level(cdclk,
|
|
ARRAY_SIZE(icl_voltage_level_max_cdclk),
|
|
icl_voltage_level_max_cdclk);
|
|
}
|
|
|
|
static u8 ehl_calc_voltage_level(int cdclk)
|
|
{
|
|
static const int ehl_voltage_level_max_cdclk[] = {
|
|
[0] = 180000,
|
|
[1] = 312000,
|
|
[2] = 326400,
|
|
/*
|
|
* Bspec lists the limit as 556.8 MHz, but some JSL
|
|
* development boards (at least) boot with 652.8 MHz
|
|
*/
|
|
[3] = 652800,
|
|
};
|
|
|
|
return calc_voltage_level(cdclk,
|
|
ARRAY_SIZE(ehl_voltage_level_max_cdclk),
|
|
ehl_voltage_level_max_cdclk);
|
|
}
|
|
|
|
static u8 tgl_calc_voltage_level(int cdclk)
|
|
{
|
|
static const int tgl_voltage_level_max_cdclk[] = {
|
|
[0] = 312000,
|
|
[1] = 326400,
|
|
[2] = 556800,
|
|
[3] = 652800,
|
|
};
|
|
|
|
return calc_voltage_level(cdclk,
|
|
ARRAY_SIZE(tgl_voltage_level_max_cdclk),
|
|
tgl_voltage_level_max_cdclk);
|
|
}
|
|
|
|
static u8 rplu_calc_voltage_level(int cdclk)
|
|
{
|
|
static const int rplu_voltage_level_max_cdclk[] = {
|
|
[0] = 312000,
|
|
[1] = 480000,
|
|
[2] = 556800,
|
|
[3] = 652800,
|
|
};
|
|
|
|
return calc_voltage_level(cdclk,
|
|
ARRAY_SIZE(rplu_voltage_level_max_cdclk),
|
|
rplu_voltage_level_max_cdclk);
|
|
}
|
|
|
|
static u8 xe3lpd_calc_voltage_level(int cdclk)
|
|
{
|
|
/*
|
|
* Starting with xe3lpd power controller does not need the voltage
|
|
* index when doing the modeset update. This function is best left
|
|
* defined but returning 0 to the mask.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
static void icl_readout_refclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
u32 dssm = intel_de_read(display, SKL_DSSM) & ICL_DSSM_CDCLK_PLL_REFCLK_MASK;
|
|
|
|
switch (dssm) {
|
|
default:
|
|
MISSING_CASE(dssm);
|
|
fallthrough;
|
|
case ICL_DSSM_CDCLK_PLL_REFCLK_24MHz:
|
|
cdclk_config->ref = 24000;
|
|
break;
|
|
case ICL_DSSM_CDCLK_PLL_REFCLK_19_2MHz:
|
|
cdclk_config->ref = 19200;
|
|
break;
|
|
case ICL_DSSM_CDCLK_PLL_REFCLK_38_4MHz:
|
|
cdclk_config->ref = 38400;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void bxt_de_pll_readout(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
u32 val, ratio;
|
|
|
|
if (display->platform.dg2)
|
|
cdclk_config->ref = 38400;
|
|
else if (DISPLAY_VER(display) >= 11)
|
|
icl_readout_refclk(display, cdclk_config);
|
|
else
|
|
cdclk_config->ref = 19200;
|
|
|
|
val = intel_de_read(display, BXT_DE_PLL_ENABLE);
|
|
if ((val & BXT_DE_PLL_PLL_ENABLE) == 0 ||
|
|
(val & BXT_DE_PLL_LOCK) == 0) {
|
|
/*
|
|
* CDCLK PLL is disabled, the VCO/ratio doesn't matter, but
|
|
* setting it to zero is a way to signal that.
|
|
*/
|
|
cdclk_config->vco = 0;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* DISPLAY_VER >= 11 have the ratio directly in the PLL enable register,
|
|
* gen9lp had it in a separate PLL control register.
|
|
*/
|
|
if (DISPLAY_VER(display) >= 11)
|
|
ratio = val & ICL_CDCLK_PLL_RATIO_MASK;
|
|
else
|
|
ratio = intel_de_read(display, BXT_DE_PLL_CTL) & BXT_DE_PLL_RATIO_MASK;
|
|
|
|
cdclk_config->vco = ratio * cdclk_config->ref;
|
|
}
|
|
|
|
static void bxt_get_cdclk(struct intel_display *display,
|
|
struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
u32 squash_ctl = 0;
|
|
u32 divider;
|
|
int div;
|
|
|
|
bxt_de_pll_readout(display, cdclk_config);
|
|
|
|
if (DISPLAY_VER(display) >= 12)
|
|
cdclk_config->bypass = cdclk_config->ref / 2;
|
|
else if (DISPLAY_VER(display) >= 11)
|
|
cdclk_config->bypass = 50000;
|
|
else
|
|
cdclk_config->bypass = cdclk_config->ref;
|
|
|
|
if (cdclk_config->vco == 0) {
|
|
cdclk_config->cdclk = cdclk_config->bypass;
|
|
goto out;
|
|
}
|
|
|
|
divider = intel_de_read(display, CDCLK_CTL) & BXT_CDCLK_CD2X_DIV_SEL_MASK;
|
|
|
|
switch (divider) {
|
|
case BXT_CDCLK_CD2X_DIV_SEL_1:
|
|
div = 2;
|
|
break;
|
|
case BXT_CDCLK_CD2X_DIV_SEL_1_5:
|
|
div = 3;
|
|
break;
|
|
case BXT_CDCLK_CD2X_DIV_SEL_2:
|
|
div = 4;
|
|
break;
|
|
case BXT_CDCLK_CD2X_DIV_SEL_4:
|
|
div = 8;
|
|
break;
|
|
default:
|
|
MISSING_CASE(divider);
|
|
return;
|
|
}
|
|
|
|
if (HAS_CDCLK_SQUASH(display))
|
|
squash_ctl = intel_de_read(display, CDCLK_SQUASH_CTL);
|
|
|
|
if (squash_ctl & CDCLK_SQUASH_ENABLE) {
|
|
u16 waveform;
|
|
int size;
|
|
|
|
size = REG_FIELD_GET(CDCLK_SQUASH_WINDOW_SIZE_MASK, squash_ctl) + 1;
|
|
waveform = REG_FIELD_GET(CDCLK_SQUASH_WAVEFORM_MASK, squash_ctl) >> (16 - size);
|
|
|
|
cdclk_config->cdclk = DIV_ROUND_CLOSEST(hweight16(waveform) *
|
|
cdclk_config->vco, size * div);
|
|
} else {
|
|
cdclk_config->cdclk = DIV_ROUND_CLOSEST(cdclk_config->vco, div);
|
|
}
|
|
|
|
out:
|
|
if (DISPLAY_VER(display) >= 20)
|
|
cdclk_config->joined_mbus = intel_de_read(display, MBUS_CTL) & MBUS_JOIN;
|
|
/*
|
|
* Can't read this out :( Let's assume it's
|
|
* at least what the CDCLK frequency requires.
|
|
*/
|
|
cdclk_config->voltage_level =
|
|
intel_cdclk_calc_voltage_level(display, cdclk_config->cdclk);
|
|
}
|
|
|
|
static void bxt_de_pll_disable(struct intel_display *display)
|
|
{
|
|
intel_de_write(display, BXT_DE_PLL_ENABLE, 0);
|
|
|
|
/* Timeout 200us */
|
|
if (intel_de_wait_for_clear_ms(display,
|
|
BXT_DE_PLL_ENABLE, BXT_DE_PLL_LOCK, 1))
|
|
drm_err(display->drm, "timeout waiting for DE PLL unlock\n");
|
|
|
|
display->cdclk.hw.vco = 0;
|
|
}
|
|
|
|
static void bxt_de_pll_enable(struct intel_display *display, int vco)
|
|
{
|
|
int ratio = DIV_ROUND_CLOSEST(vco, display->cdclk.hw.ref);
|
|
|
|
intel_de_rmw(display, BXT_DE_PLL_CTL,
|
|
BXT_DE_PLL_RATIO_MASK, BXT_DE_PLL_RATIO(ratio));
|
|
|
|
intel_de_write(display, BXT_DE_PLL_ENABLE, BXT_DE_PLL_PLL_ENABLE);
|
|
|
|
/* Timeout 200us */
|
|
if (intel_de_wait_for_set_ms(display,
|
|
BXT_DE_PLL_ENABLE, BXT_DE_PLL_LOCK, 1))
|
|
drm_err(display->drm, "timeout waiting for DE PLL lock\n");
|
|
|
|
display->cdclk.hw.vco = vco;
|
|
}
|
|
|
|
static void icl_cdclk_pll_disable(struct intel_display *display)
|
|
{
|
|
/*
|
|
* Wa_13012396614:
|
|
* Fixes: A sporadic race condition between MDCLK selection and PLL
|
|
* enabling.
|
|
* Workaround:
|
|
* Change programming of MDCLK source selection in CDCLK_CTL:
|
|
* - When disabling the CDCLK PLL, first set MDCLK source to be CD2XCLK.
|
|
* - When enabling the CDCLK PLL, update MDCLK source selection only
|
|
* after the PLL is enabled (which is already done as part of the
|
|
* normal flow of _bxt_set_cdclk()).
|
|
*/
|
|
if (intel_display_wa(display, 13012396614))
|
|
intel_de_rmw(display, CDCLK_CTL, MDCLK_SOURCE_SEL_MASK, MDCLK_SOURCE_SEL_CD2XCLK);
|
|
|
|
intel_de_rmw(display, BXT_DE_PLL_ENABLE,
|
|
BXT_DE_PLL_PLL_ENABLE, 0);
|
|
|
|
/* Timeout 200us */
|
|
if (intel_de_wait_for_clear_ms(display, BXT_DE_PLL_ENABLE, BXT_DE_PLL_LOCK, 1))
|
|
drm_err(display->drm, "timeout waiting for CDCLK PLL unlock\n");
|
|
|
|
display->cdclk.hw.vco = 0;
|
|
}
|
|
|
|
static void icl_cdclk_pll_enable(struct intel_display *display, int vco)
|
|
{
|
|
int ratio = DIV_ROUND_CLOSEST(vco, display->cdclk.hw.ref);
|
|
u32 val;
|
|
|
|
val = ICL_CDCLK_PLL_RATIO(ratio);
|
|
intel_de_write(display, BXT_DE_PLL_ENABLE, val);
|
|
|
|
val |= BXT_DE_PLL_PLL_ENABLE;
|
|
intel_de_write(display, BXT_DE_PLL_ENABLE, val);
|
|
|
|
/* Timeout 200us */
|
|
if (intel_de_wait_for_set_ms(display, BXT_DE_PLL_ENABLE, BXT_DE_PLL_LOCK, 1))
|
|
drm_err(display->drm, "timeout waiting for CDCLK PLL lock\n");
|
|
|
|
display->cdclk.hw.vco = vco;
|
|
}
|
|
|
|
static void adlp_cdclk_pll_crawl(struct intel_display *display, int vco)
|
|
{
|
|
int ratio = DIV_ROUND_CLOSEST(vco, display->cdclk.hw.ref);
|
|
u32 val;
|
|
|
|
/* Write PLL ratio without disabling */
|
|
val = ICL_CDCLK_PLL_RATIO(ratio) | BXT_DE_PLL_PLL_ENABLE;
|
|
intel_de_write(display, BXT_DE_PLL_ENABLE, val);
|
|
|
|
/* Submit freq change request */
|
|
val |= BXT_DE_PLL_FREQ_REQ;
|
|
intel_de_write(display, BXT_DE_PLL_ENABLE, val);
|
|
|
|
/* Timeout 200us */
|
|
if (intel_de_wait_for_set_ms(display, BXT_DE_PLL_ENABLE,
|
|
BXT_DE_PLL_LOCK | BXT_DE_PLL_FREQ_REQ_ACK, 1))
|
|
drm_err(display->drm, "timeout waiting for FREQ change request ack\n");
|
|
|
|
val &= ~BXT_DE_PLL_FREQ_REQ;
|
|
intel_de_write(display, BXT_DE_PLL_ENABLE, val);
|
|
|
|
display->cdclk.hw.vco = vco;
|
|
}
|
|
|
|
static u32 bxt_cdclk_cd2x_pipe(struct intel_display *display, enum pipe pipe)
|
|
{
|
|
if (DISPLAY_VER(display) >= 12) {
|
|
if (pipe == INVALID_PIPE)
|
|
return TGL_CDCLK_CD2X_PIPE_NONE;
|
|
else
|
|
return TGL_CDCLK_CD2X_PIPE(pipe);
|
|
} else if (DISPLAY_VER(display) >= 11) {
|
|
if (pipe == INVALID_PIPE)
|
|
return ICL_CDCLK_CD2X_PIPE_NONE;
|
|
else
|
|
return ICL_CDCLK_CD2X_PIPE(pipe);
|
|
} else {
|
|
if (pipe == INVALID_PIPE)
|
|
return BXT_CDCLK_CD2X_PIPE_NONE;
|
|
else
|
|
return BXT_CDCLK_CD2X_PIPE(pipe);
|
|
}
|
|
}
|
|
|
|
static u32 bxt_cdclk_cd2x_div_sel(struct intel_display *display,
|
|
int cdclk, int vco, u16 waveform)
|
|
{
|
|
u32 ret;
|
|
|
|
/* cdclk = vco / 2 / div{1,1.5,2,4} */
|
|
switch (cdclk_divider(cdclk, vco, waveform)) {
|
|
default:
|
|
drm_WARN_ON(display->drm,
|
|
cdclk != display->cdclk.hw.bypass);
|
|
drm_WARN_ON(display->drm, vco != 0);
|
|
fallthrough;
|
|
case 2:
|
|
ret = BXT_CDCLK_CD2X_DIV_SEL_1;
|
|
break;
|
|
case 3:
|
|
ret = BXT_CDCLK_CD2X_DIV_SEL_1_5;
|
|
break;
|
|
case 4:
|
|
ret = BXT_CDCLK_CD2X_DIV_SEL_2;
|
|
break;
|
|
case 8:
|
|
ret = BXT_CDCLK_CD2X_DIV_SEL_4;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* On Xe3_LPD onward, the expectation is to always have
|
|
* BXT_CDCLK_CD2X_DIV_SEL_1 as the default.
|
|
*/
|
|
if (DISPLAY_VER(display) >= 30)
|
|
drm_WARN_ON(display->drm, ret != BXT_CDCLK_CD2X_DIV_SEL_1);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static u16 cdclk_squash_waveform(struct intel_display *display,
|
|
int cdclk)
|
|
{
|
|
const struct intel_cdclk_vals *table = display->cdclk.table;
|
|
int i;
|
|
|
|
if (cdclk == display->cdclk.hw.bypass)
|
|
return 0;
|
|
|
|
for (i = 0; table[i].refclk; i++)
|
|
if (table[i].refclk == display->cdclk.hw.ref &&
|
|
table[i].cdclk == cdclk)
|
|
return table[i].waveform;
|
|
|
|
drm_WARN(display->drm, 1, "cdclk %d not valid for refclk %u\n",
|
|
cdclk, display->cdclk.hw.ref);
|
|
|
|
return 0xffff;
|
|
}
|
|
|
|
static void icl_cdclk_pll_update(struct intel_display *display, int vco)
|
|
{
|
|
if (display->cdclk.hw.vco != 0 &&
|
|
display->cdclk.hw.vco != vco)
|
|
icl_cdclk_pll_disable(display);
|
|
|
|
if (display->cdclk.hw.vco != vco)
|
|
icl_cdclk_pll_enable(display, vco);
|
|
}
|
|
|
|
static void bxt_cdclk_pll_update(struct intel_display *display, int vco)
|
|
{
|
|
if (display->cdclk.hw.vco != 0 &&
|
|
display->cdclk.hw.vco != vco)
|
|
bxt_de_pll_disable(display);
|
|
|
|
if (display->cdclk.hw.vco != vco)
|
|
bxt_de_pll_enable(display, vco);
|
|
}
|
|
|
|
static void dg2_cdclk_squash_program(struct intel_display *display,
|
|
u16 waveform)
|
|
{
|
|
u32 squash_ctl = 0;
|
|
|
|
if (waveform)
|
|
squash_ctl = CDCLK_SQUASH_ENABLE |
|
|
CDCLK_SQUASH_WINDOW_SIZE(0xf) | waveform;
|
|
|
|
intel_de_write(display, CDCLK_SQUASH_CTL, squash_ctl);
|
|
}
|
|
|
|
static bool cdclk_pll_is_unknown(unsigned int vco)
|
|
{
|
|
/*
|
|
* Ensure driver does not take the crawl path for the
|
|
* case when the vco is set to ~0 in the
|
|
* sanitize path.
|
|
*/
|
|
return vco == ~0;
|
|
}
|
|
|
|
static bool mdclk_source_is_cdclk_pll(struct intel_display *display)
|
|
{
|
|
return DISPLAY_VER(display) >= 20;
|
|
}
|
|
|
|
static u32 xe2lpd_mdclk_source_sel(struct intel_display *display)
|
|
{
|
|
if (mdclk_source_is_cdclk_pll(display))
|
|
return MDCLK_SOURCE_SEL_CDCLK_PLL;
|
|
|
|
return MDCLK_SOURCE_SEL_CD2XCLK;
|
|
}
|
|
|
|
int intel_mdclk_cdclk_ratio(struct intel_display *display,
|
|
const struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
if (mdclk_source_is_cdclk_pll(display))
|
|
return DIV_ROUND_UP(cdclk_config->vco, cdclk_config->cdclk);
|
|
|
|
/* Otherwise, source for MDCLK is CD2XCLK. */
|
|
return 2;
|
|
}
|
|
|
|
static void xe2lpd_mdclk_cdclk_ratio_program(struct intel_display *display,
|
|
const struct intel_cdclk_config *cdclk_config)
|
|
{
|
|
intel_dbuf_mdclk_cdclk_ratio_update(display,
|
|
intel_mdclk_cdclk_ratio(display, cdclk_config),
|
|
cdclk_config->joined_mbus);
|
|
}
|
|
|
|
static bool cdclk_compute_crawl_and_squash_midpoint(struct intel_display *display,
|
|
const struct intel_cdclk_config *old_cdclk_config,
|
|
const struct intel_cdclk_config *new_cdclk_config,
|
|
struct intel_cdclk_config *mid_cdclk_config)
|
|
{
|
|
u16 old_waveform, new_waveform, mid_waveform;
|
|
int old_div, new_div, mid_div;
|
|
|
|
/* Return if PLL is in an unknown state, force a complete disable and re-enable. */
|
|
if (cdclk_pll_is_unknown(old_cdclk_config->vco))
|
|
return false;
|
|
|
|
/* Return if both Squash and Crawl are not present */
|
|
if (!HAS_CDCLK_CRAWL(display) || !HAS_CDCLK_SQUASH(display))
|
|
return false;
|
|
|
|
old_waveform = cdclk_squash_waveform(display, old_cdclk_config->cdclk);
|
|
new_waveform = cdclk_squash_waveform(display, new_cdclk_config->cdclk);
|
|
|
|
/* Return if Squash only or Crawl only is the desired action */
|
|
if (old_cdclk_config->vco == 0 || new_cdclk_config->vco == 0 ||
|
|
old_cdclk_config->vco == new_cdclk_config->vco ||
|
|
old_waveform == new_waveform)
|
|
return false;
|
|
|
|
old_div = cdclk_divider(old_cdclk_config->cdclk,
|
|
old_cdclk_config->vco, old_waveform);
|
|
new_div = cdclk_divider(new_cdclk_config->cdclk,
|
|
new_cdclk_config->vco, new_waveform);
|
|
|
|
/*
|
|
* Should not happen currently. We might need more midpoint
|
|
* transitions if we need to also change the cd2x divider.
|
|
*/
|
|
if (drm_WARN_ON(display->drm, old_div != new_div))
|
|
return false;
|
|
|
|
*mid_cdclk_config = *new_cdclk_config;
|
|
|
|
/*
|
|
* Populate the mid_cdclk_config accordingly.
|
|
* - If moving to a higher cdclk, the desired action is squashing.
|
|
* The mid cdclk config should have the new (squash) waveform.
|
|
* - If moving to a lower cdclk, the desired action is crawling.
|
|
* The mid cdclk config should have the new vco.
|
|
*/
|
|
|
|
if (cdclk_squash_divider(new_waveform) > cdclk_squash_divider(old_waveform)) {
|
|
mid_cdclk_config->vco = old_cdclk_config->vco;
|
|
mid_div = old_div;
|
|
mid_waveform = new_waveform;
|
|
} else {
|
|
mid_cdclk_config->vco = new_cdclk_config->vco;
|
|
mid_div = new_div;
|
|
mid_waveform = old_waveform;
|
|
}
|
|
|
|
mid_cdclk_config->cdclk = DIV_ROUND_CLOSEST(cdclk_squash_divider(mid_waveform) *
|
|
mid_cdclk_config->vco,
|
|
cdclk_squash_len * mid_div);
|
|
|
|
/* make sure the mid clock came out sane */
|
|
|
|
drm_WARN_ON(display->drm, mid_cdclk_config->cdclk <
|
|
min(old_cdclk_config->cdclk, new_cdclk_config->cdclk));
|
|
drm_WARN_ON(display->drm, mid_cdclk_config->cdclk >
|
|
display->cdclk.max_cdclk_freq);
|
|
drm_WARN_ON(display->drm, cdclk_squash_waveform(display, mid_cdclk_config->cdclk) !=
|
|
mid_waveform);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool pll_enable_wa_needed(struct intel_display *display)
|
|
{
|
|
return (DISPLAY_VERx100(display) == 2000 ||
|
|
DISPLAY_VERx100(display) == 1400 ||
|
|
display->platform.dg2) &&
|
|
display->cdclk.hw.vco > 0;
|
|
}
|
|
|
|
static u32 bxt_cdclk_ctl(struct intel_display *display,
|
|
const struct intel_cdclk_config *cdclk_config,
|
|
enum pipe pipe)
|
|
{
|
|
int cdclk = cdclk_config->cdclk;
|
|
int vco = cdclk_config->vco;
|
|
u16 waveform;
|
|
u32 val;
|
|
|
|
waveform = cdclk_squash_waveform(display, cdclk);
|
|
|
|
val = bxt_cdclk_cd2x_div_sel(display, cdclk, vco, waveform);
|
|
|
|
if (DISPLAY_VER(display) < 30)
|
|
val |= bxt_cdclk_cd2x_pipe(display, pipe);
|
|
|
|
/*
|
|
* Disable SSA Precharge when CD clock frequency < 500 MHz,
|
|
* enable otherwise.
|
|
*/
|
|
if ((display->platform.geminilake || display->platform.broxton) &&
|
|
cdclk >= 500000)
|
|
val |= BXT_CDCLK_SSA_PRECHARGE_ENABLE;
|
|
|
|
if (DISPLAY_VER(display) >= 20) {
|
|
/*
|
|
* Wa_13012396614 requires selecting CD2XCLK as MDCLK source
|
|
* prior to disabling the PLL, which is already handled by
|
|
* icl_cdclk_pll_disable(). Here we are just making sure
|
|
* we keep the expected value.
|
|
*/
|
|
if (intel_display_wa(display, 13012396614) && vco == 0)
|
|
val |= MDCLK_SOURCE_SEL_CD2XCLK;
|
|
else
|
|
val |= xe2lpd_mdclk_source_sel(display);
|
|
} else {
|
|
val |= skl_cdclk_decimal(cdclk);
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static void _bxt_set_cdclk(struct intel_display *display,
|
|
const struct intel_cdclk_config *cdclk_config,
|
|
enum pipe pipe)
|
|
{
|
|
int cdclk = cdclk_config->cdclk;
|
|
int vco = cdclk_config->vco;
|
|
|
|
if (HAS_CDCLK_CRAWL(display) && display->cdclk.hw.vco > 0 && vco > 0 &&
|
|
!cdclk_pll_is_unknown(display->cdclk.hw.vco)) {
|
|
if (display->cdclk.hw.vco != vco)
|
|
adlp_cdclk_pll_crawl(display, vco);
|
|
} else if (DISPLAY_VER(display) >= 11) {
|
|
/* wa_15010685871: dg2, mtl */
|
|
if (pll_enable_wa_needed(display))
|
|
dg2_cdclk_squash_program(display, 0);
|
|
|
|
icl_cdclk_pll_update(display, vco);
|
|
} else {
|
|
bxt_cdclk_pll_update(display, vco);
|
|
}
|
|
|
|
if (HAS_CDCLK_SQUASH(display)) {
|
|
u16 waveform = cdclk_squash_waveform(display, cdclk);
|
|
|
|
dg2_cdclk_squash_program(display, waveform);
|
|
}
|
|
|
|
intel_de_write(display, CDCLK_CTL, bxt_cdclk_ctl(display, cdclk_config, pipe));
|
|
|
|
if (pipe != INVALID_PIPE)
|
|
intel_crtc_wait_for_next_vblank(intel_crtc_for_pipe(display, pipe));
|
|
}
|
|
|
|
static void bxt_set_cdclk(struct intel_display *display,
|
|
const struct intel_cdclk_config *cdclk_config,
|
|
enum pipe pipe)
|
|
{
|
|
struct intel_cdclk_config mid_cdclk_config;
|
|
int cdclk = cdclk_config->cdclk;
|
|
int ret = 0;
|
|
|
|
/*
|
|
* Inform power controller of upcoming frequency change.
|
|
* Display versions 14 and beyond do not follow the PUnit
|
|
* mailbox communication, skip
|
|
* this step.
|
|
*/
|
|
if (DISPLAY_VER(display) >= 14 || display->platform.dg2)
|
|
; /* NOOP */
|
|
else if (DISPLAY_VER(display) >= 11)
|
|
ret = intel_pcode_request(display->drm, SKL_PCODE_CDCLK_CONTROL,
|
|
SKL_CDCLK_PREPARE_FOR_CHANGE,
|
|
SKL_CDCLK_READY_FOR_CHANGE,
|
|
SKL_CDCLK_READY_FOR_CHANGE, 3);
|
|
else
|
|
/*
|
|
* BSpec requires us to wait up to 150usec, but that leads to
|
|
* timeouts; the 2ms used here is based on experiment.
|
|
*/
|
|
ret = intel_pcode_write_timeout(display->drm,
|
|
HSW_PCODE_DE_WRITE_FREQ_REQ,
|
|
0x80000000, 2);
|
|
|
|
if (ret) {
|
|
drm_err(display->drm,
|
|
"Failed to inform PCU about cdclk change (err %d, freq %d)\n",
|
|
ret, cdclk);
|
|
return;
|
|
}
|
|
|
|
if (DISPLAY_VER(display) >= 20 && cdclk < display->cdclk.hw.cdclk)
|
|
xe2lpd_mdclk_cdclk_ratio_program(display, cdclk_config);
|
|
|
|
if (cdclk_compute_crawl_and_squash_midpoint(display, &display->cdclk.hw,
|
|
cdclk_config, &mid_cdclk_config)) {
|
|
_bxt_set_cdclk(display, &mid_cdclk_config, pipe);
|
|
_bxt_set_cdclk(display, cdclk_config, pipe);
|
|
} else {
|
|
_bxt_set_cdclk(display, cdclk_config, pipe);
|
|
}
|
|
|
|
if (DISPLAY_VER(display) >= 20 && cdclk > display->cdclk.hw.cdclk)
|
|
xe2lpd_mdclk_cdclk_ratio_program(display, cdclk_config);
|
|
|
|
if (DISPLAY_VER(display) >= 14)
|
|
/*
|
|
* NOOP - No Pcode communication needed for
|
|
* Display versions 14 and beyond
|
|
*/;
|
|
else if (DISPLAY_VER(display) >= 11 && !display->platform.dg2)
|
|
ret = intel_pcode_write(display->drm, SKL_PCODE_CDCLK_CONTROL,
|
|
cdclk_config->voltage_level);
|
|
if (DISPLAY_VER(display) < 11) {
|
|
/*
|
|
* The timeout isn't specified, the 2ms used here is based on
|
|
* experiment.
|
|
* FIXME: Waiting for the request completion could be delayed
|
|
* until the next PCODE request based on BSpec.
|
|
*/
|
|
ret = intel_pcode_write_timeout(display->drm,
|
|
HSW_PCODE_DE_WRITE_FREQ_REQ,
|
|
cdclk_config->voltage_level, 2);
|
|
}
|
|
if (ret) {
|
|
drm_err(display->drm,
|
|
"PCode CDCLK freq set failed, (err %d, freq %d)\n",
|
|
ret, cdclk);
|
|
return;
|
|
}
|
|
|
|
intel_update_cdclk(display);
|
|
|
|
if (DISPLAY_VER(display) >= 11)
|
|
/*
|
|
* Can't read out the voltage level :(
|
|
* Let's just assume everything is as expected.
|
|
*/
|
|
display->cdclk.hw.voltage_level = cdclk_config->voltage_level;
|
|
}
|
|
|
|
static void bxt_sanitize_cdclk(struct intel_display *display)
|
|
{
|
|
u32 cdctl, expected;
|
|
int cdclk, vco;
|
|
|
|
intel_update_cdclk(display);
|
|
intel_cdclk_dump_config(display, &display->cdclk.hw, "Current CDCLK");
|
|
|
|
if (display->cdclk.hw.vco == 0 ||
|
|
display->cdclk.hw.cdclk == display->cdclk.hw.bypass)
|
|
goto sanitize;
|
|
|
|
/* Make sure this is a legal cdclk value for the platform */
|
|
cdclk = bxt_calc_cdclk(display, display->cdclk.hw.cdclk);
|
|
if (cdclk != display->cdclk.hw.cdclk)
|
|
goto sanitize;
|
|
|
|
/* Make sure the VCO is correct for the cdclk */
|
|
vco = bxt_calc_cdclk_pll_vco(display, cdclk);
|
|
if (vco != display->cdclk.hw.vco)
|
|
goto sanitize;
|
|
|
|
/*
|
|
* Some BIOS versions leave an incorrect decimal frequency value and
|
|
* set reserved MBZ bits in CDCLK_CTL at least during exiting from S4,
|
|
* so sanitize this register.
|
|
*/
|
|
cdctl = intel_de_read(display, CDCLK_CTL);
|
|
expected = bxt_cdclk_ctl(display, &display->cdclk.hw, INVALID_PIPE);
|
|
|
|
/*
|
|
* Let's ignore the pipe field, since BIOS could have configured the
|
|
* dividers both syncing to an active pipe, or asynchronously
|
|
* (PIPE_NONE).
|
|
*/
|
|
cdctl &= ~bxt_cdclk_cd2x_pipe(display, INVALID_PIPE);
|
|
expected &= ~bxt_cdclk_cd2x_pipe(display, INVALID_PIPE);
|
|
|
|
if (cdctl == expected)
|
|
/* All well; nothing to sanitize */
|
|
return;
|
|
|
|
sanitize:
|
|
drm_dbg_kms(display->drm, "Sanitizing cdclk programmed by pre-os\n");
|
|
|
|
/* force cdclk programming */
|
|
display->cdclk.hw.cdclk = 0;
|
|
|
|
/* force full PLL disable + enable */
|
|
display->cdclk.hw.vco = ~0;
|
|
}
|
|
|
|
static void bxt_cdclk_init_hw(struct intel_display *display)
|
|
{
|
|
struct intel_cdclk_config cdclk_config;
|
|
|
|
bxt_sanitize_cdclk(display);
|
|
|
|
if (display->cdclk.hw.cdclk != 0 &&
|
|
display->cdclk.hw.vco != 0)
|
|
return;
|
|
|
|
cdclk_config = display->cdclk.hw;
|
|
|
|
/*
|
|
* FIXME:
|
|
* - The initial CDCLK needs to be read from VBT.
|
|
* Need to make this change after VBT has changes for BXT.
|
|
*/
|
|
cdclk_config.cdclk = bxt_calc_cdclk(display, 0);
|
|
cdclk_config.vco = bxt_calc_cdclk_pll_vco(display, cdclk_config.cdclk);
|
|
cdclk_config.voltage_level =
|
|
intel_cdclk_calc_voltage_level(display, cdclk_config.cdclk);
|
|
|
|
bxt_set_cdclk(display, &cdclk_config, INVALID_PIPE);
|
|
}
|
|
|
|
static void bxt_cdclk_uninit_hw(struct intel_display *display)
|
|
{
|
|
struct intel_cdclk_config cdclk_config = display->cdclk.hw;
|
|
|
|
cdclk_config.cdclk = cdclk_config.bypass;
|
|
cdclk_config.vco = 0;
|
|
cdclk_config.voltage_level =
|
|
intel_cdclk_calc_voltage_level(display, cdclk_config.cdclk);
|
|
|
|
bxt_set_cdclk(display, &cdclk_config, INVALID_PIPE);
|
|
}
|
|
|
|
/**
|
|
* intel_cdclk_init_hw - Initialize CDCLK hardware
|
|
* @display: display instance
|
|
*
|
|
* Initialize CDCLK. This consists mainly of initializing display->cdclk.hw and
|
|
* sanitizing the state of the hardware if needed. This is generally done only
|
|
* during the display core initialization sequence, after which the DMC will
|
|
* take care of turning CDCLK off/on as needed.
|
|
*/
|
|
void intel_cdclk_init_hw(struct intel_display *display)
|
|
{
|
|
if (DISPLAY_VER(display) >= 10 || display->platform.broxton)
|
|
bxt_cdclk_init_hw(display);
|
|
else if (DISPLAY_VER(display) == 9)
|
|
skl_cdclk_init_hw(display);
|
|
}
|
|
|
|
/**
|
|
* intel_cdclk_uninit_hw - Uninitialize CDCLK hardware
|
|
* @display: display instance
|
|
*
|
|
* Uninitialize CDCLK. This is done only during the display core
|
|
* uninitialization sequence.
|
|
*/
|
|
void intel_cdclk_uninit_hw(struct intel_display *display)
|
|
{
|
|
if (DISPLAY_VER(display) >= 10 || display->platform.broxton)
|
|
bxt_cdclk_uninit_hw(display);
|
|
else if (DISPLAY_VER(display) == 9)
|
|
skl_cdclk_uninit_hw(display);
|
|
}
|
|
|
|
static bool intel_cdclk_can_crawl_and_squash(struct intel_display *display,
|
|
const struct intel_cdclk_config *a,
|
|
const struct intel_cdclk_config *b)
|
|
{
|
|
u16 old_waveform;
|
|
u16 new_waveform;
|
|
|
|
drm_WARN_ON(display->drm, cdclk_pll_is_unknown(a->vco));
|
|
|
|
if (a->vco == 0 || b->vco == 0)
|
|
return false;
|
|
|
|
if (!HAS_CDCLK_CRAWL(display) || !HAS_CDCLK_SQUASH(display))
|
|
return false;
|
|
|
|
old_waveform = cdclk_squash_waveform(display, a->cdclk);
|
|
new_waveform = cdclk_squash_waveform(display, b->cdclk);
|
|
|
|
return a->vco != b->vco &&
|
|
old_waveform != new_waveform;
|
|
}
|
|
|
|
static bool intel_cdclk_can_crawl(struct intel_display *display,
|
|
const struct intel_cdclk_config *a,
|
|
const struct intel_cdclk_config *b)
|
|
{
|
|
int a_div, b_div;
|
|
|
|
if (!HAS_CDCLK_CRAWL(display))
|
|
return false;
|
|
|
|
/*
|
|
* The vco and cd2x divider will change independently
|
|
* from each, so we disallow cd2x change when crawling.
|
|
*/
|
|
a_div = DIV_ROUND_CLOSEST(a->vco, a->cdclk);
|
|
b_div = DIV_ROUND_CLOSEST(b->vco, b->cdclk);
|
|
|
|
return a->vco != 0 && b->vco != 0 &&
|
|
a->vco != b->vco &&
|
|
a_div == b_div &&
|
|
a->ref == b->ref;
|
|
}
|
|
|
|
static bool intel_cdclk_can_squash(struct intel_display *display,
|
|
const struct intel_cdclk_config *a,
|
|
const struct intel_cdclk_config *b)
|
|
{
|
|
/*
|
|
* FIXME should store a bit more state in intel_cdclk_config
|
|
* to differentiate squasher vs. cd2x divider properly. For
|
|
* the moment all platforms with squasher use a fixed cd2x
|
|
* divider.
|
|
*/
|
|
if (!HAS_CDCLK_SQUASH(display))
|
|
return false;
|
|
|
|
return a->cdclk != b->cdclk &&
|
|
a->vco != 0 &&
|
|
a->vco == b->vco &&
|
|
a->ref == b->ref;
|
|
}
|
|
|
|
/**
|
|
* intel_cdclk_clock_changed - Check whether the clock changed
|
|
* @a: first CDCLK configuration
|
|
* @b: second CDCLK configuration
|
|
*
|
|
* Returns:
|
|
* True if CDCLK changed in a way that requires re-programming and
|
|
* False otherwise.
|
|
*/
|
|
bool intel_cdclk_clock_changed(const struct intel_cdclk_config *a,
|
|
const struct intel_cdclk_config *b)
|
|
{
|
|
return a->cdclk != b->cdclk ||
|
|
a->vco != b->vco ||
|
|
a->ref != b->ref;
|
|
}
|
|
|
|
/**
|
|
* intel_cdclk_can_cd2x_update - Determine if changing between the two CDCLK
|
|
* configurations requires only a cd2x divider update
|
|
* @display: display instance
|
|
* @a: first CDCLK configuration
|
|
* @b: second CDCLK configuration
|
|
*
|
|
* Returns:
|
|
* True if changing between the two CDCLK configurations
|
|
* can be done with just a cd2x divider update, false if not.
|
|
*/
|
|
static bool intel_cdclk_can_cd2x_update(struct intel_display *display,
|
|
const struct intel_cdclk_config *a,
|
|
const struct intel_cdclk_config *b)
|
|
{
|
|
/* Older hw doesn't have the capability */
|
|
if (DISPLAY_VER(display) < 10 && !display->platform.broxton)
|
|
return false;
|
|
|
|
/*
|
|
* FIXME should store a bit more state in intel_cdclk_config
|
|
* to differentiate squasher vs. cd2x divider properly. For
|
|
* the moment all platforms with squasher use a fixed cd2x
|
|
* divider.
|
|
*/
|
|
if (HAS_CDCLK_SQUASH(display))
|
|
return false;
|
|
|
|
return a->cdclk != b->cdclk &&
|
|
a->vco != 0 &&
|
|
a->vco == b->vco &&
|
|
a->ref == b->ref;
|
|
}
|
|
|
|
/**
|
|
* intel_cdclk_changed - Determine if two CDCLK configurations are different
|
|
* @a: first CDCLK configuration
|
|
* @b: second CDCLK configuration
|
|
*
|
|
* Returns:
|
|
* True if the CDCLK configurations don't match, false if they do.
|
|
*/
|
|
static bool intel_cdclk_changed(const struct intel_cdclk_config *a,
|
|
const struct intel_cdclk_config *b)
|
|
{
|
|
return intel_cdclk_clock_changed(a, b) ||
|
|
a->voltage_level != b->voltage_level;
|
|
}
|
|
|
|
void intel_cdclk_dump_config(struct intel_display *display,
|
|
const struct intel_cdclk_config *cdclk_config,
|
|
const char *context)
|
|
{
|
|
drm_dbg_kms(display->drm, "%s %d kHz, VCO %d kHz, ref %d kHz, bypass %d kHz, voltage level %d\n",
|
|
context, cdclk_config->cdclk, cdclk_config->vco,
|
|
cdclk_config->ref, cdclk_config->bypass,
|
|
cdclk_config->voltage_level);
|
|
}
|
|
|
|
static void intel_pcode_notify(struct intel_display *display,
|
|
u8 voltage_level,
|
|
u8 active_pipe_count,
|
|
u16 cdclk,
|
|
bool cdclk_update_valid,
|
|
bool pipe_count_update_valid)
|
|
{
|
|
int ret;
|
|
u32 update_mask = 0;
|
|
|
|
if (!display->platform.dg2)
|
|
return;
|
|
|
|
update_mask = DISPLAY_TO_PCODE_UPDATE_MASK(cdclk, active_pipe_count, voltage_level);
|
|
|
|
if (cdclk_update_valid)
|
|
update_mask |= DISPLAY_TO_PCODE_CDCLK_VALID;
|
|
|
|
if (pipe_count_update_valid)
|
|
update_mask |= DISPLAY_TO_PCODE_PIPE_COUNT_VALID;
|
|
|
|
ret = intel_pcode_request(display->drm, SKL_PCODE_CDCLK_CONTROL,
|
|
SKL_CDCLK_PREPARE_FOR_CHANGE |
|
|
update_mask,
|
|
SKL_CDCLK_READY_FOR_CHANGE,
|
|
SKL_CDCLK_READY_FOR_CHANGE, 3);
|
|
if (ret)
|
|
drm_err(display->drm,
|
|
"Failed to inform PCU about display config (err %d)\n",
|
|
ret);
|
|
}
|
|
|
|
static void intel_set_cdclk(struct intel_display *display,
|
|
const struct intel_cdclk_config *cdclk_config,
|
|
enum pipe pipe, const char *context)
|
|
{
|
|
struct intel_encoder *encoder;
|
|
|
|
if (!intel_cdclk_changed(&display->cdclk.hw, cdclk_config))
|
|
return;
|
|
|
|
if (drm_WARN_ON_ONCE(display->drm, !display->funcs.cdclk->set_cdclk))
|
|
return;
|
|
|
|
intel_cdclk_dump_config(display, cdclk_config, context);
|
|
|
|
for_each_intel_encoder_with_psr(display->drm, encoder) {
|
|
struct intel_dp *intel_dp = enc_to_intel_dp(encoder);
|
|
|
|
intel_psr_pause(intel_dp);
|
|
}
|
|
|
|
intel_audio_cdclk_change_pre(display);
|
|
|
|
/*
|
|
* Lock aux/gmbus while we change cdclk in case those
|
|
* functions use cdclk. Not all platforms/ports do,
|
|
* but we'll lock them all for simplicity.
|
|
*/
|
|
mutex_lock(&display->gmbus.mutex);
|
|
for_each_intel_dp(display->drm, encoder) {
|
|
struct intel_dp *intel_dp = enc_to_intel_dp(encoder);
|
|
|
|
mutex_lock_nest_lock(&intel_dp->aux.hw_mutex,
|
|
&display->gmbus.mutex);
|
|
}
|
|
|
|
intel_cdclk_set_cdclk(display, cdclk_config, pipe);
|
|
|
|
for_each_intel_dp(display->drm, encoder) {
|
|
struct intel_dp *intel_dp = enc_to_intel_dp(encoder);
|
|
|
|
mutex_unlock(&intel_dp->aux.hw_mutex);
|
|
}
|
|
mutex_unlock(&display->gmbus.mutex);
|
|
|
|
for_each_intel_encoder_with_psr(display->drm, encoder) {
|
|
struct intel_dp *intel_dp = enc_to_intel_dp(encoder);
|
|
|
|
intel_psr_resume(intel_dp);
|
|
}
|
|
|
|
intel_audio_cdclk_change_post(display);
|
|
|
|
if (drm_WARN(display->drm,
|
|
intel_cdclk_changed(&display->cdclk.hw, cdclk_config),
|
|
"cdclk state doesn't match!\n")) {
|
|
intel_cdclk_dump_config(display, &display->cdclk.hw, "[hw state]");
|
|
intel_cdclk_dump_config(display, cdclk_config, "[sw state]");
|
|
}
|
|
}
|
|
|
|
static bool dg2_power_well_count(struct intel_display *display,
|
|
const struct intel_cdclk_state *cdclk_state)
|
|
{
|
|
return display->platform.dg2 ? hweight8(cdclk_state->active_pipes) : 0;
|
|
}
|
|
|
|
static void intel_cdclk_pcode_pre_notify(struct intel_atomic_state *state)
|
|
{
|
|
struct intel_display *display = to_intel_display(state);
|
|
const struct intel_cdclk_state *old_cdclk_state =
|
|
intel_atomic_get_old_cdclk_state(state);
|
|
const struct intel_cdclk_state *new_cdclk_state =
|
|
intel_atomic_get_new_cdclk_state(state);
|
|
unsigned int cdclk = 0; u8 voltage_level, num_active_pipes = 0;
|
|
bool change_cdclk, update_pipe_count;
|
|
|
|
if (!intel_cdclk_changed(&old_cdclk_state->actual,
|
|
&new_cdclk_state->actual) &&
|
|
dg2_power_well_count(display, old_cdclk_state) ==
|
|
dg2_power_well_count(display, new_cdclk_state))
|
|
return;
|
|
|
|
/* According to "Sequence Before Frequency Change", voltage level set to 0x3 */
|
|
voltage_level = DISPLAY_TO_PCODE_VOLTAGE_MAX;
|
|
|
|
change_cdclk = new_cdclk_state->actual.cdclk != old_cdclk_state->actual.cdclk;
|
|
update_pipe_count = dg2_power_well_count(display, new_cdclk_state) >
|
|
dg2_power_well_count(display, old_cdclk_state);
|
|
|
|
/*
|
|
* According to "Sequence Before Frequency Change",
|
|
* if CDCLK is increasing, set bits 25:16 to upcoming CDCLK,
|
|
* if CDCLK is decreasing or not changing, set bits 25:16 to current CDCLK,
|
|
* which basically means we choose the maximum of old and new CDCLK, if we know both
|
|
*/
|
|
if (change_cdclk)
|
|
cdclk = max(new_cdclk_state->actual.cdclk, old_cdclk_state->actual.cdclk);
|
|
|
|
/*
|
|
* According to "Sequence For Pipe Count Change",
|
|
* if pipe count is increasing, set bits 25:16 to upcoming pipe count
|
|
* (power well is enabled)
|
|
* no action if it is decreasing, before the change
|
|
*/
|
|
if (update_pipe_count)
|
|
num_active_pipes = dg2_power_well_count(display, new_cdclk_state);
|
|
|
|
intel_pcode_notify(display, voltage_level, num_active_pipes, cdclk,
|
|
change_cdclk, update_pipe_count);
|
|
}
|
|
|
|
static void intel_cdclk_pcode_post_notify(struct intel_atomic_state *state)
|
|
{
|
|
struct intel_display *display = to_intel_display(state);
|
|
const struct intel_cdclk_state *new_cdclk_state =
|
|
intel_atomic_get_new_cdclk_state(state);
|
|
const struct intel_cdclk_state *old_cdclk_state =
|
|
intel_atomic_get_old_cdclk_state(state);
|
|
unsigned int cdclk = 0; u8 voltage_level, num_active_pipes = 0;
|
|
bool update_cdclk, update_pipe_count;
|
|
|
|
/* According to "Sequence After Frequency Change", set voltage to used level */
|
|
voltage_level = new_cdclk_state->actual.voltage_level;
|
|
|
|
update_cdclk = new_cdclk_state->actual.cdclk != old_cdclk_state->actual.cdclk;
|
|
update_pipe_count = dg2_power_well_count(display, new_cdclk_state) <
|
|
dg2_power_well_count(display, old_cdclk_state);
|
|
|
|
/*
|
|
* According to "Sequence After Frequency Change",
|
|
* set bits 25:16 to current CDCLK
|
|
*/
|
|
if (update_cdclk)
|
|
cdclk = new_cdclk_state->actual.cdclk;
|
|
|
|
/*
|
|
* According to "Sequence For Pipe Count Change",
|
|
* if pipe count is decreasing, set bits 25:16 to current pipe count,
|
|
* after the change(power well is disabled)
|
|
* no action if it is increasing, after the change
|
|
*/
|
|
if (update_pipe_count)
|
|
num_active_pipes = dg2_power_well_count(display, new_cdclk_state);
|
|
|
|
intel_pcode_notify(display, voltage_level, num_active_pipes, cdclk,
|
|
update_cdclk, update_pipe_count);
|
|
}
|
|
|
|
bool intel_cdclk_is_decreasing_later(struct intel_atomic_state *state)
|
|
{
|
|
const struct intel_cdclk_state *old_cdclk_state =
|
|
intel_atomic_get_old_cdclk_state(state);
|
|
const struct intel_cdclk_state *new_cdclk_state =
|
|
intel_atomic_get_new_cdclk_state(state);
|
|
|
|
return new_cdclk_state && !new_cdclk_state->disable_pipes &&
|
|
new_cdclk_state->actual.cdclk < old_cdclk_state->actual.cdclk;
|
|
}
|
|
|
|
/**
|
|
* intel_set_cdclk_pre_plane_update - Push the CDCLK state to the hardware
|
|
* @state: intel atomic state
|
|
*
|
|
* Program the hardware before updating the HW plane state based on the
|
|
* new CDCLK state, if necessary.
|
|
*/
|
|
void
|
|
intel_set_cdclk_pre_plane_update(struct intel_atomic_state *state)
|
|
{
|
|
struct intel_display *display = to_intel_display(state);
|
|
const struct intel_cdclk_state *old_cdclk_state =
|
|
intel_atomic_get_old_cdclk_state(state);
|
|
const struct intel_cdclk_state *new_cdclk_state =
|
|
intel_atomic_get_new_cdclk_state(state);
|
|
struct intel_cdclk_config cdclk_config;
|
|
enum pipe pipe;
|
|
|
|
if (!new_cdclk_state)
|
|
return;
|
|
|
|
if (!intel_cdclk_changed(&old_cdclk_state->actual,
|
|
&new_cdclk_state->actual))
|
|
return;
|
|
|
|
if (display->platform.dg2)
|
|
intel_cdclk_pcode_pre_notify(state);
|
|
|
|
if (new_cdclk_state->disable_pipes) {
|
|
cdclk_config = new_cdclk_state->actual;
|
|
pipe = INVALID_PIPE;
|
|
} else {
|
|
if (new_cdclk_state->actual.cdclk >= old_cdclk_state->actual.cdclk) {
|
|
cdclk_config = new_cdclk_state->actual;
|
|
pipe = new_cdclk_state->pipe;
|
|
} else {
|
|
cdclk_config = old_cdclk_state->actual;
|
|
pipe = INVALID_PIPE;
|
|
}
|
|
|
|
cdclk_config.voltage_level = max(new_cdclk_state->actual.voltage_level,
|
|
old_cdclk_state->actual.voltage_level);
|
|
}
|
|
|
|
/*
|
|
* mbus joining will be changed later by
|
|
* intel_dbuf_mbus_{pre,post}_ddb_update()
|
|
*/
|
|
cdclk_config.joined_mbus = old_cdclk_state->actual.joined_mbus;
|
|
|
|
drm_WARN_ON(display->drm, !new_cdclk_state->base.changed);
|
|
|
|
intel_set_cdclk(display, &cdclk_config, pipe,
|
|
"Pre changing CDCLK to");
|
|
}
|
|
|
|
/**
|
|
* intel_set_cdclk_post_plane_update - Push the CDCLK state to the hardware
|
|
* @state: intel atomic state
|
|
*
|
|
* Program the hardware after updating the HW plane state based on the
|
|
* new CDCLK state, if necessary.
|
|
*/
|
|
void
|
|
intel_set_cdclk_post_plane_update(struct intel_atomic_state *state)
|
|
{
|
|
struct intel_display *display = to_intel_display(state);
|
|
const struct intel_cdclk_state *old_cdclk_state =
|
|
intel_atomic_get_old_cdclk_state(state);
|
|
const struct intel_cdclk_state *new_cdclk_state =
|
|
intel_atomic_get_new_cdclk_state(state);
|
|
enum pipe pipe;
|
|
|
|
if (!new_cdclk_state)
|
|
return;
|
|
|
|
if (!intel_cdclk_changed(&old_cdclk_state->actual,
|
|
&new_cdclk_state->actual))
|
|
return;
|
|
|
|
if (display->platform.dg2)
|
|
intel_cdclk_pcode_post_notify(state);
|
|
|
|
if (!new_cdclk_state->disable_pipes &&
|
|
new_cdclk_state->actual.cdclk < old_cdclk_state->actual.cdclk)
|
|
pipe = new_cdclk_state->pipe;
|
|
else
|
|
pipe = INVALID_PIPE;
|
|
|
|
drm_WARN_ON(display->drm, !new_cdclk_state->base.changed);
|
|
|
|
intel_set_cdclk(display, &new_cdclk_state->actual, pipe,
|
|
"Post changing CDCLK to");
|
|
}
|
|
|
|
/* pixels per CDCLK */
|
|
static int intel_cdclk_ppc(struct intel_display *display, bool double_wide)
|
|
{
|
|
return DISPLAY_VER(display) >= 10 || double_wide ? 2 : 1;
|
|
}
|
|
|
|
/* max pixel rate as % of CDCLK (not accounting for PPC) */
|
|
static int intel_cdclk_guardband(struct intel_display *display)
|
|
{
|
|
if (DISPLAY_VER(display) >= 9 ||
|
|
display->platform.broadwell || display->platform.haswell)
|
|
return 100;
|
|
else if (display->platform.cherryview)
|
|
return 95;
|
|
else
|
|
return 90;
|
|
}
|
|
|
|
static int _intel_pixel_rate_to_cdclk(const struct intel_crtc_state *crtc_state, int pixel_rate)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc_state);
|
|
int ppc = intel_cdclk_ppc(display, crtc_state->double_wide);
|
|
int guardband = intel_cdclk_guardband(display);
|
|
|
|
return DIV_ROUND_UP(pixel_rate * 100, guardband * ppc);
|
|
}
|
|
|
|
static int intel_pixel_rate_to_cdclk(const struct intel_crtc_state *crtc_state)
|
|
{
|
|
return _intel_pixel_rate_to_cdclk(crtc_state, crtc_state->pixel_rate);
|
|
}
|
|
|
|
static int intel_planes_min_cdclk(const struct intel_crtc_state *crtc_state)
|
|
{
|
|
struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc);
|
|
struct intel_display *display = to_intel_display(crtc);
|
|
struct intel_plane *plane;
|
|
int min_cdclk = 0;
|
|
|
|
for_each_intel_plane_on_crtc(display->drm, crtc, plane)
|
|
min_cdclk = max(min_cdclk, crtc_state->plane_min_cdclk[plane->id]);
|
|
|
|
return min_cdclk;
|
|
}
|
|
|
|
int intel_crtc_min_cdclk(const struct intel_crtc_state *crtc_state)
|
|
{
|
|
int min_cdclk;
|
|
|
|
if (!crtc_state->hw.enable)
|
|
return 0;
|
|
|
|
min_cdclk = intel_pixel_rate_to_cdclk(crtc_state);
|
|
min_cdclk = max(min_cdclk, intel_crtc_bw_min_cdclk(crtc_state));
|
|
min_cdclk = max(min_cdclk, intel_fbc_min_cdclk(crtc_state));
|
|
min_cdclk = max(min_cdclk, hsw_ips_min_cdclk(crtc_state));
|
|
min_cdclk = max(min_cdclk, intel_audio_min_cdclk(crtc_state));
|
|
min_cdclk = max(min_cdclk, vlv_dsi_min_cdclk(crtc_state));
|
|
min_cdclk = max(min_cdclk, intel_planes_min_cdclk(crtc_state));
|
|
min_cdclk = max(min_cdclk, intel_vdsc_min_cdclk(crtc_state));
|
|
|
|
return min_cdclk;
|
|
}
|
|
|
|
static int intel_cdclk_update_crtc_min_cdclk(struct intel_atomic_state *state,
|
|
struct intel_crtc *crtc,
|
|
int old_min_cdclk, int new_min_cdclk,
|
|
bool *need_cdclk_calc)
|
|
{
|
|
struct intel_display *display = to_intel_display(state);
|
|
struct intel_cdclk_state *cdclk_state;
|
|
bool allow_cdclk_decrease = intel_any_crtc_needs_modeset(state);
|
|
int ret;
|
|
|
|
if (new_min_cdclk == old_min_cdclk)
|
|
return 0;
|
|
|
|
if (!allow_cdclk_decrease && new_min_cdclk < old_min_cdclk)
|
|
return 0;
|
|
|
|
cdclk_state = intel_atomic_get_cdclk_state(state);
|
|
if (IS_ERR(cdclk_state))
|
|
return PTR_ERR(cdclk_state);
|
|
|
|
old_min_cdclk = cdclk_state->min_cdclk[crtc->pipe];
|
|
|
|
if (new_min_cdclk == old_min_cdclk)
|
|
return 0;
|
|
|
|
if (!allow_cdclk_decrease && new_min_cdclk < old_min_cdclk)
|
|
return 0;
|
|
|
|
cdclk_state->min_cdclk[crtc->pipe] = new_min_cdclk;
|
|
|
|
ret = intel_atomic_lock_global_state(&cdclk_state->base);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*need_cdclk_calc = true;
|
|
|
|
drm_dbg_kms(display->drm,
|
|
"[CRTC:%d:%s] min cdclk: %d kHz -> %d kHz\n",
|
|
crtc->base.base.id, crtc->base.name,
|
|
old_min_cdclk, new_min_cdclk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int intel_cdclk_update_dbuf_bw_min_cdclk(struct intel_atomic_state *state,
|
|
int old_min_cdclk, int new_min_cdclk,
|
|
bool *need_cdclk_calc)
|
|
{
|
|
struct intel_display *display = to_intel_display(state);
|
|
struct intel_cdclk_state *cdclk_state;
|
|
bool allow_cdclk_decrease = intel_any_crtc_needs_modeset(state);
|
|
int ret;
|
|
|
|
if (new_min_cdclk == old_min_cdclk)
|
|
return 0;
|
|
|
|
if (!allow_cdclk_decrease && new_min_cdclk < old_min_cdclk)
|
|
return 0;
|
|
|
|
cdclk_state = intel_atomic_get_cdclk_state(state);
|
|
if (IS_ERR(cdclk_state))
|
|
return PTR_ERR(cdclk_state);
|
|
|
|
old_min_cdclk = cdclk_state->dbuf_bw_min_cdclk;
|
|
|
|
if (new_min_cdclk == old_min_cdclk)
|
|
return 0;
|
|
|
|
if (!allow_cdclk_decrease && new_min_cdclk < old_min_cdclk)
|
|
return 0;
|
|
|
|
cdclk_state->dbuf_bw_min_cdclk = new_min_cdclk;
|
|
|
|
ret = intel_atomic_lock_global_state(&cdclk_state->base);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*need_cdclk_calc = true;
|
|
|
|
drm_dbg_kms(display->drm,
|
|
"dbuf bandwidth min cdclk: %d kHz -> %d kHz\n",
|
|
old_min_cdclk, new_min_cdclk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool glk_cdclk_audio_wa_needed(struct intel_display *display,
|
|
const struct intel_cdclk_state *cdclk_state)
|
|
{
|
|
return display->platform.geminilake &&
|
|
cdclk_state->enabled_pipes &&
|
|
!is_power_of_2(cdclk_state->enabled_pipes);
|
|
}
|
|
|
|
static int intel_compute_min_cdclk(struct intel_atomic_state *state)
|
|
{
|
|
struct intel_display *display = to_intel_display(state);
|
|
struct intel_cdclk_state *cdclk_state =
|
|
intel_atomic_get_new_cdclk_state(state);
|
|
enum pipe pipe;
|
|
int min_cdclk;
|
|
|
|
min_cdclk = cdclk_state->force_min_cdclk;
|
|
min_cdclk = max(min_cdclk, cdclk_state->dbuf_bw_min_cdclk);
|
|
for_each_pipe(display, pipe)
|
|
min_cdclk = max(min_cdclk, cdclk_state->min_cdclk[pipe]);
|
|
|
|
/*
|
|
* Avoid glk_force_audio_cdclk() causing excessive screen
|
|
* blinking when multiple pipes are active by making sure
|
|
* CDCLK frequency is always high enough for audio. With a
|
|
* single active pipe we can always change CDCLK frequency
|
|
* by changing the cd2x divider (see glk_cdclk_table[]) and
|
|
* thus a full modeset won't be needed then.
|
|
*/
|
|
if (glk_cdclk_audio_wa_needed(display, cdclk_state))
|
|
min_cdclk = max(min_cdclk, 2 * 96000);
|
|
|
|
if (min_cdclk > display->cdclk.max_cdclk_freq) {
|
|
drm_dbg_kms(display->drm,
|
|
"required cdclk (%d kHz) exceeds max (%d kHz)\n",
|
|
min_cdclk, display->cdclk.max_cdclk_freq);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return min_cdclk;
|
|
}
|
|
|
|
/*
|
|
* Account for port clock min voltage level requirements.
|
|
* This only really does something on DISPLA_VER >= 11 but can be
|
|
* called on earlier platforms as well.
|
|
*
|
|
* Note that this functions assumes that 0 is
|
|
* the lowest voltage value, and higher values
|
|
* correspond to increasingly higher voltages.
|
|
*
|
|
* Should that relationship no longer hold on
|
|
* future platforms this code will need to be
|
|
* adjusted.
|
|
*/
|
|
static int bxt_compute_min_voltage_level(struct intel_atomic_state *state)
|
|
{
|
|
struct intel_display *display = to_intel_display(state);
|
|
struct intel_cdclk_state *cdclk_state =
|
|
intel_atomic_get_new_cdclk_state(state);
|
|
struct intel_crtc *crtc;
|
|
struct intel_crtc_state *crtc_state;
|
|
u8 min_voltage_level;
|
|
int i;
|
|
enum pipe pipe;
|
|
|
|
for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) {
|
|
int ret;
|
|
|
|
if (crtc_state->hw.enable)
|
|
min_voltage_level = crtc_state->min_voltage_level;
|
|
else
|
|
min_voltage_level = 0;
|
|
|
|
if (cdclk_state->min_voltage_level[crtc->pipe] == min_voltage_level)
|
|
continue;
|
|
|
|
cdclk_state->min_voltage_level[crtc->pipe] = min_voltage_level;
|
|
|
|
ret = intel_atomic_lock_global_state(&cdclk_state->base);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
min_voltage_level = 0;
|
|
for_each_pipe(display, pipe)
|
|
min_voltage_level = max(min_voltage_level,
|
|
cdclk_state->min_voltage_level[pipe]);
|
|
|
|
return min_voltage_level;
|
|
}
|
|
|
|
static int vlv_modeset_calc_cdclk(struct intel_atomic_state *state)
|
|
{
|
|
struct intel_display *display = to_intel_display(state);
|
|
struct intel_cdclk_state *cdclk_state =
|
|
intel_atomic_get_new_cdclk_state(state);
|
|
int min_cdclk, cdclk;
|
|
|
|
min_cdclk = intel_compute_min_cdclk(state);
|
|
if (min_cdclk < 0)
|
|
return min_cdclk;
|
|
|
|
cdclk = vlv_calc_cdclk(display, min_cdclk);
|
|
|
|
cdclk_state->logical.cdclk = cdclk;
|
|
cdclk_state->logical.voltage_level =
|
|
vlv_calc_voltage_level(display, cdclk);
|
|
|
|
if (!cdclk_state->active_pipes) {
|
|
cdclk = vlv_calc_cdclk(display, cdclk_state->force_min_cdclk);
|
|
|
|
cdclk_state->actual.cdclk = cdclk;
|
|
cdclk_state->actual.voltage_level =
|
|
vlv_calc_voltage_level(display, cdclk);
|
|
} else {
|
|
cdclk_state->actual = cdclk_state->logical;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bdw_modeset_calc_cdclk(struct intel_atomic_state *state)
|
|
{
|
|
struct intel_cdclk_state *cdclk_state =
|
|
intel_atomic_get_new_cdclk_state(state);
|
|
int min_cdclk, cdclk;
|
|
|
|
min_cdclk = intel_compute_min_cdclk(state);
|
|
if (min_cdclk < 0)
|
|
return min_cdclk;
|
|
|
|
cdclk = bdw_calc_cdclk(min_cdclk);
|
|
|
|
cdclk_state->logical.cdclk = cdclk;
|
|
cdclk_state->logical.voltage_level =
|
|
bdw_calc_voltage_level(cdclk);
|
|
|
|
if (!cdclk_state->active_pipes) {
|
|
cdclk = bdw_calc_cdclk(cdclk_state->force_min_cdclk);
|
|
|
|
cdclk_state->actual.cdclk = cdclk;
|
|
cdclk_state->actual.voltage_level =
|
|
bdw_calc_voltage_level(cdclk);
|
|
} else {
|
|
cdclk_state->actual = cdclk_state->logical;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int skl_dpll0_vco(struct intel_atomic_state *state)
|
|
{
|
|
struct intel_display *display = to_intel_display(state);
|
|
struct intel_cdclk_state *cdclk_state =
|
|
intel_atomic_get_new_cdclk_state(state);
|
|
struct intel_crtc *crtc;
|
|
struct intel_crtc_state *crtc_state;
|
|
int vco, i;
|
|
|
|
vco = cdclk_state->logical.vco;
|
|
if (!vco)
|
|
vco = display->cdclk.skl_preferred_vco_freq;
|
|
|
|
for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) {
|
|
if (!crtc_state->hw.enable)
|
|
continue;
|
|
|
|
if (!intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP))
|
|
continue;
|
|
|
|
/*
|
|
* DPLL0 VCO may need to be adjusted to get the correct
|
|
* clock for eDP. This will affect cdclk as well.
|
|
*/
|
|
switch (crtc_state->port_clock / 2) {
|
|
case 108000:
|
|
case 216000:
|
|
vco = 8640000;
|
|
break;
|
|
default:
|
|
vco = 8100000;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return vco;
|
|
}
|
|
|
|
static int skl_modeset_calc_cdclk(struct intel_atomic_state *state)
|
|
{
|
|
struct intel_cdclk_state *cdclk_state =
|
|
intel_atomic_get_new_cdclk_state(state);
|
|
int min_cdclk, cdclk, vco;
|
|
|
|
min_cdclk = intel_compute_min_cdclk(state);
|
|
if (min_cdclk < 0)
|
|
return min_cdclk;
|
|
|
|
vco = skl_dpll0_vco(state);
|
|
|
|
cdclk = skl_calc_cdclk(min_cdclk, vco);
|
|
|
|
cdclk_state->logical.vco = vco;
|
|
cdclk_state->logical.cdclk = cdclk;
|
|
cdclk_state->logical.voltage_level =
|
|
skl_calc_voltage_level(cdclk);
|
|
|
|
if (!cdclk_state->active_pipes) {
|
|
cdclk = skl_calc_cdclk(cdclk_state->force_min_cdclk, vco);
|
|
|
|
cdclk_state->actual.vco = vco;
|
|
cdclk_state->actual.cdclk = cdclk;
|
|
cdclk_state->actual.voltage_level =
|
|
skl_calc_voltage_level(cdclk);
|
|
} else {
|
|
cdclk_state->actual = cdclk_state->logical;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bxt_modeset_calc_cdclk(struct intel_atomic_state *state)
|
|
{
|
|
struct intel_display *display = to_intel_display(state);
|
|
struct intel_cdclk_state *cdclk_state =
|
|
intel_atomic_get_new_cdclk_state(state);
|
|
int min_cdclk, min_voltage_level, cdclk, vco;
|
|
|
|
min_cdclk = intel_compute_min_cdclk(state);
|
|
if (min_cdclk < 0)
|
|
return min_cdclk;
|
|
|
|
min_voltage_level = bxt_compute_min_voltage_level(state);
|
|
if (min_voltage_level < 0)
|
|
return min_voltage_level;
|
|
|
|
cdclk = bxt_calc_cdclk(display, min_cdclk);
|
|
vco = bxt_calc_cdclk_pll_vco(display, cdclk);
|
|
|
|
cdclk_state->logical.vco = vco;
|
|
cdclk_state->logical.cdclk = cdclk;
|
|
cdclk_state->logical.voltage_level =
|
|
max_t(int, min_voltage_level,
|
|
intel_cdclk_calc_voltage_level(display, cdclk));
|
|
|
|
if (!cdclk_state->active_pipes) {
|
|
cdclk = bxt_calc_cdclk(display, cdclk_state->force_min_cdclk);
|
|
vco = bxt_calc_cdclk_pll_vco(display, cdclk);
|
|
|
|
cdclk_state->actual.vco = vco;
|
|
cdclk_state->actual.cdclk = cdclk;
|
|
cdclk_state->actual.voltage_level =
|
|
intel_cdclk_calc_voltage_level(display, cdclk);
|
|
} else {
|
|
cdclk_state->actual = cdclk_state->logical;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fixed_modeset_calc_cdclk(struct intel_atomic_state *state)
|
|
{
|
|
int min_cdclk;
|
|
|
|
/*
|
|
* We can't change the cdclk frequency, but we still want to
|
|
* check that the required minimum frequency doesn't exceed
|
|
* the actual cdclk frequency.
|
|
*/
|
|
min_cdclk = intel_compute_min_cdclk(state);
|
|
if (min_cdclk < 0)
|
|
return min_cdclk;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct intel_global_state *intel_cdclk_duplicate_state(struct intel_global_obj *obj)
|
|
{
|
|
struct intel_cdclk_state *cdclk_state;
|
|
|
|
cdclk_state = kmemdup(obj->state, sizeof(*cdclk_state), GFP_KERNEL);
|
|
if (!cdclk_state)
|
|
return NULL;
|
|
|
|
cdclk_state->pipe = INVALID_PIPE;
|
|
cdclk_state->disable_pipes = false;
|
|
|
|
return &cdclk_state->base;
|
|
}
|
|
|
|
static void intel_cdclk_destroy_state(struct intel_global_obj *obj,
|
|
struct intel_global_state *state)
|
|
{
|
|
kfree(state);
|
|
}
|
|
|
|
static const struct intel_global_state_funcs intel_cdclk_funcs = {
|
|
.atomic_duplicate_state = intel_cdclk_duplicate_state,
|
|
.atomic_destroy_state = intel_cdclk_destroy_state,
|
|
};
|
|
|
|
struct intel_cdclk_state *
|
|
intel_atomic_get_cdclk_state(struct intel_atomic_state *state)
|
|
{
|
|
struct intel_display *display = to_intel_display(state);
|
|
struct intel_global_state *cdclk_state;
|
|
|
|
cdclk_state = intel_atomic_get_global_obj_state(state, &display->cdclk.obj);
|
|
if (IS_ERR(cdclk_state))
|
|
return ERR_CAST(cdclk_state);
|
|
|
|
return to_intel_cdclk_state(cdclk_state);
|
|
}
|
|
|
|
static int intel_cdclk_modeset_checks(struct intel_atomic_state *state,
|
|
bool *need_cdclk_calc)
|
|
{
|
|
struct intel_display *display = to_intel_display(state);
|
|
const struct intel_cdclk_state *old_cdclk_state;
|
|
struct intel_cdclk_state *new_cdclk_state;
|
|
int ret;
|
|
|
|
if (!intel_any_crtc_enable_changed(state) &&
|
|
!intel_any_crtc_active_changed(state))
|
|
return 0;
|
|
|
|
new_cdclk_state = intel_atomic_get_cdclk_state(state);
|
|
if (IS_ERR(new_cdclk_state))
|
|
return PTR_ERR(new_cdclk_state);
|
|
|
|
old_cdclk_state = intel_atomic_get_old_cdclk_state(state);
|
|
|
|
new_cdclk_state->enabled_pipes =
|
|
intel_calc_enabled_pipes(state, old_cdclk_state->enabled_pipes);
|
|
|
|
new_cdclk_state->active_pipes =
|
|
intel_calc_active_pipes(state, old_cdclk_state->active_pipes);
|
|
|
|
ret = intel_atomic_lock_global_state(&new_cdclk_state->base);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!old_cdclk_state->active_pipes != !new_cdclk_state->active_pipes)
|
|
*need_cdclk_calc = true;
|
|
|
|
if (glk_cdclk_audio_wa_needed(display, old_cdclk_state) !=
|
|
glk_cdclk_audio_wa_needed(display, new_cdclk_state))
|
|
*need_cdclk_calc = true;
|
|
|
|
if (dg2_power_well_count(display, old_cdclk_state) !=
|
|
dg2_power_well_count(display, new_cdclk_state))
|
|
*need_cdclk_calc = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int intel_crtcs_calc_min_cdclk(struct intel_atomic_state *state,
|
|
bool *need_cdclk_calc)
|
|
{
|
|
const struct intel_crtc_state *old_crtc_state;
|
|
const struct intel_crtc_state *new_crtc_state;
|
|
struct intel_crtc *crtc;
|
|
int i, ret;
|
|
|
|
for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state,
|
|
new_crtc_state, i) {
|
|
ret = intel_cdclk_update_crtc_min_cdclk(state, crtc,
|
|
old_crtc_state->min_cdclk,
|
|
new_crtc_state->min_cdclk,
|
|
need_cdclk_calc);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int intel_cdclk_state_set_joined_mbus(struct intel_atomic_state *state, bool joined_mbus)
|
|
{
|
|
struct intel_cdclk_state *cdclk_state;
|
|
|
|
cdclk_state = intel_atomic_get_cdclk_state(state);
|
|
if (IS_ERR(cdclk_state))
|
|
return PTR_ERR(cdclk_state);
|
|
|
|
cdclk_state->actual.joined_mbus = joined_mbus;
|
|
cdclk_state->logical.joined_mbus = joined_mbus;
|
|
|
|
return intel_atomic_lock_global_state(&cdclk_state->base);
|
|
}
|
|
|
|
int intel_cdclk_init(struct intel_display *display)
|
|
{
|
|
struct intel_cdclk_state *cdclk_state;
|
|
|
|
cdclk_state = kzalloc_obj(*cdclk_state);
|
|
if (!cdclk_state)
|
|
return -ENOMEM;
|
|
|
|
intel_atomic_global_obj_init(display, &display->cdclk.obj,
|
|
&cdclk_state->base, &intel_cdclk_funcs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool intel_cdclk_need_serialize(struct intel_display *display,
|
|
const struct intel_cdclk_state *old_cdclk_state,
|
|
const struct intel_cdclk_state *new_cdclk_state)
|
|
{
|
|
/*
|
|
* We need to poke hw for DG2, because we notify PCode if
|
|
* pipe power well count changes.
|
|
*/
|
|
return intel_cdclk_changed(&old_cdclk_state->actual,
|
|
&new_cdclk_state->actual) ||
|
|
dg2_power_well_count(display, old_cdclk_state) !=
|
|
dg2_power_well_count(display, new_cdclk_state);
|
|
}
|
|
|
|
static int intel_modeset_calc_cdclk(struct intel_atomic_state *state)
|
|
{
|
|
struct intel_display *display = to_intel_display(state);
|
|
const struct intel_cdclk_state *old_cdclk_state;
|
|
struct intel_cdclk_state *new_cdclk_state;
|
|
enum pipe pipe = INVALID_PIPE;
|
|
int ret;
|
|
|
|
new_cdclk_state = intel_atomic_get_cdclk_state(state);
|
|
if (IS_ERR(new_cdclk_state))
|
|
return PTR_ERR(new_cdclk_state);
|
|
|
|
old_cdclk_state = intel_atomic_get_old_cdclk_state(state);
|
|
|
|
ret = intel_cdclk_modeset_calc_cdclk(state);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (intel_cdclk_need_serialize(display, old_cdclk_state, new_cdclk_state)) {
|
|
/*
|
|
* Also serialize commits across all crtcs
|
|
* if the actual hw needs to be poked.
|
|
*/
|
|
ret = intel_atomic_serialize_global_state(&new_cdclk_state->base);
|
|
if (ret)
|
|
return ret;
|
|
} else if (intel_cdclk_changed(&old_cdclk_state->logical,
|
|
&new_cdclk_state->logical)) {
|
|
ret = intel_atomic_lock_global_state(&new_cdclk_state->base);
|
|
if (ret)
|
|
return ret;
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
if (is_power_of_2(new_cdclk_state->active_pipes) &&
|
|
intel_cdclk_can_cd2x_update(display,
|
|
&old_cdclk_state->actual,
|
|
&new_cdclk_state->actual)) {
|
|
struct intel_crtc *crtc;
|
|
struct intel_crtc_state *crtc_state;
|
|
|
|
pipe = ilog2(new_cdclk_state->active_pipes);
|
|
crtc = intel_crtc_for_pipe(display, pipe);
|
|
|
|
crtc_state = intel_atomic_get_crtc_state(&state->base, crtc);
|
|
if (IS_ERR(crtc_state))
|
|
return PTR_ERR(crtc_state);
|
|
|
|
if (intel_crtc_needs_modeset(crtc_state))
|
|
pipe = INVALID_PIPE;
|
|
}
|
|
|
|
if (intel_cdclk_can_crawl_and_squash(display,
|
|
&old_cdclk_state->actual,
|
|
&new_cdclk_state->actual)) {
|
|
drm_dbg_kms(display->drm,
|
|
"Can change cdclk via crawling and squashing\n");
|
|
} else if (intel_cdclk_can_squash(display,
|
|
&old_cdclk_state->actual,
|
|
&new_cdclk_state->actual)) {
|
|
drm_dbg_kms(display->drm,
|
|
"Can change cdclk via squashing\n");
|
|
} else if (intel_cdclk_can_crawl(display,
|
|
&old_cdclk_state->actual,
|
|
&new_cdclk_state->actual)) {
|
|
drm_dbg_kms(display->drm,
|
|
"Can change cdclk via crawling\n");
|
|
} else if (pipe != INVALID_PIPE) {
|
|
new_cdclk_state->pipe = pipe;
|
|
|
|
drm_dbg_kms(display->drm,
|
|
"Can change cdclk cd2x divider with pipe %c active\n",
|
|
pipe_name(pipe));
|
|
} else if (intel_cdclk_clock_changed(&old_cdclk_state->actual,
|
|
&new_cdclk_state->actual)) {
|
|
/* All pipes must be switched off while we change the cdclk. */
|
|
ret = intel_modeset_all_pipes_late(state, "CDCLK change");
|
|
if (ret)
|
|
return ret;
|
|
|
|
new_cdclk_state->disable_pipes = true;
|
|
|
|
drm_dbg_kms(display->drm,
|
|
"Modeset required for cdclk change\n");
|
|
}
|
|
|
|
if (intel_mdclk_cdclk_ratio(display, &old_cdclk_state->actual) !=
|
|
intel_mdclk_cdclk_ratio(display, &new_cdclk_state->actual)) {
|
|
int ratio = intel_mdclk_cdclk_ratio(display, &new_cdclk_state->actual);
|
|
|
|
ret = intel_dbuf_state_set_mdclk_cdclk_ratio(state, ratio);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
drm_dbg_kms(display->drm,
|
|
"New cdclk calculated to be logical %u kHz, actual %u kHz\n",
|
|
new_cdclk_state->logical.cdclk,
|
|
new_cdclk_state->actual.cdclk);
|
|
drm_dbg_kms(display->drm,
|
|
"New voltage level calculated to be logical %u, actual %u\n",
|
|
new_cdclk_state->logical.voltage_level,
|
|
new_cdclk_state->actual.voltage_level);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int intel_cdclk_atomic_check(struct intel_atomic_state *state)
|
|
{
|
|
const struct intel_cdclk_state *old_cdclk_state;
|
|
struct intel_cdclk_state *new_cdclk_state;
|
|
bool need_cdclk_calc = false;
|
|
int ret;
|
|
|
|
ret = intel_cdclk_modeset_checks(state, &need_cdclk_calc);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = intel_crtcs_calc_min_cdclk(state, &need_cdclk_calc);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = intel_dbuf_bw_calc_min_cdclk(state, &need_cdclk_calc);
|
|
if (ret)
|
|
return ret;
|
|
|
|
old_cdclk_state = intel_atomic_get_old_cdclk_state(state);
|
|
new_cdclk_state = intel_atomic_get_new_cdclk_state(state);
|
|
|
|
if (new_cdclk_state &&
|
|
old_cdclk_state->force_min_cdclk != new_cdclk_state->force_min_cdclk) {
|
|
ret = intel_atomic_lock_global_state(&new_cdclk_state->base);
|
|
if (ret)
|
|
return ret;
|
|
|
|
need_cdclk_calc = true;
|
|
}
|
|
|
|
if (need_cdclk_calc) {
|
|
ret = intel_modeset_calc_cdclk(state);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void intel_cdclk_update_hw_state(struct intel_display *display)
|
|
{
|
|
const struct intel_dbuf_bw_state *dbuf_bw_state =
|
|
to_intel_dbuf_bw_state(display->dbuf_bw.obj.state);
|
|
struct intel_cdclk_state *cdclk_state =
|
|
to_intel_cdclk_state(display->cdclk.obj.state);
|
|
struct intel_crtc *crtc;
|
|
|
|
cdclk_state->enabled_pipes = 0;
|
|
cdclk_state->active_pipes = 0;
|
|
|
|
for_each_intel_crtc(display->drm, crtc) {
|
|
const struct intel_crtc_state *crtc_state =
|
|
to_intel_crtc_state(crtc->base.state);
|
|
enum pipe pipe = crtc->pipe;
|
|
|
|
if (crtc_state->hw.enable)
|
|
cdclk_state->enabled_pipes |= BIT(pipe);
|
|
if (crtc_state->hw.active)
|
|
cdclk_state->active_pipes |= BIT(pipe);
|
|
|
|
cdclk_state->min_cdclk[pipe] = crtc_state->min_cdclk;
|
|
cdclk_state->min_voltage_level[pipe] = crtc_state->min_voltage_level;
|
|
}
|
|
|
|
cdclk_state->dbuf_bw_min_cdclk = intel_dbuf_bw_min_cdclk(display, dbuf_bw_state);
|
|
}
|
|
|
|
void intel_cdclk_crtc_disable_noatomic(struct intel_crtc *crtc)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc);
|
|
|
|
intel_cdclk_update_hw_state(display);
|
|
}
|
|
|
|
static int intel_compute_max_dotclk(struct intel_display *display)
|
|
{
|
|
int ppc = intel_cdclk_ppc(display, HAS_DOUBLE_WIDE(display));
|
|
int guardband = intel_cdclk_guardband(display);
|
|
int max_cdclk_freq = display->cdclk.max_cdclk_freq;
|
|
|
|
return ppc * max_cdclk_freq * guardband / 100;
|
|
}
|
|
|
|
/**
|
|
* intel_update_max_cdclk - Determine the maximum support CDCLK frequency
|
|
* @display: display instance
|
|
*
|
|
* Determine the maximum CDCLK frequency the platform supports, and also
|
|
* derive the maximum dot clock frequency the maximum CDCLK frequency
|
|
* allows.
|
|
*/
|
|
void intel_update_max_cdclk(struct intel_display *display)
|
|
{
|
|
if (DISPLAY_VER(display) >= 35) {
|
|
display->cdclk.max_cdclk_freq = 787200;
|
|
} else if (DISPLAY_VERx100(display) >= 3002) {
|
|
display->cdclk.max_cdclk_freq = 480000;
|
|
} else if (DISPLAY_VER(display) >= 30) {
|
|
display->cdclk.max_cdclk_freq = 691200;
|
|
} else if (display->platform.jasperlake || display->platform.elkhartlake) {
|
|
if (display->cdclk.hw.ref == 24000)
|
|
display->cdclk.max_cdclk_freq = 552000;
|
|
else
|
|
display->cdclk.max_cdclk_freq = 556800;
|
|
} else if (DISPLAY_VER(display) >= 11) {
|
|
if (display->cdclk.hw.ref == 24000)
|
|
display->cdclk.max_cdclk_freq = 648000;
|
|
else
|
|
display->cdclk.max_cdclk_freq = 652800;
|
|
} else if (display->platform.geminilake) {
|
|
display->cdclk.max_cdclk_freq = 316800;
|
|
} else if (display->platform.broxton) {
|
|
display->cdclk.max_cdclk_freq = 624000;
|
|
} else if (DISPLAY_VER(display) == 9) {
|
|
u32 limit = intel_de_read(display, SKL_DFSM) & SKL_DFSM_CDCLK_LIMIT_MASK;
|
|
int max_cdclk, vco;
|
|
|
|
vco = display->cdclk.skl_preferred_vco_freq;
|
|
drm_WARN_ON(display->drm, vco != 8100000 && vco != 8640000);
|
|
|
|
/*
|
|
* Use the lower (vco 8640) cdclk values as a
|
|
* first guess. skl_calc_cdclk() will correct it
|
|
* if the preferred vco is 8100 instead.
|
|
*/
|
|
if (limit == SKL_DFSM_CDCLK_LIMIT_675)
|
|
max_cdclk = 617143;
|
|
else if (limit == SKL_DFSM_CDCLK_LIMIT_540)
|
|
max_cdclk = 540000;
|
|
else if (limit == SKL_DFSM_CDCLK_LIMIT_450)
|
|
max_cdclk = 432000;
|
|
else
|
|
max_cdclk = 308571;
|
|
|
|
display->cdclk.max_cdclk_freq = skl_calc_cdclk(max_cdclk, vco);
|
|
} else if (display->platform.broadwell) {
|
|
/*
|
|
* FIXME with extra cooling we can allow
|
|
* 540 MHz for ULX and 675 Mhz for ULT.
|
|
* How can we know if extra cooling is
|
|
* available? PCI ID, VTB, something else?
|
|
*/
|
|
if (intel_de_read(display, FUSE_STRAP) & HSW_CDCLK_LIMIT)
|
|
display->cdclk.max_cdclk_freq = 450000;
|
|
else if (display->platform.broadwell_ulx)
|
|
display->cdclk.max_cdclk_freq = 450000;
|
|
else if (display->platform.broadwell_ult)
|
|
display->cdclk.max_cdclk_freq = 540000;
|
|
else
|
|
display->cdclk.max_cdclk_freq = 675000;
|
|
} else if (display->platform.cherryview) {
|
|
display->cdclk.max_cdclk_freq = 320000;
|
|
} else if (display->platform.valleyview) {
|
|
display->cdclk.max_cdclk_freq = 400000;
|
|
} else {
|
|
/* otherwise assume cdclk is fixed */
|
|
display->cdclk.max_cdclk_freq = display->cdclk.hw.cdclk;
|
|
}
|
|
|
|
display->cdclk.max_dotclk_freq = intel_compute_max_dotclk(display);
|
|
|
|
drm_dbg(display->drm, "Max CD clock rate: %d kHz\n",
|
|
display->cdclk.max_cdclk_freq);
|
|
|
|
drm_dbg(display->drm, "Max dotclock rate: %d kHz\n",
|
|
display->cdclk.max_dotclk_freq);
|
|
}
|
|
|
|
/**
|
|
* intel_update_cdclk - Determine the current CDCLK frequency
|
|
* @display: display instance
|
|
*
|
|
* Determine the current CDCLK frequency.
|
|
*/
|
|
void intel_update_cdclk(struct intel_display *display)
|
|
{
|
|
intel_cdclk_get_cdclk(display, &display->cdclk.hw);
|
|
|
|
/*
|
|
* 9:0 CMBUS [sic] CDCLK frequency (cdfreq):
|
|
* Programmng [sic] note: bit[9:2] should be programmed to the number
|
|
* of cdclk that generates 4MHz reference clock freq which is used to
|
|
* generate GMBus clock. This will vary with the cdclk freq.
|
|
*/
|
|
if (display->platform.valleyview || display->platform.cherryview)
|
|
intel_de_write(display, GMBUSFREQ_VLV,
|
|
DIV_ROUND_UP(display->cdclk.hw.cdclk, 1000));
|
|
}
|
|
|
|
static int dg1_rawclk(struct intel_display *display)
|
|
{
|
|
/*
|
|
* DG1 always uses a 38.4 MHz rawclk. The bspec tells us
|
|
* "Program Numerator=2, Denominator=4, Divider=37 decimal."
|
|
*/
|
|
intel_de_write(display, PCH_RAWCLK_FREQ,
|
|
CNP_RAWCLK_DEN(4) | CNP_RAWCLK_DIV(37) | ICP_RAWCLK_NUM(2));
|
|
|
|
return 38400;
|
|
}
|
|
|
|
static int cnp_rawclk(struct intel_display *display)
|
|
{
|
|
int divider, fraction;
|
|
u32 rawclk;
|
|
|
|
if (intel_de_read(display, SFUSE_STRAP) & SFUSE_STRAP_RAW_FREQUENCY) {
|
|
/* 24 MHz */
|
|
divider = 24000;
|
|
fraction = 0;
|
|
} else {
|
|
/* 19.2 MHz */
|
|
divider = 19000;
|
|
fraction = 200;
|
|
}
|
|
|
|
rawclk = CNP_RAWCLK_DIV(divider / 1000);
|
|
if (fraction) {
|
|
int numerator = 1;
|
|
|
|
rawclk |= CNP_RAWCLK_DEN(DIV_ROUND_CLOSEST(numerator * 1000,
|
|
fraction) - 1);
|
|
if (INTEL_PCH_TYPE(display) >= PCH_ICP)
|
|
rawclk |= ICP_RAWCLK_NUM(numerator);
|
|
}
|
|
|
|
intel_de_write(display, PCH_RAWCLK_FREQ, rawclk);
|
|
return divider + fraction;
|
|
}
|
|
|
|
static int pch_rawclk(struct intel_display *display)
|
|
{
|
|
return (intel_de_read(display, PCH_RAWCLK_FREQ) & RAWCLK_FREQ_MASK) * 1000;
|
|
}
|
|
|
|
static int i9xx_hrawclk(struct intel_display *display)
|
|
{
|
|
/* hrawclock is 1/4 the FSB frequency */
|
|
return DIV_ROUND_CLOSEST(intel_fsb_freq(display), 4);
|
|
}
|
|
|
|
/**
|
|
* intel_read_rawclk - Determine the current RAWCLK frequency
|
|
* @display: display instance
|
|
*
|
|
* Determine the current RAWCLK frequency. RAWCLK is a fixed
|
|
* frequency clock so this needs to done only once.
|
|
*/
|
|
u32 intel_read_rawclk(struct intel_display *display)
|
|
{
|
|
u32 freq;
|
|
|
|
if (INTEL_PCH_TYPE(display) >= PCH_MTL)
|
|
/*
|
|
* MTL always uses a 38.4 MHz rawclk. The bspec tells us
|
|
* "RAWCLK_FREQ defaults to the values for 38.4 and does
|
|
* not need to be programmed."
|
|
*/
|
|
freq = 38400;
|
|
else if (INTEL_PCH_TYPE(display) >= PCH_DG1)
|
|
freq = dg1_rawclk(display);
|
|
else if (INTEL_PCH_TYPE(display) >= PCH_CNP)
|
|
freq = cnp_rawclk(display);
|
|
else if (HAS_PCH_SPLIT(display))
|
|
freq = pch_rawclk(display);
|
|
else if (display->platform.valleyview || display->platform.cherryview)
|
|
freq = vlv_clock_get_hrawclk(display->drm);
|
|
else if (DISPLAY_VER(display) >= 3)
|
|
freq = i9xx_hrawclk(display);
|
|
else
|
|
/* no rawclk on other platforms, or no need to know it */
|
|
return 0;
|
|
|
|
return freq;
|
|
}
|
|
|
|
static int i915_cdclk_info_show(struct seq_file *m, void *unused)
|
|
{
|
|
struct intel_display *display = m->private;
|
|
|
|
seq_printf(m, "Current CD clock frequency: %d kHz\n", display->cdclk.hw.cdclk);
|
|
seq_printf(m, "Max CD clock frequency: %d kHz\n", display->cdclk.max_cdclk_freq);
|
|
seq_printf(m, "Max pixel clock frequency: %d kHz\n", display->cdclk.max_dotclk_freq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_SHOW_ATTRIBUTE(i915_cdclk_info);
|
|
|
|
void intel_cdclk_debugfs_register(struct intel_display *display)
|
|
{
|
|
debugfs_create_file("i915_cdclk_info", 0444, display->drm->debugfs_root,
|
|
display, &i915_cdclk_info_fops);
|
|
}
|
|
|
|
static const struct intel_cdclk_funcs xe3lpd_cdclk_funcs = {
|
|
.get_cdclk = bxt_get_cdclk,
|
|
.set_cdclk = bxt_set_cdclk,
|
|
.modeset_calc_cdclk = bxt_modeset_calc_cdclk,
|
|
.calc_voltage_level = xe3lpd_calc_voltage_level,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs rplu_cdclk_funcs = {
|
|
.get_cdclk = bxt_get_cdclk,
|
|
.set_cdclk = bxt_set_cdclk,
|
|
.modeset_calc_cdclk = bxt_modeset_calc_cdclk,
|
|
.calc_voltage_level = rplu_calc_voltage_level,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs tgl_cdclk_funcs = {
|
|
.get_cdclk = bxt_get_cdclk,
|
|
.set_cdclk = bxt_set_cdclk,
|
|
.modeset_calc_cdclk = bxt_modeset_calc_cdclk,
|
|
.calc_voltage_level = tgl_calc_voltage_level,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs ehl_cdclk_funcs = {
|
|
.get_cdclk = bxt_get_cdclk,
|
|
.set_cdclk = bxt_set_cdclk,
|
|
.modeset_calc_cdclk = bxt_modeset_calc_cdclk,
|
|
.calc_voltage_level = ehl_calc_voltage_level,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs icl_cdclk_funcs = {
|
|
.get_cdclk = bxt_get_cdclk,
|
|
.set_cdclk = bxt_set_cdclk,
|
|
.modeset_calc_cdclk = bxt_modeset_calc_cdclk,
|
|
.calc_voltage_level = icl_calc_voltage_level,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs bxt_cdclk_funcs = {
|
|
.get_cdclk = bxt_get_cdclk,
|
|
.set_cdclk = bxt_set_cdclk,
|
|
.modeset_calc_cdclk = bxt_modeset_calc_cdclk,
|
|
.calc_voltage_level = bxt_calc_voltage_level,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs skl_cdclk_funcs = {
|
|
.get_cdclk = skl_get_cdclk,
|
|
.set_cdclk = skl_set_cdclk,
|
|
.modeset_calc_cdclk = skl_modeset_calc_cdclk,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs bdw_cdclk_funcs = {
|
|
.get_cdclk = bdw_get_cdclk,
|
|
.set_cdclk = bdw_set_cdclk,
|
|
.modeset_calc_cdclk = bdw_modeset_calc_cdclk,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs chv_cdclk_funcs = {
|
|
.get_cdclk = vlv_get_cdclk,
|
|
.set_cdclk = chv_set_cdclk,
|
|
.modeset_calc_cdclk = vlv_modeset_calc_cdclk,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs vlv_cdclk_funcs = {
|
|
.get_cdclk = vlv_get_cdclk,
|
|
.set_cdclk = vlv_set_cdclk,
|
|
.modeset_calc_cdclk = vlv_modeset_calc_cdclk,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs hsw_cdclk_funcs = {
|
|
.get_cdclk = hsw_get_cdclk,
|
|
.modeset_calc_cdclk = fixed_modeset_calc_cdclk,
|
|
};
|
|
|
|
/* SNB, IVB, 965G, 945G */
|
|
static const struct intel_cdclk_funcs fixed_400mhz_cdclk_funcs = {
|
|
.get_cdclk = fixed_400mhz_get_cdclk,
|
|
.modeset_calc_cdclk = fixed_modeset_calc_cdclk,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs ilk_cdclk_funcs = {
|
|
.get_cdclk = fixed_450mhz_get_cdclk,
|
|
.modeset_calc_cdclk = fixed_modeset_calc_cdclk,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs gm45_cdclk_funcs = {
|
|
.get_cdclk = gm45_get_cdclk,
|
|
.modeset_calc_cdclk = fixed_modeset_calc_cdclk,
|
|
};
|
|
|
|
/* G45 uses G33 */
|
|
|
|
static const struct intel_cdclk_funcs i965gm_cdclk_funcs = {
|
|
.get_cdclk = i965gm_get_cdclk,
|
|
.modeset_calc_cdclk = fixed_modeset_calc_cdclk,
|
|
};
|
|
|
|
/* i965G uses fixed 400 */
|
|
|
|
static const struct intel_cdclk_funcs pnv_cdclk_funcs = {
|
|
.get_cdclk = pnv_get_cdclk,
|
|
.modeset_calc_cdclk = fixed_modeset_calc_cdclk,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs g33_cdclk_funcs = {
|
|
.get_cdclk = g33_get_cdclk,
|
|
.modeset_calc_cdclk = fixed_modeset_calc_cdclk,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs i945gm_cdclk_funcs = {
|
|
.get_cdclk = i945gm_get_cdclk,
|
|
.modeset_calc_cdclk = fixed_modeset_calc_cdclk,
|
|
};
|
|
|
|
/* i945G uses fixed 400 */
|
|
|
|
static const struct intel_cdclk_funcs i915gm_cdclk_funcs = {
|
|
.get_cdclk = i915gm_get_cdclk,
|
|
.modeset_calc_cdclk = fixed_modeset_calc_cdclk,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs i915g_cdclk_funcs = {
|
|
.get_cdclk = fixed_333mhz_get_cdclk,
|
|
.modeset_calc_cdclk = fixed_modeset_calc_cdclk,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs i865g_cdclk_funcs = {
|
|
.get_cdclk = fixed_266mhz_get_cdclk,
|
|
.modeset_calc_cdclk = fixed_modeset_calc_cdclk,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs i85x_cdclk_funcs = {
|
|
.get_cdclk = i85x_get_cdclk,
|
|
.modeset_calc_cdclk = fixed_modeset_calc_cdclk,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs i845g_cdclk_funcs = {
|
|
.get_cdclk = fixed_200mhz_get_cdclk,
|
|
.modeset_calc_cdclk = fixed_modeset_calc_cdclk,
|
|
};
|
|
|
|
static const struct intel_cdclk_funcs i830_cdclk_funcs = {
|
|
.get_cdclk = fixed_133mhz_get_cdclk,
|
|
.modeset_calc_cdclk = fixed_modeset_calc_cdclk,
|
|
};
|
|
|
|
/**
|
|
* intel_init_cdclk_hooks - Initialize CDCLK related modesetting hooks
|
|
* @display: display instance
|
|
*/
|
|
void intel_init_cdclk_hooks(struct intel_display *display)
|
|
{
|
|
if (DISPLAY_VER(display) >= 35) {
|
|
display->funcs.cdclk = &xe3lpd_cdclk_funcs;
|
|
display->cdclk.table = xe3p_lpd_cdclk_table;
|
|
} else if (DISPLAY_VER(display) >= 30) {
|
|
display->funcs.cdclk = &xe3lpd_cdclk_funcs;
|
|
display->cdclk.table = xe3lpd_cdclk_table;
|
|
} else if (DISPLAY_VER(display) >= 20) {
|
|
display->funcs.cdclk = &rplu_cdclk_funcs;
|
|
display->cdclk.table = xe2lpd_cdclk_table;
|
|
} else if (DISPLAY_VERx100(display) >= 1401) {
|
|
display->funcs.cdclk = &rplu_cdclk_funcs;
|
|
display->cdclk.table = xe2hpd_cdclk_table;
|
|
} else if (DISPLAY_VER(display) >= 14) {
|
|
display->funcs.cdclk = &rplu_cdclk_funcs;
|
|
display->cdclk.table = mtl_cdclk_table;
|
|
} else if (display->platform.dg2) {
|
|
display->funcs.cdclk = &tgl_cdclk_funcs;
|
|
display->cdclk.table = dg2_cdclk_table;
|
|
} else if (display->platform.alderlake_p) {
|
|
/* Wa_22011320316:adl-p[a0] */
|
|
if (display->platform.alderlake_p && IS_DISPLAY_STEP(display, STEP_A0, STEP_B0)) {
|
|
display->cdclk.table = adlp_a_step_cdclk_table;
|
|
display->funcs.cdclk = &tgl_cdclk_funcs;
|
|
} else if (display->platform.alderlake_p_raptorlake_u) {
|
|
display->cdclk.table = rplu_cdclk_table;
|
|
display->funcs.cdclk = &rplu_cdclk_funcs;
|
|
} else {
|
|
display->cdclk.table = adlp_cdclk_table;
|
|
display->funcs.cdclk = &tgl_cdclk_funcs;
|
|
}
|
|
} else if (display->platform.rocketlake) {
|
|
display->funcs.cdclk = &tgl_cdclk_funcs;
|
|
display->cdclk.table = rkl_cdclk_table;
|
|
} else if (DISPLAY_VER(display) >= 12) {
|
|
display->funcs.cdclk = &tgl_cdclk_funcs;
|
|
display->cdclk.table = icl_cdclk_table;
|
|
} else if (display->platform.jasperlake || display->platform.elkhartlake) {
|
|
display->funcs.cdclk = &ehl_cdclk_funcs;
|
|
display->cdclk.table = icl_cdclk_table;
|
|
} else if (DISPLAY_VER(display) >= 11) {
|
|
display->funcs.cdclk = &icl_cdclk_funcs;
|
|
display->cdclk.table = icl_cdclk_table;
|
|
} else if (display->platform.geminilake || display->platform.broxton) {
|
|
display->funcs.cdclk = &bxt_cdclk_funcs;
|
|
if (display->platform.geminilake)
|
|
display->cdclk.table = glk_cdclk_table;
|
|
else
|
|
display->cdclk.table = bxt_cdclk_table;
|
|
} else if (DISPLAY_VER(display) == 9) {
|
|
display->funcs.cdclk = &skl_cdclk_funcs;
|
|
} else if (display->platform.broadwell) {
|
|
display->funcs.cdclk = &bdw_cdclk_funcs;
|
|
} else if (display->platform.haswell) {
|
|
display->funcs.cdclk = &hsw_cdclk_funcs;
|
|
} else if (display->platform.cherryview) {
|
|
display->funcs.cdclk = &chv_cdclk_funcs;
|
|
} else if (display->platform.valleyview) {
|
|
display->funcs.cdclk = &vlv_cdclk_funcs;
|
|
} else if (display->platform.sandybridge || display->platform.ivybridge) {
|
|
display->funcs.cdclk = &fixed_400mhz_cdclk_funcs;
|
|
} else if (display->platform.ironlake) {
|
|
display->funcs.cdclk = &ilk_cdclk_funcs;
|
|
} else if (display->platform.gm45) {
|
|
display->funcs.cdclk = &gm45_cdclk_funcs;
|
|
} else if (display->platform.g45) {
|
|
display->funcs.cdclk = &g33_cdclk_funcs;
|
|
} else if (display->platform.i965gm) {
|
|
display->funcs.cdclk = &i965gm_cdclk_funcs;
|
|
} else if (display->platform.i965g) {
|
|
display->funcs.cdclk = &fixed_400mhz_cdclk_funcs;
|
|
} else if (display->platform.pineview) {
|
|
display->funcs.cdclk = &pnv_cdclk_funcs;
|
|
} else if (display->platform.g33) {
|
|
display->funcs.cdclk = &g33_cdclk_funcs;
|
|
} else if (display->platform.i945gm) {
|
|
display->funcs.cdclk = &i945gm_cdclk_funcs;
|
|
} else if (display->platform.i945g) {
|
|
display->funcs.cdclk = &fixed_400mhz_cdclk_funcs;
|
|
} else if (display->platform.i915gm) {
|
|
display->funcs.cdclk = &i915gm_cdclk_funcs;
|
|
} else if (display->platform.i915g) {
|
|
display->funcs.cdclk = &i915g_cdclk_funcs;
|
|
} else if (display->platform.i865g) {
|
|
display->funcs.cdclk = &i865g_cdclk_funcs;
|
|
} else if (display->platform.i85x) {
|
|
display->funcs.cdclk = &i85x_cdclk_funcs;
|
|
} else if (display->platform.i845g) {
|
|
display->funcs.cdclk = &i845g_cdclk_funcs;
|
|
} else if (display->platform.i830) {
|
|
display->funcs.cdclk = &i830_cdclk_funcs;
|
|
}
|
|
|
|
if (drm_WARN(display->drm, !display->funcs.cdclk,
|
|
"Unknown platform. Assuming i830\n"))
|
|
display->funcs.cdclk = &i830_cdclk_funcs;
|
|
}
|
|
|
|
int intel_cdclk_logical(const struct intel_cdclk_state *cdclk_state)
|
|
{
|
|
return cdclk_state->logical.cdclk;
|
|
}
|
|
|
|
int intel_cdclk_actual(const struct intel_cdclk_state *cdclk_state)
|
|
{
|
|
return cdclk_state->actual.cdclk;
|
|
}
|
|
|
|
int intel_cdclk_actual_voltage_level(const struct intel_cdclk_state *cdclk_state)
|
|
{
|
|
return cdclk_state->actual.voltage_level;
|
|
}
|
|
|
|
int intel_cdclk_min_cdclk(const struct intel_cdclk_state *cdclk_state, enum pipe pipe)
|
|
{
|
|
return cdclk_state->min_cdclk[pipe];
|
|
}
|
|
|
|
bool intel_cdclk_pmdemand_needs_update(struct intel_atomic_state *state)
|
|
{
|
|
const struct intel_cdclk_state *new_cdclk_state, *old_cdclk_state;
|
|
|
|
new_cdclk_state = intel_atomic_get_new_cdclk_state(state);
|
|
old_cdclk_state = intel_atomic_get_old_cdclk_state(state);
|
|
|
|
if (new_cdclk_state &&
|
|
(new_cdclk_state->actual.cdclk != old_cdclk_state->actual.cdclk ||
|
|
new_cdclk_state->actual.voltage_level != old_cdclk_state->actual.voltage_level))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void intel_cdclk_force_min_cdclk(struct intel_cdclk_state *cdclk_state, int force_min_cdclk)
|
|
{
|
|
cdclk_state->force_min_cdclk = force_min_cdclk;
|
|
}
|
|
|
|
void intel_cdclk_read_hw(struct intel_display *display)
|
|
{
|
|
struct intel_cdclk_state *cdclk_state;
|
|
|
|
cdclk_state = to_intel_cdclk_state(display->cdclk.obj.state);
|
|
|
|
intel_update_cdclk(display);
|
|
intel_cdclk_dump_config(display, &display->cdclk.hw, "Current CDCLK");
|
|
cdclk_state->actual = display->cdclk.hw;
|
|
cdclk_state->logical = display->cdclk.hw;
|
|
}
|
|
|
|
static int calc_cdclk(const struct intel_crtc_state *crtc_state, int min_cdclk)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc_state);
|
|
|
|
if (DISPLAY_VER(display) >= 10 || display->platform.broxton) {
|
|
return bxt_calc_cdclk(display, min_cdclk);
|
|
} else if (DISPLAY_VER(display) == 9) {
|
|
int vco;
|
|
|
|
vco = display->cdclk.skl_preferred_vco_freq;
|
|
if (vco == 0)
|
|
vco = 8100000;
|
|
|
|
return skl_calc_cdclk(min_cdclk, vco);
|
|
} else if (display->platform.broadwell) {
|
|
return bdw_calc_cdclk(min_cdclk);
|
|
} else if (display->platform.cherryview || display->platform.valleyview) {
|
|
return vlv_calc_cdclk(display, min_cdclk);
|
|
} else {
|
|
return display->cdclk.max_cdclk_freq;
|
|
}
|
|
}
|
|
|
|
static unsigned int _intel_cdclk_prefill_adj(const struct intel_crtc_state *crtc_state,
|
|
int clock, int min_cdclk)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc_state);
|
|
int ppc = intel_cdclk_ppc(display, crtc_state->double_wide);
|
|
int cdclk = calc_cdclk(crtc_state, min_cdclk);
|
|
|
|
return min(0x10000, DIV_ROUND_UP_ULL((u64)clock << 16, ppc * cdclk));
|
|
}
|
|
|
|
unsigned int intel_cdclk_prefill_adjustment(const struct intel_crtc_state *crtc_state)
|
|
{
|
|
/* FIXME use the actual min_cdclk for the pipe here */
|
|
return intel_cdclk_prefill_adjustment_worst(crtc_state);
|
|
}
|
|
|
|
unsigned int intel_cdclk_prefill_adjustment_worst(const struct intel_crtc_state *crtc_state)
|
|
{
|
|
int clock = crtc_state->hw.pipe_mode.crtc_clock;
|
|
int min_cdclk;
|
|
|
|
/*
|
|
* FIXME could perhaps consider a few more of the factors
|
|
* that go the per-crtc min_cdclk. Namely anything that
|
|
* only changes during full modesets.
|
|
*
|
|
* FIXME this assumes 1:1 scaling, but the other _worst() stuff
|
|
* assumes max downscaling, so the final result will be
|
|
* unrealistically bad. Figure out where the actual maximum value
|
|
* lies and use that to compute a more realistic worst case
|
|
* estimate...
|
|
*/
|
|
min_cdclk = _intel_pixel_rate_to_cdclk(crtc_state, clock);
|
|
|
|
return _intel_cdclk_prefill_adj(crtc_state, clock, min_cdclk);
|
|
}
|
|
|
|
int intel_cdclk_min_cdclk_for_prefill(const struct intel_crtc_state *crtc_state,
|
|
unsigned int prefill_lines_unadjusted,
|
|
unsigned int prefill_lines_available)
|
|
{
|
|
struct intel_display *display = to_intel_display(crtc_state);
|
|
const struct drm_display_mode *pipe_mode = &crtc_state->hw.pipe_mode;
|
|
int ppc = intel_cdclk_ppc(display, crtc_state->double_wide);
|
|
|
|
return DIV_ROUND_UP_ULL(mul_u32_u32(pipe_mode->crtc_clock, prefill_lines_unadjusted),
|
|
ppc * prefill_lines_available);
|
|
}
|