blob: b013392d40fcfe77de75751e6bfc0f7a5200a3a9 [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.
#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> DpBitRateMhzToSkylakeLinkRate(
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 = DpBitRateMhzToSkylakeLinkRate(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