blob: 0722da4f8cd7940b99d02ee523b3cc97000e1fc4 [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/mmio/mmio-buffer.h>
#include <gtest/gtest.h>
#include <mock-mmio-range/mock-mmio-range.h>
namespace i915 {
namespace {
// 24MHz, from IHD-OS-SKL-Vol 2c-05.16 Part 2 page 705.
constexpr uint32_t kKabyLakeStandardRawClock = 0b0000'0000'0000'0000'0000'0000'0001'1000;
// 12MHz, theoretical.
constexpr uint32_t kKabyLakeHalfRawClock = 0b0000'0000'0000'0000'0000'0000'0000'1100;
// 19.2MHz. Based on IHD-OS-TGL-Vol 2c-1.22-Rev2.0 Part 2 page 1083 but the bits
// don't match. See PchEngine::RawClock() documentation for justification.
constexpr uint32_t kTigerLakeStandardRawClock = 0b0001'0000'0001'0010'0000'1000'0000'0000;
// 24.0MHz. Based on IHD-OS-TGL-Vol 2c-1.22-Rev2.0 Part 2 page 1083 but the bits
// don't match. See PchEngine::RawClock() documentation for justification.
constexpr uint32_t kTigerLakeAlternateRawClock = 0b0000'0000'0001'0111'0000'0000'0000'0000;
// 38.4MHz, from IHD-OS-DG1-Vol 2c-2.21 Part 2 page 1131.
constexpr uint32_t kDg1StandardRawClock = 0b0001'0000'0010'0101'0001'0000'0000'0000;
// Maximum value that can be read (theoretical, breaks documented invariants).
// 1031MHz: Divider = 1024 (1023 + 1), Denominator = 7, Numerator = 1 (0 + 1).
constexpr uint32_t kTigerLakeMaxRawClock = 0b0000'0011'1111'1111'0011'1000'0000'0000;
// 100us, from IHD-OS-SKL-Vol 2c-05.16 Part 2 page 628.
constexpr uint32_t kKabyLakeStandardPpDivisor = 0x0004'af00;
// 50us assuming standard clock, theoretical.
constexpr uint32_t kKabyLakeDoublePpDivisor = 0x0009'5f00;
constexpr int kSChicken1Offset = 0xc2000;
constexpr int kSFuseStrapOffset = 0xc2014;
constexpr int kRawClkOffset = 0xc6204;
constexpr int kPpStatusOffset = 0xc7200;
constexpr int kPpControlOffset = 0xc7204;
constexpr int kPpOnDelays = 0xc7208;
constexpr int kPpOffDelays = 0xc720c;
constexpr int kPpDivisor = 0xc7210;
constexpr int kSblcPwmCtl1Offset = 0xc8250;
constexpr int kSblcPwmCtl2Offset = 0xc8254;
constexpr int kSblcPwmFreqOffset = 0xc8254;
constexpr int kSblcPwmDutyOffset = 0xc8258;
constexpr int kNdeRstWrnOpt = 0x46408;
constexpr int kAtlasGpuDeviceId = 0x591c;
constexpr int kNuc7GpuDeviceId = 0x5916;
constexpr int kDell5420GpuDeviceId = 0x9a49;
TEST(PchClockParametersTest, Equality) {
static constexpr PchClockParameters lhs = {
.raw_clock_hz = 24'000'000,
.panel_power_clock_hz = 10'000,
};
PchClockParameters rhs = lhs;
EXPECT_EQ(lhs, rhs);
rhs = lhs;
rhs.raw_clock_hz = 24'000'001;
EXPECT_NE(lhs, rhs);
rhs = lhs;
rhs.panel_power_clock_hz = 10'001;
EXPECT_NE(lhs, rhs);
}
TEST(PchPanelParametersTest, Equality) {
// The parameters are inspired from the eDP and SPWG standards, but are
// tweaked so each delay is unique. This is intended to help catch bugs where
// fields are compared incorrectly.
static constexpr PchPanelParameters lhs = {
.power_on_to_hpd_aux_ready_delay_micros = 90'000, // eDP T1+T3 max
.power_on_to_backlight_on_delay_micros = 260'000, // SPWG T1+T2+T5 max/min
.backlight_off_to_video_end_delay_micros = 200'000, // SPWG T6 min
.video_end_to_power_off_delay_micros = 500'000, // eDP T10 max
.power_cycle_delay_micros = 900'000,
.backlight_pwm_frequency_hz = 1'000,
.power_down_on_reset = true,
.backlight_pwm_inverted = false,
};
PchPanelParameters rhs = lhs;
EXPECT_EQ(lhs, lhs);
rhs = lhs;
rhs.power_on_to_hpd_aux_ready_delay_micros = 90'001;
EXPECT_NE(lhs, rhs);
rhs = lhs;
rhs.power_on_to_backlight_on_delay_micros = 260'001;
EXPECT_NE(lhs, rhs);
rhs = lhs;
rhs.backlight_off_to_video_end_delay_micros = 200'001;
EXPECT_NE(lhs, rhs);
rhs = lhs;
rhs.video_end_to_power_off_delay_micros = 500'001;
EXPECT_NE(lhs, rhs);
rhs = lhs;
rhs.power_cycle_delay_micros = 900'001;
EXPECT_NE(lhs, rhs);
rhs = lhs;
rhs.backlight_pwm_frequency_hz = 1'001;
EXPECT_NE(lhs, rhs);
rhs = lhs;
rhs.power_down_on_reset = false;
EXPECT_NE(lhs, rhs);
rhs.backlight_pwm_inverted = true;
EXPECT_NE(lhs, rhs);
}
TEST(PchPanelPowerTargetTest, Equality) {
// This struct has many bit fields, so checking for mismatched bits requires a
// different approach from above.
static constexpr PchPanelPowerTarget lhs = {
.power_on = false,
.backlight_on = false,
.force_power_on = false,
.brightness_pwm_counter_on = false,
};
PchPanelPowerTarget rhs = lhs;
EXPECT_EQ(lhs, rhs);
rhs = lhs;
rhs.power_on = true;
EXPECT_NE(lhs, rhs);
EXPECT_EQ(rhs, rhs);
rhs = lhs;
rhs.backlight_on = true;
EXPECT_NE(lhs, rhs);
EXPECT_EQ(rhs, rhs);
rhs = lhs;
rhs.force_power_on = true;
EXPECT_NE(lhs, rhs);
EXPECT_EQ(rhs, rhs);
rhs = lhs;
rhs.brightness_pwm_counter_on = true;
EXPECT_NE(lhs, rhs);
EXPECT_EQ(rhs, rhs);
}
class PchEngineTest : public ::testing::Test {
public:
PchEngineTest() = default;
~PchEngineTest() override = default;
void SetUp() override {}
void TearDown() override { mmio_range_.CheckAllAccessesReplayed(); }
protected:
constexpr static int kMmioRangeSize = 0x100000;
ddk_mock::MockMmioRange mmio_range_{kMmioRangeSize, ddk_mock::MockMmioRange::Size::k32};
fdf::MmioBuffer mmio_buffer_{mmio_range_.GetMmioBuffer()};
};
TEST_F(PchEngineTest, KabyLakeZeroedRegisters) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = 0},
{.address = kPpControlOffset, .value = 0},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kPpDivisor, .value = 0},
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmCtl2Offset, .value = 0},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(0, clock_parameters.raw_clock_hz);
EXPECT_EQ(0, clock_parameters.panel_power_clock_hz);
const PchPanelPowerTarget power_target = pch_engine.PanelPowerTarget();
EXPECT_EQ(false, power_target.power_on);
EXPECT_EQ(false, power_target.backlight_on);
EXPECT_EQ(false, power_target.force_power_on);
EXPECT_EQ(false, power_target.brightness_pwm_counter_on);
const PchPanelParameters panel_parameters = pch_engine.PanelParameters();
EXPECT_EQ(0, panel_parameters.power_on_to_hpd_aux_ready_delay_micros);
EXPECT_EQ(0, panel_parameters.power_on_to_backlight_on_delay_micros);
EXPECT_EQ(0, panel_parameters.backlight_off_to_video_end_delay_micros);
EXPECT_EQ(0, panel_parameters.video_end_to_power_off_delay_micros);
EXPECT_EQ(0, panel_parameters.power_cycle_delay_micros);
EXPECT_EQ(0, panel_parameters.backlight_pwm_frequency_hz);
EXPECT_EQ(false, panel_parameters.power_down_on_reset);
EXPECT_EQ(false, panel_parameters.backlight_pwm_inverted);
EXPECT_EQ(0.0, pch_engine.PanelBrightness());
}
TEST_F(PchEngineTest, TigerLakeZeroedRegisters) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = 0},
{.address = kPpControlOffset, .value = 0},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmFreqOffset, .value = 0},
{.address = kSblcPwmDutyOffset, .value = 0},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(1'000'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
const PchPanelPowerTarget power_target = pch_engine.PanelPowerTarget();
EXPECT_EQ(false, power_target.power_on);
EXPECT_EQ(false, power_target.backlight_on);
EXPECT_EQ(false, power_target.force_power_on);
EXPECT_EQ(false, power_target.brightness_pwm_counter_on);
const PchPanelParameters panel_parameters = pch_engine.PanelParameters();
EXPECT_EQ(0, panel_parameters.power_on_to_hpd_aux_ready_delay_micros);
EXPECT_EQ(0, panel_parameters.power_on_to_backlight_on_delay_micros);
EXPECT_EQ(0, panel_parameters.backlight_off_to_video_end_delay_micros);
EXPECT_EQ(0, panel_parameters.video_end_to_power_off_delay_micros);
EXPECT_EQ(0, panel_parameters.power_cycle_delay_micros);
EXPECT_EQ(0, panel_parameters.backlight_pwm_frequency_hz);
EXPECT_EQ(false, panel_parameters.power_down_on_reset);
EXPECT_EQ(false, panel_parameters.backlight_pwm_inverted);
EXPECT_EQ(0.0, pch_engine.PanelBrightness());
}
TEST_F(PchEngineTest, KabyLakeNuc7BootloaderConfig) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = 0x18},
{.address = kPpControlOffset, .value = 0x00},
{.address = kPpOnDelays, .value = 0x0000'0000},
{.address = kPpOffDelays, .value = 0x0000'0000},
{.address = kPpDivisor, .value = 0x0004'af00},
{.address = kSblcPwmCtl1Offset, .value = 0x0000'0000},
{.address = kSblcPwmCtl2Offset, .value = 0x0000'0000},
}));
PchEngine pch_engine(&mmio_buffer_, kNuc7GpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(24'000'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
const PchPanelPowerTarget power_target = pch_engine.PanelPowerTarget();
EXPECT_EQ(false, power_target.power_on);
EXPECT_EQ(false, power_target.backlight_on);
EXPECT_EQ(false, power_target.force_power_on);
EXPECT_EQ(false, power_target.brightness_pwm_counter_on);
const PchPanelParameters panel_parameters = pch_engine.PanelParameters();
EXPECT_EQ(0, panel_parameters.power_on_to_hpd_aux_ready_delay_micros);
EXPECT_EQ(0, panel_parameters.power_on_to_backlight_on_delay_micros);
EXPECT_EQ(0, panel_parameters.backlight_off_to_video_end_delay_micros);
EXPECT_EQ(0, panel_parameters.video_end_to_power_off_delay_micros);
EXPECT_EQ(0, panel_parameters.power_cycle_delay_micros);
EXPECT_EQ(0, panel_parameters.backlight_pwm_frequency_hz);
EXPECT_EQ(false, panel_parameters.power_down_on_reset);
EXPECT_EQ(false, panel_parameters.backlight_pwm_inverted);
EXPECT_EQ(0.0, pch_engine.PanelBrightness());
}
TEST_F(PchEngineTest, KabyLakeAtlasBootloaderConfig) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = 0x18},
{.address = kPpControlOffset, .value = 0x07},
{.address = kPpOnDelays, .value = 0x0000'0000},
{.address = kPpOffDelays, .value = 0x01f4'0000},
{.address = kPpDivisor, .value = 0x0004'af06},
{.address = kSblcPwmCtl1Offset, .value = 0x8000'0000},
{.address = kSblcPwmCtl2Offset, .value = 0x1d4c'1d4c},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(24'000'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
const PchPanelPowerTarget power_target = pch_engine.PanelPowerTarget();
EXPECT_EQ(true, power_target.power_on);
EXPECT_EQ(true, power_target.backlight_on);
EXPECT_EQ(false, power_target.force_power_on);
EXPECT_EQ(true, power_target.brightness_pwm_counter_on);
const PchPanelParameters panel_parameters = pch_engine.PanelParameters();
EXPECT_EQ(0, panel_parameters.power_on_to_hpd_aux_ready_delay_micros);
EXPECT_EQ(0, panel_parameters.power_on_to_backlight_on_delay_micros);
EXPECT_EQ(0, panel_parameters.backlight_off_to_video_end_delay_micros);
EXPECT_EQ(50'000, panel_parameters.video_end_to_power_off_delay_micros);
EXPECT_EQ(500'000, panel_parameters.power_cycle_delay_micros);
EXPECT_EQ(200, panel_parameters.backlight_pwm_frequency_hz);
EXPECT_EQ(true, panel_parameters.power_down_on_reset);
EXPECT_EQ(false, panel_parameters.backlight_pwm_inverted);
EXPECT_EQ(1.0, pch_engine.PanelBrightness());
}
TEST_F(PchEngineTest, KabyLakeAtlasSecureBootloaderConfig) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = 0x18},
{.address = kPpControlOffset, .value = 0x08},
{.address = kPpOnDelays, .value = 0x0000'0000},
{.address = kPpOffDelays, .value = 0x0000'0000},
{.address = kPpDivisor, .value = 0x0004'af00},
{.address = kSblcPwmCtl1Offset, .value = 0x0000'0000},
{.address = kSblcPwmCtl2Offset, .value = 0x0000'0000},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(24'000'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
const PchPanelPowerTarget power_target = pch_engine.PanelPowerTarget();
EXPECT_EQ(false, power_target.power_on);
EXPECT_EQ(false, power_target.backlight_on);
EXPECT_EQ(true, power_target.force_power_on);
EXPECT_EQ(false, power_target.brightness_pwm_counter_on);
const PchPanelParameters panel_parameters = pch_engine.PanelParameters();
EXPECT_EQ(0, panel_parameters.power_on_to_hpd_aux_ready_delay_micros);
EXPECT_EQ(0, panel_parameters.power_on_to_backlight_on_delay_micros);
EXPECT_EQ(0, panel_parameters.backlight_off_to_video_end_delay_micros);
EXPECT_EQ(0, panel_parameters.video_end_to_power_off_delay_micros);
EXPECT_EQ(0, panel_parameters.power_cycle_delay_micros);
EXPECT_EQ(0, panel_parameters.backlight_pwm_frequency_hz);
EXPECT_EQ(false, panel_parameters.power_down_on_reset);
EXPECT_EQ(false, panel_parameters.backlight_pwm_inverted);
EXPECT_EQ(0.0, pch_engine.PanelBrightness());
}
TEST_F(PchEngineTest, TigerLakeDell5420BootloaderConfig) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0x901},
{.address = kRawClkOffset, .value = 0x1012'0800},
{.address = kPpControlOffset, .value = 0x67},
{.address = kPpOnDelays, .value = 0x0001'0001},
{.address = kPpOffDelays, .value = 0x01f4'0001},
{.address = kSblcPwmCtl1Offset, .value = 0x8000'0000},
{.address = kSblcPwmFreqOffset, .value = 0x0001'7700},
{.address = kSblcPwmDutyOffset, .value = 0x0001'7700},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(19'200'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
const PchPanelPowerTarget power_target = pch_engine.PanelPowerTarget();
EXPECT_EQ(true, power_target.power_on);
EXPECT_EQ(true, power_target.backlight_on);
EXPECT_EQ(false, power_target.force_power_on);
EXPECT_EQ(true, power_target.brightness_pwm_counter_on);
const PchPanelParameters panel_parameters = pch_engine.PanelParameters();
EXPECT_EQ(100, panel_parameters.power_on_to_hpd_aux_ready_delay_micros);
EXPECT_EQ(100, panel_parameters.power_on_to_backlight_on_delay_micros);
EXPECT_EQ(100, panel_parameters.backlight_off_to_video_end_delay_micros);
EXPECT_EQ(50'000, panel_parameters.video_end_to_power_off_delay_micros);
EXPECT_EQ(500'000, panel_parameters.power_cycle_delay_micros);
EXPECT_EQ(200, panel_parameters.backlight_pwm_frequency_hz);
EXPECT_EQ(true, panel_parameters.power_down_on_reset);
EXPECT_EQ(false, panel_parameters.backlight_pwm_inverted);
}
TEST_F(PchEngineTest, TigerLakeNuc11BootloaderConfig) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0x900},
{.address = kRawClkOffset, .value = 0x1012'0800},
{.address = kPpControlOffset, .value = 0x08},
{.address = kPpOnDelays, .value = 0x0000'0000},
{.address = kPpOffDelays, .value = 0x0000'0000},
{.address = kSblcPwmCtl1Offset, .value = 0x0000'0000},
{.address = kSblcPwmFreqOffset, .value = 0x0000'0000},
{.address = kSblcPwmDutyOffset, .value = 0x0000'0000},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(19'200'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
const PchPanelPowerTarget power_target = pch_engine.PanelPowerTarget();
EXPECT_EQ(false, power_target.power_on);
EXPECT_EQ(false, power_target.backlight_on);
EXPECT_EQ(true, power_target.force_power_on);
EXPECT_EQ(false, power_target.brightness_pwm_counter_on);
const PchPanelParameters panel_parameters = pch_engine.PanelParameters();
EXPECT_EQ(0, panel_parameters.power_on_to_hpd_aux_ready_delay_micros);
EXPECT_EQ(0, panel_parameters.power_on_to_backlight_on_delay_micros);
EXPECT_EQ(0, panel_parameters.backlight_off_to_video_end_delay_micros);
EXPECT_EQ(0, panel_parameters.video_end_to_power_off_delay_micros);
EXPECT_EQ(0, panel_parameters.power_cycle_delay_micros);
EXPECT_EQ(0, panel_parameters.backlight_pwm_frequency_hz);
EXPECT_EQ(false, panel_parameters.power_down_on_reset);
EXPECT_EQ(false, panel_parameters.backlight_pwm_inverted);
EXPECT_EQ(0.0, pch_engine.PanelBrightness());
}
TEST_F(PchEngineTest, KabyLakeRestoreClockParameters) {
// The register values are based on real values, and slightly modified to
// catch register-swapping bugs.
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = 0x18},
{.address = kPpControlOffset, .value = 0x07},
{.address = kPpOnDelays, .value = 0x0001'0001},
{.address = kPpOffDelays, .value = 0x01f4'0000},
{.address = kPpDivisor, .value = 0x0004'af06},
{.address = kSblcPwmCtl1Offset, .value = 0x8000'0000},
{.address = kSblcPwmCtl2Offset, .value = 0x1d4c'1d4c},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kRawClkOffset, .value = 0x18, .write = true},
{.address = kPpDivisor, .value = 0x0004'af06, .write = true},
{.address = kSChicken1Offset, .value = 0, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.RestoreClockParameters();
}
TEST_F(PchEngineTest, TigerLakeRestoreClockParameters) {
// The register values are based on real values, and slightly modified to
// catch register-swapping bugs.
//
// S_CHICKEN1 has bit 7 set to check that RestoreClockParameters() implements
// the workaround that requires resetting that bit.
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0x981},
{.address = kRawClkOffset, .value = 0x1012'0800},
{.address = kPpControlOffset, .value = 0x67},
{.address = kPpOnDelays, .value = 0x0001'0001},
{.address = kPpOffDelays, .value = 0x01f4'0001},
{.address = kSblcPwmCtl1Offset, .value = 0x8000'0000},
{.address = kSblcPwmFreqOffset, .value = 0x0001'7700},
{.address = kSblcPwmDutyOffset, .value = 0x0001'7700},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kRawClkOffset, .value = 0x1012'0800, .write = true},
{.address = kSChicken1Offset, .value = 0x901, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
pch_engine.RestoreClockParameters();
}
TEST_F(PchEngineTest, KabyLakeRestoreNonClockParameters) {
// The register values are based on real values, and slightly modified to
// catch register-swapping bugs.
//
// PP_CONTROL bits 0 and 2 and SBLC_PWM_CTL1 bit 31 are set to check that
// RestoreParameters() turn off panel power and disable the backlight PWM.
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = 0x18},
{.address = kPpControlOffset, .value = 0x07},
{.address = kPpOnDelays, .value = 0x0001'0001},
{.address = kPpOffDelays, .value = 0x01f4'0000},
{.address = kPpDivisor, .value = 0x0004'af06},
{.address = kSblcPwmCtl1Offset, .value = 0xa000'0000},
{.address = kSblcPwmCtl2Offset, .value = 0x1d4c'122c},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpOnDelays, .value = 0x0001'0001, .write = true},
{.address = kPpOffDelays, .value = 0x01f4'0000, .write = true},
{.address = kPpControlOffset, .value = 0x02, .write = true},
{.address = kSblcPwmCtl2Offset, .value = 0x1d4c'122c, .write = true},
{.address = kSblcPwmCtl1Offset, .value = 0x2000'0000, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.RestoreNonClockParameters();
}
TEST_F(PchEngineTest, TigerLakeRestoreNonClockParameters) {
// The register values are based on real values, and slightly modified to
// catch register-swapping bugs.
//
// PP_CONTROL bits 0 and 2 and SBLC_PWM_CTL1 bit 31 are set to check that
// RestoreParameters() turn off panel power and disable the backlight PWM.
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0x981},
{.address = kRawClkOffset, .value = 0x1012'0800},
{.address = kPpControlOffset, .value = 0x67},
{.address = kPpOnDelays, .value = 0x0001'0001},
{.address = kPpOffDelays, .value = 0x01f4'0001},
{.address = kSblcPwmCtl1Offset, .value = 0xa000'0000},
{.address = kSblcPwmFreqOffset, .value = 0x0001'7700},
{.address = kSblcPwmDutyOffset, .value = 0x0001'2200},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpOnDelays, .value = 0x0001'0001, .write = true},
{.address = kPpOffDelays, .value = 0x01f4'0001, .write = true},
{.address = kPpControlOffset, .value = 0x62, .write = true},
{.address = kSblcPwmFreqOffset, .value = 0x0001'7700, .write = true},
{.address = kSblcPwmDutyOffset, .value = 0x0001'2200, .write = true},
{.address = kSblcPwmCtl1Offset, .value = 0x2000'0000, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
pch_engine.RestoreNonClockParameters();
}
class PchEngineResetHandshakeTest : public PchEngineTest {
public:
// Set up expectations for PCH registers.
void SetUp() override {
PchEngineTest::SetUp();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
{.address = kPpControlOffset, .value = 0},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kPpDivisor, .value = kKabyLakeStandardPpDivisor},
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmCtl2Offset, .value = 0},
}));
}
};
TEST_F(PchEngineResetHandshakeTest, GenericSetPchResetHandshakeEnabled) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kNdeRstWrnOpt, .value = 0},
{.address = kNdeRstWrnOpt, .value = 0x10, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPchResetHandshake(true);
}
TEST_F(PchEngineResetHandshakeTest, GenericSetPchResetHandshakeEnabledNoChange) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kNdeRstWrnOpt, .value = 0},
{.address = kNdeRstWrnOpt, .value = 0x10, .write = true},
{.address = kNdeRstWrnOpt, .value = 0x10},
{.address = kNdeRstWrnOpt, .value = 0x10},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPchResetHandshake(true);
pch_engine.SetPchResetHandshake(true); // No MMIO writes.
pch_engine.SetPchResetHandshake(true); // No MMIO writes.
}
TEST_F(PchEngineResetHandshakeTest, GenericSetPchResetHandshakeEnabledFromAtlasBootloaderState) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kNdeRstWrnOpt, .value = 0x30},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPchResetHandshake(true); // No MMIO writes.
}
TEST_F(PchEngineResetHandshakeTest, GenericSetPchResetHandshakeDisabled) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kNdeRstWrnOpt, .value = 0xff},
{.address = kNdeRstWrnOpt, .value = 0xef, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPchResetHandshake(false);
}
TEST_F(PchEngineResetHandshakeTest, GenericSetPchResetHandshakeDisabledNoChange) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kNdeRstWrnOpt, .value = 0xff},
{.address = kNdeRstWrnOpt, .value = 0xef, .write = true},
{.address = kNdeRstWrnOpt, .value = 0xef},
{.address = kNdeRstWrnOpt, .value = 0xef},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPchResetHandshake(false);
pch_engine.SetPchResetHandshake(false); // No MMIO writes.
pch_engine.SetPchResetHandshake(false); // No MMIO writes.
}
class PchEngineKabyLakeClockTest : public PchEngineTest {
public:
// Set up expectations for registers except for RAWCLK_FREQ and PP_DIVISOR.
template <typename Lambda1, typename Lambda2>
void SetPchMmioExpectations(Lambda1 raw_clock_expectations, Lambda2 panel_divisor_expectations) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
}));
raw_clock_expectations();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpControlOffset, .value = 0},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
}));
panel_divisor_expectations();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmCtl2Offset, .value = 0},
}));
}
};
TEST_F(PchEngineKabyLakeClockTest, StandardClockStandardDivisor) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = kKabyLakeStandardRawClock}); },
[&]() { mmio_range_.Expect({.address = kPpDivisor, .value = kKabyLakeStandardPpDivisor}); });
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(24'000'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
}
TEST_F(PchEngineKabyLakeClockTest, HalfClockStandardDivisor) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = kKabyLakeHalfRawClock}); },
[&]() { mmio_range_.Expect({.address = kPpDivisor, .value = kKabyLakeStandardPpDivisor}); });
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(12'000'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(5'000, clock_parameters.panel_power_clock_hz);
}
TEST_F(PchEngineKabyLakeClockTest, StandardClockDoubleDivisor) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = kKabyLakeStandardRawClock}); },
[&]() { mmio_range_.Expect({.address = kPpDivisor, .value = kKabyLakeDoublePpDivisor}); });
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(24'000'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(5'000, clock_parameters.panel_power_clock_hz);
}
TEST_F(PchEngineKabyLakeClockTest, Zeros) {
SetPchMmioExpectations([&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = 0}); },
[&]() { mmio_range_.Expect({.address = kPpDivisor, .value = 0}); });
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(0, clock_parameters.raw_clock_hz);
EXPECT_EQ(0, clock_parameters.panel_power_clock_hz);
}
TEST_F(PchEngineKabyLakeClockTest, Ones) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = 0x0000'03ff}); },
[&]() { mmio_range_.Expect({.address = kPpDivisor, .value = 0xffff'ff00}); });
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(1'023'000'000, clock_parameters.raw_clock_hz);
// 30 is 1,023,000,000 / (2 ** 25).
EXPECT_EQ(30, clock_parameters.panel_power_clock_hz);
}
TEST_F(PchEngineKabyLakeClockTest, SetStandardClockStandardDivisor) {
SetPchMmioExpectations([&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = 0}); },
[&]() { mmio_range_.Expect({.address = kPpDivisor, .value = 0}); });
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock, .write = true},
{.address = kPpDivisor, .value = kKabyLakeStandardPpDivisor, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetClockParameters(PchClockParameters{
.raw_clock_hz = 24'000'000,
.panel_power_clock_hz = 10'000,
});
}
TEST_F(PchEngineKabyLakeClockTest, SetStandardClockStandardDivisorNoChange) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = kKabyLakeStandardRawClock}); },
[&]() { mmio_range_.Expect({.address = kPpDivisor, .value = kKabyLakeStandardPpDivisor}); });
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetClockParameters(PchClockParameters{
.raw_clock_hz = 24'000'000,
.panel_power_clock_hz = 10'000,
});
}
TEST_F(PchEngineKabyLakeClockTest, SetHalfClockDoubleDivisor) {
SetPchMmioExpectations([&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = 0}); },
[&]() { mmio_range_.Expect({.address = kPpDivisor, .value = 0}); });
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kRawClkOffset, .value = kKabyLakeHalfRawClock, .write = true},
{.address = kPpDivisor, .value = kKabyLakeDoublePpDivisor, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetClockParameters(PchClockParameters{
.raw_clock_hz = 12'000'000,
.panel_power_clock_hz = 2'500,
});
}
TEST_F(PchEngineKabyLakeClockTest, SetRawClockOverflow) {
SetPchMmioExpectations([&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = 0}); },
[&]() { mmio_range_.Expect({.address = kPpDivisor, .value = 0}); });
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kRawClkOffset, .value = 0x0000'03ff, .write = true},
{.address = kPpDivisor, .value = 0xffff'ff00, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetClockParameters(PchClockParameters{
.raw_clock_hz = 0x7fff'ffff,
.panel_power_clock_hz = 1,
});
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(1'023'000'000, clock_parameters.raw_clock_hz);
// 30 is 1,023,000,000 / (2 ** 25).
EXPECT_EQ(30, clock_parameters.panel_power_clock_hz);
}
TEST_F(PchEngineKabyLakeClockTest, SetDivisorUnderflow) {
SetPchMmioExpectations([&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = 0}); },
[&]() { mmio_range_.Expect({.address = kPpDivisor, .value = 0}); });
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kRawClkOffset, .value = 0x0000'0001, .write = true},
{.address = kPpDivisor, .value = 0x0000'0100, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetClockParameters(PchClockParameters{
.raw_clock_hz = 1'000'000,
.panel_power_clock_hz = 500'000,
});
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(1'000'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(250'000, clock_parameters.panel_power_clock_hz);
}
TEST_F(PchEngineKabyLakeClockTest, FixClockParameters) {
SetPchMmioExpectations([&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = 0}); },
[&]() { mmio_range_.Expect({.address = kPpDivisor, .value = 0}); });
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(0, clock_parameters.raw_clock_hz);
EXPECT_EQ(0, clock_parameters.panel_power_clock_hz);
pch_engine.FixClockParameters(clock_parameters);
EXPECT_EQ(24'000'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
}
class PchEngineTigerLakeClockParametersTest : public PchEngineTest {
public:
// Set up expectations for registers except for RAWCLK_FREQ.
template <typename Lambda>
void SetPchMmioExpectations(Lambda raw_clock_expectations) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
}));
raw_clock_expectations();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpControlOffset, .value = 0},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmFreqOffset, .value = 0},
{.address = kSblcPwmDutyOffset, .value = 0},
}));
}
};
TEST_F(PchEngineTigerLakeClockParametersTest, StandardClock) {
SetPchMmioExpectations([&]() {
mmio_range_.Expect({.address = kRawClkOffset, .value = kTigerLakeStandardRawClock});
});
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(19'200'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
}
TEST_F(PchEngineTigerLakeClockParametersTest, AlternateClock) {
SetPchMmioExpectations([&]() {
mmio_range_.Expect({.address = kRawClkOffset, .value = kTigerLakeAlternateRawClock});
});
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(24'000'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
}
TEST_F(PchEngineTigerLakeClockParametersTest, Dg1StandardClock) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = kDg1StandardRawClock}); });
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(38'400'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
}
TEST_F(PchEngineTigerLakeClockParametersTest, Zeros) {
SetPchMmioExpectations([&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = 0}); });
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(1'000'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
}
TEST_F(PchEngineTigerLakeClockParametersTest, Ones) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = 0xffff'ffff}); });
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
// Integer part = 1024 (1023 + 1), numerator = 7, denominator = 16 (15 + 1).
EXPECT_EQ(1'024'437'500, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
}
TEST_F(PchEngineTigerLakeClockParametersTest, SetStandardClock) {
SetPchMmioExpectations([&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = 0}); });
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kRawClkOffset, .value = kTigerLakeStandardRawClock, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
pch_engine.SetClockParameters(PchClockParameters{
.raw_clock_hz = 19'200'000,
.panel_power_clock_hz = 10'000,
});
}
TEST_F(PchEngineTigerLakeClockParametersTest, SetStandardClockNoChange) {
SetPchMmioExpectations([&]() {
mmio_range_.Expect({.address = kRawClkOffset, .value = kTigerLakeStandardRawClock});
});
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
pch_engine.SetClockParameters(PchClockParameters{
.raw_clock_hz = 19'200'000,
.panel_power_clock_hz = 10'000,
});
}
TEST_F(PchEngineTigerLakeClockParametersTest, SetAlternateClock) {
SetPchMmioExpectations([&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = 0}); });
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kRawClkOffset, .value = kTigerLakeAlternateRawClock, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
pch_engine.SetClockParameters(PchClockParameters{
.raw_clock_hz = 24'000'000,
.panel_power_clock_hz = 10'000,
});
}
TEST_F(PchEngineTigerLakeClockParametersTest, SetDg1StandardClock) {
SetPchMmioExpectations([&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = 0}); });
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kRawClkOffset, .value = kDg1StandardRawClock, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
pch_engine.SetClockParameters(PchClockParameters{
.raw_clock_hz = 38'400'000,
.panel_power_clock_hz = 10'000,
});
}
TEST_F(PchEngineTigerLakeClockParametersTest, SetOverflow) {
SetPchMmioExpectations([&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = 0}); });
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kRawClkOffset, .value = 0x1fff'3800, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
pch_engine.SetClockParameters(PchClockParameters{
// Maximum 31-bit integer value congruent to 999,999 modulo 1,000,000.
.raw_clock_hz = 0x7ff89ebf,
.panel_power_clock_hz = 10'000,
});
const PchClockParameters clock_parameters = pch_engine.ClockParameters();
// Integer = 1024 (1023 + 1), numerator = 7, denominator = 8 (7 + 1).
EXPECT_EQ(1'024'875'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
}
TEST_F(PchEngineTigerLakeClockParametersTest, FixClockParametersToStandardRawClock) {
SetPchMmioExpectations([&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = 0}); });
mmio_range_.Expect({.address = kSFuseStrapOffset, .value = 0});
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(1'000'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
pch_engine.FixClockParameters(clock_parameters);
EXPECT_EQ(19'200'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
}
TEST_F(PchEngineTigerLakeClockParametersTest, FixClockParametersToAlternateRawClock) {
SetPchMmioExpectations([&]() { mmio_range_.Expect({.address = kRawClkOffset, .value = 0}); });
mmio_range_.Expect({.address = kSFuseStrapOffset, .value = 0x0000'0100});
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
PchClockParameters clock_parameters = pch_engine.ClockParameters();
EXPECT_EQ(1'000'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
pch_engine.FixClockParameters(clock_parameters);
EXPECT_EQ(24'000'000, clock_parameters.raw_clock_hz);
EXPECT_EQ(10'000, clock_parameters.panel_power_clock_hz);
}
class PchEnginePanelPowerTargetTest : public PchEngineTest {
public:
// Set up expectations for registers except for PP_CONTROL and SBLC_PWM_CTL1.
template <typename Lambda1, typename Lambda2>
void SetPchMmioExpectations(Lambda1 power_control_expectations,
Lambda2 backlight_control_expectations) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
}));
power_control_expectations();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kPpDivisor, .value = kKabyLakeStandardPpDivisor},
}));
backlight_control_expectations();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmCtl2Offset, .value = 0},
}));
}
};
TEST_F(PchEnginePanelPowerTargetTest, GenericAllFlagsOff) {
SetPchMmioExpectations(
// The bits around the control flags are on to catch bit mapping errors.
[&]() { mmio_range_.Expect({.address = kPpControlOffset, .value = 0xf2}); },
[&]() { mmio_range_.Expect({.address = kSblcPwmCtl1Offset, .value = 0}); });
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
const PchPanelPowerTarget power_target = pch_engine.PanelPowerTarget();
EXPECT_EQ(false, power_target.power_on);
EXPECT_EQ(false, power_target.backlight_on);
EXPECT_EQ(false, power_target.force_power_on);
EXPECT_EQ(false, power_target.brightness_pwm_counter_on);
}
TEST_F(PchEnginePanelPowerTargetTest, GenericPowerOn) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kPpControlOffset, .value = 0x01}); },
[&]() { mmio_range_.Expect({.address = kSblcPwmCtl1Offset, .value = 0}); });
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
const PchPanelPowerTarget power_target = pch_engine.PanelPowerTarget();
EXPECT_EQ(true, power_target.power_on);
EXPECT_EQ(false, power_target.backlight_on);
EXPECT_EQ(false, power_target.force_power_on);
EXPECT_EQ(false, power_target.brightness_pwm_counter_on);
}
TEST_F(PchEnginePanelPowerTargetTest, GenericBacklightOn) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kPpControlOffset, .value = 0x04}); },
[&]() { mmio_range_.Expect({.address = kSblcPwmCtl1Offset, .value = 0}); });
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
const PchPanelPowerTarget power_target = pch_engine.PanelPowerTarget();
EXPECT_EQ(false, power_target.power_on);
EXPECT_EQ(true, power_target.backlight_on);
EXPECT_EQ(false, power_target.force_power_on);
EXPECT_EQ(false, power_target.brightness_pwm_counter_on);
}
TEST_F(PchEnginePanelPowerTargetTest, GenericForcePowerOn) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kPpControlOffset, .value = 0x08}); },
[&]() { mmio_range_.Expect({.address = kSblcPwmCtl1Offset, .value = 0}); });
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
const PchPanelPowerTarget power_target = pch_engine.PanelPowerTarget();
EXPECT_EQ(false, power_target.power_on);
EXPECT_EQ(false, power_target.backlight_on);
EXPECT_EQ(true, power_target.force_power_on);
EXPECT_EQ(false, power_target.brightness_pwm_counter_on);
}
TEST_F(PchEnginePanelPowerTargetTest, GenericBrightnessPwmCounterOn) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kPpControlOffset, .value = 0}); },
[&]() { mmio_range_.Expect({.address = kSblcPwmCtl1Offset, .value = 0x8000'0000}); });
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
const PchPanelPowerTarget power_target = pch_engine.PanelPowerTarget();
EXPECT_EQ(false, power_target.power_on);
EXPECT_EQ(false, power_target.backlight_on);
EXPECT_EQ(false, power_target.force_power_on);
EXPECT_EQ(true, power_target.brightness_pwm_counter_on);
}
TEST_F(PchEnginePanelPowerTargetTest, GenericSetPowerOn) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kPpControlOffset, .value = 0x00}); },
[&]() { mmio_range_.Expect({.address = kSblcPwmCtl1Offset, .value = 0}); });
mmio_range_.Expect({.address = kPpControlOffset, .value = 0x01, .write = true});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelPowerTarget(PchPanelPowerTarget{
.power_on = true,
.backlight_on = false,
.force_power_on = false,
.brightness_pwm_counter_on = false,
});
}
TEST_F(PchEnginePanelPowerTargetTest, GenericSetPowerOff) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kPpControlOffset, .value = 0x01}); },
[&]() { mmio_range_.Expect({.address = kSblcPwmCtl1Offset, .value = 0}); });
mmio_range_.Expect({.address = kPpControlOffset, .value = 0x00, .write = true});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelPowerTarget(PchPanelPowerTarget{
.power_on = false,
.backlight_on = false,
.force_power_on = false,
.brightness_pwm_counter_on = false,
});
}
TEST_F(PchEnginePanelPowerTargetTest, GenericSetPowerOnFromForcePowerOn) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kPpControlOffset, .value = 0x08}); },
[&]() { mmio_range_.Expect({.address = kSblcPwmCtl1Offset, .value = 0}); });
mmio_range_.Expect({.address = kPpControlOffset, .value = 0x01, .write = true});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelPowerTarget(PchPanelPowerTarget{
.power_on = true,
.backlight_on = false,
.force_power_on = false,
.brightness_pwm_counter_on = false,
});
}
TEST_F(PchEnginePanelPowerTargetTest, GenericSetPowerOnNoChange) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kPpControlOffset, .value = 0x01}); },
[&]() { mmio_range_.Expect({.address = kSblcPwmCtl1Offset, .value = 0}); });
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelPowerTarget(PchPanelPowerTarget{
.power_on = true,
.backlight_on = false,
.force_power_on = false,
.brightness_pwm_counter_on = false,
});
}
TEST_F(PchEnginePanelPowerTargetTest, GenericSetBacklightOnFromPowerOn) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kPpControlOffset, .value = 0x01}); },
[&]() { mmio_range_.Expect({.address = kSblcPwmCtl1Offset, .value = 0}); });
mmio_range_.Expect({.address = kPpControlOffset, .value = 0x05, .write = true});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelPowerTarget(PchPanelPowerTarget{
.power_on = true,
.backlight_on = true,
.force_power_on = false,
.brightness_pwm_counter_on = false,
});
}
TEST_F(PchEnginePanelPowerTargetTest, GenericSetBacklightOnBrightnessPwmOnFromPowerOn) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kPpControlOffset, .value = 0x01}); },
[&]() { mmio_range_.Expect({.address = kSblcPwmCtl1Offset, .value = 0}); });
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpControlOffset, .value = 0x05, .write = true},
{.address = kSblcPwmCtl1Offset, .value = 0x8000'0000, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelPowerTarget(PchPanelPowerTarget{
.power_on = true,
.backlight_on = true,
.force_power_on = false,
.brightness_pwm_counter_on = true,
});
}
TEST_F(PchEnginePanelPowerTargetTest, GenericSetPowerOnBacklightOnBrightnessPwmOnNoChange) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kPpControlOffset, .value = 0x05}); },
[&]() { mmio_range_.Expect({.address = kSblcPwmCtl1Offset, .value = 0x8000'0000}); });
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelPowerTarget(PchPanelPowerTarget{
.power_on = true,
.backlight_on = true,
.force_power_on = false,
.brightness_pwm_counter_on = true,
});
}
TEST_F(PchEnginePanelPowerTargetTest, GenericSetPowerOnBacklightOn) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kPpControlOffset, .value = 0x00}); },
[&]() { mmio_range_.Expect({.address = kSblcPwmCtl1Offset, .value = 0}); });
mmio_range_.Expect({.address = kPpControlOffset, .value = 0x05, .write = true});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelPowerTarget(PchPanelPowerTarget{
.power_on = true,
.backlight_on = true,
.force_power_on = false,
.brightness_pwm_counter_on = false,
});
}
TEST_F(PchEnginePanelPowerTargetTest, GenericSetBacklightOff) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kPpControlOffset, .value = 0x05}); },
[&]() { mmio_range_.Expect({.address = kSblcPwmCtl1Offset, .value = 0}); });
mmio_range_.Expect({.address = kPpControlOffset, .value = 0x01, .write = true});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelPowerTarget(PchPanelPowerTarget{
.power_on = true,
.backlight_on = false,
.force_power_on = false,
.brightness_pwm_counter_on = false,
});
}
TEST_F(PchEnginePanelPowerTargetTest, GenericSetBacklightOffBrightnessPwmOff) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kPpControlOffset, .value = 0x05}); },
[&]() { mmio_range_.Expect({.address = kSblcPwmCtl1Offset, .value = 0xa000'0000}); });
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpControlOffset, .value = 0x01, .write = true},
{.address = kSblcPwmCtl1Offset, .value = 0x2000'0000, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelPowerTarget(PchPanelPowerTarget{
.power_on = true,
.backlight_on = false,
.force_power_on = false,
.brightness_pwm_counter_on = false,
});
}
TEST_F(PchEnginePanelPowerTargetTest, GenericSetForcePowerOff) {
SetPchMmioExpectations(
[&]() { mmio_range_.Expect({.address = kPpControlOffset, .value = 0x08}); },
[&]() { mmio_range_.Expect({.address = kSblcPwmCtl1Offset, .value = 0}); });
mmio_range_.Expect({.address = kPpControlOffset, .value = 0x00, .write = true});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelPowerTarget(PchPanelPowerTarget{
.power_on = false,
.backlight_on = false,
.force_power_on = false,
.brightness_pwm_counter_on = false,
});
}
TEST_F(PchEngineTest, KabyLakePanelParameters) {
// The parameters are inspired from the eDP and SPWG standards, but are
// tweaked so each delay is unique. This is intended to help catch bugs where
// values are incorrectly mapped to register fields.
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
// The bits around `power_down_on_reset` are set, to catch mapping errors.
{.address = kPpControlOffset, .value = 0x05},
{.address = kPpOnDelays, .value = 0x0384'0a28},
{.address = kPpOffDelays, .value = 0x1388'07d0},
{.address = kPpDivisor, .value = 0x0004'af0a},
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmCtl2Offset, .value = 0x05dc'0000},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
const PchPanelParameters panel_parameters = pch_engine.PanelParameters();
EXPECT_EQ(90'000, panel_parameters.power_on_to_hpd_aux_ready_delay_micros);
EXPECT_EQ(260'000, panel_parameters.power_on_to_backlight_on_delay_micros);
EXPECT_EQ(200'000, panel_parameters.backlight_off_to_video_end_delay_micros);
EXPECT_EQ(500'000, panel_parameters.video_end_to_power_off_delay_micros);
EXPECT_EQ(900'000, panel_parameters.power_cycle_delay_micros);
EXPECT_EQ(1'000, panel_parameters.backlight_pwm_frequency_hz);
EXPECT_EQ(false, panel_parameters.power_down_on_reset);
EXPECT_EQ(false, panel_parameters.backlight_pwm_inverted);
}
TEST_F(PchEngineTest, KabyLakePanelParametersPowerDownOnResetEnabled) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
// Only `power_down_on_reset` is set, to catch mapping errors.
{.address = kPpControlOffset, .value = 0x02},
{.address = kPpOnDelays, .value = 0x0384'0a28},
{.address = kPpOffDelays, .value = 0x1388'07d0},
{.address = kPpDivisor, .value = 0x0004'af0a},
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmCtl2Offset, .value = 0x05dc'0000},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
const PchPanelParameters panel_parameters = pch_engine.PanelParameters();
EXPECT_EQ(90'000, panel_parameters.power_on_to_hpd_aux_ready_delay_micros);
EXPECT_EQ(260'000, panel_parameters.power_on_to_backlight_on_delay_micros);
EXPECT_EQ(200'000, panel_parameters.backlight_off_to_video_end_delay_micros);
EXPECT_EQ(500'000, panel_parameters.video_end_to_power_off_delay_micros);
EXPECT_EQ(900'000, panel_parameters.power_cycle_delay_micros);
EXPECT_EQ(1'000, panel_parameters.backlight_pwm_frequency_hz);
EXPECT_EQ(true, panel_parameters.power_down_on_reset);
EXPECT_EQ(false, panel_parameters.backlight_pwm_inverted);
}
TEST_F(PchEngineTest, KabyLakePanelParametersBacklightPwmInverted) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
{.address = kPpControlOffset, .value = 0x05},
{.address = kPpOnDelays, .value = 0x0384'0a28},
{.address = kPpOffDelays, .value = 0x1388'07d0},
{.address = kPpDivisor, .value = 0x0004'af0a},
// Only `backlight_pwm_inverted` is set, to catch mapping errors.
{.address = kSblcPwmCtl1Offset, .value = 0x2000'0000},
{.address = kSblcPwmCtl2Offset, .value = 0x05dc'0000},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
const PchPanelParameters panel_parameters = pch_engine.PanelParameters();
EXPECT_EQ(90'000, panel_parameters.power_on_to_hpd_aux_ready_delay_micros);
EXPECT_EQ(260'000, panel_parameters.power_on_to_backlight_on_delay_micros);
EXPECT_EQ(200'000, panel_parameters.backlight_off_to_video_end_delay_micros);
EXPECT_EQ(500'000, panel_parameters.video_end_to_power_off_delay_micros);
EXPECT_EQ(900'000, panel_parameters.power_cycle_delay_micros);
EXPECT_EQ(1'000, panel_parameters.backlight_pwm_frequency_hz);
EXPECT_EQ(false, panel_parameters.power_down_on_reset);
EXPECT_EQ(true, panel_parameters.backlight_pwm_inverted);
}
TEST_F(PchEngineTest, TigerLakePanelParametersStandardRawClock) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kTigerLakeStandardRawClock},
// The bits around `power_down_on_reset` are set, to catch mapping errors.
{.address = kPpControlOffset, .value = 0xc5},
{.address = kPpOnDelays, .value = 0x0384'0a28},
{.address = kPpOffDelays, .value = 0x1388'07d0},
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmFreqOffset, .value = 0x4b00},
{.address = kSblcPwmDutyOffset, .value = 0},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
const PchPanelParameters panel_parameters = pch_engine.PanelParameters();
EXPECT_EQ(90'000, panel_parameters.power_on_to_hpd_aux_ready_delay_micros);
EXPECT_EQ(260'000, panel_parameters.power_on_to_backlight_on_delay_micros);
EXPECT_EQ(200'000, panel_parameters.backlight_off_to_video_end_delay_micros);
EXPECT_EQ(500'000, panel_parameters.video_end_to_power_off_delay_micros);
EXPECT_EQ(1'100'000, panel_parameters.power_cycle_delay_micros);
EXPECT_EQ(1'000, panel_parameters.backlight_pwm_frequency_hz);
EXPECT_EQ(false, panel_parameters.power_down_on_reset);
EXPECT_EQ(false, panel_parameters.backlight_pwm_inverted);
}
TEST_F(PchEngineTest, TigerLakePanelParametersAlternateRawClock) {
// The parameters are inspired from the eDP and SPWG standards, but are
// tweaked so each delay is unique. This is intended to help catch bugs where
// values are incorrectly mapped to register fields.
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
// The different raw clock value must not influence how we interpret the
// fields in the PP_* registers. The delays there are all relative to the
// panel power sequencing clock, which is fixed to 10 KHz on Tiger Lake.
//
// On the other hand, the differences should impact the SBLC_* registers,
// which are relative to the raw clock.
{.address = kRawClkOffset, .value = kTigerLakeAlternateRawClock},
// The bits around `power_down_on_reset` are set, to catch mapping errors.
{.address = kPpControlOffset, .value = 0xc5},
{.address = kPpOnDelays, .value = 0x0384'0a28},
{.address = kPpOffDelays, .value = 0x1388'07d0},
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmFreqOffset, .value = 0x5dc0},
{.address = kSblcPwmDutyOffset, .value = 0},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
const PchPanelParameters panel_parameters = pch_engine.PanelParameters();
EXPECT_EQ(90'000, panel_parameters.power_on_to_hpd_aux_ready_delay_micros);
EXPECT_EQ(260'000, panel_parameters.power_on_to_backlight_on_delay_micros);
EXPECT_EQ(200'000, panel_parameters.backlight_off_to_video_end_delay_micros);
EXPECT_EQ(500'000, panel_parameters.video_end_to_power_off_delay_micros);
EXPECT_EQ(1'100'000, panel_parameters.power_cycle_delay_micros);
EXPECT_EQ(1'000, panel_parameters.backlight_pwm_frequency_hz);
EXPECT_EQ(false, panel_parameters.power_down_on_reset);
EXPECT_EQ(false, panel_parameters.backlight_pwm_inverted);
}
TEST_F(PchEngineTest, KabyLakeSetPanelParameters) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
{.address = kPpControlOffset, .value = 0},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kPpDivisor, .value = 0x0004'af00},
{.address = kSblcPwmCtl1Offset, .value = 0x2000'0000},
{.address = kSblcPwmCtl2Offset, .value = 0},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpOnDelays, .value = 0x0384'0a28, .write = true},
{.address = kPpOffDelays, .value = 0x1388'07d0, .write = true},
{.address = kPpDivisor, .value = 0x0004'af0a, .write = true},
{.address = kPpControlOffset, .value = 0x02, .write = true},
{.address = kSblcPwmCtl2Offset, .value = 0x05dc'0000, .write = true},
{.address = kSblcPwmCtl1Offset, .value = 0x0000'0000, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
// The parameters are inspired from the eDP and SPWG standards, but are
// tweaked so each delay is unique. This is intended to help catch bugs where
// values are incorrectly mapped to register fields.
pch_engine.SetPanelParameters(PchPanelParameters{
.power_on_to_hpd_aux_ready_delay_micros = 90'000, // eDP T1+T3 max
.power_on_to_backlight_on_delay_micros = 260'000, // SPWG T1+T2+T5 max/min
.backlight_off_to_video_end_delay_micros = 200'000, // SPWG T6 min
.video_end_to_power_off_delay_micros = 500'000, // eDP T10 max
.power_cycle_delay_micros = 900'000,
.backlight_pwm_frequency_hz = 1'000,
.power_down_on_reset = true,
.backlight_pwm_inverted = false,
});
}
TEST_F(PchEngineTest, KabyLakeSetPanelParametersWhileBacklightPwmIsOn) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
{.address = kPpControlOffset, .value = 0},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kPpDivisor, .value = 0x0004'af00},
{.address = kSblcPwmCtl1Offset, .value = 0xa000'0000},
{.address = kSblcPwmCtl2Offset, .value = 0},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpOnDelays, .value = 0x0384'0a28, .write = true},
{.address = kPpOffDelays, .value = 0x1388'07d0, .write = true},
{.address = kPpDivisor, .value = 0x0004'af0a, .write = true},
{.address = kPpControlOffset, .value = 0x02, .write = true},
// The backlight PWM must be disabled before changing its frequency.
{.address = kSblcPwmCtl1Offset, .value = 0x2000'0000, .write = true},
{.address = kSblcPwmCtl2Offset, .value = 0x05dc'0000, .write = true},
{.address = kSblcPwmCtl1Offset, .value = 0x8000'0000, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
// Parameters from PchEngineTest.KabyLakeSetPanelParameters above.
pch_engine.SetPanelParameters(PchPanelParameters{
.power_on_to_hpd_aux_ready_delay_micros = 90'000, // eDP T1+T3 max
.power_on_to_backlight_on_delay_micros = 260'000, // SPWG T1+T2+T5 max/min
.backlight_off_to_video_end_delay_micros = 200'000, // SPWG T6 min
.video_end_to_power_off_delay_micros = 500'000, // eDP T10 max
.power_cycle_delay_micros = 900'000,
.backlight_pwm_frequency_hz = 1'000,
.power_down_on_reset = true,
.backlight_pwm_inverted = false,
});
}
// PCH panel parameters that should result in all-zero register fields.
//
// These values check for underflows in value handling. For example, setting the
// power cycle delay involves a subtraction, which is subject to underflow.
constexpr PchPanelParameters kPanelParametersZeros = {
.power_on_to_hpd_aux_ready_delay_micros = 0,
.power_on_to_backlight_on_delay_micros = 0,
.backlight_off_to_video_end_delay_micros = 0,
.video_end_to_power_off_delay_micros = 0,
.power_cycle_delay_micros = 0,
.backlight_pwm_frequency_hz = 0x7fff'ffff,
.power_down_on_reset = false,
.backlight_pwm_inverted = false,
};
class PchEngineKabyLakeSetPanelParametersZerosTest : public PchEngineTest {
public:
void SetUp() override {
PchEngineTest::SetUp();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
{.address = kPpControlOffset, .value = 0x0a},
{.address = kPpOnDelays, .value = 0x0001'0001},
{.address = kPpOffDelays, .value = 0x0001'0001},
{.address = kPpDivisor, .value = 0x0004'af06},
{.address = kSblcPwmCtl1Offset, .value = 0x2000'0000},
{.address = kSblcPwmCtl2Offset, .value = 0x0000'ffff},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpOnDelays, .value = 0, .write = true},
{.address = kPpOffDelays, .value = 0, .write = true},
{.address = kPpDivisor, .value = 0x0004'af01, .write = true},
{.address = kPpControlOffset, .value = 0x08, .write = true},
{.address = kSblcPwmCtl2Offset, .value = 0x0001'0000, .write = true},
{.address = kSblcPwmCtl1Offset, .value = 0x0000'0000, .write = true},
}));
}
};
TEST_F(PchEngineKabyLakeSetPanelParametersZerosTest, Once) {
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
// All the MMIO assertions are in SetUp(), because they're shared with the
// NoChange test below.
pch_engine.SetPanelParameters(kPanelParametersZeros);
}
TEST_F(PchEngineKabyLakeSetPanelParametersZerosTest, RepeatedWithNoChange) {
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelParameters(kPanelParametersZeros);
pch_engine.SetPanelParameters(kPanelParametersZeros); // No MMIO writes.
}
// PCH panel parameters with maximum values.
//
// These values check for overflows in value handling.
constexpr PchPanelParameters kPanelParametersOverflow = {
.power_on_to_hpd_aux_ready_delay_micros = 0x7fff'ffff,
.power_on_to_backlight_on_delay_micros = 0x7fff'ffff,
.backlight_off_to_video_end_delay_micros = 0x7fff'ffff,
.video_end_to_power_off_delay_micros = 0x7fff'ffff,
.power_cycle_delay_micros = 0x7fff'ffff,
.backlight_pwm_frequency_hz = 1,
.power_down_on_reset = true,
.backlight_pwm_inverted = true,
};
class PchEngineKabyLakeSetPanelParametersOverflowTest : public PchEngineTest {
public:
void SetUp() override {
PchEngineTest::SetUp();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
{.address = kPpControlOffset, .value = 0},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kPpDivisor, .value = 0x0004'af00},
{.address = kSblcPwmCtl1Offset, .value = 0},
// These values check for overflow in brightness-preserving logic.
{.address = kSblcPwmCtl2Offset, .value = 0x0001'ffff},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpOnDelays, .value = 0x1fff'1fff, .write = true},
{.address = kPpOffDelays, .value = 0x1fff'1fff, .write = true},
{.address = kPpDivisor, .value = 0x0004'af1f, .write = true},
{.address = kPpControlOffset, .value = 0x02, .write = true},
{.address = kSblcPwmCtl2Offset, .value = 0xffff'ffff, .write = true},
{.address = kSblcPwmCtl1Offset, .value = 0x2000'0000, .write = true},
}));
}
};
TEST_F(PchEngineKabyLakeSetPanelParametersOverflowTest, Once) {
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
// All the MMIO assertions are in SetUp(), because they're shared with the
// NoChange test below.
pch_engine.SetPanelParameters(kPanelParametersOverflow);
const PchPanelParameters panel_parameters = pch_engine.PanelParameters();
EXPECT_EQ(819'100, panel_parameters.power_on_to_hpd_aux_ready_delay_micros);
EXPECT_EQ(819'100, panel_parameters.power_on_to_backlight_on_delay_micros);
EXPECT_EQ(819'100, panel_parameters.backlight_off_to_video_end_delay_micros);
EXPECT_EQ(819'100, panel_parameters.video_end_to_power_off_delay_micros);
EXPECT_EQ(3'000'000, panel_parameters.power_cycle_delay_micros);
// IHD-OS-KBL-Vol 12-1.17 page 196 and IHD-OS-SKL-Vol 12-05.16 page 189.
EXPECT_EQ(23, panel_parameters.backlight_pwm_frequency_hz);
}
TEST_F(PchEngineKabyLakeSetPanelParametersOverflowTest, RepeatedWithNoChange) {
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelParameters(kPanelParametersOverflow);
pch_engine.SetPanelParameters(kPanelParametersOverflow); // No MMIO writes.
}
TEST_F(PchEngineTest, KabyLakeSetPanelParametersOnlyPowerDownOnReset) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
{.address = kPpControlOffset, .value = 0},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kPpDivisor, .value = 0x0004'af01},
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmCtl2Offset, .value = 0x0001'0000},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpControlOffset, .value = 0x02, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelParameters(PchPanelParameters{
.power_on_to_hpd_aux_ready_delay_micros = 0,
.power_on_to_backlight_on_delay_micros = 0,
.backlight_off_to_video_end_delay_micros = 0,
.video_end_to_power_off_delay_micros = 0,
.power_cycle_delay_micros = 0,
.backlight_pwm_frequency_hz = 0x7fff'ffff,
.power_down_on_reset = true,
.backlight_pwm_inverted = false,
});
}
TEST_F(PchEngineTest, KabyLakeSetPanelParametersOnlyBacklightPwmInverted) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
{.address = kPpControlOffset, .value = 0},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kPpDivisor, .value = 0x0004'af01},
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmCtl2Offset, .value = 0x0001'0000},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmCtl1Offset, .value = 0x2000'0000, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelParameters(PchPanelParameters{
.power_on_to_hpd_aux_ready_delay_micros = 0,
.power_on_to_backlight_on_delay_micros = 0,
.backlight_off_to_video_end_delay_micros = 0,
.video_end_to_power_off_delay_micros = 0,
.power_cycle_delay_micros = 0,
.backlight_pwm_frequency_hz = 0x7fff'ffff,
.power_down_on_reset = false,
.backlight_pwm_inverted = true,
});
}
TEST_F(PchEngineTest, TigerLakeSetPanelParameters) {
// PP_CONTROL is non-zero to check that control bits are mixed correctly with
// the delay field.
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kTigerLakeStandardRawClock},
{.address = kPpControlOffset, .value = 0x08},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kSblcPwmCtl1Offset, .value = 0x2000'0000},
{.address = kSblcPwmFreqOffset, .value = 0},
{.address = kSblcPwmDutyOffset, .value = 0x0001},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpOnDelays, .value = 0x0384'0a28, .write = true},
{.address = kPpOffDelays, .value = 0x1388'07d0, .write = true},
{.address = kPpControlOffset, .value = 0x8a, .write = true},
{.address = kSblcPwmFreqOffset, .value = 0x4b00, .write = true},
{.address = kSblcPwmDutyOffset, .value = 0, .write = true},
{.address = kSblcPwmCtl1Offset, .value = 0, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
// The parameters are inspired from the eDP and SPWG standards, but are
// tweaked so each delay is unique. This is intended to help catch bugs where
// values are incorrectly mapped to register fields.
pch_engine.SetPanelParameters(PchPanelParameters{
.power_on_to_hpd_aux_ready_delay_micros = 90'000, // eDP T1+T3 max
.power_on_to_backlight_on_delay_micros = 260'000, // SPWG T1+T2+T5 max/min
.backlight_off_to_video_end_delay_micros = 200'000, // SPWG T6 min
.video_end_to_power_off_delay_micros = 500'000, // eDP T10 max
.power_cycle_delay_micros = 700'000,
.backlight_pwm_frequency_hz = 1'000,
.power_down_on_reset = true,
.backlight_pwm_inverted = false,
});
}
TEST_F(PchEngineTest, TigerLakeSetPanelParametersWhileBacklightPwmIsOn) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kTigerLakeStandardRawClock},
{.address = kPpControlOffset, .value = 0x08},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kSblcPwmCtl1Offset, .value = 0xa000'0000},
{.address = kSblcPwmFreqOffset, .value = 0},
{.address = kSblcPwmDutyOffset, .value = 0x0001},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpOnDelays, .value = 0x0384'0a28, .write = true},
{.address = kPpOffDelays, .value = 0x1388'07d0, .write = true},
{.address = kPpControlOffset, .value = 0x8a, .write = true},
// The backlight PWM must be disabled before changing its frequency.
{.address = kSblcPwmCtl1Offset, .value = 0x2000'0000, .write = true},
{.address = kSblcPwmFreqOffset, .value = 0x4b00, .write = true},
{.address = kSblcPwmDutyOffset, .value = 0, .write = true},
{.address = kSblcPwmCtl1Offset, .value = 0x8000'0000, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
// Parameters from PchEngineTest.TigerLakeSetPanelParameters above.
pch_engine.SetPanelParameters(PchPanelParameters{
.power_on_to_hpd_aux_ready_delay_micros = 90'000, // eDP T1+T3 max
.power_on_to_backlight_on_delay_micros = 260'000, // SPWG T1+T2+T5 max/min
.backlight_off_to_video_end_delay_micros = 200'000, // SPWG T6 min
.video_end_to_power_off_delay_micros = 500'000, // eDP T10 max
.power_cycle_delay_micros = 700'000,
.backlight_pwm_frequency_hz = 1'000,
.power_down_on_reset = true,
.backlight_pwm_inverted = false,
});
}
TEST_F(PchEngineTest, TigerLakeSetPanelParametersAlternateRawClock) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
// The different raw clock value must not influence how we interpret the
// fields in the PP_* registers. The delays there are all relative to the
// panel power sequencing clock, which is fixed to 10 KHz on Tiger Lake.
//
// On the other hand, the differences should impact the SBLC_* registers,
// which are relative to the raw clock.
{.address = kRawClkOffset, .value = kTigerLakeAlternateRawClock},
{.address = kPpControlOffset, .value = 0x08},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kSblcPwmCtl1Offset, .value = 0x2000'0000},
{.address = kSblcPwmFreqOffset, .value = 0},
{.address = kSblcPwmDutyOffset, .value = 0x0001},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpOnDelays, .value = 0x0384'0a28, .write = true},
{.address = kPpOffDelays, .value = 0x1388'07d0, .write = true},
{.address = kPpControlOffset, .value = 0x8a, .write = true},
{.address = kSblcPwmFreqOffset, .value = 0x5dc0, .write = true},
{.address = kSblcPwmDutyOffset, .value = 0, .write = true},
{.address = kSblcPwmCtl1Offset, .value = 0, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
pch_engine.SetPanelParameters(PchPanelParameters{
.power_on_to_hpd_aux_ready_delay_micros = 90'000, // eDP T1+T3 max
.power_on_to_backlight_on_delay_micros = 260'000, // SPWG T1+T2+T5 max/min
.backlight_off_to_video_end_delay_micros = 200'000, // SPWG T6 min
.video_end_to_power_off_delay_micros = 500'000, // eDP T10 max
.power_cycle_delay_micros = 700'000,
.backlight_pwm_frequency_hz = 1'000,
.power_down_on_reset = true,
.backlight_pwm_inverted = false,
});
}
class PchEngineTestTigerLakeSetPanelParametersZerosTest : public PchEngineTest {
public:
void SetUp() override {
PchEngineTest::SetUp();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
{.address = kPpControlOffset, .value = 0x4a},
{.address = kPpOnDelays, .value = 0x0001'0001},
{.address = kPpOffDelays, .value = 0x0001'0001},
{.address = kSblcPwmCtl1Offset, .value = 0x2000'0000},
// PWM duty cycle > frequency divider to check that we don't write a
// frequency that's smaller than the current Duty Cycle.
{.address = kSblcPwmFreqOffset, .value = 0},
{.address = kSblcPwmDutyOffset, .value = 0xffff'ffff},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpOnDelays, .value = 0, .write = true},
{.address = kPpOffDelays, .value = 0, .write = true},
{.address = kPpControlOffset, .value = 0x18, .write = true},
{.address = kSblcPwmFreqOffset, .value = 0x0000'0001, .write = true},
{.address = kSblcPwmDutyOffset, .value = 0, .write = true},
{.address = kSblcPwmCtl1Offset, .value = 0, .write = true},
}));
}
};
TEST_F(PchEngineTestTigerLakeSetPanelParametersZerosTest, Once) {
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
// All the MMIO assertions are in SetUp(), because they're shared with the
// NoChange test below.
pch_engine.SetPanelParameters(kPanelParametersZeros);
}
TEST_F(PchEngineTestTigerLakeSetPanelParametersZerosTest, RepeatedWithNoChange) {
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
pch_engine.SetPanelParameters(kPanelParametersZeros);
pch_engine.SetPanelParameters(kPanelParametersZeros); // No MMIO writes.
}
class PchEngineTigerLakeSetPanelParametersOverflowTest : public PchEngineTest {
public:
void SetUp() override {
PchEngineTest::SetUp();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kTigerLakeMaxRawClock},
{.address = kPpControlOffset, .value = 0x08},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kSblcPwmCtl1Offset, .value = 0},
// The frequency divider must be non-zero to get a non-zero brightness.
{.address = kSblcPwmFreqOffset, .value = 0x0000'0001},
// The maximum duty cycle value tests the brightness clamping logic.
{.address = kSblcPwmDutyOffset, .value = 0xffff'ffff},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpOnDelays, .value = 0x1fff'1fff, .write = true},
{.address = kPpOffDelays, .value = 0x1fff'1fff, .write = true},
{.address = kPpControlOffset, .value = 0x01fa, .write = true},
{.address = kSblcPwmFreqOffset, .value = 0x3d73'cfc0, .write = true},
{.address = kSblcPwmDutyOffset, .value = 0x3d73'cfc0, .write = true},
{.address = kSblcPwmCtl1Offset, .value = 0x2000'0000, .write = true},
}));
}
};
TEST_F(PchEngineTigerLakeSetPanelParametersOverflowTest, Once) {
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
// All the MMIO assertions are in SetUp(), because they're shared with the
// NoChange test below.
pch_engine.SetPanelParameters(kPanelParametersOverflow);
const PchPanelParameters panel_parameters = pch_engine.PanelParameters();
EXPECT_EQ(819'100, panel_parameters.power_on_to_hpd_aux_ready_delay_micros);
EXPECT_EQ(819'100, panel_parameters.power_on_to_backlight_on_delay_micros);
EXPECT_EQ(819'100, panel_parameters.backlight_off_to_video_end_delay_micros);
EXPECT_EQ(819'100, panel_parameters.video_end_to_power_off_delay_micros);
EXPECT_EQ(3'000'000, panel_parameters.power_cycle_delay_micros);
EXPECT_EQ(1, panel_parameters.backlight_pwm_frequency_hz);
}
TEST_F(PchEngineTigerLakeSetPanelParametersOverflowTest, RepeatedWithNoChange) {
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
pch_engine.SetPanelParameters(kPanelParametersOverflow);
pch_engine.SetPanelParameters(kPanelParametersOverflow); // No MMIO writes.
}
TEST_F(PchEngineTest, TigerLakeSetPanelParametersOnlyPowerDownOnReset) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kTigerLakeStandardRawClock},
{.address = kPpControlOffset, .value = 0x10},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmFreqOffset, .value = 0x0000'0001},
{.address = kSblcPwmDutyOffset, .value = 0},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpControlOffset, .value = 0x12, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
pch_engine.SetPanelParameters(PchPanelParameters{
.power_on_to_hpd_aux_ready_delay_micros = 0,
.power_on_to_backlight_on_delay_micros = 0,
.backlight_off_to_video_end_delay_micros = 0,
.video_end_to_power_off_delay_micros = 0,
.power_cycle_delay_micros = 0,
.backlight_pwm_frequency_hz = 0x7fff'ffff,
.power_down_on_reset = true,
.backlight_pwm_inverted = false,
});
}
TEST_F(PchEngineTest, TigerLakeSetPanelParametersOnlyBacklightPwmInverted) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kTigerLakeStandardRawClock},
{.address = kPpControlOffset, .value = 0x10},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmFreqOffset, .value = 0x0000'0001},
{.address = kSblcPwmDutyOffset, .value = 0},
}));
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmCtl1Offset, .value = 0x2000'0000, .write = true},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
pch_engine.SetPanelParameters(PchPanelParameters{
.power_on_to_hpd_aux_ready_delay_micros = 0,
.power_on_to_backlight_on_delay_micros = 0,
.backlight_off_to_video_end_delay_micros = 0,
.video_end_to_power_off_delay_micros = 0,
.power_cycle_delay_micros = 0,
.backlight_pwm_frequency_hz = 0x7fff'ffff,
.power_down_on_reset = false,
.backlight_pwm_inverted = true,
});
}
class PchEngineBrightnessPwmTest : public PchEngineTest {
public:
// Sets PP_* register expectations so no unrelated assertion is triggered.
void SetKabyLakePanelPowerReadExpectations() {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpControlOffset, .value = 0},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kPpDivisor, .value = kKabyLakeStandardPpDivisor},
}));
}
};
TEST_F(PchEngineBrightnessPwmTest, KabyLake16IncrementMinFrequency) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
}));
SetKabyLakePanelPowerReadExpectations();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmCtl2Offset, .value = 0xffff0000},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_EQ(24'000'000, pch_engine.ClockParameters().raw_clock_hz);
// IHD-OS-KBL-Vol 12-1.17 page 196 and IHD-OS-SKL-Vol 12-05.16 page 189.
EXPECT_EQ(23, pch_engine.PanelParameters().backlight_pwm_frequency_hz);
}
TEST_F(PchEngineBrightnessPwmTest, KabyLake16Increment100StepsMaxFrequency) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
}));
SetKabyLakePanelPowerReadExpectations();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmCtl2Offset, .value = 0x0064'0000},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
// IHD-OS-KBL-Vol 12-1.17 page 196 and IHD-OS-SKL-Vol 12-05.16 page 189.
EXPECT_EQ(15'000, pch_engine.PanelParameters().backlight_pwm_frequency_hz);
}
TEST_F(PchEngineBrightnessPwmTest, KabyLake16Increment256StepsMaxFrequency) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
}));
SetKabyLakePanelPowerReadExpectations();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmCtl2Offset, .value = 0x0100'0000},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
// IHD-OS-KBL-Vol 12-1.17 page 196 and IHD-OS-SKL-Vol 12-05.16 page 189.
EXPECT_EQ(5'859, pch_engine.PanelParameters().backlight_pwm_frequency_hz);
}
TEST_F(PchEngineBrightnessPwmTest, KabyLake128IncrementMinFrequency) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 1},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
}));
SetKabyLakePanelPowerReadExpectations();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmCtl2Offset, .value = 0xffff0000},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
// IHD-OS-KBL-Vol 12-1.17 page 196 and IHD-OS-SKL-Vol 12-05.16 page 189.
EXPECT_EQ(3, pch_engine.PanelParameters().backlight_pwm_frequency_hz);
}
TEST_F(PchEngineBrightnessPwmTest, KabyLake128Increment100StepsMaxFrequency) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 1},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
}));
SetKabyLakePanelPowerReadExpectations();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmCtl2Offset, .value = 0x0064'0000},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
// IHD-OS-KBL-Vol 12-1.17 page 196 and IHD-OS-SKL-Vol 12-05.16 page 189.
EXPECT_EQ(1'875, pch_engine.PanelParameters().backlight_pwm_frequency_hz);
}
TEST_F(PchEngineBrightnessPwmTest, KabyLake128Increment256StepsMaxFrequency) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 1},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
}));
SetKabyLakePanelPowerReadExpectations();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmCtl2Offset, .value = 0x0100'0000},
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
// IHD-OS-KBL-Vol 12-1.17 page 196 and IHD-OS-SKL-Vol 12-05.16 page 189.
EXPECT_EQ(732, pch_engine.PanelParameters().backlight_pwm_frequency_hz);
}
class PchEngineKabyLakeBrightnessTest : public PchEngineTest {
protected:
// Sets all the values except for SBLC_PWM_CTL2.
void SetUp() override {
PchEngineTest::SetUp();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 1},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
{.address = kPpControlOffset, .value = 0},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kPpDivisor, .value = kKabyLakeStandardPpDivisor},
{.address = kSblcPwmCtl1Offset, .value = 0x8000'0000},
}));
}
};
TEST_F(PchEngineKabyLakeBrightnessTest, ReadZero) {
mmio_range_.Expect({.address = kSblcPwmCtl2Offset, .value = 0x05dc'0000});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_EQ(0.0, pch_engine.PanelBrightness());
}
TEST_F(PchEngineKabyLakeBrightnessTest, ReadOne) {
mmio_range_.Expect({.address = kSblcPwmCtl2Offset, .value = 0x05dc'05dc});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_EQ(1.0, pch_engine.PanelBrightness());
}
TEST_F(PchEngineKabyLakeBrightnessTest, ReadSmallFraction) {
mmio_range_.Expect({.address = kSblcPwmCtl2Offset, .value = 0x05dc'0177});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_EQ(0.25, pch_engine.PanelBrightness());
}
TEST_F(PchEngineKabyLakeBrightnessTest, ReadLargeFraction) {
mmio_range_.Expect({.address = kSblcPwmCtl2Offset, .value = 0x05dc'0465});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_EQ(0.75, pch_engine.PanelBrightness());
}
TEST_F(PchEngineKabyLakeBrightnessTest, WriteSmallFraction) {
mmio_range_.Expect({.address = kSblcPwmCtl2Offset, .value = 0x05dc'05dc});
mmio_range_.Expect({.address = kSblcPwmCtl2Offset, .value = 0x05dc'0177, .write = true});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelBrightness(0.25);
}
TEST_F(PchEngineKabyLakeBrightnessTest, WriteLargeFraction) {
mmio_range_.Expect({.address = kSblcPwmCtl2Offset, .value = 0x05dc'0000});
mmio_range_.Expect({.address = kSblcPwmCtl2Offset, .value = 0x05dc'0465, .write = true});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelBrightness(0.75);
}
TEST_F(PchEngineKabyLakeBrightnessTest, WriteSmallFractionNoChange) {
mmio_range_.Expect({.address = kSblcPwmCtl2Offset, .value = 0x05dc'0177});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelBrightness(0.25);
}
TEST_F(PchEngineKabyLakeBrightnessTest, WriteMisconfiguredNoChange) {
mmio_range_.Expect({.address = kSblcPwmCtl2Offset, .value = 0x0000'1111});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
pch_engine.SetPanelBrightness(1.0);
}
class PchEngineTigerLakeBrightnessTest : public PchEngineTest {
protected:
// Sets all the values except for SBLC_PWM_DUTY and SBLC_PWM_FREQ.
void SetUp() override {
PchEngineTest::SetUp();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 0},
{.address = kRawClkOffset, .value = kTigerLakeStandardRawClock},
// The bits around `power_down_on_reset` are set, to catch mapping errors.
{.address = kPpControlOffset, .value = 0xc5},
{.address = kPpOnDelays, .value = 0x0384'0a28},
{.address = kPpOffDelays, .value = 0x1388'07d0},
{.address = kSblcPwmCtl1Offset, .value = 0},
}));
}
};
TEST_F(PchEngineTigerLakeBrightnessTest, ReadZero) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmFreqOffset, .value = 0x9600},
{.address = kSblcPwmDutyOffset, .value = 0},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
EXPECT_EQ(0.0, pch_engine.PanelBrightness());
}
TEST_F(PchEngineTigerLakeBrightnessTest, ReadOne) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmFreqOffset, .value = 0x9600},
{.address = kSblcPwmDutyOffset, .value = 0x9600},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
EXPECT_EQ(1.0, pch_engine.PanelBrightness());
}
TEST_F(PchEngineTigerLakeBrightnessTest, ReadSmallFraction) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmFreqOffset, .value = 0x9600},
{.address = kSblcPwmDutyOffset, .value = 0x004b},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
EXPECT_EQ(0.001953125, pch_engine.PanelBrightness()); // (1 / 2) ^ 9
}
TEST_F(PchEngineTigerLakeBrightnessTest, ReadLargeFraction) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmFreqOffset, .value = 0x9600},
{.address = kSblcPwmDutyOffset, .value = 0x95b5},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
EXPECT_EQ(0.998046875, pch_engine.PanelBrightness()); // 1 - (1 / 2) ^ 9
}
TEST_F(PchEngineTigerLakeBrightnessTest, WriteSmallFraction) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmFreqOffset, .value = 0x9600},
{.address = kSblcPwmDutyOffset, .value = 0x9600},
}));
mmio_range_.Expect({.address = kSblcPwmDutyOffset, .value = 0x004b, .write = true});
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
pch_engine.SetPanelBrightness(0.001953125);
}
TEST_F(PchEngineTigerLakeBrightnessTest, WriteLargeFraction) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmFreqOffset, .value = 0x9600},
{.address = kSblcPwmDutyOffset, .value = 0x0000},
}));
mmio_range_.Expect({.address = kSblcPwmDutyOffset, .value = 0x95b5, .write = true});
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
pch_engine.SetPanelBrightness(0.998046875);
}
TEST_F(PchEngineTigerLakeBrightnessTest, WriteSmallFractionNoChange) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmFreqOffset, .value = 0x9600},
{.address = kSblcPwmDutyOffset, .value = 0x004b},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
pch_engine.SetPanelBrightness(0.001953125);
}
TEST_F(PchEngineTigerLakeBrightnessTest, WriteMisconfiguredNoChange) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSblcPwmFreqOffset, .value = 0x0000},
{.address = kSblcPwmDutyOffset, .value = 0x1111},
}));
PchEngine pch_engine(&mmio_buffer_, kDell5420GpuDeviceId);
pch_engine.SetPanelBrightness(1.0);
}
class PchEnginePanelPowerStateTest : public PchEngineTest {
protected:
void SetUp() override {
PchEngineTest::SetUp();
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kSChicken1Offset, .value = 1},
{.address = kRawClkOffset, .value = kKabyLakeStandardRawClock},
{.address = kPpControlOffset, .value = 0},
{.address = kPpOnDelays, .value = 0},
{.address = kPpOffDelays, .value = 0},
{.address = kPpDivisor, .value = kKabyLakeStandardPpDivisor},
{.address = kSblcPwmCtl1Offset, .value = 0},
{.address = kSblcPwmCtl2Offset, .value = 0x0100'0000},
}));
}
};
TEST_F(PchEnginePanelPowerStateTest, PoweredDown) {
mmio_range_.Expect({.address = kPpStatusOffset, .value = 0x0000'0000});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_EQ(PchPanelPowerState::kPoweredDown, pch_engine.PanelPowerState());
}
TEST_F(PchEnginePanelPowerStateTest, PoweredUp) {
mmio_range_.Expect({.address = kPpStatusOffset, .value = 0x8000'0000});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_EQ(PchPanelPowerState::kPoweredUp, pch_engine.PanelPowerState());
}
TEST_F(PchEnginePanelPowerStateTest, PoweringUp) {
mmio_range_.Expect({.address = kPpStatusOffset, .value = 0x1000'0000});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_EQ(PchPanelPowerState::kPoweringUp, pch_engine.PanelPowerState());
}
TEST_F(PchEnginePanelPowerStateTest, PoweringDown) {
mmio_range_.Expect({.address = kPpStatusOffset, .value = 0xa000'0000});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_EQ(PchPanelPowerState::kPoweringDown, pch_engine.PanelPowerState());
}
TEST_F(PchEnginePanelPowerStateTest, PowerCycleDelay) {
mmio_range_.Expect({.address = kPpStatusOffset, .value = 0x0800'0000});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_EQ(PchPanelPowerState::kWaitingForPowerCycleDelay, pch_engine.PanelPowerState());
}
TEST_F(PchEnginePanelPowerStateTest, PoweringUpWaitingForPowerCycleDelay) {
mmio_range_.Expect({.address = kPpStatusOffset, .value = 0x1800'0000});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_EQ(PchPanelPowerState::kWaitingForPowerCycleDelay, pch_engine.PanelPowerState());
}
TEST_F(PchEnginePanelPowerStateTest, PoweredDownIgnoringReservedTransition) {
mmio_range_.Expect({.address = kPpStatusOffset, .value = 0x3000'0000});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_EQ(PchPanelPowerState::kPoweredDown, pch_engine.PanelPowerState());
}
TEST_F(PchEnginePanelPowerStateTest, PoweredUpIgnoringReservedTransition) {
mmio_range_.Expect({.address = kPpStatusOffset, .value = 0xb000'0000});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_EQ(PchPanelPowerState::kPoweredUp, pch_engine.PanelPowerState());
}
TEST_F(PchEnginePanelPowerStateTest, PowerCycleDelayIgnoringReservedTransition) {
mmio_range_.Expect({.address = kPpStatusOffset, .value = 0x3800'0000});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_EQ(PchPanelPowerState::kWaitingForPowerCycleDelay, pch_engine.PanelPowerState());
}
TEST_F(PchEnginePanelPowerStateTest, PoweredUpIgnoringPowerCycleDelay) {
mmio_range_.Expect({.address = kPpStatusOffset, .value = 0x8800'0000});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_EQ(PchPanelPowerState::kPoweredUp, pch_engine.PanelPowerState());
}
TEST_F(PchEnginePanelPowerStateTest, PoweredUpIgnoringPowerCycleDelayAndReservedTransition) {
mmio_range_.Expect({.address = kPpStatusOffset, .value = 0xb800'0000});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_EQ(PchPanelPowerState::kPoweredUp, pch_engine.PanelPowerState());
}
TEST_F(PchEnginePanelPowerStateTest, WaitForPanelPowerStateInstant) {
mmio_range_.Expect({.address = kPpStatusOffset, .value = 0x8000'0000});
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_TRUE(pch_engine.WaitForPanelPowerState(PchPanelPowerState::kPoweredUp, 30'000));
}
TEST_F(PchEnginePanelPowerStateTest, WaitForPanelPowerStateAfter20Ms) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpStatusOffset, .value = 0x0000'0000}, // Powered down.
{.address = kPpStatusOffset, .value = 0x1000'0000}, // Powering up.
{.address = kPpStatusOffset, .value = 0x8000'0000}, // Powered up.
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_TRUE(pch_engine.WaitForPanelPowerState(PchPanelPowerState::kPoweredUp, 30'000));
}
TEST_F(PchEnginePanelPowerStateTest, WaitForPanelPowerStateLastChance) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpStatusOffset, .value = 0x0000'0000}, // Powered down.
{.address = kPpStatusOffset, .value = 0x1000'0000}, // Powering up.
{.address = kPpStatusOffset, .value = 0x1000'0000}, // Powering up.
{.address = kPpStatusOffset, .value = 0x8000'0000}, // Powered up.
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_TRUE(pch_engine.WaitForPanelPowerState(PchPanelPowerState::kPoweredUp, 30'000));
}
TEST_F(PchEnginePanelPowerStateTest, WaitForPanelPowerStateTimeout) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpStatusOffset, .value = 0x0000'0000}, // Powered down.
{.address = kPpStatusOffset, .value = 0x1000'0000}, // Powering up.
{.address = kPpStatusOffset, .value = 0x1000'0000}, // Powering up.
{.address = kPpStatusOffset, .value = 0x1000'0000}, // Powering up.
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_FALSE(pch_engine.WaitForPanelPowerState(PchPanelPowerState::kPoweredUp, 30'000));
}
TEST_F(PchEnginePanelPowerStateTest, WaitForPanelPowerStateTimeoutRounding) {
mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({
{.address = kPpStatusOffset, .value = 0x0000'0000}, // Powered down.
{.address = kPpStatusOffset, .value = 0x1000'0000}, // Powering up.
{.address = kPpStatusOffset, .value = 0x1000'0000}, // Powering up.
{.address = kPpStatusOffset, .value = 0x1000'0000}, // Powering up.
}));
PchEngine pch_engine(&mmio_buffer_, kAtlasGpuDeviceId);
EXPECT_FALSE(pch_engine.WaitForPanelPowerState(PchPanelPowerState::kPoweredUp, 21'000));
}
} // namespace
} // namespace i915