blob: 24754bdb08823c549f8411b7822aa5ea3d09fbfb [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/pch-engine.h"
#include <lib/ddk/debug.h>
#include <lib/mmio/mmio-buffer.h>
#include <lib/zx/time.h>
#include <zircon/assert.h>
#include <algorithm>
#include <cinttypes>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <tuple>
#include "src/graphics/display/drivers/intel-i915/pci-ids.h"
#include "src/graphics/display/drivers/intel-i915/poll-until.h"
#include "src/graphics/display/drivers/intel-i915/registers-ddi.h"
#include "src/graphics/display/drivers/intel-i915/registers-pch.h"
namespace i915 {
namespace {
// The frequency of the (inferred) PCH clock used for panel power sequencing.
//
// This is the value requested in the Kaby Lake and Skylake PRMs. The register
// reference (Vol 2c) in the Tiger Lake and DG1 PRMs mention the same
// resolution, but doesn't describe any method for changing it.
//
// Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 2 page 629
// Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 2 page 623
constexpr int32_t kPrescribedPanelPowerClockHz = 10'000;
} // namespace
bool operator==(const PchClockParameters& lhs, const PchClockParameters& rhs) noexcept {
return std::make_tuple(lhs.raw_clock_hz, lhs.panel_power_clock_hz) ==
std::make_tuple(rhs.raw_clock_hz, rhs.panel_power_clock_hz);
}
void PchPanelParameters::Fix() {
if (power_cycle_delay_micros == 0) {
// Maximum values based on eDP and SPWG Notebook Panel standards.
power_cycle_delay_micros = 500'000;
// eDP T1+T3 max.
if (power_on_to_hpd_aux_ready_delay_micros == 0) {
power_on_to_hpd_aux_ready_delay_micros = 90'000;
}
// SPWG T1+T2+T5 max/min.
if (power_on_to_backlight_on_delay_micros == 0) {
power_on_to_backlight_on_delay_micros = 260'000;
}
// SPWG T6 min
if (backlight_off_to_video_end_delay_micros == 0) {
backlight_off_to_video_end_delay_micros = 200'000;
}
// eDP T10 max
if (video_end_to_power_off_delay_micros == 0) {
video_end_to_power_off_delay_micros = 500'000;
}
}
if (backlight_pwm_frequency_hz < 1'000) {
backlight_pwm_frequency_hz = 1'000;
}
// Always recommended.
power_down_on_reset = true;
}
bool operator==(const PchPanelParameters& lhs, const PchPanelParameters& rhs) noexcept {
return std::make_tuple(
lhs.power_on_to_hpd_aux_ready_delay_micros, lhs.power_on_to_backlight_on_delay_micros,
lhs.backlight_off_to_video_end_delay_micros, lhs.video_end_to_power_off_delay_micros,
lhs.power_cycle_delay_micros, lhs.backlight_pwm_frequency_hz, lhs.power_down_on_reset,
lhs.backlight_pwm_inverted) ==
std::make_tuple(
rhs.power_on_to_hpd_aux_ready_delay_micros, rhs.power_on_to_backlight_on_delay_micros,
rhs.backlight_off_to_video_end_delay_micros, rhs.video_end_to_power_off_delay_micros,
rhs.power_cycle_delay_micros, rhs.backlight_pwm_frequency_hz, rhs.power_down_on_reset,
rhs.backlight_pwm_inverted);
}
bool operator==(const PchPanelPowerTarget& lhs, const PchPanelPowerTarget& rhs) noexcept {
return std::make_tuple(lhs.power_on, lhs.backlight_on, lhs.force_power_on,
lhs.brightness_pwm_counter_on) ==
std::make_tuple(rhs.power_on, rhs.backlight_on, rhs.force_power_on,
rhs.brightness_pwm_counter_on);
}
PchEngine::PchEngine(fdf::MmioBuffer* mmio_buffer, uint16_t device_id)
: mmio_buffer_(mmio_buffer), device_id_(device_id) {
ZX_DEBUG_ASSERT(mmio_buffer);
// Register reads are ordered by MMIO address. There are no other ordering
// requirements, and this ordering might have a slight performance advantage,
// if the range is prefetchable.
misc_ = registers::PchChicken1::Get().ReadFrom(mmio_buffer);
clock_ = registers::PchRawClock::Get().ReadFrom(mmio_buffer);
panel_power_control_ = registers::PchPanelPowerControl::Get().ReadFrom(mmio_buffer);
panel_power_on_delays_ = registers::PchPanelPowerOnDelays::Get().ReadFrom(mmio_buffer);
panel_power_off_delays_ = registers::PchPanelPowerOffDelays::Get().ReadFrom(mmio_buffer);
if (is_skl(device_id) || is_kbl(device_id)) {
panel_power_clock_delay_ = registers::PchPanelPowerClockDelay::Get().ReadFrom(mmio_buffer);
}
backlight_control_ = registers::PchBacklightControl::Get().ReadFrom(mmio_buffer);
if (is_skl(device_id) || is_kbl(device_id)) {
backlight_freq_duty_ = registers::PchBacklightFreqDuty::Get().ReadFrom(mmio_buffer);
}
if (is_tgl(device_id)) {
backlight_pwm_freq_ = registers::PchBacklightFreq::Get().ReadFrom(mmio_buffer);
backlight_pwm_duty_ = registers::PchBacklightDuty::Get().ReadFrom(mmio_buffer);
}
}
void PchEngine::SetPchResetHandshake(bool enabled) {
auto display_reset_options = registers::DisplayResetOptions::Get().ReadFrom(mmio_buffer_);
if (display_reset_options.pch_reset_handshake() == enabled)
return;
display_reset_options.set_pch_reset_handshake(enabled).WriteTo(mmio_buffer_);
}
void PchEngine::RestoreClockParameters() {
clock_.WriteTo(mmio_buffer_);
if (is_skl(device_id_) || is_kbl(device_id_)) {
panel_power_clock_delay_.WriteTo(mmio_buffer_);
}
if (is_tgl(device_id_)) {
// The restore side of the workaround for the PCH display engine clock
// remaining enabled during suspend. The PRM documents two version of the
// workaround. We implement the version that resets the
// `pch_display_clock_disable` field during restore, because this version is
// resilient to the boot firmware changing the field.
//
// Lakefield: IHD-OS-LKF-Vol 14-4.21 page 15
// Tiger Lake: IHD-OS-TGL-Vol 14-12.21 page 18 and page 50
// Ice Lake: IHD-OS-ICLLP-Vol 14-1.20 page 33
misc_.set_pch_display_clock_disable(0);
}
// The workaround above suggests that the `misc` register may re-enable the
// PCH display engine clock. To be safe, we restore it after restoring the
// clock configuration registers.
misc_.WriteTo(mmio_buffer_);
}
void PchEngine::RestoreNonClockParameters() {
// At this stage, the panel must remain powered down, and the brightness PWM
// must be disabled. The pipes and transcoders are not yet restored. Later in
// the recovery process, the panel and brightness will be restored, if
// necessary.
panel_power_control_.set_power_state_target(0).set_backlight_enabled(0);
backlight_control_.set_pwm_counter_enabled(0);
panel_power_on_delays_.WriteTo(mmio_buffer_);
panel_power_off_delays_.WriteTo(mmio_buffer_);
// The panel power sequence delays must be configured before turning on the
// panel. This requirement is met if we restore `panel_control_` after
// restoring all the other registers that configure the panel power sequence.
//
// On Kaby Lake and Skylake, the dependencies include the `misc_` and
// `panel_power_clock_delay_` registers. These registers are handled by
// RestoreClockParameters(), which must have been called earlier.
//
// Writing to `panel_control_` is currently guaranteed not to turn on
// the panel. Our restore code will continue working if this changes.
panel_power_control_.WriteTo(mmio_buffer_);
if (is_skl(device_id_) || is_kbl(device_id_)) {
backlight_freq_duty_.WriteTo(mmio_buffer_);
}
if (is_tgl(device_id_)) {
backlight_pwm_freq_.WriteTo(mmio_buffer_);
backlight_pwm_duty_.WriteTo(mmio_buffer_);
}
// The brightness PWM frequency and duty cycle must be configured before
// enabling the PWM. This requirement is met if we restore
// `backlight_control_` after restoring all other backlight PWM registers.
//
// Writing to `backlight_control_` is currently guaranteed not to enable the
// PWM. Our restore code will continue working if this changes.
backlight_control_.WriteTo(mmio_buffer_);
}
PchPanelPowerState PchEngine::PanelPowerState() {
auto status = registers::PchPanelPowerStatus::Get().ReadFrom(mmio_buffer_);
auto power_transition = status.PowerTransition();
if (power_transition == registers::PchPanelPowerStatus::Transition::kPoweringDown) {
// According to Intel's PRM, status.panel_on() should be 1.
return PchPanelPowerState::kPoweringDown;
}
if (power_transition == registers::PchPanelPowerStatus::Transition::kPoweringUp) {
// According to Intel's PRM, status.panel_on() should be 0.
// The power up sequence includes waiting for a T12 (power cycle) delay.
if (status.power_cycle_delay_active())
return PchPanelPowerState::kWaitingForPowerCycleDelay;
return PchPanelPowerState::kPoweringUp;
}
if (status.panel_on())
return PchPanelPowerState::kPoweredUp;
if (status.power_cycle_delay_active())
return PchPanelPowerState::kWaitingForPowerCycleDelay;
return PchPanelPowerState::kPoweredDown;
}
bool PchEngine::WaitForPanelPowerState(PchPanelPowerState power_state, int timeout_us) {
ZX_ASSERT(timeout_us > 0);
// Typical timeout values are hundreds of ms. A granularity of 10ms strikes a
// decent balance between unnecessarily waiting, and taking the CPU away from
// other tasks.
static constexpr int wait_granularity_us = 10'000;
static constexpr zx::duration wait_granularity = zx::usec(wait_granularity_us);
// The subtraction and division are safe because `wait_granularity_us` is
// guaranteed to be non-negative.
int poll_intervals = (timeout_us + wait_granularity_us - 1) / wait_granularity_us;
return PollUntil([&] { return PanelPowerState() == power_state; }, wait_granularity,
poll_intervals);
}
PchClockParameters PchEngine::ClockParameters() const {
return PchClockParameters{
.raw_clock_hz = RawClockHz(),
.panel_power_clock_hz = PanelPowerClockHz(),
};
}
void PchEngine::SetClockParameters(const PchClockParameters& parameters) {
SetRawClockHz(parameters.raw_clock_hz);
SetPanelPowerClockHz(parameters.panel_power_clock_hz);
}
void PchEngine::FixClockParameters(PchClockParameters& parameters) const {
if (parameters.panel_power_clock_hz == 0) {
// Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 2 page 629
// Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 2 page 623
//
// On devices where the panel power sequencing clock is not configurable,
// ClockParameters() returns the correct value.
parameters.panel_power_clock_hz = kPrescribedPanelPowerClockHz;
}
if (is_skl(device_id_) || is_kbl(device_id_) || is_test_device(device_id_)) {
// Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 2 page 712
// Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 2 page 705
if (parameters.raw_clock_hz == 0) {
// The boot firmware should really have set the PCH raw clock. Use the
// documented default.
parameters.raw_clock_hz = 24'000'000;
}
return;
}
if (is_tgl(device_id_)) {
// IHD-OS-TGL-Vol 2c-1.22-Rev2.0 Part 2 page 1185 and pages 1083-1084
auto pch_fuses = registers::PchDisplayFuses::Get().ReadFrom(mmio_buffer_);
parameters.raw_clock_hz = pch_fuses.rawclk_is_24mhz() ? 24'000'000 : 19'200'000;
return;
}
ZX_ASSERT_MSG(false, "Unsupported PCI device ID %d", device_id_);
}
int32_t PchEngine::RawClockHz() const {
if (is_skl(device_id_) || is_kbl(device_id_)) {
// Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 2 page 712
// Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 2 page 705
// The cast does not cause UB, because mhz() is a 10-bit field.
//
// For the same reason, the maximum configurable frequency is 1023MHz, which
// fits in 30 bits when expressed in Hertz. So, the multiplication is
// guaranteed not to overflow (which would cause UB).
return static_cast<int32_t>(clock_.mhz()) * 1'000'000;
}
if (is_tgl(device_id_)) {
// Tiger Lake: IHD-OS-TGL-Vol 2c-1.22-Rev2.0 Part 2 pages 1083-1084
// DG1: IHD-OS-DG1-Vol 2c-2.21 Part 2 pages 1131-1132
//
// The Tiger Lake and DG1 PRMs document different patterns for the
// integer part of the frequency (the microsecond counter divider). The
// Tiger Lake manual suggests that the integer is stored exactly as-is. The
// DG1 manual suggests that the integer field stores the real value - 1.
//
// The production Tiger Lake devices we've encountered (NUC11, Dell 5420)
// use the approach documented in the DG1 manual.
// The cast does not cause UB, because integer() is a 10-bit field.
//
// For the same reason, the maximum configurable frequency is 1024MHz, which
// fits in 30 bits when expressed in Hertz. So, the multiplication is
// guaranteed not to overflow (which would cause UB).
const int32_t integer = (static_cast<int32_t>(clock_.integer()) + 1) * 1'000'000;
// The cast does not cause UB because fraction_numerator() is a 3-bit field.
//
// For the same reason, the maximum configurable numerator is 7, which fits
// in 13 bits when expressed in Hertz. So, the multiplication is guaranteed
// not to overflow (which would cause UB).
const int32_t numerator = static_cast<int32_t>(clock_.fraction_numerator()) * 1'000'000;
// The cast does not cause UB, because fraction_denominator() is a 4-bit field.
//
// For the same reason, range of configurable denominators is 1-16. So, the
// addition is guaranteed not to overflow (which would cause UB).
const int32_t denominator = static_cast<int32_t>(clock_.fraction_denominator()) + 1;
// The division does not cause UB, because the denominator is >= 1.
//
// The range of results is from 0 (0 / 1) to 7,000,000 (7,000,000 / 1). So,
// the division is guaranteed not to overflow (which would cause UB).
const int32_t fraction = numerator / denominator;
// The maximum addition result is 1,031,000,000 which fits in 31 bits. So,
// the addition will not overflow.
return integer + fraction;
}
if (is_test_device(device_id_)) {
return 24'000'000; // Kaby Lake default raw clock.
}
ZX_ASSERT_MSG(false, "Unsupported PCI device ID %d", device_id_);
return 0;
}
void PchEngine::SetRawClockHz(int32_t raw_clock_hz) {
ZX_ASSERT(raw_clock_hz >= 1'000'000);
const uint32_t old_clock = clock_.reg_value();
if (is_skl(device_id_) || is_kbl(device_id_)) {
// Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 2 page 712
// Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 2 page 705
// `mhz` is a 10-bit field.
static constexpr int32_t kMaxRawMhz = (1 << 10) - 1;
const int32_t raw_mhz = std::min(raw_clock_hz / 1'000'000, kMaxRawMhz);
clock_.set_mhz(raw_mhz);
} else if (is_tgl(device_id_)) {
// Tiger Lake: IHD-OS-TGL-Vol 2c-1.22-Rev2.0 Part 2 pages 1083-1084
// DG1: IHD-OS-DG1-Vol 2c-2.21 Part 2 pages 1131-1132
//
// The Tiger Lake code uses the relationships suggested in the DG1 manual.
// See the RawClockHz() comments for a detailed justification.
// `integer` is a 10-bit field.
static constexpr int32_t kMaxRawInteger = (1 << 10) - 1;
// The subtraction result is non-negative, because `raw_clock_hz` is at
// least 1,000,000, so the division result is at least 1.
const int32_t raw_integer = std::min(raw_clock_hz / 1'000'000 - 1, kMaxRawInteger);
const int32_t target_fraction_hz = raw_clock_hz % 1'000'000;
// Find the `numerator` and `denominator` that yield a fraction closest to
// the target fraction. The first guess is 0 / 1.
int32_t raw_numerator = 0, raw_denominator = 0;
// std::abs(0 - target_fraction_hz) is `target_fraction_hz`.
int32_t min_diff_hz = target_fraction_hz;
static constexpr int32_t kMaxNumerator = (1 << 3) - 1; // 3-bit field
static constexpr int32_t kMaxDenominator = (1 << 4); // 4-bit field, offset by 1
for (int32_t numerator = 1; numerator <= kMaxNumerator; ++numerator) {
// The multiplication will not overflow (causing UB) because `numerator`
// is a 3-bit unsigned integer. So the result is at most 7,000,000.
const int32_t numerator_hz = numerator * 1'000'000;
// The fraction must always be less than 1.
for (int32_t denominator = numerator + 1; denominator <= kMaxDenominator; ++denominator) {
const int32_t fraction_hz = numerator_hz / denominator;
// The subtraction result will not overflow 32 bits (causing UB) because
// `fraction_hz` is between 0 and 7,000,000 and `target_fraction_hz` is
// between 0 and 1,000,000.
const int32_t diff_hz = std::abs(fraction_hz - target_fraction_hz);
if (diff_hz < min_diff_hz) {
min_diff_hz = diff_hz;
raw_numerator = numerator;
raw_denominator = denominator - 1;
}
}
}
clock_.set_integer(raw_integer)
.set_fraction_numerator(raw_numerator)
.set_fraction_denominator(raw_denominator);
} else if (is_test_device(device_id_)) {
// Stubbed out for integration tests.
} else {
ZX_ASSERT_MSG(false, "Unsupported PCI device ID %d", device_id_);
}
if (clock_.reg_value() != old_clock) {
clock_.WriteTo(mmio_buffer_);
}
}
int32_t PchEngine::PanelPowerClockHz() const {
if (is_skl(device_id_) || is_kbl(device_id_)) {
// Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 2 page 629
// Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 2 page 623
// The cast does not cause UB because clock_divider() is a 24-bit field.
const int32_t raw_divider = static_cast<int32_t>(panel_power_clock_delay_.clock_divider());
// clock_divider() is a 24-bit field, so the addition result will fit in 25
// bits, and the multiplication result will fit in 26 bits. So, the
// multiplication is guaranteed not to overflow (causing UB).
const int32_t divider = (raw_divider + 1) * 2;
// The division will not cause UB because divider is >= 2. The division
// result is guaranteed to be non-negative, because both operands are
// non-negative.
//
// The maximum result (configurable value) is 515Mhz. The maximum result
// without breaking documented invariants is 512.4375Mhz.
return RawClockHz() / divider;
}
if (is_tgl(device_id_)) {
// No documented register for changing the panel power clock divider on
// Tiger Lake. The clock should always be set to 10kHz.
return kPrescribedPanelPowerClockHz;
}
if (is_test_device(device_id_)) {
return kPrescribedPanelPowerClockHz;
}
ZX_ASSERT_MSG(false, "Unsupported PCI device ID %d", device_id_);
return kPrescribedPanelPowerClockHz;
}
void PchEngine::SetPanelPowerClockHz(int32_t panel_power_clock_hz) {
ZX_ASSERT(panel_power_clock_hz > 0);
if (is_skl(device_id_) || is_kbl(device_id_)) {
// Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 2 page 629
// Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 2 page 623
// The division will not cause UB because `panel_power_clock_hz` must be
// non-negative. The division result is non-negative because both inputs are
// non-negative.
const int32_t divider = RawClockHz() / panel_power_clock_hz;
// `clock_divider` is a 24-bit field and must not be set to zero.
static constexpr int32_t kMaxRawDivider = (1 << 24) - 1;
// The subtraction result fits in 32 bits (will not cause UB) because the
// left-hand side is the result of a division by 2, so its range is at most
// half of the range of int32_t.
const int32_t raw_divider = std::max(std::min(divider / 2 - 1, kMaxRawDivider), 1);
const uint32_t old_panel_power_clock_delay = panel_power_clock_delay_.reg_value();
panel_power_clock_delay_.set_clock_divider(raw_divider);
if (panel_power_clock_delay_.reg_value() != old_panel_power_clock_delay) {
panel_power_clock_delay_.WriteTo(mmio_buffer_);
}
return;
}
if (is_tgl(device_id_)) {
// No documented register for changing the panel power clock divider on
// Tiger Lake. The clock should always be set to 10kHz.
return;
}
if (is_test_device(device_id_)) {
// Stubbed out for integration tests.
return;
}
ZX_ASSERT_MSG(false, "Unsupported PCI device ID %d", device_id_);
}
PchPanelParameters PchEngine::PanelParameters() const {
// Return zeros instead of crashing if the PCH is not clocked correctly. This
// lets us log the PCH configuration even when it's invalid.
const int32_t panel_power_clock_hz = PanelPowerClockHz();
const int32_t multiplier = (panel_power_clock_hz == 0) ? 0 : 1'000'000 / panel_power_clock_hz;
// The casts do not cause UB because the register fields are 13-bit values.
const int32_t raw_power_on_to_hpd_aux_ready_delay =
static_cast<int32_t>(panel_power_on_delays_.power_on_to_hpd_aux_ready_delay());
const int32_t raw_power_on_to_backlight_on_delay =
static_cast<int32_t>(panel_power_on_delays_.power_on_to_backlight_on_delay());
const int32_t raw_backlight_off_to_video_end_delay =
static_cast<int32_t>(panel_power_off_delays_.backlight_off_to_video_end_delay());
const int32_t raw_video_end_to_power_off_delay =
static_cast<int32_t>(panel_power_off_delays_.video_end_to_power_off_delay());
int32_t raw_power_cycle_delay;
uint32_t backlight_pwm_divider;
if (is_skl(device_id_) || is_kbl(device_id_)) {
// The cast is not UB because power_cycle_delay() is a 5-bit field.
raw_power_cycle_delay = static_cast<int32_t>(panel_power_clock_delay_.power_cycle_delay());
if (raw_power_cycle_delay > 1)
raw_power_cycle_delay -= 1;
const uint32_t pwm_divider_granularity = misc_.backlight_pwm_multiplier() ? 128 : 16;
// The cast does not cause UB because freq_divider() is a 16-bit field. The
// multiplication will not overflow (causing UB) because maximum result fits
// in 23 bits (16-bit unsigned integer multiplied by 128).
backlight_pwm_divider = backlight_freq_duty_.freq_divider() * pwm_divider_granularity;
} else if (is_tgl(device_id_)) {
// The cast does not cause UB because power_cycle_delay() is a 5-bit field.
raw_power_cycle_delay = static_cast<int32_t>(panel_power_control_.power_cycle_delay());
if (raw_power_cycle_delay > 1)
raw_power_cycle_delay -= 1;
// The cast does not cause UB because freq_divider() is a 32-bit field.
backlight_pwm_divider = static_cast<uint32_t>(backlight_pwm_freq_.divider());
} else if (is_test_device(device_id_)) {
raw_power_cycle_delay = 0;
backlight_pwm_divider = 0;
} else {
ZX_ASSERT_MSG(false, "Unsupported PCI device ID %d", device_id_);
}
// The cast does not cause UB because RawClockHz() fits in 30 bits.
const uint32_t raw_clock_hz = static_cast<uint32_t>(RawClockHz());
const int32_t backlight_pwm_frequency_hz =
// The multiplication will not overflow (causing UB) because
// `raw_clock_hz` fits in 30 bits.
(backlight_pwm_divider == 0 || raw_clock_hz * 2 < backlight_pwm_divider)
? 0
// The golden results in the unit tests, which are lifted from the PRMs,
// require rounding. The addition will not overflow (causing UB) because
// `raw_clock_hz` fits in 30 bits, and `backlight_pwm_divider` can be at
// most twice as large.
: (raw_clock_hz + backlight_pwm_divider / 2) / backlight_pwm_divider;
return PchPanelParameters{
// The maximum theoretical multiplication result is 8191
// The multiplication results fit in 33 bits, because `multiplier` fits in
// 20 bits, and the raw delay values fit in 13 bits.
.power_on_to_hpd_aux_ready_delay_micros =
raw_power_on_to_hpd_aux_ready_delay * static_cast<int64_t>(multiplier),
.power_on_to_backlight_on_delay_micros =
raw_power_on_to_backlight_on_delay * static_cast<int64_t>(multiplier),
.backlight_off_to_video_end_delay_micros =
raw_backlight_off_to_video_end_delay * static_cast<int64_t>(multiplier),
.video_end_to_power_off_delay_micros =
raw_video_end_to_power_off_delay * static_cast<int64_t>(multiplier),
// The first multiplication result is at most 31,000 because
// `raw_power_cycle_delay` fits in 5 bits. So, int32_t is sufficient for
// the multiplication result, and no overflow (UB) will occur.
//
//
// The second multiplication result fits in 35 bits, because `multiplier`
// fits in 20 bits, and the first multiplication result fits in 15 bits.
.power_cycle_delay_micros = static_cast<int64_t>(raw_power_cycle_delay * 1'000) * multiplier,
.backlight_pwm_frequency_hz = backlight_pwm_frequency_hz,
// The casts are safe because they involve 1-bit fields.
.power_down_on_reset = static_cast<bool>(panel_power_control_.power_down_on_reset()),
.backlight_pwm_inverted = static_cast<bool>(backlight_control_.pwm_polarity_inverted()),
};
}
void PchEngine::SetPanelParameters(const PchPanelParameters& parameters) {
SetPanelPowerSequenceParameters(parameters);
SetPanelBacklightPwmParameters(parameters);
}
void PchEngine::SetPanelPowerSequenceParameters(const PchPanelParameters& parameters) {
ZX_ASSERT(parameters.power_on_to_hpd_aux_ready_delay_micros >= 0);
ZX_ASSERT(parameters.power_on_to_backlight_on_delay_micros >= 0);
ZX_ASSERT(parameters.backlight_off_to_video_end_delay_micros >= 0);
ZX_ASSERT(parameters.video_end_to_power_off_delay_micros >= 0);
ZX_ASSERT(parameters.power_cycle_delay_micros >= 0);
const int32_t panel_power_clock_hz = PanelPowerClockHz();
ZX_ASSERT_MSG(panel_power_clock_hz > 0, "PCH not clocked correctly");
const int32_t power_delay_divider = 1'000'000 / panel_power_clock_hz;
ZX_ASSERT_MSG(power_delay_divider > 0, "PCH not clocked correctly");
const uint32_t old_power_on_delays = panel_power_on_delays_.reg_value();
const uint32_t old_power_off_delays = panel_power_off_delays_.reg_value();
// The raw delays are written into 13-bit register fields.
constexpr int64_t kMaxRawDelay = (1 << 13) - 1;
// The casts do not cause UB because the std::min() results fit in 13 bits.
const int32_t raw_power_on_to_hpd_aux_ready_delay = static_cast<int32_t>(std::min(
parameters.power_on_to_hpd_aux_ready_delay_micros / power_delay_divider, kMaxRawDelay));
const int32_t raw_power_on_to_backlight_on_delay = static_cast<int32_t>(std::min(
parameters.power_on_to_backlight_on_delay_micros / power_delay_divider, kMaxRawDelay));
const int32_t raw_backlight_off_to_video_end_delay = static_cast<int32_t>(std::min(
parameters.backlight_off_to_video_end_delay_micros / power_delay_divider, kMaxRawDelay));
const int32_t raw_video_end_to_power_off_delay = static_cast<int32_t>(
std::min(parameters.video_end_to_power_off_delay_micros / power_delay_divider, kMaxRawDelay));
panel_power_on_delays_.set_power_on_to_hpd_aux_ready_delay(raw_power_on_to_hpd_aux_ready_delay)
.set_power_on_to_backlight_on_delay(raw_power_on_to_backlight_on_delay);
panel_power_off_delays_.set_backlight_off_to_video_end_delay(raw_backlight_off_to_video_end_delay)
.set_video_end_to_power_off_delay(raw_video_end_to_power_off_delay);
if (panel_power_on_delays_.reg_value() != old_power_on_delays) {
panel_power_on_delays_.WriteTo(mmio_buffer_);
}
if (panel_power_off_delays_.reg_value() != old_power_off_delays) {
panel_power_off_delays_.WriteTo(mmio_buffer_);
}
// This delay is written in a 5-bit register field.
constexpr int64_t kMaxRawPowerCycleDelay = (1 << 5) - 1;
// The multiplication will not overflow (causing UB), because
// `power_delay_divider` fits in 20 bits. So, the multiplication result will
// fit in 30 bits.
const int32_t raw_power_delay_divider = power_delay_divider * 1'000;
// The division is not UB because we ensure that `power_delay_divider` is
// positive above. The addition will not overflow (causing UB), because the
// previous division's result is at most 1,000 times less than the maximum
// integer. The cast does not cause UB because the std::min() result fits in 5
// bits.
const int32_t raw_power_cycle_delay = static_cast<int32_t>(std::min(
parameters.power_cycle_delay_micros / raw_power_delay_divider + 1, kMaxRawPowerCycleDelay));
const uint32_t old_panel_power_control = panel_power_control_.reg_value();
if (is_kbl(device_id_) || is_skl(device_id_)) {
const uint32_t old_panel_power_clock_delay = panel_power_clock_delay_.reg_value();
panel_power_clock_delay_.set_power_cycle_delay(raw_power_cycle_delay);
if (panel_power_clock_delay_.reg_value() != old_panel_power_clock_delay) {
panel_power_clock_delay_.WriteTo(mmio_buffer_);
}
} else if (is_tgl(device_id_)) {
panel_power_control_.set_power_cycle_delay(raw_power_cycle_delay);
} else if (is_test_device(device_id_)) {
// Stubbed out for integration tests.
} else {
ZX_ASSERT_MSG(false, "Unsupported PCI device ID %d", device_id_);
}
panel_power_control_.set_power_down_on_reset(parameters.power_down_on_reset);
if (panel_power_control_.reg_value() != old_panel_power_control) {
panel_power_control_.WriteTo(mmio_buffer_);
}
}
namespace {
// Computes the PWM duty cycle to be used with a new frequency.
//
// The return value is guaranteed to be <= `frequency_divider`.
//
// It's safe to pass un-validated register contents directly to this function.
// Returns zero (0% brightness) if `old_frequency_divider` is zero
// (un-configured PWM). Returns `frequency_divider` if `old_duty_cycle` exceeds
// `old_frequency_divider`, clamping the brightness to 100% in case the PWM is
// configured incorrectly.
//
// The arguments and return types must be uint32_t because some display engines
// (currently Tiger Lake and DG1) use 32-bit (unsigned) register fields to
// represent the frequency divider and duty cycle.
uint32_t ScaledPwmDutyCycle(uint32_t frequency_divider, uint32_t old_duty_cycle,
uint32_t old_frequency_divider) {
if (old_frequency_divider == 0)
return 0;
// The multiplication will not overflow because both factors are 32-bit
// integers.
const uint64_t scaled_duty_cycle =
(uint64_t{old_duty_cycle} * frequency_divider) / old_frequency_divider;
// The cast is safe because std::min()'s result will be at most
// `frequency_divider`, which fits in 32 bits.
const uint32_t clamped_duty_cycle =
static_cast<uint32_t>(std::min<uint64_t>(scaled_duty_cycle, frequency_divider));
return clamped_duty_cycle;
}
} // namespace
void PchEngine::SetPanelBacklightPwmParameters(const PchPanelParameters& parameters) {
ZX_ASSERT(parameters.backlight_pwm_frequency_hz > 0);
// This implements the sections "Panel Power and Backlight" > "Backlight
// Enabling Sequence" and "Backlight Frequency Change Sequence" under section
// in the display engine PRMs.
//
// Tiger Lake: IHD-OS-TGL-Vol 12-1.22-Rev2.0 pages 426-427
// DG1: IHD-OS-DG1-Vol 12-2.21 pages 349-350
// Ice Lake: IHD-OS-ICLLP-Vol 12-1.22-Rev2.0 pages 370-371
// The backlight PWM must be disabled while changing the PWM frequency. This
// is not a theoretical issue -- we observed a panel whose backlight remains
// off for minutes if we attempt to change the PWM frequency while the PWM
// remains enabled. We also want to avoid disabling and re-enabling the PWM if
// we're only going to change the duty cycle (brightness).
//
// To accomplish this, the SetPanelBacklightPwmParameters*() methods called
// below disable the PWM if necessary. `old_backlight_control` captures the
// PWM enablement state before we make any changes, so it is correctly
// restored before the end of the method.
//
// Disabling the brightness PWM while the panel backlight is enabled is
// supported, and results in well-defined behavior. The backlight goes to 100%
// brightness. (As an aside, this seems like the best failure mode we could
// have hoped for. A flicker of brightness seems better than a flicker of
// complete darkness, which is the other plausible alternative.)
const uint32_t old_backlight_control = backlight_control_.reg_value();
if (is_kbl(device_id_) || is_skl(device_id_)) {
SetPanelBacklightPwmParametersKabyLake(parameters);
} else if (is_tgl(device_id_)) {
SetPanelBacklightPwmParametersTigerLake(parameters);
} else if (is_test_device(device_id_)) {
// Stubbed out for integration tests.
return;
} else {
ZX_ASSERT_MSG(false, "Unsupported PCI device ID %d", device_id_);
}
// `set_reg_value()` undoes any changes that SetPanelBacklightPwmParameters*()
// might have applied.
backlight_control_.set_reg_value(old_backlight_control)
.set_pwm_polarity_inverted(parameters.backlight_pwm_inverted);
if (backlight_control_.reg_value() != old_backlight_control) {
backlight_control_.WriteTo(mmio_buffer_);
}
}
void PchEngine::SetPanelBacklightPwmParametersKabyLake(const PchPanelParameters& parameters) {
const int32_t pwm_divider_granularity = misc_.backlight_pwm_multiplier() ? 128 : 16;
// std::min() explicitly clamps one of the multipliers so that the
// multiplication will not overflow, which would cause UB.
//
// Clamping is sufficient (as opposed to using int64_t) because the dividend
// is the PCH clock frequency, which fits in 30 bits.
//
// The result is positive because the caller ensures that
// `backlight_pwm_frequency_hz` is positive.
const int32_t pwm_divider =
std::min(parameters.backlight_pwm_frequency_hz,
std::numeric_limits<int32_t>::max() / pwm_divider_granularity) *
pwm_divider_granularity;
// The frequency divider and duty cycle are 16-bit fields.
static constexpr int32_t kMaxRawField = (1 << 16) - 1;
// The division will not cause UB because `pwm_divider` is positive. The Intel
// PRMs don't explicitly state that the PWM frequency divider shouldn't be
// zero. We assume this is a good idea.
const int32_t new_frequency_divider =
std::max(1, std::min(RawClockHz() / pwm_divider, kMaxRawField));
ZX_DEBUG_ASSERT(new_frequency_divider > 0);
const uint32_t raw_frequency_divider = static_cast<uint32_t>(new_frequency_divider);
// The cast does not cause UB because `raw_frequncy_divider` fits in 16 bits.
const uint32_t raw_duty_cycle =
ScaledPwmDutyCycle(static_cast<uint32_t>(raw_frequency_divider),
backlight_freq_duty_.duty_cycle(), backlight_freq_duty_.freq_divider());
ZX_ASSERT(raw_duty_cycle <= raw_frequency_divider);
ZX_ASSERT(raw_duty_cycle <= kMaxRawField); // Implied by the check above.
const uint32_t old_backlight_freq_duty = backlight_freq_duty_.reg_value();
if (backlight_freq_duty_.freq_divider() != raw_frequency_divider) {
// The backlight PWM must be turned off while changing the frequency. The
// SetPanelBacklightPwmParameters() implementation has a deeper explanation.
if (backlight_control_.pwm_counter_enabled()) {
backlight_control_.set_pwm_counter_enabled(false).WriteTo(mmio_buffer_);
}
}
backlight_freq_duty_.set_freq_divider(raw_frequency_divider);
backlight_freq_duty_.set_duty_cycle(raw_duty_cycle);
if (backlight_freq_duty_.reg_value() != old_backlight_freq_duty) {
backlight_freq_duty_.WriteTo(mmio_buffer_);
}
}
void PchEngine::SetPanelBacklightPwmParametersTigerLake(const PchPanelParameters& parameters) {
// The cast is safe because RawClockHz() is non-negative and fits in 32 bits,
// so the division result will also fit in 32 bits.
const uint32_t raw_frequency_divider =
static_cast<uint32_t>(std::max(1, RawClockHz() / parameters.backlight_pwm_frequency_hz));
// We use the logical values in diffing (instead of the raw register values)
// because the logical values perfectly map to the register values.
const uint32_t old_frequency_divider = backlight_pwm_freq_.divider();
const uint32_t old_duty_cycle = backlight_pwm_duty_.value();
if (old_frequency_divider != raw_frequency_divider) {
// The backlight PWM must be turned off while changing the frequency. The
// SetPanelBacklightPwmParameters() implementation has a deeper explanation.
//
// Doing this here means we don't need to worry about possibly (briefly)
// breaking the invariant that the PWM duty cycle must not exceed the PWM
// frequency divider.
if (backlight_control_.pwm_counter_enabled()) {
backlight_control_.set_pwm_counter_enabled(false).WriteTo(mmio_buffer_);
}
backlight_pwm_freq_.set_divider(raw_frequency_divider).WriteTo(mmio_buffer_);
}
const uint32_t raw_duty_cycle =
ScaledPwmDutyCycle(raw_frequency_divider, old_duty_cycle, old_frequency_divider);
ZX_ASSERT(raw_duty_cycle <= raw_frequency_divider);
if (old_duty_cycle != raw_duty_cycle) {
backlight_pwm_duty_.set_value(raw_duty_cycle).WriteTo(mmio_buffer_);
}
}
PchPanelPowerTarget PchEngine::PanelPowerTarget() const {
return PchPanelPowerTarget{
// The casts are safe because these are all 1-bit fields.
.power_on = static_cast<bool>(panel_power_control_.power_state_target()),
.backlight_on = static_cast<bool>(panel_power_control_.backlight_enabled()),
.force_power_on = static_cast<bool>(panel_power_control_.vdd_always_on()),
.brightness_pwm_counter_on = static_cast<bool>(backlight_control_.pwm_counter_enabled()),
};
}
void PchEngine::SetPanelPowerTarget(const PchPanelPowerTarget& power_target) {
const uint32_t old_panel_power_control = panel_power_control_.reg_value();
panel_power_control_.set_power_state_target(power_target.power_on)
.set_backlight_enabled(power_target.backlight_on)
.set_vdd_always_on(power_target.force_power_on);
const uint32_t old_backlight_control = backlight_control_.reg_value();
backlight_control_.set_pwm_counter_enabled(power_target.brightness_pwm_counter_on);
if (panel_power_control_.reg_value() != old_panel_power_control) {
panel_power_control_.WriteTo(mmio_buffer_);
}
if (backlight_control_.reg_value() != old_backlight_control) {
backlight_control_.WriteTo(mmio_buffer_);
}
}
double PchEngine::PanelBrightness() const {
uint32_t pwm_duty;
uint32_t pwm_freq_divider;
if (is_skl(device_id_) || is_kbl(device_id_)) {
pwm_duty = backlight_freq_duty_.duty_cycle();
pwm_freq_divider = backlight_freq_duty_.freq_divider();
} else if (is_tgl(device_id_)) {
pwm_duty = backlight_pwm_duty_.value();
pwm_freq_divider = backlight_pwm_freq_.divider();
} else if (is_test_device(device_id_)) {
pwm_duty = 0;
pwm_freq_divider = 1;
} else {
ZX_ASSERT_MSG(false, "Unsupported PCI device ID %d", device_id_);
}
if (pwm_freq_divider == 0) {
// This matches the brightness level "preserved" by SetPanelParameters().
return 0;
}
ZX_ASSERT_MSG(pwm_duty <= pwm_freq_divider, "Brightness PWM is configured incorrectly");
return static_cast<double>(pwm_duty) / static_cast<double>(pwm_freq_divider);
}
void PchEngine::SetPanelBrightness(double brightness) {
ZX_ASSERT(brightness >= 0.0);
ZX_ASSERT(brightness <= 1.0);
if (is_skl(device_id_) || is_kbl(device_id_)) {
// The cast is safe because freq_divider() is a 16-bit field.
const int32_t pwm_freq_divider = static_cast<int32_t>(backlight_freq_duty_.freq_divider());
if (pwm_freq_divider == 0) {
return;
}
const int32_t pwm_duty = std::min(
// The cast is not UB because `brightness` is between 0 and 1, so the
// rounding result should be between 0 and `pwm_freq_divider`.
static_cast<int32_t>(std::round(static_cast<double>(pwm_freq_divider) * brightness)),
pwm_freq_divider);
const uint32_t old_backlight_freq_duty = backlight_freq_duty_.reg_value();
backlight_freq_duty_.set_duty_cycle(pwm_duty);
if (backlight_freq_duty_.reg_value() != old_backlight_freq_duty) {
backlight_freq_duty_.WriteTo(mmio_buffer_);
}
return;
}
if (is_tgl(device_id_)) {
const uint32_t pwm_freq_divider = backlight_pwm_freq_.divider();
if (pwm_freq_divider == 0) {
return;
}
const uint32_t pwm_duty = std::min(
// The cast to uint32_t is safe because `brightness` is between 0 and 1,
// so the rounding result should be between 0 and `pwm_freq_divider`.
static_cast<uint32_t>(std::round(static_cast<double>(pwm_freq_divider) * brightness)),
pwm_freq_divider);
// We use the logical value in diffing (instead of the raw register values)
// because the logical value perfectly maps to the register value.
if (pwm_duty != backlight_pwm_duty_.value()) {
backlight_pwm_duty_.set_value(pwm_duty).WriteTo(mmio_buffer_);
}
return;
}
if (is_test_device(device_id_)) {
return; // Stubbed out for integration tests.
}
ZX_ASSERT_MSG(false, "Unsupported PCI device ID %d", device_id_);
}
void PchEngine::Log() {
const PchClockParameters clock_parameters = ClockParameters();
zxlogf(TRACE, "PCH Raw Clock: %d Hz", clock_parameters.raw_clock_hz);
zxlogf(TRACE, "PCH Panel Power Clock frequency: %d Hz", clock_parameters.panel_power_clock_hz);
const char* state_text = "bug";
switch (PanelPowerState()) {
case PchPanelPowerState::kPoweredDown:
state_text = "powered down";
break;
case PchPanelPowerState::kWaitingForPowerCycleDelay:
state_text = "power cycle delay";
break;
case PchPanelPowerState::kPoweringUp:
state_text = "powering up";
break;
case PchPanelPowerState::kPoweredUp:
state_text = "powered up";
break;
case PchPanelPowerState::kPoweringDown:
state_text = "powering down";
break;
}
zxlogf(TRACE, "PCH Panel power state: %s", state_text);
const PchPanelPowerTarget power_target = PanelPowerTarget();
zxlogf(TRACE, "PCH Panel power target: %s", power_target.power_on ? "on" : "off");
zxlogf(TRACE, "PCH Panel backlight: %s", power_target.backlight_on ? "enabled" : "disabled");
zxlogf(TRACE, "PCH Panel VDD operation: %s",
power_target.force_power_on ? "forced on" : "standard");
zxlogf(TRACE, "PCH Backlight counter %s",
power_target.brightness_pwm_counter_on ? "enabled" : "disabled");
const PchPanelParameters panel_parameters = PanelParameters();
zxlogf(TRACE, "PCH Panel T2 delay: %" PRId64 " us",
panel_parameters.power_on_to_backlight_on_delay_micros);
zxlogf(TRACE, "PCH Panel T3 delay: %" PRId64 " us",
panel_parameters.power_on_to_hpd_aux_ready_delay_micros);
zxlogf(TRACE, "PCH Panel T9 delay: %" PRId64 " us",
panel_parameters.backlight_off_to_video_end_delay_micros);
zxlogf(TRACE, "PCH Panel T10 delay: %" PRId64 " us",
panel_parameters.video_end_to_power_off_delay_micros);
zxlogf(TRACE, "PCH Panel T12 delay: %" PRId64 " us", panel_parameters.power_cycle_delay_micros);
zxlogf(TRACE, "PCH Panel power down on reset: %s",
panel_parameters.power_down_on_reset ? "on" : "off");
zxlogf(TRACE, "PCH Backlight PWM frequency: %" PRId32 " Hz",
panel_parameters.backlight_pwm_frequency_hz);
zxlogf(TRACE, "PCH Backlight PWM polarity: %s",
panel_parameters.backlight_pwm_inverted ? "inverted" : "not inverted");
zxlogf(TRACE, "NDE_RSTWRN_OPT: %" PRIx32,
registers::DisplayResetOptions::Get().ReadFrom(mmio_buffer_).reg_value());
zxlogf(TRACE, "SCHICKEN_1: %" PRIx32, misc_.reg_value());
zxlogf(TRACE, "RAWCLK_FREQ: %" PRIx32, clock_.reg_value());
zxlogf(TRACE, "PP_CONTROL: %" PRIx32, panel_power_control_.reg_value());
zxlogf(TRACE, "PP_ON_DELAYS: %" PRIx32, panel_power_on_delays_.reg_value());
zxlogf(TRACE, "PP_OFF_DELAYS: %" PRIx32, panel_power_off_delays_.reg_value());
zxlogf(TRACE, "PP_STATUS: %" PRIx32,
registers::PchPanelPowerStatus::Get().ReadFrom(mmio_buffer_).reg_value());
if (is_skl(device_id_) || is_kbl(device_id_)) {
zxlogf(TRACE, "PP_DIVISOR: %" PRIx32, panel_power_clock_delay_.reg_value());
}
zxlogf(TRACE, "SBLC_PWM_CTL1: %" PRIx32, backlight_control_.reg_value());
if (is_skl(device_id_) || is_kbl(device_id_)) {
zxlogf(TRACE, "SBLC_PWM_CTL2: %" PRIx32, backlight_freq_duty_.reg_value());
}
if (is_tgl(device_id_)) {
zxlogf(TRACE, "SBLC_PWM_FREQ: %" PRIx32, backlight_pwm_freq_.reg_value());
zxlogf(TRACE, "SBLC_PWM_DUTY: %" PRIx32, backlight_pwm_duty_.reg_value());
}
}
} // namespace i915