blob: b69bb0e151dd676153130597fc95e3e7e4f680cf [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/mmio-buffer.h>
#include <lib/zx/time.h>
#include <zircon/assert.h>
#include <unordered_set>
#include "src/graphics/display/drivers/intel-i915/hardware-common.h"
#include "src/graphics/display/drivers/intel-i915/pci-ids.h"
#include "src/graphics/display/drivers/intel-i915/poll-until.h"
#include "src/graphics/display/drivers/intel-i915/registers.h"
namespace i915 {
namespace {
bool SetPowerWellImpl(const PowerWellInfo& power_well_info, bool enable,
const fdf::MmioBuffer* mmio_space, int32_t timeout_for_pwr_well_ctl_state_us,
int32_t timeout_for_fuse_state_us) {
if (power_well_info.always_on) {
return true;
}
// Sequences from IHD-OS-TGL-Vol 12-12.21 "Sequences for Power Wells".
// "Enable sequence" on page 220, "Disable sequence" on page 221.
auto power_well_reg = registers::PowerWellControl::Get().ReadFrom(mmio_space);
power_well_reg.power_request(power_well_info.request_bit_index).set(enable);
power_well_reg.WriteTo(mmio_space);
if (enable) {
power_well_reg.ReadFrom(mmio_space);
if (!PollUntil(
[&] {
return registers::PowerWellControl::Get()
.ReadFrom(mmio_space)
.power_state(power_well_info.state_bit_index)
.get();
},
zx::usec(1), timeout_for_pwr_well_ctl_state_us)) {
zxlogf(ERROR, "Failed to enable power well (%s)", power_well_info.name);
return false;
}
if (!PollUntil(
[&] {
ZX_DEBUG_ASSERT_MSG(
power_well_info.fuse_dist_bit_index <= std::numeric_limits<uint32_t>::max(),
"%zu overflows uint32_t", power_well_info.fuse_dist_bit_index);
return registers::FuseStatus::Get()
.ReadFrom(mmio_space)
.dist_status(static_cast<uint32_t>(power_well_info.fuse_dist_bit_index));
},
zx::usec(1), timeout_for_fuse_state_us)) {
zxlogf(ERROR, "Power well (%s) distribution failed", power_well_info.name);
return false;
}
}
return true;
}
const std::unordered_map<PowerWellId, PowerWellInfo> kPowerWellInfoTestDevice = {
{PowerWellId::PG1,
{
.name = "Power Well 1",
.always_on = true,
.state_bit_index = 0,
.request_bit_index = 1,
.fuse_dist_bit_index = 2,
.parent = PowerWellId::PG1,
}},
};
// A fake power well implementation used only for integration tests.
class TestPowerWell : public Power {
public:
explicit TestPowerWell(fdf::MmioBuffer* mmio_space)
: Power(mmio_space, &kPowerWellInfoTestDevice) {}
void Resume() override {}
PowerWellRef GetCdClockPowerWellRef() override { return PowerWellRef(this, PowerWellId::PG1); }
PowerWellRef GetPipePowerWellRef(PipeId pipe_id) override {
return PowerWellRef(this, PowerWellId::PG1);
}
PowerWellRef GetDdiPowerWellRef(DdiId ddi_id) override {
return PowerWellRef(this, PowerWellId::PG1);
}
bool GetDdiIoPowerState(DdiId ddi_id) override {
if (ddi_state_.find(ddi_id) == ddi_state_.end()) {
ddi_state_[ddi_id] = false;
}
return ddi_state_[ddi_id];
}
void SetDdiIoPowerState(DdiId ddi_id, bool enable) override { ddi_state_[ddi_id] = enable; }
bool GetAuxIoPowerState(DdiId ddi_id) override {
if (aux_state_.find(ddi_id) == aux_state_.end()) {
aux_state_[ddi_id] = false;
}
return aux_state_[ddi_id] = true;
}
void SetAuxIoPowerState(DdiId ddi_id, bool enable) override { aux_state_[ddi_id] = enable; }
private:
void SetPowerWell(PowerWellId power_well, bool enable) override {}
std::unordered_map<DdiId, bool> ddi_state_;
std::unordered_map<DdiId, bool> aux_state_;
};
const std::unordered_map<PowerWellId, PowerWellInfo> kPowerWellInfoSkylake = {
{PowerWellId::PG1,
{
.name = "Power Well 1",
.always_on = true,
.state_bit_index = 28,
.request_bit_index = 29,
.fuse_dist_bit_index = 26,
.parent = PowerWellId::PG1,
}},
{
PowerWellId::PG2,
{
.name = "Power Well 2",
.state_bit_index = 30,
.request_bit_index = 31,
.fuse_dist_bit_index = 25,
.parent = PowerWellId::PG1,
},
},
};
// Power implementation for Sky lake and Kaby lake platforms.
class PowerSkylake : public Power {
public:
explicit PowerSkylake(fdf::MmioBuffer* mmio_space) : Power(mmio_space, &kPowerWellInfoSkylake) {}
void Resume() override {
if (ref_count().find(PowerWellId::PG2) != ref_count().end()) {
SetPowerWell(PowerWellId::PG2, /* enable */ true);
}
}
PowerWellRef GetCdClockPowerWellRef() override { return PowerWellRef(this, PowerWellId::PG1); }
PowerWellRef GetPipePowerWellRef(PipeId pipe_id) override {
return PowerWellRef(this, pipe_id == PipeId::PIPE_A ? PowerWellId::PG1 : PowerWellId::PG2);
}
PowerWellRef GetDdiPowerWellRef(DdiId ddi_id) override {
return PowerWellRef(this, ddi_id == DdiId::DDI_A ? PowerWellId::PG1 : PowerWellId::PG2);
}
bool GetDdiIoPowerState(DdiId ddi_id) override {
auto power_well = registers::PowerWellControl::Get().ReadFrom(mmio_space());
return power_well.ddi_io_power_state_skylake(ddi_id).get();
}
void SetDdiIoPowerState(DdiId ddi_id, bool enable) override {
auto power_well = registers::PowerWellControl::Get().ReadFrom(mmio_space());
power_well.ddi_io_power_request_skylake(ddi_id).set(1);
power_well.WriteTo(mmio_space());
}
bool GetAuxIoPowerState(DdiId ddi_id) override {
// Per https://patchwork.freedesktop.org/series/453/, toggling hardware
// resources that is controlled by DMC (display microcontroller) firmware
// is redundant and could interfere with firmware's functionality.
// Misc IO is controlled by DMC and it should be kept always on.
return true;
}
void SetAuxIoPowerState(DdiId ddi_id, bool enable) override {
// See comments above at GetAuxIoPowerState(). This method will not enable /
// disable Misc IO power on-demand.
}
private:
void SetPowerWell(PowerWellId power_well, bool enable) override {
const auto& power_well_info = power_well_info_map()->at(power_well);
constexpr int32_t kWaitForPwrWellCtlStateUs = 20;
constexpr int32_t kWaitForFuseStatusDistUs = 1;
auto ok = SetPowerWellImpl(power_well_info, enable, mmio_space(), kWaitForPwrWellCtlStateUs,
kWaitForFuseStatusDistUs);
ZX_DEBUG_ASSERT(ok);
}
std::unordered_set<DdiId> aux_io_enabled_ddis_;
};
// Dependencies between power wells from IHD-OS-TGL-Vol 12-12.21
// "Enable Sequence", pages 220-221.
//
// FUSE_STATUS bits from IHD-OS-TGL-Vol 2c-12.21 Part 1 pages 990-991.
// PWR_WELL_CTL bits from IHD-OS-TGL-Vol 2c-12.21 Part 2 pages 1063-1065.
const std::unordered_map<PowerWellId, PowerWellInfo> kPowerWellInfoTigerLake = {
// PG0 not tracked because it's managed by the CPU power controller.
{PowerWellId::PG1,
{
.name = "Power Well 1",
.always_on = true,
.state_bit_index = 0,
.request_bit_index = 1,
.fuse_dist_bit_index = 26,
.parent = PowerWellId::PG1,
}},
{PowerWellId::PG2,
{
.name = "Power Well 2",
.state_bit_index = 2,
.request_bit_index = 3,
.fuse_dist_bit_index = 25,
.parent = PowerWellId::PG1,
}},
{PowerWellId::PG3,
{
.name = "Power Well 3",
.state_bit_index = 4,
.request_bit_index = 5,
.fuse_dist_bit_index = 24,
.parent = PowerWellId::PG2,
}},
{PowerWellId::PG4,
{
.name = "Power Well 4",
.state_bit_index = 6,
.request_bit_index = 7,
.fuse_dist_bit_index = 23,
.parent = PowerWellId::PG3,
}},
{PowerWellId::PG5,
{
.name = "Power Well 5",
.state_bit_index = 8,
.request_bit_index = 9,
.fuse_dist_bit_index = 22,
.parent = PowerWellId::PG4,
}},
};
// Power well implementation for Tiger lake platforms.
class PowerTigerLake : public Power {
public:
explicit PowerTigerLake(fdf::MmioBuffer* mmio_space)
: Power(mmio_space, &kPowerWellInfoTigerLake) {}
void Resume() override {
constexpr PowerWellId kPowerWellEnableSeq[] = {
PowerWellId::PG2,
PowerWellId::PG3,
PowerWellId::PG4,
PowerWellId::PG5,
};
for (const auto power_well : kPowerWellEnableSeq) {
if (ref_count().find(power_well) != ref_count().end()) {
SetPowerWell(power_well, /* enable */ true);
}
}
}
PowerWellRef GetCdClockPowerWellRef() override { return PowerWellRef(this, PowerWellId::PG1); }
PowerWellRef GetPipePowerWellRef(PipeId pipe_id) override {
// Power well assignments from IHD-OS-TGL-Vol 12-12.21
// "Functions Within Each Well", pages 219-220.
// TODO(https://fxbug.dev/42177916): Add all pipes supported by gen12.
switch (pipe_id) {
case PipeId::PIPE_A:
return PowerWellRef(this, PowerWellId::PG1);
case PipeId::PIPE_B:
return PowerWellRef(this, PowerWellId::PG2);
case PipeId::PIPE_C:
return PowerWellRef(this, PowerWellId::PG3);
case PipeId::PIPE_INVALID:
ZX_ASSERT(false);
}
}
PowerWellRef GetDdiPowerWellRef(DdiId ddi_id) override {
// Power well assignments from IHD-OS-TGL-Vol 12-12.21
// "Functions Within Each Well", pages 219-220.
switch (ddi_id) {
case DdiId::DDI_A:
case DdiId::DDI_B:
case DdiId::DDI_C:
return PowerWellRef(this, PowerWellId::PG1);
case DdiId::DDI_TC_1:
case DdiId::DDI_TC_2:
case DdiId::DDI_TC_3:
case DdiId::DDI_TC_4:
case DdiId::DDI_TC_5:
case DdiId::DDI_TC_6:
return PowerWellRef(this, PowerWellId::PG3);
}
}
bool GetDdiIoPowerState(DdiId ddi_id) override {
auto power_well = registers::PowerWellControlDdi2::Get().ReadFrom(mmio_space());
return power_well.ddi_io_power_state_tiger_lake(ddi_id).get();
}
void SetDdiIoPowerState(DdiId ddi_id, bool enable) override {
auto power_well = registers::PowerWellControlDdi2::Get().ReadFrom(mmio_space());
power_well.ddi_io_power_request_tiger_lake(ddi_id).set(enable);
power_well.WriteTo(mmio_space());
}
bool GetAuxIoPowerState(DdiId ddi_id) override {
auto power_well = registers::PowerWellControlAux::Get().ReadFrom(mmio_space());
return power_well.powered_on_combo_or_usb_c(ddi_id);
}
void SetAuxIoPowerState(DdiId ddi_id, bool enable) override {
auto power_well = registers::PowerWellControlAux::Get().ReadFrom(mmio_space());
power_well.set_power_on_request_combo_or_usb_c(ddi_id, enable);
power_well.WriteTo(mmio_space());
}
private:
void SetPowerWell(PowerWellId power_well, bool enable) override {
const auto& power_well_info = power_well_info_map()->at(power_well);
constexpr int32_t kWaitForPwrWellCtlStateUs = 20;
constexpr int32_t kWaitForFuseStatusDistUs = 20;
auto ok = SetPowerWellImpl(power_well_info, enable, mmio_space(), kWaitForPwrWellCtlStateUs,
kWaitForFuseStatusDistUs);
ZX_DEBUG_ASSERT(ok);
}
};
} // namespace
PowerWellRef::PowerWellRef() {}
PowerWellRef::PowerWellRef(Power* power, PowerWellId power_well)
: power_(power), power_well_(power_well) {
power_->IncRefCount(power_well);
}
PowerWellRef::PowerWellRef(PowerWellRef&& o) : power_(o.power_), power_well_(o.power_well_) {
o.power_ = nullptr;
}
PowerWellRef& PowerWellRef::operator=(PowerWellRef&& o) {
if (power_) {
power_->DecRefCount(power_well_);
}
power_ = o.power_;
power_well_ = o.power_well_;
o.power_ = nullptr;
return *this;
}
PowerWellRef::~PowerWellRef() {
if (power_) {
power_->DecRefCount(power_well_);
}
}
Power::Power(fdf::MmioBuffer* mmio_space, const PowerWellInfoMap* power_well_info)
: mmio_space_(mmio_space), power_well_info_map_(power_well_info) {}
void Power::IncRefCount(PowerWellId power_well) {
ZX_DEBUG_ASSERT(power_well_info_map_ &&
power_well_info_map_->find(power_well) != power_well_info_map_->end());
auto power_well_info = power_well_info_map_->at(power_well);
auto parent = power_well_info.parent;
if (parent != power_well) {
IncRefCount(parent);
}
if (ref_count_.find(power_well) == ref_count_.end()) {
if (!power_well_info.always_on) {
SetPowerWell(power_well, /* enable */ true);
}
ref_count_[power_well] = 1;
} else {
++ref_count_[power_well];
}
}
void Power::DecRefCount(PowerWellId power_well) {
ZX_DEBUG_ASSERT(power_well_info_map_ &&
power_well_info_map_->find(power_well) != power_well_info_map_->end());
ZX_DEBUG_ASSERT(ref_count_.find(power_well) != ref_count_.end() && ref_count_.at(power_well) > 0);
auto power_well_info = power_well_info_map_->at(power_well);
if (ref_count_.at(power_well) == 1) {
if (!power_well_info.always_on) {
SetPowerWell(power_well, /* enable */ false);
}
ref_count_.erase(power_well);
} else {
--ref_count_[power_well];
}
auto parent = power_well_info.parent;
if (parent != power_well) {
DecRefCount(parent);
}
}
// static
std::unique_ptr<Power> Power::New(fdf::MmioBuffer* mmio_space, uint16_t device_id) {
if (is_skl(device_id) || is_kbl(device_id)) {
return std::make_unique<PowerSkylake>(mmio_space);
}
if (is_tgl(device_id)) {
return std::make_unique<PowerTigerLake>(mmio_space);
}
if (is_test_device(device_id)) {
return std::make_unique<TestPowerWell>(mmio_space);
}
ZX_DEBUG_ASSERT_MSG(false, "Device id %04x is not supported", device_id);
return nullptr;
}
} // namespace i915