blob: a1a8f6f0fe7cabc1e48ec49ff1dfd9d517a65103 [file] [log] [blame]
// Copyright 2021 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/dp-display.h"
#include <lib/mmio-ptr/fake.h>
#include <gtest/gtest.h>
#include "src/graphics/display/drivers/intel-i915/dpll.h"
#include "src/graphics/display/drivers/intel-i915/fake-dpcd-channel.h"
#include "src/graphics/display/drivers/intel-i915/intel-i915.h"
#include "src/graphics/display/drivers/intel-i915/pci-ids.h"
namespace {
// Value used to allocate space for the fake i915 register MMIO space.
// TODO(fxbug.dev/83998): Remove this once DpDisplay no longer depends on i915::Controller.
constexpr uint32_t kMmioSize = 0xd0000;
class TestDpll : public i915::DisplayPll {
public:
explicit TestDpll(registers::Dpll dpll) : DisplayPll(dpll) {}
~TestDpll() override = default;
bool Enable(const i915::DpllState& state) final {
enabled_ = true;
return enabled_;
}
bool Disable() final {
enabled_ = false;
return enabled_;
}
private:
bool enabled_ = false;
};
class TestDpllManager : public i915::DisplayPllManager {
public:
explicit TestDpllManager() {
plls_.resize(kDplls.size());
for (const auto dpll : kDplls) {
plls_[dpll] = std::make_unique<TestDpll>(dpll);
ref_count_[plls_[dpll].get()] = 0;
}
}
std::optional<i915::DpllState> LoadState(registers::Ddi ddi) final {
i915::DpllState state = i915::DpDpllState{
.dp_rate = registers::DpllControl1::LinkRate::k2700Mhz,
};
return std::make_optional(state);
}
private:
constexpr static auto kDplls = {registers::Dpll::DPLL_0, registers::Dpll::DPLL_1,
registers::Dpll::DPLL_2};
bool MapImpl(registers::Ddi ddi, registers::Dpll dpll) final { return true; }
bool UnmapImpl(registers::Ddi ddi) final { return true; }
i915::DisplayPll* FindBestDpll(registers::Ddi ddi, bool is_edp,
const i915::DpllState& state) final {
for (const auto dpll : kDplls) {
if (ref_count_[plls_[dpll].get()] == 0) {
return plls_[dpll].get();
}
}
return nullptr;
}
};
class DpDisplayTest : public ::testing::Test {
protected:
DpDisplayTest()
: controller_(nullptr),
mmio_buffer_({
.vaddr = FakeMmioPtr(buffer_),
.offset = 0,
.size = kMmioSize,
.vmo = ZX_HANDLE_INVALID,
}) {
std::memset(buffer_, 0, sizeof(buffer_));
}
void SetUp() override {
controller_.SetMmioForTesting(mmio_buffer_.View(0));
controller_.SetDpllManagerForTesting(std::make_unique<TestDpllManager>());
controller_.SetPowerWellForTesting(
i915::Power::New(controller_.mmio_space(), i915::kTestDeviceDid));
fake_dpcd_.SetDefaults();
}
void TearDown() override {
// Unset so controller teardown doesn't crash.
controller_.ResetMmioSpaceForTesting();
}
std::unique_ptr<i915::DpDisplay> MakeDisplay(registers::Ddi ddi, uint64_t id = 1) {
// TODO(fxbug.dev/86038): In normal operation a DpDisplay is not fully constructed until it
// receives a call to DisplayDevice::Query, then either DisplayDevice::Init() (for a hotplug or
// initially powered-off display) OR DisplayDevice::AttachPipe() and
// DisplayDevice::LoadACtiveMode() (for a pre-initialized display, e.g. bootloader-configured
// eDP). For testing we only initialize until the Query() stage. The states of a DpDisplay
// should become easier to reason about if remove the partially-initialized states.
auto display = std::make_unique<i915::DpDisplay>(&controller_, id, ddi, &fake_dpcd_, &node_);
if (!display->Query()) {
return nullptr;
}
return display;
}
i915::Controller* controller() { return &controller_; }
i915::testing::FakeDpcdChannel* fake_dpcd() { return &fake_dpcd_; }
fdf::MmioBuffer* mmio_buffer() { return &mmio_buffer_; }
private:
// TODO(fxbug.dev/83998): Remove DpDisplay's dependency on i915::Controller which will remove the
// need for much of what's in SetUp() and TearDown().
i915::Controller controller_;
uint8_t buffer_[kMmioSize];
fdf::MmioBuffer mmio_buffer_;
inspect::Node node_;
i915::testing::FakeDpcdChannel fake_dpcd_;
};
// Tests that display creation fails if the DP sink count is not 1, as MST is not supported.
TEST_F(DpDisplayTest, MultipleSinksNotSupported) {
fake_dpcd()->SetSinkCount(2);
ASSERT_EQ(nullptr, MakeDisplay(registers::DDI_A));
}
// Tests that the maximum supported lane count is 2 when DDI_A lane capability control is not
// supported.
TEST_F(DpDisplayTest, ReducedMaxLaneCountWhenDdiALaneCapControlNotSupported) {
auto ddi_buf_ctl = registers::DdiRegs(registers::DDI_A).DdiBufControl().ReadFrom(mmio_buffer());
ddi_buf_ctl.set_ddi_a_lane_capability_control(0);
ddi_buf_ctl.WriteTo(mmio_buffer());
fake_dpcd()->SetMaxLaneCount(4);
auto display = MakeDisplay(registers::DDI_A);
ASSERT_NE(nullptr, display);
EXPECT_EQ(2, display->lane_count());
}
// Tests that the maximum supported lane count is selected when DDI_A lane capability control is
// supported.
TEST_F(DpDisplayTest, MaxLaneCount) {
auto ddi_buf_ctl = registers::DdiRegs(registers::DDI_A).DdiBufControl().ReadFrom(mmio_buffer());
ddi_buf_ctl.set_ddi_a_lane_capability_control(1);
ddi_buf_ctl.WriteTo(mmio_buffer());
fake_dpcd()->SetMaxLaneCount(4);
auto display = MakeDisplay(registers::DDI_A);
ASSERT_NE(nullptr, display);
EXPECT_EQ(4, display->lane_count());
}
// Tests that the link rate is set to the maximum supported rate based on DPCD data upon
// initialization via Init().
TEST_F(DpDisplayTest, LinkRateSelectionViaInit) {
// Set up the IGD, DPLL, panel power control, and DisplayPort lane status registers for
// DpDisplay::Init() to succeed. Configuring the IGD op region to indicate eDP will cause
// Controller to assign DPLL0 to the display.
// TODO(fxbug.dev/83998): It shouldn't be necessary to rely on this logic in Controller to test
// DpDisplay. Can DpDisplay be told that it is eDP during construction time instead of querying
// Controller for it every time?
controller()->igd_opregion_for_testing()->SetIsEdpForTesting(registers::DDI_A, true);
auto dpll_status = registers::DpllStatus::Get().ReadFrom(mmio_buffer());
dpll_status.set_reg_value(1u);
dpll_status.WriteTo(mmio_buffer());
auto panel_status = registers::PanelPowerStatus::Get().ReadFrom(mmio_buffer());
panel_status.set_on_status(1);
panel_status.WriteTo(mmio_buffer());
controller()->power()->SetDdiIoPowerState(registers::DDI_A, /* enable */ true);
fake_dpcd()->registers[dpcd::DPCD_LANE0_1_STATUS] = 0xFF;
fake_dpcd()->SetMaxLinkRate(dpcd::LinkBw::k5400Mbps);
auto display = MakeDisplay(registers::DDI_A);
ASSERT_NE(nullptr, display);
EXPECT_TRUE(display->Init());
EXPECT_EQ(5400u, display->link_rate_mhz());
}
// Tests that the link rate is set to a caller-assigned value upon initialization with
// InitWithDpllState.
TEST_F(DpDisplayTest, LinkRateSelectionViaInitWithDpllState) {
// The max link rate should be disregarded by InitWithDpllState.
fake_dpcd()->SetMaxLinkRate(dpcd::LinkBw::k5400Mbps);
auto display = MakeDisplay(registers::DDI_A);
ASSERT_NE(nullptr, display);
i915::DpllState dpll_state = i915::DpDpllState{
.dp_rate = registers::DpllControl1::LinkRate::k2160Mhz,
};
display->InitWithDpllState(&dpll_state);
EXPECT_EQ(4320u, display->link_rate_mhz());
}
// Tests that the brightness value is obtained using the i915 south backlight control register
// when the related eDP DPCD capability is not supported.
TEST_F(DpDisplayTest, GetBacklightBrightnessUsesSouthBacklightRegister) {
// The brightness value is the ratio between duty cycle and modulation frequency.
registers::SouthBacklightCtl2::Get()
.FromValue(0)
.set_modulation_freq(1024)
.set_duty_cycle(512)
.WriteTo(controller()->mmio_space());
controller()->igd_opregion_for_testing()->SetIsEdpForTesting(registers::DDI_A, true);
auto display = MakeDisplay(registers::DDI_A);
ASSERT_NE(nullptr, display);
EXPECT_FLOAT_EQ(0.5, display->GetBacklightBrightness());
}
// Tests that the brightness value is obtained from the related eDP DPCD registers when supported.
TEST_F(DpDisplayTest, GetBacklightBrightnessUsesDpcd) {
constexpr uint16_t kDpcdBrightness100 = 0xFFFF;
constexpr uint16_t kDpcdBrightness20 = 0x3333;
// The brightness value is the ratio between duty cycle and modulation frequency (50% == 512/1024)
// We'll intentionally configure the DPCD brightness value to something different to prove that
// the SouthBacklightCtl2 register is not used.
constexpr uint16_t kIntelBrightnessDenominator = 1024;
constexpr uint16_t kIntelBrightnessNominator = 512;
registers::SouthBacklightCtl2::Get()
.FromValue(0)
.set_modulation_freq(kIntelBrightnessDenominator)
.set_duty_cycle(kIntelBrightnessNominator)
.WriteTo(controller()->mmio_space());
controller()->igd_opregion_for_testing()->SetIsEdpForTesting(registers::DDI_A, true);
fake_dpcd()->SetEdpCapable(dpcd::EdpRevision::k1_4);
fake_dpcd()->SetEdpBacklightBrightnessCapable();
// Set the brightness to 100%.
fake_dpcd()->registers[dpcd::DPCD_EDP_BACKLIGHT_BRIGHTNESS_LSB] = kDpcdBrightness100 & 0xFF;
fake_dpcd()->registers[dpcd::DPCD_EDP_BACKLIGHT_BRIGHTNESS_MSB] = kDpcdBrightness100 >> 8;
auto display = MakeDisplay(registers::DDI_A);
ASSERT_NE(nullptr, display);
EXPECT_FLOAT_EQ(1.0, display->GetBacklightBrightness());
// Set the brightness to 20%.
fake_dpcd()->registers[dpcd::DPCD_EDP_BACKLIGHT_BRIGHTNESS_LSB] = kDpcdBrightness20 & 0xFF;
fake_dpcd()->registers[dpcd::DPCD_EDP_BACKLIGHT_BRIGHTNESS_MSB] = kDpcdBrightness20 >> 8;
display = MakeDisplay(registers::DDI_A);
ASSERT_NE(nullptr, display);
EXPECT_FLOAT_EQ(0.2, display->GetBacklightBrightness());
}
} // namespace