blob: fd7fca49f3bb5924468e95a5fc149d2acfd8fb0f [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_GRAPHICS_DISPLAY_DRIVERS_INTEL_I915_DPLL_H_
#define SRC_GRAPHICS_DISPLAY_DRIVERS_INTEL_I915_DPLL_H_
#include <lib/mmio/mmio-buffer.h>
#include <lib/zx/result.h>
#include <zircon/assert.h>
#include <string>
#include <unordered_map>
#include <variant>
#include "src/graphics/display/drivers/intel-i915/hardware-common.h"
#include "src/graphics/display/drivers/intel-i915/scoped-value-change.h"
namespace i915 {
// High-level configuration of a PLL that serves as a DDI clock source.
//
// The information included here is used to decide whether a PLL (Phase-Locked
// Loop circuit) that is already configured in a certain way can serve as the
// clock source for a DDI that is being configured.
//
// This structure omits some low-level details needed to configure a PLL for DDI
// usage. The omitted details are fully determined by (and can be derived from)
// the information here.
//
// The default-constructed instance is an empty value.
struct DdiPllConfig {
// The DDI clock rate.
//
// This is half the bitrate on each link lane, because DDIs use both clock
// edges (rising and falling) to push bits onto the links.
int32_t ddi_clock_khz = 0;
// True if the PLL output uses SSC (Spread Spectrum Clocking).
bool spread_spectrum_clocking = false;
// True if this DPLL can be used for DisplayPort links.
bool admits_display_port = false;
// True if this DPLL can be used for HDMI links.
bool admits_hdmi = false;
// True for configurations that may lead to correct hardware operation.
//
// This method is intended to be used as a precondition check. Invalid
// configurations are definitely not suitable for use with hardware.
bool IsValid() const;
// True for invalid configurations that mean "no value".
//
// The empty value is intended for reporting "not found" errors, such as
// not finding a valid configuration that meets some constraints.
bool IsEmpty() const { return ddi_clock_khz == 0; }
};
bool operator==(const DdiPllConfig& lhs, const DdiPllConfig& rhs) noexcept;
bool operator!=(const DdiPllConfig& lhs, const DdiPllConfig& rhs) noexcept;
// Manages a PLL (Phase-Locked Loop circuit) that serves as a DDI clock source.
//
// This is an abstract base class. Subclasses implement the configuration
// protocols, which are specific to each type of PLL.
class DisplayPll {
public:
virtual ~DisplayPll() = default;
DisplayPll(const DisplayPll&) = delete;
DisplayPll(DisplayPll&&) = delete;
DisplayPll& operator=(const DisplayPll&) = delete;
DisplayPll& operator=(DisplayPll&&) = delete;
// Configures this PLL and waits for it to lock.
//
// Returns true if the PLL is locked to the desired configuration. Returns
// false if something went wrong.
//
// `pll_config` must be valid.
//
// This method is not idempotent. The PLL must not already be enabled.
bool Enable(const DdiPllConfig& pll_config);
// Disables this PLL. Also powers off the PLL, if possible.
//
// The PLL must not be used as a clock source by any of the powered-up DDIs.
//
// This method is not idempotent. The PLL must be locked to a configuration
// by a successful Enable() call.
bool Disable();
const std::string& name() const { return name_; }
PllId pll_id() const { return pll_id_; }
// The configuration that the PLL is locked to.
//
// Returns an empty configuration if the PLL is disabled.
const DdiPllConfig& config() const { return config_; }
protected:
explicit DisplayPll(PllId pll_id);
// Same API as `Enable()`.
//
// Implementations can assume that logging and state updating are taken care
// of, and focus on the register-level configuration.
virtual bool DoEnable(const DdiPllConfig& pll_config) = 0;
// Same API as `Disable()`.
//
// Implementations can assume that logging and state updating are taken care
// of, and focus on the register-level configuration.
virtual bool DoDisable() = 0;
// See `config()` for details.
void set_config(const DdiPllConfig& config) { config_ = config; }
private:
PllId pll_id_;
std::string name_;
DdiPllConfig config_ = {};
};
// Tracks all the PLLs used as DDI clock sources in a display engine.
class DisplayPllManager {
public:
virtual ~DisplayPllManager() = default;
DisplayPllManager(const DisplayPllManager&) = delete;
DisplayPllManager(DisplayPllManager&&) = delete;
DisplayPllManager& operator=(const DisplayPllManager&) = delete;
DisplayPllManager& operator=(DisplayPllManager&&) = delete;
// Returns the DDI clock configuration for `ddi`.
//
// Returns an empty `DdiPllConfig` if the DDI does not have a PLL configured
// as its clock source, if the PLL is not enabled, or if the PLL configuration
// is invalid. Otherwise, returns a valid DdiPllConfig.
//
// TODO(fxbug.com/112752): This API needs to be revised.
virtual DdiPllConfig LoadState(DdiId ddi_id) = 0;
// Configures a DDI's clock source to match the desired configuration.
//
// On success, returns the PLL configured as the DDI's clock source. On
// failure, returns null.
//
// `ddi` must be usable on this display engine (not fused off), disabled and
// powered down. Use `LoadState()` to have the manager reflect an association
// between a powered-up DDI and its clock source.
//
// `pll_config` must be valid.
//
// This process entails finding a PLL that can be used as this DDI's clock
// source, configuring the PLL, waiting for the PLL to lock, and associating
// the PLL with the DDI. If any of these steps fails, the entire operation is
// considered to have failed.
DisplayPll* SetDdiPllConfig(DdiId ddi_id, bool is_edp, const DdiPllConfig& desired_config);
// Resets a DDI's clock source configuration.
//
// Returns true if the DDI's clock source is reset. This method is idempotent,
// so it will return true when called with a DDI without a configured clock
// source.
//
// `ddi` must be usable on this display engine (not fused off), disabled and
// powered down.
//
// This method is idempotent. It (quickly) succeeds if the DDI does not have a
// clock source.
//
// If the PLL that served as the DDI's clock source becomes unused after this
// operation, the PLL is disabled and powered down, if possible.
bool ResetDdiPll(DdiId ddi_id);
// True if the PLL configured as a DDI's clock source matches a configuration.
//
// Returns false if the DDI does not have any clock source configured.
//
// `ddi` must be usable on this display engine (not fused off).
bool DdiPllMatchesConfig(DdiId ddi_id, const DdiPllConfig& desired_config);
protected:
DisplayPllManager() = default;
// Configures a PLL to serve as a DDI's clock source.
//
// `pll` must be locked to the desired configuration. `ddi` must be usable on
// this display engine (not fused off), disabled and powered down. `pll` must
// be usable as a source clock for `ddi`.
//
// This method is idempotent. It succeeds if `ddi` already has `pll`
// configured as its clock source.
//
// Implementations perform the register-level configuration, while assuming
// that logging and state updating are taken care of.
virtual bool SetDdiClockSource(DdiId ddi_id, PllId pll_id) = 0;
// Resets the DDI's clock source so it doesn't use any PLL.
//
// `ddi` must be usable on this display engine (not fused off), disabled and
// powered down.
//
// This method is idempotent. It succeeds if `ddi` does not have any clock
// source.
//
// Implementations perform the register-level configuration, while assuming
// that logging and state updating are taken care of.
virtual bool ResetDdiClockSource(DdiId ddi_id) = 0;
// Returns the most suitable PLL to serve as a DDI's clock source.
//
// Returns null if the search fails. On success, returns a `DisplayPll` for a
// PLL that is either unused, or is already locked to the desired
// configuration.
//
// `ddi` must be usable on this display engine (not fused off), disabled and
// powered down. `desired_config` must be valid.
//
// Implementations perform the register-level configuration, while assuming
// that logging and state updating are taken care of.
virtual DisplayPll* FindPllFor(DdiId ddi_id, bool is_edp, const DdiPllConfig& desired_config) = 0;
std::unordered_map<PllId, std::unique_ptr<DisplayPll>> plls_;
std::unordered_map<DisplayPll*, size_t> ref_count_;
std::unordered_map<DdiId, DisplayPll*> ddi_to_dpll_;
};
// DPLL (Display PLL) for Kaby Lake and Skylake display engines.
//
// DPLLs are shareable across multiple DDIs. DPLL 0 is special-cased on Kaby
// Lake and Skylake, because its VCO (Voltage-Controlled Oscillator) output is
// also used to drive the CDCLK (core display clock).
class DpllSkylake : public DisplayPll {
public:
DpllSkylake(fdf::MmioBuffer* mmio_space, PllId pll_id);
~DpllSkylake() override = default;
protected:
// DisplayPll overrides:
bool DoEnable(const DdiPllConfig& pll_config) final;
bool DoDisable() final;
private:
bool ConfigureForHdmi(const DdiPllConfig& pll_config);
bool ConfigureForDisplayPort(const DdiPllConfig& pll_config);
fdf::MmioBuffer* mmio_space_ = nullptr;
};
class DpllManagerSkylake : public DisplayPllManager {
public:
explicit DpllManagerSkylake(fdf::MmioBuffer* mmio_space);
~DpllManagerSkylake() override = default;
// DisplayPllManager overrides:
DdiPllConfig LoadState(DdiId ddi_id) final;
private:
// DisplayPllManager overrides:
bool SetDdiClockSource(DdiId ddi_id, PllId pll_id) final;
bool ResetDdiClockSource(DdiId ddi_id) final;
DisplayPll* FindPllFor(DdiId ddi_id, bool is_edp, const DdiPllConfig& desired_config) final;
fdf::MmioBuffer* mmio_space_ = nullptr;
};
// Display PLL (DPLL) for Tiger Lake display engines.
//
// DPLLs are shareable across Combo PHYs. Multiple PHYs can use the same DPLL,
// as long as they require the same frequency and SSC (Spread-Spectrum Clocking)
// characteristics.
class DisplayPllTigerLake : public DisplayPll {
public:
DisplayPllTigerLake(fdf::MmioBuffer* mmio_space, PllId pll_id);
~DisplayPllTigerLake() override = default;
// Tests that simulate retries must use the overrides below to avoid flakiness
// stemming from scheduling variability. Tests that simulate timeouts should
// use the overrides below to get the DisplayPllTigerLake to issue a
// deterministic MMIO access pattern.
static ScopedValueChange<int> OverrideLockWaitTimeoutUsForTesting(int timeout_us);
static ScopedValueChange<int> OverridePowerOnWaitTimeoutMsForTesting(int timeout_ms);
protected:
// DisplayPll overrides:
bool DoEnable(const DdiPllConfig& pll_config) final;
bool DoDisable() final;
private:
fdf::MmioBuffer* mmio_space_ = nullptr;
};
// DKL (Dekel) PLLs for Tiger Lake display engines.
//
// Each TC (Type-C) DDI has a dedicated PLL tied to it.
class DekelPllTigerLake : public DisplayPll {
public:
DekelPllTigerLake(fdf::MmioBuffer* mmio_space, PllId pll_id);
~DekelPllTigerLake() override = default;
// Returns DDI enum of the DDI tied to current Dekel PLL.
DdiId ddi_id() const;
protected:
// DisplayPll overrides:
bool DoEnable(const DdiPllConfig& pll_config) final;
bool DoDisable() final;
private:
bool EnableHdmi(const DdiPllConfig& pll_config);
bool EnableDp(const DdiPllConfig& pll_config);
fdf::MmioBuffer* mmio_space_ = nullptr;
};
class DpllManagerTigerLake : public DisplayPllManager {
public:
explicit DpllManagerTigerLake(fdf::MmioBuffer* mmio_space);
~DpllManagerTigerLake() override = default;
// DisplayPllManager overrides:
DdiPllConfig LoadState(DdiId ddi_id) final;
private:
DdiPllConfig LoadStateForComboDdi(DdiId ddi_id);
DdiPllConfig LoadStateForTypeCDdi(DdiId ddi_id);
// DisplayPllManager overrides:
bool SetDdiClockSource(DdiId ddi_id, PllId pll_id) final;
bool ResetDdiClockSource(DdiId ddi_id) final;
DisplayPll* FindPllFor(DdiId ddi_id, bool is_edp, const DdiPllConfig& desired_config) final;
uint32_t reference_clock_khz_ = 0u;
fdf::MmioBuffer* mmio_space_ = nullptr;
};
} // namespace i915
#endif // SRC_GRAPHICS_DISPLAY_DRIVERS_INTEL_I915_DPLL_H_