blob: dd9202abc0fd963402bedd300d435e945c34ca71 [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/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