| // 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. |
| |
| #include "src/graphics/display/drivers/intel-i915/dpll.h" |
| |
| #include <zircon/assert.h> |
| |
| #include <optional> |
| #include <variant> |
| |
| #include "src/graphics/display/drivers/intel-i915/intel-i915.h" |
| #include "src/graphics/display/drivers/intel-i915/macros.h" |
| #include "src/graphics/display/drivers/intel-i915/registers-ddi.h" |
| #include "src/graphics/display/drivers/intel-i915/registers-dpll.h" |
| |
| namespace i915 { |
| |
| namespace { |
| |
| std::string GetDpllName(registers::Dpll dpll) { |
| switch (dpll) { |
| case registers::DPLL_0: |
| return "DPLL 0"; |
| case registers::DPLL_1: |
| return "DPLL 1"; |
| case registers::DPLL_2: |
| return "DPLL 2"; |
| case registers::DPLL_3: |
| return "DPLL 3"; |
| default: |
| return "DPLL Invalid"; |
| } |
| } |
| |
| bool CompareDpllStates(const DpllState& a, const DpllState& b) { |
| if (std::holds_alternative<DpDpllState>(a)) { |
| if (!std::holds_alternative<DpDpllState>(b)) { |
| return false; |
| } |
| |
| const auto& dp_a = std::get<DpDpllState>(a); |
| const auto& dp_b = std::get<DpDpllState>(b); |
| return dp_a.dp_bit_rate_mhz == dp_b.dp_bit_rate_mhz; |
| } |
| |
| if (std::holds_alternative<HdmiDpllState>(a)) { |
| if (!std::holds_alternative<HdmiDpllState>(b)) { |
| return false; |
| } |
| |
| const auto& hdmi_a = std::get<HdmiDpllState>(a); |
| const auto& hdmi_b = std::get<HdmiDpllState>(b); |
| |
| return hdmi_a.dco_int == hdmi_b.dco_int && hdmi_a.dco_frac == hdmi_b.dco_frac && |
| hdmi_a.q == hdmi_b.q && hdmi_a.q_mode == hdmi_b.q_mode && hdmi_a.k == hdmi_b.k && |
| hdmi_a.p == hdmi_b.p && hdmi_a.cf == hdmi_b.cf; |
| } |
| |
| ZX_DEBUG_ASSERT_MSG(false, "Comparing unsupported DpllState"); |
| return false; |
| } |
| |
| std::optional<registers::DpllControl1::LinkRate> DpBitRateMhzToSklLinkRate( |
| uint32_t dp_bit_rate_mhz) { |
| switch (dp_bit_rate_mhz) { |
| case 5400: |
| return registers::DpllControl1::LinkRate::k2700Mhz; |
| case 2700: |
| return registers::DpllControl1::LinkRate::k1350Mhz; |
| case 1620: |
| return registers::DpllControl1::LinkRate::k810Mhz; |
| case 3240: |
| return registers::DpllControl1::LinkRate::k1620Mhz; |
| case 2160: |
| return registers::DpllControl1::LinkRate::k1080Mhz; |
| case 4320: |
| return registers::DpllControl1::LinkRate::k2160Mhz; |
| default: |
| return std::nullopt; |
| } |
| } |
| |
| std::optional<uint32_t> SklLinkRateToDpBitRateMhz(registers::DpllControl1::LinkRate link_rate) { |
| switch (link_rate) { |
| case registers::DpllControl1::LinkRate::k2700Mhz: |
| return 5400; |
| case registers::DpllControl1::LinkRate::k1350Mhz: |
| return 2700; |
| case registers::DpllControl1::LinkRate::k810Mhz: |
| return 1620; |
| case registers::DpllControl1::LinkRate::k1620Mhz: |
| return 3240; |
| case registers::DpllControl1::LinkRate::k1080Mhz: |
| return 2160; |
| case registers::DpllControl1::LinkRate::k2160Mhz: |
| return 4320; |
| default: |
| return std::nullopt; |
| } |
| } |
| |
| } // namespace |
| |
| DisplayPll::DisplayPll(registers::Dpll dpll) : dpll_(dpll), name_(GetDpllName(dpll)) {} |
| |
| DisplayPll* DisplayPllManager::Map(registers::Ddi ddi, bool is_edp, const DpllState& state) { |
| if (ddi_to_dpll_.find(ddi) != ddi_to_dpll_.end()) { |
| Unmap(ddi); |
| } |
| |
| DisplayPll* best_dpll = FindBestDpll(ddi, is_edp, state); |
| if (!best_dpll) { |
| zxlogf(ERROR, "Cannot find an available DPLL for DDI %d", ddi); |
| return nullptr; |
| } |
| |
| if (ref_count_[best_dpll] > 0 || best_dpll->Enable(state)) { |
| if (!MapImpl(ddi, best_dpll->dpll())) { |
| zxlogf(ERROR, "Failed to map DDI %d to DPLL (%s)", ddi, best_dpll->name().c_str()); |
| return nullptr; |
| } |
| ref_count_[best_dpll]++; |
| ddi_to_dpll_[ddi] = best_dpll; |
| return best_dpll; |
| } |
| return nullptr; |
| } |
| |
| bool DisplayPllManager::Unmap(registers::Ddi ddi) { |
| if (ddi_to_dpll_.find(ddi) == ddi_to_dpll_.end()) { |
| return true; |
| } |
| |
| DisplayPll* dpll = ddi_to_dpll_[ddi]; |
| if (!UnmapImpl(ddi)) { |
| zxlogf(ERROR, "Failed to unmap DPLL (%s) for DDI %d", dpll->name().c_str(), ddi); |
| return false; |
| } |
| |
| ZX_DEBUG_ASSERT(ref_count_[dpll] > 0); |
| if (--ref_count_[dpll] == 0) { |
| ddi_to_dpll_.erase(ddi); |
| return dpll->Disable(); |
| } |
| return true; |
| } |
| |
| bool DisplayPllManager::PllNeedsReset(registers::Ddi ddi, const DpllState& new_state) { |
| if (ddi_to_dpll_.find(ddi) == ddi_to_dpll_.end()) { |
| return true; |
| } |
| return !CompareDpllStates(ddi_to_dpll_[ddi]->state(), new_state); |
| } |
| |
| SklDpll::SklDpll(fdf::MmioBuffer* mmio_space, registers::Dpll dpll) |
| : DisplayPll(dpll), mmio_space_(mmio_space) {} |
| |
| bool SklDpll::Enable(const DpllState& state) { |
| if (enabled_) { |
| zxlogf(ERROR, "DPLL (%s) Enable(): Already enabled!", name().c_str()); |
| return false; |
| } |
| |
| if (std::holds_alternative<HdmiDpllState>(state)) { |
| enabled_ = EnableHdmi(std::get<HdmiDpllState>(state)); |
| } else { |
| enabled_ = EnableDp(std::get<DpDpllState>(state)); |
| } |
| |
| if (enabled_) { |
| set_state(state); |
| } |
| return enabled_; |
| } |
| |
| bool SklDpll::EnableDp(const DpDpllState& dp_state) { |
| // Configure this DPLL to produce a suitable clock signal. |
| auto dpll_ctrl1 = registers::DpllControl1::Get().ReadFrom(mmio_space_); |
| dpll_ctrl1.dpll_hdmi_mode(dpll()).set(0); |
| dpll_ctrl1.dpll_ssc_enable(dpll()).set(0); |
| auto dp_rate = DpBitRateMhzToSklLinkRate(dp_state.dp_bit_rate_mhz); |
| if (!dp_rate.has_value()) { |
| zxlogf(ERROR, "Invalid DP bit rate: %u MHz", dp_state.dp_bit_rate_mhz); |
| return false; |
| } |
| dpll_ctrl1.SetLinkRate(dpll(), *dp_rate); |
| dpll_ctrl1.dpll_override(dpll()).set(1); |
| dpll_ctrl1.WriteTo(mmio_space_); |
| dpll_ctrl1.ReadFrom(mmio_space_); // Posting read |
| |
| // Enable this DPLL and wait for it to lock |
| auto dpll_enable = registers::DpllEnable::Get(dpll()).ReadFrom(mmio_space_); |
| dpll_enable.set_enable_dpll(1); |
| dpll_enable.WriteTo(mmio_space_); |
| if (!WAIT_ON_MS(registers::DpllStatus::Get().ReadFrom(mmio_space_).dpll_lock(dpll()).get(), 5)) { |
| zxlogf(ERROR, "DPLL failed to lock"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool SklDpll::EnableHdmi(const HdmiDpllState& hdmi_state) { |
| // Set the DPLL control settings |
| auto dpll_ctrl1 = registers::DpllControl1::Get().ReadFrom(mmio_space_); |
| dpll_ctrl1.dpll_hdmi_mode(dpll()).set(1); |
| dpll_ctrl1.dpll_override(dpll()).set(1); |
| dpll_ctrl1.dpll_ssc_enable(dpll()).set(0); |
| dpll_ctrl1.WriteTo(mmio_space_); |
| dpll_ctrl1.ReadFrom(mmio_space_); // Posting read |
| |
| // Set the DCO frequency |
| auto dpll_cfg1 = registers::DpllConfig1::Get(dpll()).FromValue(0); |
| dpll_cfg1.set_frequency_enable(1); |
| dpll_cfg1.set_dco_integer(hdmi_state.dco_int); |
| dpll_cfg1.set_dco_fraction(hdmi_state.dco_frac); |
| dpll_cfg1.WriteTo(mmio_space_); |
| dpll_cfg1.ReadFrom(mmio_space_); // Posting read |
| |
| // Set the divisors and central frequency |
| auto dpll_cfg2 = registers::DpllConfig2::Get(dpll()).FromValue(0); |
| dpll_cfg2.set_qdiv_ratio(hdmi_state.q); |
| dpll_cfg2.set_qdiv_mode(hdmi_state.q_mode); |
| dpll_cfg2.set_kdiv_ratio(hdmi_state.k); |
| dpll_cfg2.set_pdiv_ratio(hdmi_state.p); |
| dpll_cfg2.set_central_freq(hdmi_state.cf); |
| dpll_cfg2.WriteTo(mmio_space_); |
| dpll_cfg2.ReadFrom(mmio_space_); // Posting read |
| |
| // Enable and wait for the DPLL |
| auto dpll_enable = registers::DpllEnable::Get(dpll()).ReadFrom(mmio_space_); |
| dpll_enable.set_enable_dpll(1); |
| dpll_enable.WriteTo(mmio_space_); |
| if (!WAIT_ON_MS(registers::DpllStatus ::Get().ReadFrom(mmio_space_).dpll_lock(dpll()).get(), 5)) { |
| zxlogf(ERROR, "hdmi: DPLL failed to lock"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool SklDpll::Disable() { |
| if (!enabled_) { |
| zxlogf(INFO, "Dpll %s Disable(): Already disabled", name().c_str()); |
| return true; |
| } |
| |
| // We don't want to disable DPLL0, since that drives cdclk. |
| if (dpll() != registers::DPLL_0) { |
| auto dpll_enable = registers::DpllEnable::Get(dpll()).ReadFrom(mmio_space_); |
| dpll_enable.set_enable_dpll(0); |
| dpll_enable.WriteTo(mmio_space_); |
| } |
| enabled_ = false; |
| |
| return true; |
| } |
| |
| SklDpllManager::SklDpllManager(fdf::MmioBuffer* mmio_space) : mmio_space_(mmio_space) { |
| plls_.resize(registers::kDpllCount); |
| constexpr std::array<registers::Dpll, 4> kSklDplls = { |
| registers::Dpll::DPLL_0, |
| registers::Dpll::DPLL_1, |
| registers::Dpll::DPLL_2, |
| registers::Dpll::DPLL_3, |
| }; |
| |
| for (const auto dpll : kSklDplls) { |
| plls_[dpll] = std::unique_ptr<SklDpll>(new SklDpll(mmio_space, dpll)); |
| ref_count_[plls_[dpll].get()] = 0; |
| } |
| } |
| |
| bool SklDpllManager::MapImpl(registers::Ddi ddi, registers::Dpll dpll) { |
| // Direct the DPLL to the DDI |
| auto dpll_ctrl2 = registers::DpllControl2::Get().ReadFrom(mmio_space_); |
| dpll_ctrl2.ddi_select_override(ddi).set(1); |
| dpll_ctrl2.ddi_clock_off(ddi).set(0); |
| dpll_ctrl2.ddi_clock_select(ddi).set(dpll); |
| dpll_ctrl2.WriteTo(mmio_space_); |
| |
| return true; |
| } |
| |
| bool SklDpllManager::UnmapImpl(registers::Ddi ddi) { |
| auto dpll_ctrl2 = registers::DpllControl2::Get().ReadFrom(mmio_space_); |
| dpll_ctrl2.ddi_clock_off(ddi).set(1); |
| dpll_ctrl2.WriteTo(mmio_space_); |
| |
| return true; |
| } |
| |
| std::optional<DpllState> SklDpllManager::LoadState(registers::Ddi ddi) { |
| auto dpll_ctrl2 = registers::DpllControl2::Get().ReadFrom(mmio_space_); |
| if (dpll_ctrl2.ddi_clock_off(ddi).get()) { |
| return std::nullopt; |
| } |
| |
| auto dpll = static_cast<registers::Dpll>(dpll_ctrl2.ddi_clock_select(ddi).get()); |
| auto dpll_enable = registers::DpllEnable::Get(dpll).ReadFrom(mmio_space_); |
| if (!dpll_enable.enable_dpll()) { |
| return std::nullopt; |
| } |
| |
| // Remove stale mappings first. |
| if (ddi_to_dpll_.find(ddi) != ddi_to_dpll_.end()) { |
| ZX_DEBUG_ASSERT(ref_count_.find(ddi_to_dpll_[ddi]) != ref_count_.end() && |
| ref_count_.at(ddi_to_dpll_[ddi]) > 0); |
| --ref_count_[ddi_to_dpll_[ddi]]; |
| ddi_to_dpll_.erase(ddi); |
| } |
| |
| ddi_to_dpll_[ddi] = plls_[dpll].get(); |
| ++ref_count_[ddi_to_dpll_[ddi]]; |
| |
| DpllState new_state; |
| auto dpll_ctrl1 = registers::DpllControl1::Get().ReadFrom(mmio_space_); |
| bool is_hdmi = dpll_ctrl1.dpll_hdmi_mode(dpll).get(); |
| if (is_hdmi) { |
| auto dpll_cfg1 = registers::DpllConfig1::Get(dpll).ReadFrom(mmio_space_); |
| auto dpll_cfg2 = registers::DpllConfig2::Get(dpll).ReadFrom(mmio_space_); |
| |
| new_state = HdmiDpllState{ |
| .dco_int = static_cast<uint16_t>(dpll_cfg1.dco_integer()), |
| .dco_frac = static_cast<uint16_t>(dpll_cfg1.dco_fraction()), |
| .q = static_cast<uint8_t>(dpll_cfg2.qdiv_ratio()), |
| .q_mode = static_cast<uint8_t>(dpll_cfg2.qdiv_mode()), |
| .k = static_cast<uint8_t>(dpll_cfg2.kdiv_ratio()), |
| .p = static_cast<uint8_t>(dpll_cfg2.pdiv_ratio()), |
| .cf = static_cast<uint8_t>(dpll_cfg2.central_freq()), |
| }; |
| } else { |
| auto dp_bit_rate_mhz = SklLinkRateToDpBitRateMhz(dpll_ctrl1.GetLinkRate(dpll)); |
| if (!dp_bit_rate_mhz.has_value()) { |
| zxlogf(ERROR, "Invalid DPLL link rate from DPLL %d", dpll); |
| return std::nullopt; |
| } |
| |
| new_state = DpDpllState{ |
| .dp_bit_rate_mhz = *dp_bit_rate_mhz, |
| }; |
| } |
| |
| return std::make_optional(new_state); |
| } |
| |
| DisplayPll* SklDpllManager::FindBestDpll(registers::Ddi ddi, bool is_edp, const DpllState& state) { |
| DisplayPll* res = nullptr; |
| if (is_edp) { |
| ZX_DEBUG_ASSERT(std::holds_alternative<DpDpllState>(state)); |
| |
| DisplayPll* dpll_0 = plls_[registers::DPLL_0].get(); |
| if (ref_count_[dpll_0] == 0 || CompareDpllStates(dpll_0->state(), state)) { |
| res = dpll_0; |
| } |
| } else { |
| DisplayPll* const kCandidates[] = { |
| plls_[registers::Dpll::DPLL_1].get(), |
| plls_[registers::Dpll::DPLL_3].get(), |
| plls_[registers::Dpll::DPLL_2].get(), |
| }; |
| for (DisplayPll* candidate : kCandidates) { |
| if (ref_count_[candidate] == 0 || CompareDpllStates(candidate->state(), state)) { |
| res = candidate; |
| break; |
| } |
| } |
| } |
| |
| if (res) { |
| zxlogf(DEBUG, "Will select DPLL %s", res->name().c_str()); |
| } else { |
| zxlogf(WARNING, "Failed to allocate DPLL"); |
| } |
| |
| return res; |
| } |
| |
| } // namespace i915 |