| // 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_ |