blob: 57cfedad3faf8001c2a0699dd0175d38f517c663 [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-config.h"
#include <lib/stdcompat/span.h>
#include <zircon/assert.h>
#include <cstdint>
#include <cstdlib>
#include <limits>
namespace i915 {
cpp20::span<const int8_t> DpllSupportedFrequencyDividersKabyLake() {
// This list merges the odd and even dividers in the "Pseudocode to Find HDMI
// and DVI DPLL Programming" section in the display engine PRMs.
//
// The register-level reference sugggests that there are valid dividers that
// are not listed here. For example, any multiple of 4 below 1024 can be
// achieved using K (P0) = 2, Q (P1) = 1-255, P (P2) = 2.
//
// Kaby Lake: IHD-OS-KBL-Vol 12-1.17 pages 135-136
// Skylake: IHD-OS-SKL-Vol 12-05.16 pages 132-133
static constexpr int8_t kDividers[] = {3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 15, 16, 18, 20,
21, 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};
return kDividers;
}
cpp20::span<const int8_t> DpllSupportedFrequencyDividersTigerLake() {
// Tiger Lake: IHD-OS-TGL-Vol 12-1.22-Rev2.0 pages 181-182
// TODO(costan): These aren't ordered anymore.
static constexpr int8_t kDividers[] = {
2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 28, 30, 32, 36, 40, 42, 44, 48, 50, 52, 54, 56,
60, 64, 66, 68, 70, 72, 76, 78, 80, 84, 88, 90, 92, 96, 98, 100, 102, 3, 5, 7, 9, 15, 21};
return kDividers;
}
DpllOscillatorConfig CreateDpllOscillatorConfigKabyLake(int32_t afe_clock_khz) {
ZX_ASSERT(afe_clock_khz > 0);
// The implementation conceptually follows the big `For` loop in the
// "Pseudocode to Find HDMI and DVI DPLL Programming" section in the display
// engine PRMs.
//
// Kaby Lake: IHD-OS-KBL-Vol 12-1.17 pages 135-136
// Skylake: IHD-OS-SKL-Vol 12-05.16 pages 132-133
static constexpr int32_t kCenterFrequenciesKhz[] = {8'400'000, 9'000'000, 9'600'000};
DpllOscillatorConfig result;
int32_t min_deviation = std::numeric_limits<int32_t>::max();
const cpp20::span<const int8_t> supported_dividers = DpllSupportedFrequencyDividersKabyLake();
// The PRM asks that we prefer even frequency dividers so strongly that we'll
// chose any acceptable DPLL configuration with an even divider over any
// configuration with an old divider.
static constexpr bool kWantEvenDivider[] = {true, false};
for (const bool& want_even_divider : kWantEvenDivider) {
for (const int32_t& center_frequency_khz : kCenterFrequenciesKhz) {
// The DCO frequency must be within [-6%, +1%] of the center DCO
// frequency. We compute the ends of this range below.
//
// The DCO frequencies are all in the Mhz range, so the divisions below
// are exact. `max_frequency_khz` and `min_frequency_khz` are at most
// 9,696,000.
const int32_t max_frequency_khz = center_frequency_khz + (center_frequency_khz / 100);
const int32_t min_frequency_khz = center_frequency_khz - 6 * (center_frequency_khz / 100);
// The PLL output (AFE clock) frequency is the DCO (Digitally-Controlled
// Oscillator) frequency divided by the frequency divider. More compactly,
// AFE clock frequency = DCO frequency / divider
//
// Rearranging terms gives us the following equations we'll use below.
// DCO frequency = AFE clock frequency * divider
// divider = DCO frequency / AFE clock frequency
//
// The target AFE clock frequency is fixed (given to this function), and
// there is an acceptable range of the DCO frequencies. This leads to an
// acceptable range of dividers, computed below.
//
// All supported dividers are integers. In order to stay within the range,
// we must round down the maximum divider and round up the minimum
// divider.
const int32_t max_divider = max_frequency_khz / afe_clock_khz;
const int32_t min_divider = (min_frequency_khz + afe_clock_khz - 1) / afe_clock_khz;
if (max_divider < supported_dividers.front() || min_divider > supported_dividers.back()) {
continue;
}
// Iterate over all supported frequency divider values, and save the value
// that gives the lowest deviation from the DCO center frequency. The
// number of supported dividers is small enough that binary search
// wouldn't yield a meaningful improvement.
for (const int8_t& candidate_divider : supported_dividers) {
if (candidate_divider > max_divider) {
break;
}
if (candidate_divider < min_divider) {
continue;
}
const bool is_divider_even = (candidate_divider % 2) == 0;
if (is_divider_even != want_even_divider) {
continue;
}
// The multiplication will not overflow (causing UB) because the result
// is guaranteed to fall in the range of `min_frequency_khz` and
// `max_frequency_khz`. This is because of the range checks on
// `candidate_divider` above.
const int32_t frequency_khz = static_cast<int32_t>(candidate_divider * afe_clock_khz);
ZX_DEBUG_ASSERT(frequency_khz >= min_frequency_khz);
ZX_DEBUG_ASSERT(frequency_khz <= max_frequency_khz);
// `dco_frequency_khz` is within [-6%, +1%] of `dco_frequency_khz`, so
// the maximum `absolute_difference` is 6% of the highest DCO center
// frequency, which is 5,760,000.
const int32_t absolute_deviation = std::abs(frequency_khz - center_frequency_khz);
// We follow the pseudocode in spirit, by computing the ratio between
// the frequency difference and the center frequency. We avoid using
// floating-point computation by scaling the difference by 1,000,000
// before the division.
//
// The range for `absolute_deviation` dictates that the multiplication
// below uses 64-bit integers. At the same time, the division result
// will be at most 6% of 1,000,000, which fits comfortably in a 32-bit
// integer.
const int32_t relative_deviation =
static_cast<int32_t>((int64_t{1'000'000} * absolute_deviation) / center_frequency_khz);
if (relative_deviation < min_deviation) {
min_deviation = relative_deviation;
result = DpllOscillatorConfig{
.center_frequency_khz = center_frequency_khz,
.frequency_khz = frequency_khz,
.frequency_divider = candidate_divider,
};
}
}
}
if (result.frequency_divider != 0) {
break;
}
}
return result;
}
DpllOscillatorConfig CreateDpllOscillatorConfigForHdmiTigerLake(int32_t afe_clock_khz) {
ZX_ASSERT(afe_clock_khz > 0);
// The implementation conceptually follows the big `foreach` loop in the
// the "Pseudo-code for HDMI Mode DPLL Programming" section in the display
// engine PRMs.
//
// Tiger Lake: IHD-OS-TGL-Vol 12-1.22-Rev2.0 pages 181-182
static constexpr int32_t kMinFrequencyKhz = 7'998'000;
static constexpr int32_t kMaxFrequencyKhz = 10'000'000;
static constexpr int32_t kCenterFrequencyKhz = 8'999'000;
DpllOscillatorConfig result;
int32_t min_deviation = std::numeric_limits<int32_t>::max();
const cpp20::span<const int8_t> supported_dividers = DpllSupportedFrequencyDividersTigerLake();
// The PLL output (AFE clock) frequency is the DCO (Digitally-Controlled
// Oscillator) frequency divided by the frequency divider. More compactly,
// AFE clock frequency = DCO frequency / divider
//
// Rearranging terms gives us the following equations we'll use below.
// DCO frequency = AFE clock frequency * divider
// divider = DCO frequency / AFE clock frequency
//
// The target AFE clock frequency is fixed (given to this function), and
// there is an acceptable range of the DCO frequencies. This leads to an
// acceptable range of dividers, computed below.
//
// All supported dividers are integers. In order to stay within the range,
// we must round down the maximum divider and round up the minimum
// divider.
const int32_t max_divider = kMaxFrequencyKhz / afe_clock_khz;
const int32_t min_divider = (kMinFrequencyKhz + afe_clock_khz - 1) / afe_clock_khz;
// Iterate over all supported frequency divider values, and save the value
// that gives the lowest deviation from the DCO center frequency. The
// number of supported dividers is small enough that binary search
// wouldn't yield a meaningful improvement.
for (const int8_t& candidate_divider : supported_dividers) {
if (candidate_divider < min_divider || candidate_divider > max_divider) {
continue;
}
// The multiplication will not overflow (causing UB) because the result
// is guaranteed to fall in the range of `min_frequency_khz` and
// `max_frequency_khz`. This is because of the range checks on
// `candidate_divider` above.
const int32_t frequency_khz = static_cast<int32_t>(candidate_divider * afe_clock_khz);
ZX_DEBUG_ASSERT(frequency_khz >= kMinFrequencyKhz);
ZX_DEBUG_ASSERT(frequency_khz <= kMaxFrequencyKhz);
// `dco_frequency_khz` is within [-12%, +12%] of `dco_frequency_khz`, so
// the maximum `absolute_difference` is 12% of the highest DCO center
// frequency, which is 1,152,000.
const int32_t absolute_deviation = std::abs(frequency_khz - kCenterFrequencyKhz);
if (absolute_deviation < min_deviation) {
min_deviation = absolute_deviation;
result = DpllOscillatorConfig{
.center_frequency_khz = kCenterFrequencyKhz,
.frequency_khz = frequency_khz,
.frequency_divider = candidate_divider,
};
}
}
return result;
}
DpllOscillatorConfig CreateDpllOscillatorConfigForDisplayPortTigerLake(int32_t afe_clock_khz) {
ZX_ASSERT(afe_clock_khz > 0);
DpllOscillatorConfig result = CreateDpllOscillatorConfigForHdmiTigerLake(afe_clock_khz);
// These are the only cases where the HDMI algorithm deviates from the
// DisplayPort table.
if (afe_clock_khz == 1'350'000 || afe_clock_khz == 810'000 || afe_clock_khz == 1'620'000) {
result.frequency_khz = 8'100'000;
ZX_DEBUG_ASSERT(result.frequency_khz % afe_clock_khz == 0);
result.frequency_divider = static_cast<int8_t>(result.frequency_khz / afe_clock_khz);
}
return result;
}
DpllFrequencyDividerConfig CreateDpllFrequencyDividerConfigKabyLake(int8_t dco_divider) {
// The implementation conceptually follows the `getMultiplier()` function in
// the "Pseudocode to Find HDMI and DVI DPLL Programming" section in the
// display engine PRMs.
//
// Kaby Lake: IHD-OS-KBL-Vol 12-1.17 pages 135-136
// Skylake: IHD-OS-SKL-Vol 12-05.16 pages 132-133
if (dco_divider % 2 == 0) {
const int8_t dco_divider_half = static_cast<int8_t>(dco_divider / 2);
// The pseudocode has one if whose predicate is a big "or" clause comparing
// the half-divider with all valid P2 (K) divider values. The loop below is
// equivalent.
static constexpr int8_t kP2DividerValues[] = {1, 2, 3, 5};
for (const int8_t& p2_divider : kP2DividerValues) {
if (dco_divider_half == p2_divider) {
return {.p0_p_divider = 2, .p1_q_divider = 1, .p2_k_divider = dco_divider_half};
}
}
// The pseudocode has a few if branches checking if the half-divider is
// evenly divided by any valid P0 (P) divider values. The loop below is
// equivalent.
static constexpr int8_t kP0DividerValues[] = {2, 3, 7};
for (const int8_t& p0_divider : kP0DividerValues) {
if ((dco_divider_half % p0_divider) == 0) {
return {.p0_p_divider = p0_divider,
.p1_q_divider = static_cast<int8_t>(dco_divider_half / p0_divider),
.p2_k_divider = 2};
}
}
ZX_ASSERT_MSG(false, "Unhandled divider %d", dco_divider);
}
if (dco_divider == 3 || dco_divider == 9) {
return {
.p0_p_divider = 3, .p1_q_divider = 1, .p2_k_divider = static_cast<int8_t>(dco_divider / 3)};
}
// The pseudocode uses the P0 (P) divider for 5 and 7. That is incorrect,
// because the P0 divider can only do 1/2/3/7.
//
// Taking a step back, there is a single solution that meets all the (P, Q, K)
// constraints for all odd dividers that include 5 or 7 in their prime factor
// decomposition. Q must be 1 because we can't set K to 2. So the 5 / 7 prime
// factor must be set in P / K.
if (dco_divider == 5 || dco_divider == 15 || dco_divider == 35) {
return {
.p0_p_divider = static_cast<int8_t>(dco_divider / 5), .p1_q_divider = 1, .p2_k_divider = 5};
}
if (dco_divider == 7 || dco_divider == 21) {
return {
.p0_p_divider = 7, .p1_q_divider = 1, .p2_k_divider = static_cast<int8_t>(dco_divider / 7)};
}
ZX_ASSERT_MSG(false, "Unhandled divider %d", dco_divider);
}
DpllFrequencyDividerConfig CreateDpllFrequencyDividerConfigTigerLake(int8_t dco_divider) {
// The implementation conceptually follows the "Good divider found" block in
// the "Pseudo-code for HDMI Mode DPLL Programming" section in the display
// engine PRMs.
//
// Tiger Lake: IHD-OS-TGL-Vol 12-1.22-Rev2.0 pages 181-182
if (dco_divider % 2 == 0) {
const int8_t dco_divider_half = static_cast<int8_t>(dco_divider / 2);
if (dco_divider == 2) {
return {.p0_p_divider = 2, .p1_q_divider = 1, .p2_k_divider = 1};
}
// The pseudocode has a few if branches checking for valid P0 (P) divider
// values. The comparisons check the divider directly against P0 values, or
// against 2x the P0 (P) divider values. The difference only matters for
// P0 = 2.
//
// The loop below is equivalent. It uses Kaby Lake / Skylake PRM approach of
// checking the half-divider against P0 (P) values directly, which is
// clearer.
static constexpr int8_t kP0DividerValues[] = {2, 3, 5, 7};
for (const int8_t& p0_divider : kP0DividerValues) {
if ((dco_divider_half % p0_divider) == 0) {
return {.p0_p_divider = p0_divider,
.p1_q_divider = static_cast<int8_t>(dco_divider_half / p0_divider),
.p2_k_divider = 2};
}
}
ZX_ASSERT_MSG(false, "Unhandled divider %d", dco_divider);
}
if (dco_divider == 3 || dco_divider == 5 || dco_divider == 7) {
return {.p0_p_divider = dco_divider, .p1_q_divider = 1, .p2_k_divider = 1};
}
ZX_ASSERT_MSG(dco_divider % 3 == 0, "Unhandled divider %d", dco_divider);
return {
.p0_p_divider = static_cast<int8_t>(dco_divider / 3), .p1_q_divider = 1, .p2_k_divider = 3};
}
} // namespace i915