Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/gpu/drm/i915/display/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 132 kB image not shown  

Quelle  intel_dpll_mgr.c   Sprache: C

 
/*
 * Copyright © 2006-2016 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/math.h>
#include <linux/string_helpers.h>

#include <drm/drm_print.h>

#include "bxt_dpio_phy_regs.h"
#include "i915_utils.h"
#include "intel_cx0_phy.h"
#include "intel_de.h"
#include "intel_display_regs.h"
#include "intel_display_types.h"
#include "intel_dkl_phy.h"
#include "intel_dkl_phy_regs.h"
#include "intel_dpio_phy.h"
#include "intel_dpll.h"
#include "intel_dpll_mgr.h"
#include "intel_hti.h"
#include "intel_mg_phy_regs.h"
#include "intel_pch_refclk.h"
#include "intel_step.h"
#include "intel_tc.h"

/**
 * DOC: Display PLLs
 *
 * Display PLLs used for driving outputs vary by platform. While some have
 * per-pipe or per-encoder dedicated PLLs, others allow the use of any PLL
 * from a pool. In the latter scenario, it is possible that multiple pipes
 * share a PLL if their configurations match.
 *
 * This file provides an abstraction over display PLLs. The function
 * intel_dpll_init() initializes the PLLs for the given platform.  The
 * users of a PLL are tracked and that tracking is integrated with the atomic
 * modset interface. During an atomic operation, required PLLs can be reserved
 * for a given CRTC and encoder configuration by calling
 * intel_dpll_reserve() and previously reserved PLLs can be released
 * with intel_dpll_release().
 * Changes to the users are first staged in the atomic state, and then made
 * effective by calling intel_dpll_swap_state() during the atomic
 * commit phase.
 */


/* platform specific hooks for managing DPLLs */
struct intel_dpll_funcs {
 /*
 * Hook for enabling the pll, called from intel_enable_dpll() if
 * the pll is not already enabled.
 */

 void (*enable)(struct intel_display *display,
         struct intel_dpll *pll,
         const struct intel_dpll_hw_state *dpll_hw_state);

 /*
 * Hook for disabling the pll, called from intel_disable_dpll()
 * only when it is safe to disable the pll, i.e., there are no more
 * tracked users for it.
 */

 void (*disable)(struct intel_display *display,
   struct intel_dpll *pll);

 /*
 * Hook for reading the values currently programmed to the DPLL
 * registers. This is used for initial hw state readout and state
 * verification after a mode set.
 */

 bool (*get_hw_state)(struct intel_display *display,
        struct intel_dpll *pll,
        struct intel_dpll_hw_state *dpll_hw_state);

 /*
 * Hook for calculating the pll's output frequency based on its passed
 * in state.
 */

 int (*get_freq)(struct intel_display *i915,
   const struct intel_dpll *pll,
   const struct intel_dpll_hw_state *dpll_hw_state);
};

struct intel_dpll_mgr {
 const struct dpll_info *dpll_info;

 int (*compute_dplls)(struct intel_atomic_state *state,
        struct intel_crtc *crtc,
        struct intel_encoder *encoder);
 int (*get_dplls)(struct intel_atomic_state *state,
    struct intel_crtc *crtc,
    struct intel_encoder *encoder);
 void (*put_dplls)(struct intel_atomic_state *state,
     struct intel_crtc *crtc);
 void (*update_active_dpll)(struct intel_atomic_state *state,
       struct intel_crtc *crtc,
       struct intel_encoder *encoder);
 void (*update_ref_clks)(struct intel_display *display);
 void (*dump_hw_state)(struct drm_printer *p,
         const struct intel_dpll_hw_state *dpll_hw_state);
 bool (*compare_hw_state)(const struct intel_dpll_hw_state *a,
     const struct intel_dpll_hw_state *b);
};

static void
intel_atomic_duplicate_dpll_state(struct intel_display *display,
      struct intel_dpll_state *dpll_state)
{
 struct intel_dpll *pll;
 int i;

 /* Copy dpll state */
 for_each_dpll(display, pll, i)
  dpll_state[pll->index] = pll->state;
}

static struct intel_dpll_state *
intel_atomic_get_dpll_state(struct drm_atomic_state *s)
{
 struct intel_atomic_state *state = to_intel_atomic_state(s);
 struct intel_display *display = to_intel_display(state);

 drm_WARN_ON(s->dev, !drm_modeset_is_locked(&s->dev->mode_config.connection_mutex));

 if (!state->dpll_set) {
  state->dpll_set = true;

  intel_atomic_duplicate_dpll_state(display,
        state->dpll_state);
 }

 return state->dpll_state;
}

/**
 * intel_get_dpll_by_id - get a DPLL given its id
 * @display: intel_display device instance
 * @id: pll id
 *
 * Returns:
 * A pointer to the DPLL with @id
 */

struct intel_dpll *
intel_get_dpll_by_id(struct intel_display *display,
       enum intel_dpll_id id)
{
 struct intel_dpll *pll;
 int i;

 for_each_dpll(display, pll, i) {
  if (pll->info->id == id)
   return pll;
 }

 MISSING_CASE(id);
 return NULL;
}

/* For ILK+ */
void assert_dpll(struct intel_display *display,
   struct intel_dpll *pll,
   bool state)
{
 bool cur_state;
 struct intel_dpll_hw_state hw_state;

 if (drm_WARN(display->drm, !pll,
       "asserting DPLL %s with no DPLL\n", str_on_off(state)))
  return;

 cur_state = intel_dpll_get_hw_state(display, pll, &hw_state);
 INTEL_DISPLAY_STATE_WARN(display, cur_state != state,
     "%s assertion failure (expected %s, current %s)\n",
     pll->info->name, str_on_off(state),
     str_on_off(cur_state));
}

static enum tc_port icl_pll_id_to_tc_port(enum intel_dpll_id id)
{
 return TC_PORT_1 + id - DPLL_ID_ICL_MGPLL1;
}

enum intel_dpll_id icl_tc_port_to_pll_id(enum tc_port tc_port)
{
 return tc_port - TC_PORT_1 + DPLL_ID_ICL_MGPLL1;
}

static i915_reg_t
intel_combo_pll_enable_reg(struct intel_display *display,
      struct intel_dpll *pll)
{
 if (display->platform.dg1)
  return DG1_DPLL_ENABLE(pll->info->id);
 else if ((display->platform.jasperlake || display->platform.elkhartlake) &&
   (pll->info->id == DPLL_ID_EHL_DPLL4))
  return MG_PLL_ENABLE(0);

 return ICL_DPLL_ENABLE(pll->info->id);
}

static i915_reg_t
intel_tc_pll_enable_reg(struct intel_display *display,
   struct intel_dpll *pll)
{
 const enum intel_dpll_id id = pll->info->id;
 enum tc_port tc_port = icl_pll_id_to_tc_port(id);

 if (display->platform.alderlake_p)
  return ADLP_PORTTC_PLL_ENABLE(tc_port);

 return MG_PLL_ENABLE(tc_port);
}

static void _intel_enable_shared_dpll(struct intel_display *display,
          struct intel_dpll *pll)
{
 if (pll->info->power_domain)
  pll->wakeref = intel_display_power_get(display, pll->info->power_domain);

 pll->info->funcs->enable(display, pll, &pll->state.hw_state);
 pll->on = true;
}

static void _intel_disable_shared_dpll(struct intel_display *display,
           struct intel_dpll *pll)
{
 pll->info->funcs->disable(display, pll);
 pll->on = false;

 if (pll->info->power_domain)
  intel_display_power_put(display, pll->info->power_domain, pll->wakeref);
}

/**
 * intel_dpll_enable - enable a CRTC's DPLL
 * @crtc_state: CRTC, and its state, which has a DPLL
 *
 * Enable DPLL used by @crtc.
 */

void intel_dpll_enable(const struct intel_crtc_state *crtc_state)
{
 struct intel_display *display = to_intel_display(crtc_state);
 struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc);
 struct intel_dpll *pll = crtc_state->intel_dpll;
 unsigned int pipe_mask = intel_crtc_joined_pipe_mask(crtc_state);
 unsigned int old_mask;

 if (drm_WARN_ON(display->drm, !pll))
  return;

 mutex_lock(&display->dpll.lock);
 old_mask = pll->active_mask;

 if (drm_WARN_ON(display->drm, !(pll->state.pipe_mask & pipe_mask)) ||
     drm_WARN_ON(display->drm, pll->active_mask & pipe_mask))
  goto out;

 pll->active_mask |= pipe_mask;

 drm_dbg_kms(display->drm,
      "enable %s (active 0x%x, on? %d) for [CRTC:%d:%s]\n",
      pll->info->name, pll->active_mask, pll->on,
      crtc->base.base.id, crtc->base.name);

 if (old_mask) {
  drm_WARN_ON(display->drm, !pll->on);
  assert_dpll_enabled(display, pll);
  goto out;
 }
 drm_WARN_ON(display->drm, pll->on);

 drm_dbg_kms(display->drm, "enabling %s\n", pll->info->name);

 _intel_enable_shared_dpll(display, pll);

out:
 mutex_unlock(&display->dpll.lock);
}

/**
 * intel_dpll_disable - disable a CRTC's shared DPLL
 * @crtc_state: CRTC, and its state, which has a shared DPLL
 *
 * Disable DPLL used by @crtc.
 */

void intel_dpll_disable(const struct intel_crtc_state *crtc_state)
{
 struct intel_display *display = to_intel_display(crtc_state);
 struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc);
 struct intel_dpll *pll = crtc_state->intel_dpll;
 unsigned int pipe_mask = intel_crtc_joined_pipe_mask(crtc_state);

 /* PCH only available on ILK+ */
 if (DISPLAY_VER(display) < 5)
  return;

 if (pll == NULL)
  return;

 mutex_lock(&display->dpll.lock);
 if (drm_WARN(display->drm, !(pll->active_mask & pipe_mask),
       "%s not used by [CRTC:%d:%s]\n", pll->info->name,
       crtc->base.base.id, crtc->base.name))
  goto out;

 drm_dbg_kms(display->drm,
      "disable %s (active 0x%x, on? %d) for [CRTC:%d:%s]\n",
      pll->info->name, pll->active_mask, pll->on,
      crtc->base.base.id, crtc->base.name);

 assert_dpll_enabled(display, pll);
 drm_WARN_ON(display->drm, !pll->on);

 pll->active_mask &= ~pipe_mask;
 if (pll->active_mask)
  goto out;

 drm_dbg_kms(display->drm, "disabling %s\n", pll->info->name);

 _intel_disable_shared_dpll(display, pll);

out:
 mutex_unlock(&display->dpll.lock);
}

static unsigned long
intel_dpll_mask_all(struct intel_display *display)
{
 struct intel_dpll *pll;
 unsigned long dpll_mask = 0;
 int i;

 for_each_dpll(display, pll, i) {
  drm_WARN_ON(display->drm, dpll_mask & BIT(pll->info->id));

  dpll_mask |= BIT(pll->info->id);
 }

 return dpll_mask;
}

static struct intel_dpll *
intel_find_dpll(struct intel_atomic_state *state,
  const struct intel_crtc *crtc,
  const struct intel_dpll_hw_state *dpll_hw_state,
  unsigned long dpll_mask)
{
 struct intel_display *display = to_intel_display(crtc);
 unsigned long dpll_mask_all = intel_dpll_mask_all(display);
 struct intel_dpll_state *dpll_state;
 struct intel_dpll *unused_pll = NULL;
 enum intel_dpll_id id;

 dpll_state = intel_atomic_get_dpll_state(&state->base);

 drm_WARN_ON(display->drm, dpll_mask & ~dpll_mask_all);

 for_each_set_bit(id, &dpll_mask, fls(dpll_mask_all)) {
  struct intel_dpll *pll;

  pll = intel_get_dpll_by_id(display, id);
  if (!pll)
   continue;

  /* Only want to check enabled timings first */
  if (dpll_state[pll->index].pipe_mask == 0) {
   if (!unused_pll)
    unused_pll = pll;
   continue;
  }

  if (memcmp(dpll_hw_state,
      &dpll_state[pll->index].hw_state,
      sizeof(*dpll_hw_state)) == 0) {
   drm_dbg_kms(display->drm,
        "[CRTC:%d:%s] sharing existing %s (pipe mask 0x%x, active 0x%x)\n",
        crtc->base.base.id, crtc->base.name,
        pll->info->name,
        dpll_state[pll->index].pipe_mask,
        pll->active_mask);
   return pll;
  }
 }

 /* Ok no matching timings, maybe there's a free one? */
 if (unused_pll) {
  drm_dbg_kms(display->drm, "[CRTC:%d:%s] allocated %s\n",
       crtc->base.base.id, crtc->base.name,
       unused_pll->info->name);
  return unused_pll;
 }

 return NULL;
}

/**
 * intel_dpll_crtc_get - Get a DPLL reference for a CRTC
 * @crtc: CRTC on which behalf the reference is taken
 * @pll: DPLL for which the reference is taken
 * @dpll_state: the DPLL atomic state in which the reference is tracked
 *
 * Take a reference for @pll tracking the use of it by @crtc.
 */

static void
intel_dpll_crtc_get(const struct intel_crtc *crtc,
      const struct intel_dpll *pll,
      struct intel_dpll_state *dpll_state)
{
 struct intel_display *display = to_intel_display(crtc);

 drm_WARN_ON(display->drm, (dpll_state->pipe_mask & BIT(crtc->pipe)) != 0);

 dpll_state->pipe_mask |= BIT(crtc->pipe);

 drm_dbg_kms(display->drm, "[CRTC:%d:%s] reserving %s\n",
      crtc->base.base.id, crtc->base.name, pll->info->name);
}

static void
intel_reference_dpll(struct intel_atomic_state *state,
       const struct intel_crtc *crtc,
       const struct intel_dpll *pll,
       const struct intel_dpll_hw_state *dpll_hw_state)
{
 struct intel_dpll_state *dpll_state;

 dpll_state = intel_atomic_get_dpll_state(&state->base);

 if (dpll_state[pll->index].pipe_mask == 0)
  dpll_state[pll->index].hw_state = *dpll_hw_state;

 intel_dpll_crtc_get(crtc, pll, &dpll_state[pll->index]);
}

/**
 * intel_dpll_crtc_put - Drop a DPLL reference for a CRTC
 * @crtc: CRTC on which behalf the reference is dropped
 * @pll: DPLL for which the reference is dropped
 * @dpll_state: the DPLL atomic state in which the reference is tracked
 *
 * Drop a reference for @pll tracking the end of use of it by @crtc.
 */

void
intel_dpll_crtc_put(const struct intel_crtc *crtc,
      const struct intel_dpll *pll,
      struct intel_dpll_state *dpll_state)
{
 struct intel_display *display = to_intel_display(crtc);

 drm_WARN_ON(display->drm, (dpll_state->pipe_mask & BIT(crtc->pipe)) == 0);

 dpll_state->pipe_mask &= ~BIT(crtc->pipe);

 drm_dbg_kms(display->drm, "[CRTC:%d:%s] releasing %s\n",
      crtc->base.base.id, crtc->base.name, pll->info->name);
}

static void intel_unreference_dpll(struct intel_atomic_state *state,
       const struct intel_crtc *crtc,
       const struct intel_dpll *pll)
{
 struct intel_dpll_state *dpll_state;

 dpll_state = intel_atomic_get_dpll_state(&state->base);

 intel_dpll_crtc_put(crtc, pll, &dpll_state[pll->index]);
}

static void intel_put_dpll(struct intel_atomic_state *state,
      struct intel_crtc *crtc)
{
 const struct intel_crtc_state *old_crtc_state =
  intel_atomic_get_old_crtc_state(state, crtc);
 struct intel_crtc_state *new_crtc_state =
  intel_atomic_get_new_crtc_state(state, crtc);

 new_crtc_state->intel_dpll = NULL;

 if (!old_crtc_state->intel_dpll)
  return;

 intel_unreference_dpll(state, crtc, old_crtc_state->intel_dpll);
}

/**
 * intel_dpll_swap_state - make atomic DPLL configuration effective
 * @state: atomic state
 *
 * This is the dpll version of drm_atomic_helper_swap_state() since the
 * helper does not handle driver-specific global state.
 *
 * For consistency with atomic helpers this function does a complete swap,
 * i.e. it also puts the current state into @state, even though there is no
 * need for that at this moment.
 */

void intel_dpll_swap_state(struct intel_atomic_state *state)
{
 struct intel_display *display = to_intel_display(state);
 struct intel_dpll_state *dpll_state = state->dpll_state;
 struct intel_dpll *pll;
 int i;

 if (!state->dpll_set)
  return;

 for_each_dpll(display, pll, i)
  swap(pll->state, dpll_state[pll->index]);
}

static bool ibx_pch_dpll_get_hw_state(struct intel_display *display,
          struct intel_dpll *pll,
          struct intel_dpll_hw_state *dpll_hw_state)
{
 struct i9xx_dpll_hw_state *hw_state = &dpll_hw_state->i9xx;
 const enum intel_dpll_id id = pll->info->id;
 intel_wakeref_t wakeref;
 u32 val;

 wakeref = intel_display_power_get_if_enabled(display,
           POWER_DOMAIN_DISPLAY_CORE);
 if (!wakeref)
  return false;

 val = intel_de_read(display, PCH_DPLL(id));
 hw_state->dpll = val;
 hw_state->fp0 = intel_de_read(display, PCH_FP0(id));
 hw_state->fp1 = intel_de_read(display, PCH_FP1(id));

 intel_display_power_put(display, POWER_DOMAIN_DISPLAY_CORE, wakeref);

 return val & DPLL_VCO_ENABLE;
}

static void ibx_assert_pch_refclk_enabled(struct intel_display *display)
{
 u32 val;
 bool enabled;

 val = intel_de_read(display, PCH_DREF_CONTROL);
 enabled = !!(val & (DREF_SSC_SOURCE_MASK | DREF_NONSPREAD_SOURCE_MASK |
       DREF_SUPERSPREAD_SOURCE_MASK));
 INTEL_DISPLAY_STATE_WARN(display, !enabled,
     "PCH refclk assertion failure, should be active but is disabled\n");
}

static void ibx_pch_dpll_enable(struct intel_display *display,
    struct intel_dpll *pll,
    const struct intel_dpll_hw_state *dpll_hw_state)
{
 const struct i9xx_dpll_hw_state *hw_state = &dpll_hw_state->i9xx;
 const enum intel_dpll_id id = pll->info->id;

 /* PCH refclock must be enabled first */
 ibx_assert_pch_refclk_enabled(display);

 intel_de_write(display, PCH_FP0(id), hw_state->fp0);
 intel_de_write(display, PCH_FP1(id), hw_state->fp1);

 intel_de_write(display, PCH_DPLL(id), hw_state->dpll);

 /* Wait for the clocks to stabilize. */
 intel_de_posting_read(display, PCH_DPLL(id));
 udelay(150);

 /* The pixel multiplier can only be updated once the
 * DPLL is enabled and the clocks are stable.
 *
 * So write it again.
 */

 intel_de_write(display, PCH_DPLL(id), hw_state->dpll);
 intel_de_posting_read(display, PCH_DPLL(id));
 udelay(200);
}

static void ibx_pch_dpll_disable(struct intel_display *display,
     struct intel_dpll *pll)
{
 const enum intel_dpll_id id = pll->info->id;

 intel_de_write(display, PCH_DPLL(id), 0);
 intel_de_posting_read(display, PCH_DPLL(id));
 udelay(200);
}

static int ibx_compute_dpll(struct intel_atomic_state *state,
       struct intel_crtc *crtc,
       struct intel_encoder *encoder)
{
 return 0;
}

static int ibx_get_dpll(struct intel_atomic_state *state,
   struct intel_crtc *crtc,
   struct intel_encoder *encoder)
{
 struct intel_display *display = to_intel_display(state);
 struct intel_crtc_state *crtc_state =
  intel_atomic_get_new_crtc_state(state, crtc);
 struct intel_dpll *pll;
 enum intel_dpll_id id;

 if (HAS_PCH_IBX(display)) {
  /* Ironlake PCH has a fixed PLL->PCH pipe mapping. */
  id = (enum intel_dpll_id) crtc->pipe;
  pll = intel_get_dpll_by_id(display, id);

  drm_dbg_kms(display->drm,
       "[CRTC:%d:%s] using pre-allocated %s\n",
       crtc->base.base.id, crtc->base.name,
       pll->info->name);
 } else {
  pll = intel_find_dpll(state, crtc,
          &crtc_state->dpll_hw_state,
          BIT(DPLL_ID_PCH_PLL_B) |
          BIT(DPLL_ID_PCH_PLL_A));
 }

 if (!pll)
  return -EINVAL;

 /* reference the pll */
 intel_reference_dpll(state, crtc,
        pll, &crtc_state->dpll_hw_state);

 crtc_state->intel_dpll = pll;

 return 0;
}

static void ibx_dump_hw_state(struct drm_printer *p,
         const struct intel_dpll_hw_state *dpll_hw_state)
{
 const struct i9xx_dpll_hw_state *hw_state = &dpll_hw_state->i9xx;

 drm_printf(p, "dpll_hw_state: dpll: 0x%x, dpll_md: 0x%x, "
     "fp0: 0x%x, fp1: 0x%x\n",
     hw_state->dpll,
     hw_state->dpll_md,
     hw_state->fp0,
     hw_state->fp1);
}

static bool ibx_compare_hw_state(const struct intel_dpll_hw_state *_a,
     const struct intel_dpll_hw_state *_b)
{
 const struct i9xx_dpll_hw_state *a = &_a->i9xx;
 const struct i9xx_dpll_hw_state *b = &_b->i9xx;

 return a->dpll == b->dpll &&
  a->dpll_md == b->dpll_md &&
  a->fp0 == b->fp0 &&
  a->fp1 == b->fp1;
}

static const struct intel_dpll_funcs ibx_pch_dpll_funcs = {
 .enable = ibx_pch_dpll_enable,
 .disable = ibx_pch_dpll_disable,
 .get_hw_state = ibx_pch_dpll_get_hw_state,
};

static const struct dpll_info pch_plls[] = {
 { .name = "PCH DPLL A", .funcs = &ibx_pch_dpll_funcs, .id = DPLL_ID_PCH_PLL_A, },
 { .name = "PCH DPLL B", .funcs = &ibx_pch_dpll_funcs, .id = DPLL_ID_PCH_PLL_B, },
 {}
};

static const struct intel_dpll_mgr pch_pll_mgr = {
 .dpll_info = pch_plls,
 .compute_dplls = ibx_compute_dpll,
 .get_dplls = ibx_get_dpll,
 .put_dplls = intel_put_dpll,
 .dump_hw_state = ibx_dump_hw_state,
 .compare_hw_state = ibx_compare_hw_state,
};

static void hsw_ddi_wrpll_enable(struct intel_display *display,
     struct intel_dpll *pll,
     const struct intel_dpll_hw_state *dpll_hw_state)
{
 const struct hsw_dpll_hw_state *hw_state = &dpll_hw_state->hsw;
 const enum intel_dpll_id id = pll->info->id;

 intel_de_write(display, WRPLL_CTL(id), hw_state->wrpll);
 intel_de_posting_read(display, WRPLL_CTL(id));
 udelay(20);
}

static void hsw_ddi_spll_enable(struct intel_display *display,
    struct intel_dpll *pll,
    const struct intel_dpll_hw_state *dpll_hw_state)
{
 const struct hsw_dpll_hw_state *hw_state = &dpll_hw_state->hsw;

 intel_de_write(display, SPLL_CTL, hw_state->spll);
 intel_de_posting_read(display, SPLL_CTL);
 udelay(20);
}

static void hsw_ddi_wrpll_disable(struct intel_display *display,
      struct intel_dpll *pll)
{
 const enum intel_dpll_id id = pll->info->id;

 intel_de_rmw(display, WRPLL_CTL(id), WRPLL_PLL_ENABLE, 0);
 intel_de_posting_read(display, WRPLL_CTL(id));

 /*
 * Try to set up the PCH reference clock once all DPLLs
 * that depend on it have been shut down.
 */

 if (display->dpll.pch_ssc_use & BIT(id))
  intel_init_pch_refclk(display);
}

static void hsw_ddi_spll_disable(struct intel_display *display,
     struct intel_dpll *pll)
{
 enum intel_dpll_id id = pll->info->id;

 intel_de_rmw(display, SPLL_CTL, SPLL_PLL_ENABLE, 0);
 intel_de_posting_read(display, SPLL_CTL);

 /*
 * Try to set up the PCH reference clock once all DPLLs
 * that depend on it have been shut down.
 */

 if (display->dpll.pch_ssc_use & BIT(id))
  intel_init_pch_refclk(display);
}

static bool hsw_ddi_wrpll_get_hw_state(struct intel_display *display,
           struct intel_dpll *pll,
           struct intel_dpll_hw_state *dpll_hw_state)
{
 struct hsw_dpll_hw_state *hw_state = &dpll_hw_state->hsw;
 const enum intel_dpll_id id = pll->info->id;
 intel_wakeref_t wakeref;
 u32 val;

 wakeref = intel_display_power_get_if_enabled(display,
           POWER_DOMAIN_DISPLAY_CORE);
 if (!wakeref)
  return false;

 val = intel_de_read(display, WRPLL_CTL(id));
 hw_state->wrpll = val;

 intel_display_power_put(display, POWER_DOMAIN_DISPLAY_CORE, wakeref);

 return val & WRPLL_PLL_ENABLE;
}

static bool hsw_ddi_spll_get_hw_state(struct intel_display *display,
          struct intel_dpll *pll,
          struct intel_dpll_hw_state *dpll_hw_state)
{
 struct hsw_dpll_hw_state *hw_state = &dpll_hw_state->hsw;
 intel_wakeref_t wakeref;
 u32 val;

 wakeref = intel_display_power_get_if_enabled(display,
           POWER_DOMAIN_DISPLAY_CORE);
 if (!wakeref)
  return false;

 val = intel_de_read(display, SPLL_CTL);
 hw_state->spll = val;

 intel_display_power_put(display, POWER_DOMAIN_DISPLAY_CORE, wakeref);

 return val & SPLL_PLL_ENABLE;
}

#define LC_FREQ 2700
#define LC_FREQ_2K U64_C(LC_FREQ * 2000)

#define P_MIN 2
#define P_MAX 64
#define P_INC 2

/* Constraints for PLL good behavior */
#define REF_MIN 48
#define REF_MAX 400
#define VCO_MIN 2400
#define VCO_MAX 4800

struct hsw_wrpll_rnp {
 unsigned p, n2, r2;
};

static unsigned hsw_wrpll_get_budget_for_freq(int clock)
{
 switch (clock) {
 case 25175000:
 case 25200000:
 case 27000000:
 case 27027000:
 case 37762500:
 case 37800000:
 case 40500000:
 case 40541000:
 case 54000000:
 case 54054000:
 case 59341000:
 case 59400000:
 case 72000000:
 case 74176000:
 case 74250000:
 case 81000000:
 case 81081000:
 case 89012000:
 case 89100000:
 case 108000000:
 case 108108000:
 case 111264000:
 case 111375000:
 case 148352000:
 case 148500000:
 case 162000000:
 case 162162000:
 case 222525000:
 case 222750000:
 case 296703000:
 case 297000000:
  return 0;
 case 233500000:
 case 245250000:
 case 247750000:
 case 253250000:
 case 298000000:
  return 1500;
 case 169128000:
 case 169500000:
 case 179500000:
 case 202000000:
  return 2000;
 case 256250000:
 case 262500000:
 case 270000000:
 case 272500000:
 case 273750000:
 case 280750000:
 case 281250000:
 case 286000000:
 case 291750000:
  return 4000;
 case 267250000:
 case 268500000:
  return 5000;
 default:
  return 1000;
 }
}

static void hsw_wrpll_update_rnp(u64 freq2k, unsigned int budget,
     unsigned int r2, unsigned int n2,
     unsigned int p,
     struct hsw_wrpll_rnp *best)
{
 u64 a, b, c, d, diff, diff_best;

 /* No best (r,n,p) yet */
 if (best->p == 0) {
  best->p = p;
  best->n2 = n2;
  best->r2 = r2;
  return;
 }

 /*
 * Output clock is (LC_FREQ_2K / 2000) * N / (P * R), which compares to
 * freq2k.
 *
 * delta = 1e6 *
 *    abs(freq2k - (LC_FREQ_2K * n2/(p * r2))) /
 *    freq2k;
 *
 * and we would like delta <= budget.
 *
 * If the discrepancy is above the PPM-based budget, always prefer to
 * improve upon the previous solution.  However, if you're within the
 * budget, try to maximize Ref * VCO, that is N / (P * R^2).
 */

 a = freq2k * budget * p * r2;
 b = freq2k * budget * best->p * best->r2;
 diff = abs_diff(freq2k * p * r2, LC_FREQ_2K * n2);
 diff_best = abs_diff(freq2k * best->p * best->r2,
        LC_FREQ_2K * best->n2);
 c = 1000000 * diff;
 d = 1000000 * diff_best;

 if (a < c && b < d) {
  /* If both are above the budget, pick the closer */
  if (best->p * best->r2 * diff < p * r2 * diff_best) {
   best->p = p;
   best->n2 = n2;
   best->r2 = r2;
  }
 } else if (a >= c && b < d) {
  /* If A is below the threshold but B is above it?  Update. */
  best->p = p;
  best->n2 = n2;
  best->r2 = r2;
 } else if (a >= c && b >= d) {
  /* Both are below the limit, so pick the higher n2/(r2*r2) */
  if (n2 * best->r2 * best->r2 > best->n2 * r2 * r2) {
   best->p = p;
   best->n2 = n2;
   best->r2 = r2;
  }
 }
 /* Otherwise a < c && b >= d, do nothing */
}

static void
hsw_ddi_calculate_wrpll(int clock /* in Hz */,
   unsigned *r2_out, unsigned *n2_out, unsigned *p_out)
{
 u64 freq2k;
 unsigned p, n2, r2;
 struct hsw_wrpll_rnp best = {};
 unsigned budget;

 freq2k = clock / 100;

 budget = hsw_wrpll_get_budget_for_freq(clock);

 /* Special case handling for 540 pixel clock: bypass WR PLL entirely
 * and directly pass the LC PLL to it. */

 if (freq2k == 5400000) {
  *n2_out = 2;
  *p_out = 1;
  *r2_out = 2;
  return;
 }

 /*
 * Ref = LC_FREQ / R, where Ref is the actual reference input seen by
 * the WR PLL.
 *
 * We want R so that REF_MIN <= Ref <= REF_MAX.
 * Injecting R2 = 2 * R gives:
 *   REF_MAX * r2 > LC_FREQ * 2 and
 *   REF_MIN * r2 < LC_FREQ * 2
 *
 * Which means the desired boundaries for r2 are:
 *  LC_FREQ * 2 / REF_MAX < r2 < LC_FREQ * 2 / REF_MIN
 *
 */

 for (r2 = LC_FREQ * 2 / REF_MAX + 1;
      r2 <= LC_FREQ * 2 / REF_MIN;
      r2++) {

  /*
 * VCO = N * Ref, that is: VCO = N * LC_FREQ / R
 *
 * Once again we want VCO_MIN <= VCO <= VCO_MAX.
 * Injecting R2 = 2 * R and N2 = 2 * N, we get:
 *   VCO_MAX * r2 > n2 * LC_FREQ and
 *   VCO_MIN * r2 < n2 * LC_FREQ)
 *
 * Which means the desired boundaries for n2 are:
 * VCO_MIN * r2 / LC_FREQ < n2 < VCO_MAX * r2 / LC_FREQ
 */

  for (n2 = VCO_MIN * r2 / LC_FREQ + 1;
       n2 <= VCO_MAX * r2 / LC_FREQ;
       n2++) {

   for (p = P_MIN; p <= P_MAX; p += P_INC)
    hsw_wrpll_update_rnp(freq2k, budget,
           r2, n2, p, &best);
  }
 }

 *n2_out = best.n2;
 *p_out = best.p;
 *r2_out = best.r2;
}

static int hsw_ddi_wrpll_get_freq(struct intel_display *display,
      const struct intel_dpll *pll,
      const struct intel_dpll_hw_state *dpll_hw_state)
{
 const struct hsw_dpll_hw_state *hw_state = &dpll_hw_state->hsw;
 int refclk;
 int n, p, r;
 u32 wrpll = hw_state->wrpll;

 switch (wrpll & WRPLL_REF_MASK) {
 case WRPLL_REF_SPECIAL_HSW:
  /* Muxed-SSC for BDW, non-SSC for non-ULT HSW. */
  if (display->platform.haswell && !display->platform.haswell_ult) {
   refclk = display->dpll.ref_clks.nssc;
   break;
  }
  fallthrough;
 case WRPLL_REF_PCH_SSC:
  /*
 * We could calculate spread here, but our checking
 * code only cares about 5% accuracy, and spread is a max of
 * 0.5% downspread.
 */

  refclk = display->dpll.ref_clks.ssc;
  break;
 case WRPLL_REF_LCPLL:
  refclk = 2700000;
  break;
 default:
  MISSING_CASE(wrpll);
  return 0;
 }

 r = wrpll & WRPLL_DIVIDER_REF_MASK;
 p = (wrpll & WRPLL_DIVIDER_POST_MASK) >> WRPLL_DIVIDER_POST_SHIFT;
 n = (wrpll & WRPLL_DIVIDER_FB_MASK) >> WRPLL_DIVIDER_FB_SHIFT;

 /* Convert to KHz, p & r have a fixed point portion */
 return (refclk * n / 10) / (p * r) * 2;
}

static int
hsw_ddi_wrpll_compute_dpll(struct intel_atomic_state *state,
      struct intel_crtc *crtc)
{
 struct intel_display *display = to_intel_display(state);
 struct intel_crtc_state *crtc_state =
  intel_atomic_get_new_crtc_state(state, crtc);
 struct hsw_dpll_hw_state *hw_state = &crtc_state->dpll_hw_state.hsw;
 unsigned int p, n2, r2;

 hsw_ddi_calculate_wrpll(crtc_state->port_clock * 1000, &r2, &n2, &p);

 hw_state->wrpll =
  WRPLL_PLL_ENABLE | WRPLL_REF_LCPLL |
  WRPLL_DIVIDER_REFERENCE(r2) | WRPLL_DIVIDER_FEEDBACK(n2) |
  WRPLL_DIVIDER_POST(p);

 crtc_state->port_clock = hsw_ddi_wrpll_get_freq(display, NULL,
       &crtc_state->dpll_hw_state);

 return 0;
}

static struct intel_dpll *
hsw_ddi_wrpll_get_dpll(struct intel_atomic_state *state,
         struct intel_crtc *crtc)
{
 struct intel_crtc_state *crtc_state =
  intel_atomic_get_new_crtc_state(state, crtc);

 return intel_find_dpll(state, crtc,
          &crtc_state->dpll_hw_state,
          BIT(DPLL_ID_WRPLL2) |
          BIT(DPLL_ID_WRPLL1));
}

static int
hsw_ddi_lcpll_compute_dpll(struct intel_crtc_state *crtc_state)
{
 struct intel_display *display = to_intel_display(crtc_state);
 int clock = crtc_state->port_clock;

 switch (clock / 2) {
 case 81000:
 case 135000:
 case 270000:
  return 0;
 default:
  drm_dbg_kms(display->drm, "Invalid clock for DP: %d\n",
       clock);
  return -EINVAL;
 }
}

static struct intel_dpll *
hsw_ddi_lcpll_get_dpll(struct intel_crtc_state *crtc_state)
{
 struct intel_display *display = to_intel_display(crtc_state);
 struct intel_dpll *pll;
 enum intel_dpll_id pll_id;
 int clock = crtc_state->port_clock;

 switch (clock / 2) {
 case 81000:
  pll_id = DPLL_ID_LCPLL_810;
  break;
 case 135000:
  pll_id = DPLL_ID_LCPLL_1350;
  break;
 case 270000:
  pll_id = DPLL_ID_LCPLL_2700;
  break;
 default:
  MISSING_CASE(clock / 2);
  return NULL;
 }

 pll = intel_get_dpll_by_id(display, pll_id);

 if (!pll)
  return NULL;

 return pll;
}

static int hsw_ddi_lcpll_get_freq(struct intel_display *display,
      const struct intel_dpll *pll,
      const struct intel_dpll_hw_state *dpll_hw_state)
{
 int link_clock = 0;

 switch (pll->info->id) {
 case DPLL_ID_LCPLL_810:
  link_clock = 81000;
  break;
 case DPLL_ID_LCPLL_1350:
  link_clock = 135000;
  break;
 case DPLL_ID_LCPLL_2700:
  link_clock = 270000;
  break;
 default:
  drm_WARN(display->drm, 1, "bad port clock sel\n");
  break;
 }

 return link_clock * 2;
}

static int
hsw_ddi_spll_compute_dpll(struct intel_atomic_state *state,
     struct intel_crtc *crtc)
{
 struct intel_crtc_state *crtc_state =
  intel_atomic_get_new_crtc_state(state, crtc);
 struct hsw_dpll_hw_state *hw_state = &crtc_state->dpll_hw_state.hsw;

 if (drm_WARN_ON(crtc->base.dev, crtc_state->port_clock / 2 != 135000))
  return -EINVAL;

 hw_state->spll =
  SPLL_PLL_ENABLE | SPLL_FREQ_1350MHz | SPLL_REF_MUXED_SSC;

 return 0;
}

static struct intel_dpll *
hsw_ddi_spll_get_dpll(struct intel_atomic_state *state,
        struct intel_crtc *crtc)
{
 struct intel_crtc_state *crtc_state =
  intel_atomic_get_new_crtc_state(state, crtc);

 return intel_find_dpll(state, crtc, &crtc_state->dpll_hw_state,
          BIT(DPLL_ID_SPLL));
}

static int hsw_ddi_spll_get_freq(struct intel_display *display,
     const struct intel_dpll *pll,
     const struct intel_dpll_hw_state *dpll_hw_state)
{
 const struct hsw_dpll_hw_state *hw_state = &dpll_hw_state->hsw;
 int link_clock = 0;

 switch (hw_state->spll & SPLL_FREQ_MASK) {
 case SPLL_FREQ_810MHz:
  link_clock = 81000;
  break;
 case SPLL_FREQ_1350MHz:
  link_clock = 135000;
  break;
 case SPLL_FREQ_2700MHz:
  link_clock = 270000;
  break;
 default:
  drm_WARN(display->drm, 1, "bad spll freq\n");
  break;
 }

 return link_clock * 2;
}

static int hsw_compute_dpll(struct intel_atomic_state *state,
       struct intel_crtc *crtc,
       struct intel_encoder *encoder)
{
 struct intel_crtc_state *crtc_state =
  intel_atomic_get_new_crtc_state(state, crtc);

 if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI))
  return hsw_ddi_wrpll_compute_dpll(state, crtc);
 else if (intel_crtc_has_dp_encoder(crtc_state))
  return hsw_ddi_lcpll_compute_dpll(crtc_state);
 else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_ANALOG))
  return hsw_ddi_spll_compute_dpll(state, crtc);
 else
  return -EINVAL;
}

static int hsw_get_dpll(struct intel_atomic_state *state,
   struct intel_crtc *crtc,
   struct intel_encoder *encoder)
{
 struct intel_crtc_state *crtc_state =
  intel_atomic_get_new_crtc_state(state, crtc);
 struct intel_dpll *pll = NULL;

 if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI))
  pll = hsw_ddi_wrpll_get_dpll(state, crtc);
 else if (intel_crtc_has_dp_encoder(crtc_state))
  pll = hsw_ddi_lcpll_get_dpll(crtc_state);
 else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_ANALOG))
  pll = hsw_ddi_spll_get_dpll(state, crtc);

 if (!pll)
  return -EINVAL;

 intel_reference_dpll(state, crtc,
        pll, &crtc_state->dpll_hw_state);

 crtc_state->intel_dpll = pll;

 return 0;
}

static void hsw_update_dpll_ref_clks(struct intel_display *display)
{
 display->dpll.ref_clks.ssc = 135000;
 /* Non-SSC is only used on non-ULT HSW. */
 if (intel_de_read(display, FUSE_STRAP3) & HSW_REF_CLK_SELECT)
  display->dpll.ref_clks.nssc = 24000;
 else
  display->dpll.ref_clks.nssc = 135000;
}

static void hsw_dump_hw_state(struct drm_printer *p,
         const struct intel_dpll_hw_state *dpll_hw_state)
{
 const struct hsw_dpll_hw_state *hw_state = &dpll_hw_state->hsw;

 drm_printf(p, "dpll_hw_state: wrpll: 0x%x spll: 0x%x\n",
     hw_state->wrpll, hw_state->spll);
}

static bool hsw_compare_hw_state(const struct intel_dpll_hw_state *_a,
     const struct intel_dpll_hw_state *_b)
{
 const struct hsw_dpll_hw_state *a = &_a->hsw;
 const struct hsw_dpll_hw_state *b = &_b->hsw;

 return a->wrpll == b->wrpll &&
  a->spll == b->spll;
}

static const struct intel_dpll_funcs hsw_ddi_wrpll_funcs = {
 .enable = hsw_ddi_wrpll_enable,
 .disable = hsw_ddi_wrpll_disable,
 .get_hw_state = hsw_ddi_wrpll_get_hw_state,
 .get_freq = hsw_ddi_wrpll_get_freq,
};

static const struct intel_dpll_funcs hsw_ddi_spll_funcs = {
 .enable = hsw_ddi_spll_enable,
 .disable = hsw_ddi_spll_disable,
 .get_hw_state = hsw_ddi_spll_get_hw_state,
 .get_freq = hsw_ddi_spll_get_freq,
};

static void hsw_ddi_lcpll_enable(struct intel_display *display,
     struct intel_dpll *pll,
     const struct intel_dpll_hw_state *hw_state)
{
}

static void hsw_ddi_lcpll_disable(struct intel_display *display,
      struct intel_dpll *pll)
{
}

static bool hsw_ddi_lcpll_get_hw_state(struct intel_display *display,
           struct intel_dpll *pll,
           struct intel_dpll_hw_state *dpll_hw_state)
{
 return true;
}

static const struct intel_dpll_funcs hsw_ddi_lcpll_funcs = {
 .enable = hsw_ddi_lcpll_enable,
 .disable = hsw_ddi_lcpll_disable,
 .get_hw_state = hsw_ddi_lcpll_get_hw_state,
 .get_freq = hsw_ddi_lcpll_get_freq,
};

static const struct dpll_info hsw_plls[] = {
 { .name = "WRPLL 1", .funcs = &hsw_ddi_wrpll_funcs, .id = DPLL_ID_WRPLL1, },
 { .name = "WRPLL 2", .funcs = &hsw_ddi_wrpll_funcs, .id = DPLL_ID_WRPLL2, },
 { .name = "SPLL", .funcs = &hsw_ddi_spll_funcs, .id = DPLL_ID_SPLL, },
 { .name = "LCPLL 810", .funcs = &hsw_ddi_lcpll_funcs, .id = DPLL_ID_LCPLL_810,
   .always_on = true, },
 { .name = "LCPLL 1350", .funcs = &hsw_ddi_lcpll_funcs, .id = DPLL_ID_LCPLL_1350,
   .always_on = true, },
 { .name = "LCPLL 2700", .funcs = &hsw_ddi_lcpll_funcs, .id = DPLL_ID_LCPLL_2700,
   .always_on = true, },
 {}
};

static const struct intel_dpll_mgr hsw_pll_mgr = {
 .dpll_info = hsw_plls,
 .compute_dplls = hsw_compute_dpll,
 .get_dplls = hsw_get_dpll,
 .put_dplls = intel_put_dpll,
 .update_ref_clks = hsw_update_dpll_ref_clks,
 .dump_hw_state = hsw_dump_hw_state,
 .compare_hw_state = hsw_compare_hw_state,
};

struct skl_dpll_regs {
 i915_reg_t ctl, cfgcr1, cfgcr2;
};

/* this array is indexed by the *shared* pll id */
static const struct skl_dpll_regs skl_dpll_regs[4] = {
 {
  /* DPLL 0 */
  .ctl = LCPLL1_CTL,
  /* DPLL 0 doesn't support HDMI mode */
 },
 {
  /* DPLL 1 */
  .ctl = LCPLL2_CTL,
  .cfgcr1 = DPLL_CFGCR1(SKL_DPLL1),
  .cfgcr2 = DPLL_CFGCR2(SKL_DPLL1),
 },
 {
  /* DPLL 2 */
  .ctl = WRPLL_CTL(0),
  .cfgcr1 = DPLL_CFGCR1(SKL_DPLL2),
  .cfgcr2 = DPLL_CFGCR2(SKL_DPLL2),
 },
 {
  /* DPLL 3 */
  .ctl = WRPLL_CTL(1),
  .cfgcr1 = DPLL_CFGCR1(SKL_DPLL3),
  .cfgcr2 = DPLL_CFGCR2(SKL_DPLL3),
 },
};

static void skl_ddi_pll_write_ctrl1(struct intel_display *display,
        struct intel_dpll *pll,
        const struct skl_dpll_hw_state *hw_state)
{
 const enum intel_dpll_id id = pll->info->id;

 intel_de_rmw(display, DPLL_CTRL1,
       DPLL_CTRL1_HDMI_MODE(id) |
       DPLL_CTRL1_SSC(id) |
       DPLL_CTRL1_LINK_RATE_MASK(id),
       hw_state->ctrl1 << (id * 6));
 intel_de_posting_read(display, DPLL_CTRL1);
}

static void skl_ddi_pll_enable(struct intel_display *display,
          struct intel_dpll *pll,
          const struct intel_dpll_hw_state *dpll_hw_state)
{
 const struct skl_dpll_hw_state *hw_state = &dpll_hw_state->skl;
 const struct skl_dpll_regs *regs = skl_dpll_regs;
 const enum intel_dpll_id id = pll->info->id;

 skl_ddi_pll_write_ctrl1(display, pll, hw_state);

 intel_de_write(display, regs[id].cfgcr1, hw_state->cfgcr1);
 intel_de_write(display, regs[id].cfgcr2, hw_state->cfgcr2);
 intel_de_posting_read(display, regs[id].cfgcr1);
 intel_de_posting_read(display, regs[id].cfgcr2);

 /* the enable bit is always bit 31 */
 intel_de_rmw(display, regs[id].ctl, 0, LCPLL_PLL_ENABLE);

 if (intel_de_wait_for_set(display, DPLL_STATUS, DPLL_LOCK(id), 5))
  drm_err(display->drm, "DPLL %d not locked\n", id);
}

static void skl_ddi_dpll0_enable(struct intel_display *display,
     struct intel_dpll *pll,
     const struct intel_dpll_hw_state *dpll_hw_state)
{
 const struct skl_dpll_hw_state *hw_state = &dpll_hw_state->skl;

 skl_ddi_pll_write_ctrl1(display, pll, hw_state);
}

static void skl_ddi_pll_disable(struct intel_display *display,
    struct intel_dpll *pll)
{
 const struct skl_dpll_regs *regs = skl_dpll_regs;
 const enum intel_dpll_id id = pll->info->id;

 /* the enable bit is always bit 31 */
 intel_de_rmw(display, regs[id].ctl, LCPLL_PLL_ENABLE, 0);
 intel_de_posting_read(display, regs[id].ctl);
}

static void skl_ddi_dpll0_disable(struct intel_display *display,
      struct intel_dpll *pll)
{
}

static bool skl_ddi_pll_get_hw_state(struct intel_display *display,
         struct intel_dpll *pll,
         struct intel_dpll_hw_state *dpll_hw_state)
{
 struct skl_dpll_hw_state *hw_state = &dpll_hw_state->skl;
 const struct skl_dpll_regs *regs = skl_dpll_regs;
 const enum intel_dpll_id id = pll->info->id;
 intel_wakeref_t wakeref;
 bool ret;
 u32 val;

 wakeref = intel_display_power_get_if_enabled(display,
           POWER_DOMAIN_DISPLAY_CORE);
 if (!wakeref)
  return false;

 ret = false;

 val = intel_de_read(display, regs[id].ctl);
 if (!(val & LCPLL_PLL_ENABLE))
  goto out;

 val = intel_de_read(display, DPLL_CTRL1);
 hw_state->ctrl1 = (val >> (id * 6)) & 0x3f;

 /* avoid reading back stale values if HDMI mode is not enabled */
 if (val & DPLL_CTRL1_HDMI_MODE(id)) {
  hw_state->cfgcr1 = intel_de_read(display, regs[id].cfgcr1);
  hw_state->cfgcr2 = intel_de_read(display, regs[id].cfgcr2);
 }
 ret = true;

out:
 intel_display_power_put(display, POWER_DOMAIN_DISPLAY_CORE, wakeref);

 return ret;
}

static bool skl_ddi_dpll0_get_hw_state(struct intel_display *display,
           struct intel_dpll *pll,
           struct intel_dpll_hw_state *dpll_hw_state)
{
 struct skl_dpll_hw_state *hw_state = &dpll_hw_state->skl;
 const struct skl_dpll_regs *regs = skl_dpll_regs;
 const enum intel_dpll_id id = pll->info->id;
 intel_wakeref_t wakeref;
 u32 val;
 bool ret;

 wakeref = intel_display_power_get_if_enabled(display,
           POWER_DOMAIN_DISPLAY_CORE);
 if (!wakeref)
  return false;

 ret = false;

 /* DPLL0 is always enabled since it drives CDCLK */
 val = intel_de_read(display, regs[id].ctl);
 if (drm_WARN_ON(display->drm, !(val & LCPLL_PLL_ENABLE)))
  goto out;

 val = intel_de_read(display, DPLL_CTRL1);
 hw_state->ctrl1 = (val >> (id * 6)) & 0x3f;

 ret = true;

out:
 intel_display_power_put(display, POWER_DOMAIN_DISPLAY_CORE, wakeref);

 return ret;
}

struct skl_wrpll_context {
 u64 min_deviation;  /* current minimal deviation */
 u64 central_freq;  /* chosen central freq */
 u64 dco_freq;   /* chosen dco freq */
 unsigned int p;   /* chosen divider */
};

/* DCO freq must be within +1%/-6%  of the DCO central freq */
#define SKL_DCO_MAX_PDEVIATION 100
#define SKL_DCO_MAX_NDEVIATION 600

static void skl_wrpll_try_divider(struct skl_wrpll_context *ctx,
      u64 central_freq,
      u64 dco_freq,
      unsigned int divider)
{
 u64 deviation;

 deviation = div64_u64(10000 * abs_diff(dco_freq, central_freq),
         central_freq);

 /* positive deviation */
 if (dco_freq >= central_freq) {
  if (deviation < SKL_DCO_MAX_PDEVIATION &&
      deviation < ctx->min_deviation) {
   ctx->min_deviation = deviation;
   ctx->central_freq = central_freq;
   ctx->dco_freq = dco_freq;
   ctx->p = divider;
  }
 /* negative deviation */
 } else if (deviation < SKL_DCO_MAX_NDEVIATION &&
     deviation < ctx->min_deviation) {
  ctx->min_deviation = deviation;
  ctx->central_freq = central_freq;
  ctx->dco_freq = dco_freq;
  ctx->p = divider;
 }
}

static void skl_wrpll_get_multipliers(unsigned int p,
          unsigned int *p0 /* out */,
          unsigned int *p1 /* out */,
          unsigned int *p2 /* out */)
{
 /* even dividers */
 if (p % 2 == 0) {
  unsigned int half = p / 2;

  if (half == 1 || half == 2 || half == 3 || half == 5) {
   *p0 = 2;
   *p1 = 1;
   *p2 = half;
  } else if (half % 2 == 0) {
   *p0 = 2;
   *p1 = half / 2;
   *p2 = 2;
  } else if (half % 3 == 0) {
   *p0 = 3;
   *p1 = half / 3;
   *p2 = 2;
  } else if (half % 7 == 0) {
   *p0 = 7;
   *p1 = half / 7;
   *p2 = 2;
  }
 } else if (p == 3 || p == 9) {  /* 3, 5, 7, 9, 15, 21, 35 */
  *p0 = 3;
  *p1 = 1;
  *p2 = p / 3;
 } else if (p == 5 || p == 7) {
  *p0 = p;
  *p1 = 1;
  *p2 = 1;
 } else if (p == 15) {
  *p0 = 3;
  *p1 = 1;
  *p2 = 5;
 } else if (p == 21) {
  *p0 = 7;
  *p1 = 1;
  *p2 = 3;
 } else if (p == 35) {
  *p0 = 7;
  *p1 = 1;
  *p2 = 5;
 }
}

struct skl_wrpll_params {
 u32 dco_fraction;
 u32 dco_integer;
 u32 qdiv_ratio;
 u32 qdiv_mode;
 u32 kdiv;
 u32 pdiv;
 u32 central_freq;
};

static void skl_wrpll_params_populate(struct skl_wrpll_params *params,
          u64 afe_clock,
          int ref_clock,
          u64 central_freq,
          u32 p0, u32 p1, u32 p2)
{
 u64 dco_freq;

 switch (central_freq) {
 case 9600000000ULL:
  params->central_freq = 0;
  break;
 case 9000000000ULL:
  params->central_freq = 1;
  break;
 case 8400000000ULL:
  params->central_freq = 3;
 }

 switch (p0) {
 case 1:
  params->pdiv = 0;
  break;
 case 2:
  params->pdiv = 1;
  break;
 case 3:
  params->pdiv = 2;
  break;
 case 7:
  params->pdiv = 4;
  break;
 default:
  WARN(1, "Incorrect PDiv\n");
 }

 switch (p2) {
 case 5:
  params->kdiv = 0;
  break;
 case 2:
  params->kdiv = 1;
  break;
 case 3:
  params->kdiv = 2;
  break;
 case 1:
  params->kdiv = 3;
  break;
 default:
  WARN(1, "Incorrect KDiv\n");
 }

 params->qdiv_ratio = p1;
 params->qdiv_mode = (params->qdiv_ratio == 1) ? 0 : 1;

 dco_freq = p0 * p1 * p2 * afe_clock;

 /*
 * Intermediate values are in Hz.
 * Divide by MHz to match bsepc
 */

 params->dco_integer = div_u64(dco_freq, ref_clock * KHz(1));
 params->dco_fraction =
  div_u64((div_u64(dco_freq, ref_clock / KHz(1)) -
    params->dco_integer * MHz(1)) * 0x8000, MHz(1));
}

static int
skl_ddi_calculate_wrpll(int clock,
   int ref_clock,
   struct skl_wrpll_params *wrpll_params)
{
 static const u64 dco_central_freq[3] = { 8400000000ULL,
       9000000000ULL,
       9600000000ULL };
 static const u8 even_dividers[] = {  4,  6,  8, 10, 12, 14, 16, 18, 20,
         24, 28, 30, 32, 36, 40, 42, 44,
         48, 52, 54, 56, 60, 64, 66, 68,
         70, 72, 76, 78, 80, 84, 88, 90,
         92, 96, 98 };
 static const u8 odd_dividers[] = { 3, 5, 7, 9, 15, 21, 35 };
 static const struct {
  const u8 *list;
  int n_dividers;
 } dividers[] = {
  { even_dividers, ARRAY_SIZE(even_dividers) },
  { odd_dividers, ARRAY_SIZE(odd_dividers) },
 };
 struct skl_wrpll_context ctx = {
  .min_deviation = U64_MAX,
 };
 unsigned int dco, d, i;
 unsigned int p0, p1, p2;
 u64 afe_clock = (u64)clock * 1000 * 5; /* AFE Clock is 5x Pixel clock, in Hz */

 for (d = 0; d < ARRAY_SIZE(dividers); d++) {
  for (dco = 0; dco < ARRAY_SIZE(dco_central_freq); dco++) {
   for (i = 0; i < dividers[d].n_dividers; i++) {
    unsigned int p = dividers[d].list[i];
    u64 dco_freq = p * afe_clock;

    skl_wrpll_try_divider(&ctx,
            dco_central_freq[dco],
            dco_freq,
            p);
    /*
 * Skip the remaining dividers if we're sure to
 * have found the definitive divider, we can't
 * improve a 0 deviation.
 */

    if (ctx.min_deviation == 0)
     goto skip_remaining_dividers;
   }
  }

skip_remaining_dividers:
  /*
 * If a solution is found with an even divider, prefer
 * this one.
 */

  if (d == 0 && ctx.p)
   break;
 }

 if (!ctx.p)
  return -EINVAL;

 /*
 * gcc incorrectly analyses that these can be used without being
 * initialized. To be fair, it's hard to guess.
 */

 p0 = p1 = p2 = 0;
 skl_wrpll_get_multipliers(ctx.p, &p0, &p1, &p2);
 skl_wrpll_params_populate(wrpll_params, afe_clock, ref_clock,
      ctx.central_freq, p0, p1, p2);

 return 0;
}

static int skl_ddi_wrpll_get_freq(struct intel_display *display,
      const struct intel_dpll *pll,
      const struct intel_dpll_hw_state *dpll_hw_state)
{
 const struct skl_dpll_hw_state *hw_state = &dpll_hw_state->skl;
 int ref_clock = display->dpll.ref_clks.nssc;
 u32 p0, p1, p2, dco_freq;

 p0 = hw_state->cfgcr2 & DPLL_CFGCR2_PDIV_MASK;
 p2 = hw_state->cfgcr2 & DPLL_CFGCR2_KDIV_MASK;

 if (hw_state->cfgcr2 &  DPLL_CFGCR2_QDIV_MODE(1))
  p1 = (hw_state->cfgcr2 & DPLL_CFGCR2_QDIV_RATIO_MASK) >> 8;
 else
  p1 = 1;


 switch (p0) {
 case DPLL_CFGCR2_PDIV_1:
  p0 = 1;
  break;
 case DPLL_CFGCR2_PDIV_2:
  p0 = 2;
  break;
 case DPLL_CFGCR2_PDIV_3:
  p0 = 3;
  break;
 case DPLL_CFGCR2_PDIV_7_INVALID:
  /*
 * Incorrect ASUS-Z170M BIOS setting, the HW seems to ignore bit#0,
 * handling it the same way as PDIV_7.
 */

  drm_dbg_kms(display->drm, "Invalid WRPLL PDIV divider value, fixing it.\n");
  fallthrough;
 case DPLL_CFGCR2_PDIV_7:
  p0 = 7;
  break;
 default:
  MISSING_CASE(p0);
  return 0;
 }

 switch (p2) {
 case DPLL_CFGCR2_KDIV_5:
  p2 = 5;
  break;
 case DPLL_CFGCR2_KDIV_2:
  p2 = 2;
  break;
 case DPLL_CFGCR2_KDIV_3:
  p2 = 3;
  break;
 case DPLL_CFGCR2_KDIV_1:
  p2 = 1;
  break;
 default:
  MISSING_CASE(p2);
  return 0;
 }

 dco_freq = (hw_state->cfgcr1 & DPLL_CFGCR1_DCO_INTEGER_MASK) *
     ref_clock;

 dco_freq += ((hw_state->cfgcr1 & DPLL_CFGCR1_DCO_FRACTION_MASK) >> 9) *
      ref_clock / 0x8000;

 if (drm_WARN_ON(display->drm, p0 == 0 || p1 == 0 || p2 == 0))
  return 0;

 return dco_freq / (p0 * p1 * p2 * 5);
}

static int skl_ddi_hdmi_pll_dividers(struct intel_crtc_state *crtc_state)
{
 struct intel_display *display = to_intel_display(crtc_state);
 struct skl_dpll_hw_state *hw_state = &crtc_state->dpll_hw_state.skl;
 struct skl_wrpll_params wrpll_params = {};
 int ret;

 ret = skl_ddi_calculate_wrpll(crtc_state->port_clock,
          display->dpll.ref_clks.nssc, &wrpll_params);
 if (ret)
  return ret;

 /*
 * See comment in intel_dpll_hw_state to understand why we always use 0
 * as the DPLL id in this function.
 */

 hw_state->ctrl1 =
  DPLL_CTRL1_OVERRIDE(0) |
  DPLL_CTRL1_HDMI_MODE(0);

 hw_state->cfgcr1 =
  DPLL_CFGCR1_FREQ_ENABLE |
  DPLL_CFGCR1_DCO_FRACTION(wrpll_params.dco_fraction) |
  wrpll_params.dco_integer;

 hw_state->cfgcr2 =
  DPLL_CFGCR2_QDIV_RATIO(wrpll_params.qdiv_ratio) |
  DPLL_CFGCR2_QDIV_MODE(wrpll_params.qdiv_mode) |
  DPLL_CFGCR2_KDIV(wrpll_params.kdiv) |
  DPLL_CFGCR2_PDIV(wrpll_params.pdiv) |
  wrpll_params.central_freq;

 crtc_state->port_clock = skl_ddi_wrpll_get_freq(display, NULL,
       &crtc_state->dpll_hw_state);

 return 0;
}

static int
skl_ddi_dp_set_dpll_hw_state(struct intel_crtc_state *crtc_state)
{
 struct skl_dpll_hw_state *hw_state = &crtc_state->dpll_hw_state.skl;
 u32 ctrl1;

 /*
 * See comment in intel_dpll_hw_state to understand why we always use 0
 * as the DPLL id in this function.
 */

 ctrl1 = DPLL_CTRL1_OVERRIDE(0);
 switch (crtc_state->port_clock / 2) {
 case 81000:
  ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_810, 0);
  break;
 case 135000:
  ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1350, 0);
  break;
 case 270000:
  ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_2700, 0);
  break;
  /* eDP 1.4 rates */
 case 162000:
  ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1620, 0);
  break;
 case 108000:
  ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1080, 0);
  break;
 case 216000:
  ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_2160, 0);
  break;
 }

 hw_state->ctrl1 = ctrl1;

 return 0;
}

static int skl_ddi_lcpll_get_freq(struct intel_display *display,
      const struct intel_dpll *pll,
      const struct intel_dpll_hw_state *dpll_hw_state)
{
 const struct skl_dpll_hw_state *hw_state = &dpll_hw_state->skl;
 int link_clock = 0;

 switch ((hw_state->ctrl1 & DPLL_CTRL1_LINK_RATE_MASK(0)) >>
  DPLL_CTRL1_LINK_RATE_SHIFT(0)) {
 case DPLL_CTRL1_LINK_RATE_810:
  link_clock = 81000;
  break;
 case DPLL_CTRL1_LINK_RATE_1080:
  link_clock = 108000;
  break;
 case DPLL_CTRL1_LINK_RATE_1350:
  link_clock = 135000;
  break;
 case DPLL_CTRL1_LINK_RATE_1620:
  link_clock = 162000;
  break;
 case DPLL_CTRL1_LINK_RATE_2160:
  link_clock = 216000;
  break;
 case DPLL_CTRL1_LINK_RATE_2700:
  link_clock = 270000;
  break;
 default:
  drm_WARN(display->drm, 1, "Unsupported link rate\n");
  break;
 }

 return link_clock * 2;
}

static int skl_compute_dpll(struct intel_atomic_state *state,
       struct intel_crtc *crtc,
       struct intel_encoder *encoder)
{
 struct intel_crtc_state *crtc_state =
  intel_atomic_get_new_crtc_state(state, crtc);

 if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI))
  return skl_ddi_hdmi_pll_dividers(crtc_state);
 else if (intel_crtc_has_dp_encoder(crtc_state))
  return skl_ddi_dp_set_dpll_hw_state(crtc_state);
 else
  return -EINVAL;
}

static int skl_get_dpll(struct intel_atomic_state *state,
   struct intel_crtc *crtc,
   struct intel_encoder *encoder)
{
 struct intel_crtc_state *crtc_state =
  intel_atomic_get_new_crtc_state(state, crtc);
 struct intel_dpll *pll;

 if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP))
  pll = intel_find_dpll(state, crtc,
          &crtc_state->dpll_hw_state,
          BIT(DPLL_ID_SKL_DPLL0));
 else
  pll = intel_find_dpll(state, crtc,
          &crtc_state->dpll_hw_state,
          BIT(DPLL_ID_SKL_DPLL3) |
          BIT(DPLL_ID_SKL_DPLL2) |
          BIT(DPLL_ID_SKL_DPLL1));
 if (!pll)
  return -EINVAL;

 intel_reference_dpll(state, crtc,
        pll, &crtc_state->dpll_hw_state);

 crtc_state->intel_dpll = pll;

 return 0;
}

static int skl_ddi_pll_get_freq(struct intel_display *display,
    const struct intel_dpll *pll,
    const struct intel_dpll_hw_state *dpll_hw_state)
{
 const struct skl_dpll_hw_state *hw_state = &dpll_hw_state->skl;

 /*
 * ctrl1 register is already shifted for each pll, just use 0 to get
 * the internal shift for each field
 */

 if (hw_state->ctrl1 & DPLL_CTRL1_HDMI_MODE(0))
  return skl_ddi_wrpll_get_freq(display, pll, dpll_hw_state);
 else
  return skl_ddi_lcpll_get_freq(display, pll, dpll_hw_state);
}

static void skl_update_dpll_ref_clks(struct intel_display *display)
{
 /* No SSC ref */
 display->dpll.ref_clks.nssc = display->cdclk.hw.ref;
}

static void skl_dump_hw_state(struct drm_printer *p,
         const struct intel_dpll_hw_state *dpll_hw_state)
{
 const struct skl_dpll_hw_state *hw_state = &dpll_hw_state->skl;

 drm_printf(p, "dpll_hw_state: ctrl1: 0x%x, cfgcr1: 0x%x, cfgcr2: 0x%x\n",
     hw_state->ctrl1, hw_state->cfgcr1, hw_state->cfgcr2);
}

static bool skl_compare_hw_state(const struct intel_dpll_hw_state *_a,
     const struct intel_dpll_hw_state *_b)
{
 const struct skl_dpll_hw_state *a = &_a->skl;
 const struct skl_dpll_hw_state *b = &_b->skl;

 return a->ctrl1 == b->ctrl1 &&
  a->cfgcr1 == b->cfgcr1 &&
  a->cfgcr2 == b->cfgcr2;
}

static const struct intel_dpll_funcs skl_ddi_pll_funcs = {
 .enable = skl_ddi_pll_enable,
 .disable = skl_ddi_pll_disable,
 .get_hw_state = skl_ddi_pll_get_hw_state,
 .get_freq = skl_ddi_pll_get_freq,
};

static const struct intel_dpll_funcs skl_ddi_dpll0_funcs = {
 .enable = skl_ddi_dpll0_enable,
 .disable = skl_ddi_dpll0_disable,
 .get_hw_state = skl_ddi_dpll0_get_hw_state,
 .get_freq = skl_ddi_pll_get_freq,
};

static const struct dpll_info skl_plls[] = {
 { .name = "DPLL 0", .funcs = &skl_ddi_dpll0_funcs, .id = DPLL_ID_SKL_DPLL0,
   .always_on = true, },
 { .name = "DPLL 1", .funcs = &skl_ddi_pll_funcs, .id = DPLL_ID_SKL_DPLL1, },
 { .name = "DPLL 2", .funcs = &skl_ddi_pll_funcs, .id = DPLL_ID_SKL_DPLL2, },
 { .name = "DPLL 3", .funcs = &skl_ddi_pll_funcs, .id = DPLL_ID_SKL_DPLL3, },
 {}
};

static const struct intel_dpll_mgr skl_pll_mgr = {
 .dpll_info = skl_plls,
 .compute_dplls = skl_compute_dpll,
 .get_dplls = skl_get_dpll,
 .put_dplls = intel_put_dpll,
 .update_ref_clks = skl_update_dpll_ref_clks,
 .dump_hw_state = skl_dump_hw_state,
 .compare_hw_state = skl_compare_hw_state,
};

static void bxt_ddi_pll_enable(struct intel_display *display,
          struct intel_dpll *pll,
          const struct intel_dpll_hw_state *dpll_hw_state)
{
 const struct bxt_dpll_hw_state *hw_state = &dpll_hw_state->bxt;
 enum port port = (enum port)pll->info->id; /* 1:1 port->PLL mapping */
 enum dpio_phy phy = DPIO_PHY0;
 enum dpio_channel ch = DPIO_CH0;
 u32 temp;

 bxt_port_to_phy_channel(display, port, &phy, &ch);

 /* Non-SSC reference */
 intel_de_rmw(display, BXT_PORT_PLL_ENABLE(port), 0, PORT_PLL_REF_SEL);

 if (display->platform.geminilake) {
  intel_de_rmw(display, BXT_PORT_PLL_ENABLE(port),
        0, PORT_PLL_POWER_ENABLE);

  if (wait_for_us((intel_de_read(display, BXT_PORT_PLL_ENABLE(port)) &
     PORT_PLL_POWER_STATE), 200))
   drm_err(display->drm,
    "Power state not set for PLL:%d\n", port);
 }

 /* Disable 10 bit clock */
 intel_de_rmw(display, BXT_PORT_PLL_EBB_4(phy, ch),
       PORT_PLL_10BIT_CLK_ENABLE, 0);

 /* Write P1 & P2 */
 intel_de_rmw(display, BXT_PORT_PLL_EBB_0(phy, ch),
       PORT_PLL_P1_MASK | PORT_PLL_P2_MASK, hw_state->ebb0);

 /* Write M2 integer */
 intel_de_rmw(display, BXT_PORT_PLL(phy, ch, 0),
       PORT_PLL_M2_INT_MASK, hw_state->pll0);

 /* Write N */
 intel_de_rmw(display, BXT_PORT_PLL(phy, ch, 1),
       PORT_PLL_N_MASK, hw_state->pll1);

 /* Write M2 fraction */
 intel_de_rmw(display, BXT_PORT_PLL(phy, ch, 2),
       PORT_PLL_M2_FRAC_MASK, hw_state->pll2);

 /* Write M2 fraction enable */
 intel_de_rmw(display, BXT_PORT_PLL(phy, ch, 3),
       PORT_PLL_M2_FRAC_ENABLE, hw_state->pll3);

 /* Write coeff */
 temp = intel_de_read(display, BXT_PORT_PLL(phy, ch, 6));
 temp &= ~PORT_PLL_PROP_COEFF_MASK;
 temp &= ~PORT_PLL_INT_COEFF_MASK;
 temp &= ~PORT_PLL_GAIN_CTL_MASK;
 temp |= hw_state->pll6;
 intel_de_write(display, BXT_PORT_PLL(phy, ch, 6), temp);

 /* Write calibration val */
 intel_de_rmw(display, BXT_PORT_PLL(phy, ch, 8),
       PORT_PLL_TARGET_CNT_MASK, hw_state->pll8);

 intel_de_rmw(display, BXT_PORT_PLL(phy, ch, 9),
       PORT_PLL_LOCK_THRESHOLD_MASK, hw_state->pll9);

 temp = intel_de_read(display, BXT_PORT_PLL(phy, ch, 10));
 temp &= ~PORT_PLL_DCO_AMP_OVR_EN_H;
 temp &= ~PORT_PLL_DCO_AMP_MASK;
 temp |= hw_state->pll10;
 intel_de_write(display, BXT_PORT_PLL(phy, ch, 10), temp);

 /* Recalibrate with new settings */
 temp = intel_de_read(display, BXT_PORT_PLL_EBB_4(phy, ch));
 temp |= PORT_PLL_RECALIBRATE;
 intel_de_write(display, BXT_PORT_PLL_EBB_4(phy, ch), temp);
 temp &= ~PORT_PLL_10BIT_CLK_ENABLE;
 temp |= hw_state->ebb4;
 intel_de_write(display, BXT_PORT_PLL_EBB_4(phy, ch), temp);

 /* Enable PLL */
 intel_de_rmw(display, BXT_PORT_PLL_ENABLE(port), 0, PORT_PLL_ENABLE);
 intel_de_posting_read(display, BXT_PORT_PLL_ENABLE(port));

 if (wait_for_us((intel_de_read(display, BXT_PORT_PLL_ENABLE(port)) & PORT_PLL_LOCK),
   200))
  drm_err(display->drm, "PLL %d not locked\n", port);

 if (display->platform.geminilake) {
  temp = intel_de_read(display, BXT_PORT_TX_DW5_LN(phy, ch, 0));
  temp |= DCC_DELAY_RANGE_2;
  intel_de_write(display, BXT_PORT_TX_DW5_GRP(phy, ch), temp);
 }

 /*
 * While we write to the group register to program all lanes at once we
 * can read only lane registers and we pick lanes 0/1 for that.
 */

 temp = intel_de_read(display, BXT_PORT_PCS_DW12_LN01(phy, ch));
 temp &= ~LANE_STAGGER_MASK;
 temp &= ~LANESTAGGER_STRAP_OVRD;
 temp |= hw_state->pcsdw12;
 intel_de_write(display, BXT_PORT_PCS_DW12_GRP(phy, ch), temp);
}

static void bxt_ddi_pll_disable(struct intel_display *display,
    struct intel_dpll *pll)
{
 enum port port = (enum port)pll->info->id; /* 1:1 port->PLL mapping */

 intel_de_rmw(display, BXT_PORT_PLL_ENABLE(port), PORT_PLL_ENABLE, 0);
 intel_de_posting_read(display, BXT_PORT_PLL_ENABLE(port));

 if (display->platform.geminilake) {
  intel_de_rmw(display, BXT_PORT_PLL_ENABLE(port),
        PORT_PLL_POWER_ENABLE, 0);

  if (wait_for_us(!(intel_de_read(display, BXT_PORT_PLL_ENABLE(port)) &
      PORT_PLL_POWER_STATE), 200))
   drm_err(display->drm,
    "Power state not reset for PLL:%d\n", port);
 }
}

static bool bxt_ddi_pll_get_hw_state(struct intel_display *display,
         struct intel_dpll *pll,
         struct intel_dpll_hw_state *dpll_hw_state)
{
 struct bxt_dpll_hw_state *hw_state = &dpll_hw_state->bxt;
 enum port port = (enum port)pll->info->id; /* 1:1 port->PLL mapping */
 intel_wakeref_t wakeref;
 enum dpio_phy phy;
 enum dpio_channel ch;
 u32 val;
 bool ret;

 bxt_port_to_phy_channel(display, port, &phy, &ch);

 wakeref = intel_display_power_get_if_enabled(display,
           POWER_DOMAIN_DISPLAY_CORE);
 if (!wakeref)
  return false;

 ret = false;

 val = intel_de_read(display, BXT_PORT_PLL_ENABLE(port));
 if (!(val & PORT_PLL_ENABLE))
  goto out;

 hw_state->ebb0 = intel_de_read(display, BXT_PORT_PLL_EBB_0(phy, ch));
 hw_state->ebb0 &= PORT_PLL_P1_MASK | PORT_PLL_P2_MASK;

 hw_state->ebb4 = intel_de_read(display, BXT_PORT_PLL_EBB_4(phy, ch));
 hw_state->ebb4 &= PORT_PLL_10BIT_CLK_ENABLE;

 hw_state->pll0 = intel_de_read(display, BXT_PORT_PLL(phy, ch, 0));
 hw_state->pll0 &= PORT_PLL_M2_INT_MASK;

 hw_state->pll1 = intel_de_read(display, BXT_PORT_PLL(phy, ch, 1));
 hw_state->pll1 &= PORT_PLL_N_MASK;

 hw_state->pll2 = intel_de_read(display, BXT_PORT_PLL(phy, ch, 2));
 hw_state->pll2 &= PORT_PLL_M2_FRAC_MASK;

 hw_state->pll3 = intel_de_read(display, BXT_PORT_PLL(phy, ch, 3));
 hw_state->pll3 &= PORT_PLL_M2_FRAC_ENABLE;

 hw_state->pll6 = intel_de_read(display, BXT_PORT_PLL(phy, ch, 6));
 hw_state->pll6 &= PORT_PLL_PROP_COEFF_MASK |
     PORT_PLL_INT_COEFF_MASK |
     PORT_PLL_GAIN_CTL_MASK;

 hw_state->pll8 = intel_de_read(display, BXT_PORT_PLL(phy, ch, 8));
 hw_state->pll8 &= PORT_PLL_TARGET_CNT_MASK;

 hw_state->pll9 = intel_de_read(display, BXT_PORT_PLL(phy, ch, 9));
 hw_state->pll9 &= PORT_PLL_LOCK_THRESHOLD_MASK;

 hw_state->pll10 = intel_de_read(display, BXT_PORT_PLL(phy, ch, 10));
 hw_state->pll10 &= PORT_PLL_DCO_AMP_OVR_EN_H |
      PORT_PLL_DCO_AMP_MASK;

 /*
 * While we write to the group register to program all lanes at once we
 * can read only lane registers. We configure all lanes the same way, so
 * here just read out lanes 0/1 and output a note if lanes 2/3 differ.
 */

 hw_state->pcsdw12 = intel_de_read(display,
       BXT_PORT_PCS_DW12_LN01(phy, ch));
 if (intel_de_read(display, BXT_PORT_PCS_DW12_LN23(phy, ch)) != hw_state->pcsdw12)
  drm_dbg(display->drm,
   "lane stagger config different for lane 01 (%08x) and 23 (%08x)\n",
   hw_state->pcsdw12,
   intel_de_read(display,
          BXT_PORT_PCS_DW12_LN23(phy, ch)));
 hw_state->pcsdw12 &= LANE_STAGGER_MASK | LANESTAGGER_STRAP_OVRD;

 ret = true;

out:
 intel_display_power_put(display, POWER_DOMAIN_DISPLAY_CORE, wakeref);

 return ret;
}

/* pre-calculated values for DP linkrates */
static const struct dpll bxt_dp_clk_val[] = {
 /* m2 is .22 binary fixed point */
 { .dot = 162000, .p1 = 4, .p2 = 2, .n = 1, .m1 = 2, .m2 = 0x819999a /* 32.4 */ },
 { .dot = 270000, .p1 = 4, .p2 = 1, .n = 1, .m1 = 2, .m2 = 0x6c00000 /* 27.0 */ },
 { .dot = 540000, .p1 = 2, .p2 = 1, .n = 1, .m1 = 2, .m2 = 0x6c00000 /* 27.0 */ },
 { .dot = 216000, .p1 = 3, .p2 = 2, .n = 1, .m1 = 2, .m2 = 0x819999a /* 32.4 */ },
 { .dot = 243000, .p1 = 4, .p2 = 1, .n = 1, .m1 = 2, .m2 = 0x6133333 /* 24.3 */ },
 { .dot = 324000, .p1 = 4, .p2 = 1, .n = 1, .m1 = 2, .m2 = 0x819999a /* 32.4 */ },
 { .dot = 432000, .p1 = 3, .p2 = 1, .n = 1, .m1 = 2, .m2 = 0x819999a /* 32.4 */ },
};

static int
bxt_ddi_hdmi_pll_dividers(struct intel_crtc_state *crtc_state,
     struct dpll *clk_div)
{
 struct intel_display *display = to_intel_display(crtc_state);

 /* Calculate HDMI div */
 /*
 * FIXME: tie the following calculation into
 * i9xx_crtc_compute_clock
 */

 if (!bxt_find_best_dpll(crtc_state, clk_div))
  return -EINVAL;

 drm_WARN_ON(display->drm, clk_div->m1 != 2);

 return 0;
}

static void bxt_ddi_dp_pll_dividers(struct intel_crtc_state *crtc_state,
        struct dpll *clk_div)
{
 struct intel_display *display = to_intel_display(crtc_state);
 int i;

 *clk_div = bxt_dp_clk_val[0];
 for (i = 0; i < ARRAY_SIZE(bxt_dp_clk_val); ++i) {
  if (crtc_state->port_clock == bxt_dp_clk_val[i].dot) {
   *clk_div = bxt_dp_clk_val[i];
   break;
  }
 }

 chv_calc_dpll_params(display->dpll.ref_clks.nssc, clk_div);

 drm_WARN_ON(display->drm, clk_div->vco == 0 ||
      clk_div->dot != crtc_state->port_clock);
}

static int bxt_ddi_set_dpll_hw_state(struct intel_crtc_state *crtc_state,
         const struct dpll *clk_div)
{
 struct intel_display *display = to_intel_display(crtc_state);
 struct bxt_dpll_hw_state *hw_state = &crtc_state->dpll_hw_state.bxt;
 int clock = crtc_state->port_clock;
 int vco = clk_div->vco;
 u32 prop_coef, int_coef, gain_ctl, targ_cnt;
 u32 lanestagger;

 if (vco >= 6200000 && vco <= 6700000) {
  prop_coef = 4;
  int_coef = 9;
  gain_ctl = 3;
  targ_cnt = 8;
 } else if ((vco > 5400000 && vco < 6200000) ||
   (vco >= 4800000 && vco < 5400000)) {
  prop_coef = 5;
  int_coef = 11;
  gain_ctl = 3;
  targ_cnt = 9;
 } else if (vco == 5400000) {
  prop_coef = 3;
  int_coef = 8;
  gain_ctl = 1;
  targ_cnt = 9;
 } else {
  drm_err(display->drm, "Invalid VCO\n");
  return -EINVAL;
 }

 if (clock > 270000)
  lanestagger = 0x18;
 else if (clock > 135000)
  lanestagger = 0x0d;
 else if (clock > 67000)
  lanestagger = 0x07;
 else if (clock > 33000)
  lanestagger = 0x04;
 else
  lanestagger = 0x02;

 hw_state->ebb0 = PORT_PLL_P1(clk_div->p1) | PORT_PLL_P2(clk_div->p2);
 hw_state->pll0 = PORT_PLL_M2_INT(clk_div->m2 >> 22);
 hw_state->pll1 = PORT_PLL_N(clk_div->n);
 hw_state->pll2 = PORT_PLL_M2_FRAC(clk_div->m2 & 0x3fffff);

 if (clk_div->m2 & 0x3fffff)
  hw_state->pll3 = PORT_PLL_M2_FRAC_ENABLE;

 hw_state->pll6 = PORT_PLL_PROP_COEFF(prop_coef) |
  PORT_PLL_INT_COEFF(int_coef) |
  PORT_PLL_GAIN_CTL(gain_ctl);

 hw_state->pll8 = PORT_PLL_TARGET_CNT(targ_cnt);

 hw_state->pll9 = PORT_PLL_LOCK_THRESHOLD(5);

 hw_state->pll10 = PORT_PLL_DCO_AMP(15) |
  PORT_PLL_DCO_AMP_OVR_EN_H;

 hw_state->ebb4 = PORT_PLL_10BIT_CLK_ENABLE;

 hw_state->pcsdw12 = LANESTAGGER_STRAP_OVRD | lanestagger;

 return 0;
}

static int bxt_ddi_pll_get_freq(struct intel_display *display,
    const struct intel_dpll *pll,
    const struct intel_dpll_hw_state *dpll_hw_state)
{
 const struct bxt_dpll_hw_state *hw_state = &dpll_hw_state->bxt;
 struct dpll clock;

 clock.m1 = 2;
 clock.m2 = REG_FIELD_GET(PORT_PLL_M2_INT_MASK, hw_state->pll0) << 22;
 if (hw_state->pll3 & PORT_PLL_M2_FRAC_ENABLE)
  clock.m2 |= REG_FIELD_GET(PORT_PLL_M2_FRAC_MASK,
       hw_state->pll2);
 clock.n = REG_FIELD_GET(PORT_PLL_N_MASK, hw_state->pll1);
 clock.p1 = REG_FIELD_GET(PORT_PLL_P1_MASK, hw_state->ebb0);
 clock.p2 = REG_FIELD_GET(PORT_PLL_P2_MASK, hw_state->ebb0);

 return chv_calc_dpll_params(display->dpll.ref_clks.nssc, &clock);
}

static int
bxt_ddi_dp_set_dpll_hw_state(struct intel_crtc_state *crtc_state)
{
 struct dpll clk_div = {};

 bxt_ddi_dp_pll_dividers(crtc_state, &clk_div);

 return bxt_ddi_set_dpll_hw_state(crtc_state, &clk_div);
}

static int
bxt_ddi_hdmi_set_dpll_hw_state(struct intel_crtc_state *crtc_state)
{
 struct intel_display *display = to_intel_display(crtc_state);
 struct dpll clk_div = {};
 int ret;

 bxt_ddi_hdmi_pll_dividers(crtc_state, &clk_div);

 ret = bxt_ddi_set_dpll_hw_state(crtc_state, &clk_div);
 if (ret)
  return ret;

 crtc_state->port_clock = bxt_ddi_pll_get_freq(display, NULL,
            &crtc_state->dpll_hw_state);

 return 0;
}

static int bxt_compute_dpll(struct intel_atomic_state *state,
       struct intel_crtc *crtc,
       struct intel_encoder *encoder)
{
 struct intel_crtc_state *crtc_state =
  intel_atomic_get_new_crtc_state(state, crtc);

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=93 H=94 G=93

¤ Dauer der Verarbeitung: 0.31 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.