| // 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/power.h" |
| |
| #include <lib/mmio-ptr/fake.h> |
| #include <lib/mmio/mmio.h> |
| |
| #include <memory> |
| #include <vector> |
| |
| #include <fake-mmio-reg/fake-mmio-reg.h> |
| #include <gtest/gtest.h> |
| |
| #include "src/graphics/display/drivers/intel-i915/hardware-common.h" |
| #include "src/graphics/display/drivers/intel-i915/registers.h" |
| |
| namespace i915 { |
| |
| class PowerTest : public ::testing::Test { |
| public: |
| PowerTest() = default; |
| |
| void SetUp() override { mmio_buffer_.emplace(reg_region_.GetMmioBuffer()); } |
| |
| void TearDown() override {} |
| |
| protected: |
| constexpr static uint32_t kMinimumRegCount = 0x50000 / sizeof(uint32_t); |
| ddk_fake::FakeMmioRegRegion reg_region_{sizeof(uint32_t), kMinimumRegCount}; |
| std::optional<fdf::MmioBuffer> mmio_buffer_; |
| }; |
| |
| // Verify setting Power Well 2 status on Skylake platform. |
| TEST_F(PowerTest, Skylake_PowerWell2) { |
| auto kPwrWellCtlAddr = registers::PowerWellControl::Get().addr(); |
| auto kFuseStatusAddr = registers::FuseStatus::Get().addr(); |
| |
| bool pg2_status = false; |
| |
| reg_region_[kPwrWellCtlAddr].SetWriteCallback( |
| [&pg2_status](uint64_t in) { pg2_status = in & (1 << 31); }); |
| |
| reg_region_[kPwrWellCtlAddr].SetReadCallback( |
| [&pg2_status]() -> uint64_t { return pg2_status << 30; }); |
| |
| reg_region_[kFuseStatusAddr].SetReadCallback( |
| [&pg2_status]() -> uint64_t { return pg2_status << 25; }); |
| |
| constexpr uint16_t kDeviceIdSkylake = 0x191b; |
| auto power = Power::New(&*mmio_buffer_, kDeviceIdSkylake); |
| |
| { |
| auto power_well2_ref = PowerWellRef(power.get(), PowerWellId::PG2); |
| EXPECT_TRUE(pg2_status); |
| } |
| EXPECT_FALSE(pg2_status); |
| |
| // Test resuming power well state. |
| { |
| auto power_well2_ref = PowerWellRef(power.get(), PowerWellId::PG2); |
| EXPECT_TRUE(pg2_status); |
| |
| pg2_status = false; |
| |
| power->Resume(); |
| EXPECT_TRUE(pg2_status); |
| } |
| EXPECT_FALSE(pg2_status); |
| |
| // Test creating and deleting multiple power well refs to PG2. |
| // PG2 should be disabled only after all power well refs are removed. |
| constexpr size_t kNumRefs = 10; |
| std::vector<PowerWellRef> refs(kNumRefs); |
| for (size_t i = 0; i < kNumRefs; i++) { |
| refs[i] = PowerWellRef(power.get(), PowerWellId::PG2); |
| } |
| |
| for (size_t i = 0; i < kNumRefs; i++) { |
| EXPECT_TRUE(pg2_status); |
| refs[i] = {}; |
| } |
| EXPECT_FALSE(pg2_status); |
| } |
| |
| // Verify setting Misc / AUX IO status on Skylake platform. |
| TEST_F(PowerTest, Skylake_AuxIo) { |
| constexpr uint16_t kDeviceIdSkylake = 0x191b; |
| auto power = Power::New(&*mmio_buffer_, kDeviceIdSkylake); |
| |
| EXPECT_TRUE(power->GetAuxIoPowerState(DdiId::DDI_A)); |
| EXPECT_TRUE(power->GetAuxIoPowerState(DdiId::DDI_B)); |
| |
| // Enable AUX IO. |
| power->SetAuxIoPowerState(DdiId::DDI_A, true); |
| power->SetAuxIoPowerState(DdiId::DDI_B, true); |
| |
| // On Skylake, the AUX IO power will be not disabled on-demand. |
| power->SetAuxIoPowerState(DdiId::DDI_A, false); |
| power->SetAuxIoPowerState(DdiId::DDI_B, false); |
| EXPECT_TRUE(power->GetAuxIoPowerState(DdiId::DDI_A)); |
| EXPECT_TRUE(power->GetAuxIoPowerState(DdiId::DDI_B)); |
| } |
| |
| TEST_F(PowerTest, TigerLake_DdiIo) { |
| const auto kPowerWellControlDdi2Addr = registers::PowerWellControlDdi2::Get().addr(); |
| |
| auto power_well_control_ddi_reg = registers::PowerWellControlDdi2::Get().FromValue(0); |
| |
| // Fake PowerWellControlDdi2 register, which flips the state bit once the |
| // corresponding request bit is flipped. |
| reg_region_[kPowerWellControlDdi2Addr].SetWriteCallback( |
| [&power_well_control_ddi_reg](uint64_t in) { |
| constexpr uint32_t kPowerStateBitMask = 0b010101010101010101; |
| |
| const uint32_t current_power_state_bits = |
| power_well_control_ddi_reg.reg_value() & kPowerStateBitMask; |
| const uint32_t incoming_power_state_bits = static_cast<uint32_t>(in) & kPowerStateBitMask; |
| EXPECT_EQ(incoming_power_state_bits, current_power_state_bits) |
| << "power state bits must not be modified"; |
| |
| power_well_control_ddi_reg.set_reg_value( |
| (power_well_control_ddi_reg.reg_value() & (~kPowerStateBitMask)) | |
| ((in >> 1) & kPowerStateBitMask)); |
| }); |
| reg_region_[kPowerWellControlDdi2Addr].SetReadCallback( |
| [&power_well_control_ddi_reg]() { return power_well_control_ddi_reg.reg_value(); }); |
| |
| constexpr uint16_t kDeviceIdTigerLake = 0x9a49; |
| auto power = Power::New(&*mmio_buffer_, kDeviceIdTigerLake); |
| |
| // Enable DDI IO for DDI_A. |
| EXPECT_FALSE(power->GetDdiIoPowerState(DdiId::DDI_A)); |
| power->SetDdiIoPowerState(DdiId::DDI_A, /*enable=*/true); |
| // Power state should be changed for the fake MMIO. |
| EXPECT_TRUE(power->GetDdiIoPowerState(DdiId::DDI_A)); |
| |
| EXPECT_TRUE(power_well_control_ddi_reg.ddi_io_power_state_tiger_lake(DdiId::DDI_A).get()); |
| EXPECT_FALSE(power_well_control_ddi_reg.ddi_io_power_state_tiger_lake(DdiId::DDI_B).get()); |
| EXPECT_FALSE(power_well_control_ddi_reg.ddi_io_power_state_tiger_lake(DdiId::DDI_TC_1).get()); |
| |
| // Disable DDI IO for DDI_A. |
| power->SetDdiIoPowerState(DdiId::DDI_A, /*enable=*/false); |
| EXPECT_FALSE(power->GetDdiIoPowerState(DdiId::DDI_A)); |
| |
| EXPECT_FALSE(power_well_control_ddi_reg.ddi_io_power_state_tiger_lake(DdiId::DDI_A).get()); |
| EXPECT_FALSE(power_well_control_ddi_reg.ddi_io_power_state_tiger_lake(DdiId::DDI_B).get()); |
| EXPECT_FALSE(power_well_control_ddi_reg.ddi_io_power_state_tiger_lake(DdiId::DDI_TC_1).get()); |
| |
| // Enable DDI IO for DDI_TC_1. |
| EXPECT_FALSE(power->GetDdiIoPowerState(DdiId::DDI_TC_1)); |
| power->SetDdiIoPowerState(DdiId::DDI_TC_1, /*enable=*/true); |
| // Power state should be changed for the fake MMIO. |
| EXPECT_TRUE(power->GetDdiIoPowerState(DdiId::DDI_TC_1)); |
| |
| EXPECT_TRUE(power_well_control_ddi_reg.ddi_io_power_state_tiger_lake(DdiId::DDI_TC_1).get()); |
| EXPECT_FALSE(power_well_control_ddi_reg.ddi_io_power_state_tiger_lake(DdiId::DDI_B).get()); |
| EXPECT_FALSE(power_well_control_ddi_reg.ddi_io_power_state_tiger_lake(DdiId::DDI_TC_6).get()); |
| |
| // Disable DDI IO for DDI_TC_1. |
| power->SetDdiIoPowerState(DdiId::DDI_TC_1, /*enable=*/false); |
| EXPECT_FALSE(power->GetDdiIoPowerState(DdiId::DDI_TC_1)); |
| |
| EXPECT_FALSE(power_well_control_ddi_reg.ddi_io_power_state_tiger_lake(DdiId::DDI_TC_1).get()); |
| EXPECT_FALSE(power_well_control_ddi_reg.ddi_io_power_state_tiger_lake(DdiId::DDI_B).get()); |
| EXPECT_FALSE(power_well_control_ddi_reg.ddi_io_power_state_tiger_lake(DdiId::DDI_TC_6).get()); |
| } |
| |
| // Verify setting Power Well status on Tiger lake platform. |
| TEST_F(PowerTest, TigerLake_PowerWell) { |
| auto kPwrWellCtlAddr = registers::PowerWellControl::Get().addr(); |
| auto kFuseStatusAddr = registers::FuseStatus::Get().addr(); |
| |
| std::unordered_map<PowerWellId, bool> pg_status = { |
| {PowerWellId::PG1, true}, {PowerWellId::PG2, false}, {PowerWellId::PG3, false}, |
| {PowerWellId::PG4, false}, {PowerWellId::PG5, false}, |
| }; |
| uint64_t power_well_ctl_reg = 0u; |
| |
| reg_region_[kPwrWellCtlAddr].SetWriteCallback([&pg_status, &power_well_ctl_reg](uint64_t in) { |
| pg_status[PowerWellId::PG2] = in & (1 << 3); |
| pg_status[PowerWellId::PG3] = in & (1 << 5); |
| pg_status[PowerWellId::PG4] = in & (1 << 7); |
| pg_status[PowerWellId::PG5] = in & (1 << 9); |
| |
| power_well_ctl_reg = (pg_status[PowerWellId::PG1] << 0) | (in & (1 << 1)) | |
| (pg_status[PowerWellId::PG2] << 2) | (in & (1 << 3)) | |
| (pg_status[PowerWellId::PG3] << 4) | (in & (1 << 5)) | |
| (pg_status[PowerWellId::PG4] << 6) | (in & (1 << 7)) | |
| (pg_status[PowerWellId::PG5] << 8) | (in & (1 << 9)); |
| }); |
| |
| reg_region_[kPwrWellCtlAddr].SetReadCallback( |
| [&power_well_ctl_reg]() -> uint64_t { return power_well_ctl_reg; }); |
| |
| reg_region_[kFuseStatusAddr].SetReadCallback([&pg_status]() -> uint64_t { |
| return (pg_status[PowerWellId::PG1] << 26) | (pg_status[PowerWellId::PG2] << 25) | |
| (pg_status[PowerWellId::PG3] << 24) | (pg_status[PowerWellId::PG4] << 23) | |
| (pg_status[PowerWellId::PG5] << 22); |
| }); |
| |
| constexpr uint16_t kDeviceIdTigerLake = 0x9a49; |
| auto power = Power::New(&*mmio_buffer_, kDeviceIdTigerLake); |
| |
| // When we enable a power well, all its dependencies will be enabled as well. |
| { |
| auto power_well5_ref = PowerWellRef(power.get(), PowerWellId::PG5); |
| EXPECT_TRUE(pg_status[PowerWellId::PG1]); |
| EXPECT_TRUE(pg_status[PowerWellId::PG2]); |
| EXPECT_TRUE(pg_status[PowerWellId::PG3]); |
| EXPECT_TRUE(pg_status[PowerWellId::PG4]); |
| EXPECT_TRUE(pg_status[PowerWellId::PG5]); |
| } |
| // When the power well ref is removed, refcount of all dependencies will |
| // decrease and power well will be automatically turned off if not used. |
| EXPECT_FALSE(pg_status[PowerWellId::PG2]); |
| EXPECT_FALSE(pg_status[PowerWellId::PG3]); |
| EXPECT_FALSE(pg_status[PowerWellId::PG4]); |
| EXPECT_FALSE(pg_status[PowerWellId::PG5]); |
| |
| // Verify that a power well is disabled only when *all* power well refs that |
| // depends on that power well have been removed. |
| { |
| auto power_well5_ref = PowerWellRef(power.get(), PowerWellId::PG5); |
| auto power_well3_ref = PowerWellRef(power.get(), PowerWellId::PG3); |
| EXPECT_TRUE(pg_status[PowerWellId::PG1]); |
| EXPECT_TRUE(pg_status[PowerWellId::PG2]); |
| EXPECT_TRUE(pg_status[PowerWellId::PG3]); |
| EXPECT_TRUE(pg_status[PowerWellId::PG4]); |
| EXPECT_TRUE(pg_status[PowerWellId::PG5]); |
| |
| power_well5_ref = {}; |
| EXPECT_TRUE(pg_status[PowerWellId::PG2]); |
| EXPECT_TRUE(pg_status[PowerWellId::PG3]); |
| EXPECT_FALSE(pg_status[PowerWellId::PG4]); |
| EXPECT_FALSE(pg_status[PowerWellId::PG5]); |
| |
| power_well3_ref = {}; |
| EXPECT_FALSE(pg_status[PowerWellId::PG2]); |
| EXPECT_FALSE(pg_status[PowerWellId::PG3]); |
| EXPECT_FALSE(pg_status[PowerWellId::PG4]); |
| EXPECT_FALSE(pg_status[PowerWellId::PG5]); |
| } |
| |
| // Test resuming power well state. |
| { |
| auto power_well4_ref = PowerWellRef(power.get(), PowerWellId::PG4); |
| EXPECT_TRUE(pg_status[PowerWellId::PG2]); |
| EXPECT_TRUE(pg_status[PowerWellId::PG3]); |
| EXPECT_TRUE(pg_status[PowerWellId::PG4]); |
| EXPECT_FALSE(pg_status[PowerWellId::PG5]); |
| |
| pg_status[PowerWellId::PG2] = pg_status[PowerWellId::PG3] = pg_status[PowerWellId::PG4] = |
| pg_status[PowerWellId::PG5] = false; |
| |
| power->Resume(); |
| EXPECT_TRUE(pg_status[PowerWellId::PG2]); |
| EXPECT_TRUE(pg_status[PowerWellId::PG3]); |
| EXPECT_TRUE(pg_status[PowerWellId::PG4]); |
| EXPECT_FALSE(pg_status[PowerWellId::PG5]); |
| } |
| EXPECT_FALSE(pg_status[PowerWellId::PG2]); |
| EXPECT_FALSE(pg_status[PowerWellId::PG3]); |
| EXPECT_FALSE(pg_status[PowerWellId::PG4]); |
| EXPECT_FALSE(pg_status[PowerWellId::PG5]); |
| } |
| |
| } // namespace i915 |