| // 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 |