blob: 017a5a59cba3aeacf8b7acc9111a0c795025ed9d [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-controller.h"
#include <lib/ddk/debug.h>
#include <lib/mmio/mmio-buffer.h>
#include <lib/zx/clock.h>
#include <lib/zx/result.h>
#include <lib/zx/time.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <cstdint>
#include <hwreg/bitfields.h>
#include "src/graphics/display/drivers/intel-i915/poll-until.h"
#include "src/graphics/display/drivers/intel-i915/registers-gt-mailbox.h"
#include "src/graphics/display/drivers/intel-i915/scoped-value-change.h"
namespace i915 {
namespace {
// The amount of microseconds to wait for PCU to complete a previous command.
//
// This should be at least as large as all known command timeouts below.
//
// Overridden in tests via
// PowerController::OverridePreviousCommandTimeoutUsForTesting().
int g_previous_command_timeout_us = 200;
// Timeout for the PCU firmware to reply to a voltage change request.
// Overridden in tests via
// PowerController::OverrideVoltageLevelRequestReplyTimeoutUsForTesting().
int g_voltage_level_request_reply_timeout_us = 150;
// Timeout for the PCU firmware to execute a voltage change request.
// Overridden in tests via
// PowerController::OverrideVoltageLevelRequestTotalTimeoutUsForTesting().
int g_voltage_level_request_total_timeout_us = 3'000; // 3ms
// Timeout for the PCU firmware to reply to a TCCOLD blocking change request.
// Overridden in tests via
// PowerController::OverrideTypeCColdBlockingChangeReplyTimeoutUsForTesting().
int g_typec_cold_blocking_change_reply_timeout_us = 200;
// Timeout for the PCU firmware to execute a TCCOLD blocking change request.
// Overridden in tests via
// PowerController::OverrideTypeCColdBlockingChangeTotalTimeoutUsForTesting().
int g_typec_cold_blocking_change_total_timeout_us = 600;
// Timeout for the PCU firmware to reply to a SAGV enablement change request.
// Overridden in tests via
// PowerController::OverrideSystemAgentEnablementChangeReplyTimeoutUsForTesting().
int g_system_agent_enablement_change_reply_timeout_us = 150;
// Timeout for the PCU firmware to execute a SAGV enablement change request.
// Overridden in tests via
// PowerController::OverrideSystemAgentEnablementChangeTotalTimeoutUsForTesting().
int g_system_agent_enablement_change_total_timeout_us = 1'000; // 1ms
// Timeout for the PCU firmware to reply to a memory subsystem info request.
// Overridden in tests via
// PowerController::OverrideGetMemorySubsystemInfoReplyTimeoutUsForTesting().
int g_get_memory_subsystem_info_reply_timeout_us = 150;
// Timeout for the PCU firmware to reply to a memory latency info request.
// Overridden in tests via
// PowerController::OverrideGetMemoryLatencyReplyTimeoutUsForTesting().
int g_get_memory_latency_reply_timeout_us = 100;
} // namespace
PowerController::PowerController(fdf::MmioBuffer* mmio_buffer) : mmio_buffer_(mmio_buffer) {
ZX_DEBUG_ASSERT(mmio_buffer);
}
zx::result<uint64_t> PowerController::Transact(PowerControllerCommand command) {
auto mailbox_interface = registers::PowerMailboxInterface::Get().FromValue(0);
if (!PollUntil([&] { return !mailbox_interface.ReadFrom(mmio_buffer_).has_active_transaction(); },
zx::usec(1), g_previous_command_timeout_us)) {
zxlogf(WARNING, "Timed out while waiting for PCU to finish pre-existing work");
return zx::error_result(ZX_ERR_IO_MISSED_DEADLINE);
}
auto mailbox_data0 = registers::PowerMailboxData0::Get().FromValue(0);
mailbox_data0.set_reg_value(static_cast<uint32_t>(command.data)).WriteTo(mmio_buffer_);
auto mailbox_data1 = registers::PowerMailboxData1::Get().FromValue(0);
mailbox_data1.set_reg_value(static_cast<uint32_t>(command.data >> 32)).WriteTo(mmio_buffer_);
mailbox_interface.set_command_code(command.command)
.set_param1(command.param1)
.set_param2(command.param2)
.set_has_active_transaction(true)
.WriteTo(mmio_buffer_);
if (command.timeout_us == 0) {
return zx::ok(0);
}
if (!PollUntil([&] { return !mailbox_interface.ReadFrom(mmio_buffer_).has_active_transaction(); },
zx::usec(1), command.timeout_us)) {
return zx::error_result(ZX_ERR_IO_MISSED_DEADLINE);
}
const uint32_t data_low = mailbox_data0.ReadFrom(mmio_buffer_).reg_value();
const uint32_t data_high = mailbox_data1.ReadFrom(mmio_buffer_).reg_value();
const uint64_t data = (uint64_t{data_high} << 32) | data_low;
return zx::ok(data);
}
zx::result<> PowerController::RequestDisplayVoltageLevel(int voltage_level,
RetryBehavior retry_behavior) {
// This operation is documented in the Clocking sections in Intel's display
// engine PRMs.
//
// Tiger Lake: IHD-OS-TGL-Vol 12-1.22-Rev2.0 section "Display Voltage
// Frequency Switching" > "Sequence Before Frequency Change" and
// "Sequence After Frequency Change", page 195
// Kaby Lake: IHD-OS-KBL-Vol 12-1.17 "Sequences for Changing CD Clock
// Frequency", pages 138-139
// Skylake: IHD-OS-SKL-Vol 12-05.16 "Skylake Sequences for Changing CD Clock
// Frequency", pages 135-136
// ZX_DEBUG_ASSERT() is appropriate for most cases where individual parameters
// are set incorrectly, but only correct MMIO addresses are accessed. However,
// confusing the PCU firmware can have pretty catastrophic consequences for
// the system, so we're very strict here.
ZX_ASSERT(voltage_level >= 0);
ZX_ASSERT(voltage_level <= 3);
const zx::time deadline =
(retry_behavior == RetryBehavior::kRetryUntilStateChanges)
? zx::deadline_after(zx::usec(g_voltage_level_request_total_timeout_us))
: zx::time::infinite_past();
do {
zx::result<uint64_t> mailbox_result = Transact({
.command = 0x07,
.data = static_cast<uint64_t>(voltage_level),
.timeout_us = g_voltage_level_request_reply_timeout_us,
});
if (mailbox_result.is_error()) {
return mailbox_result.take_error();
}
const bool success = (mailbox_result.value() & 1) == 1;
if (success) {
return zx::ok();
}
} while (zx::clock::get_monotonic() < deadline);
return zx::error_result(ZX_ERR_IO_REFUSED);
}
zx::result<> PowerController::SetDisplayTypeCColdBlockingTigerLake(bool blocked,
RetryBehavior retry_behavior) {
// This operation is documented in IHD-OS-TGL-Vol 12-1.22-Rev2.0, sections
// "GT Driver Mailbox to Block TCCOLD" and "GT Driver Mailbox to Unblock
// TCCOLD" sections in Intel's display engine PRMs.
//
// IHD-OS-LKF-Vol 12-4.21 also documents the TCCOLD concept, but Lakefield's
// PCU firmware uses a different API for managing TCCOLD.
const zx::time deadline =
(retry_behavior == RetryBehavior::kRetryUntilStateChanges)
? zx::deadline_after(zx::usec(g_typec_cold_blocking_change_total_timeout_us))
: zx::time::infinite_past();
const uint64_t command_data = blocked ? 0 : 1;
do {
zx::result<uint64_t> mailbox_result = Transact({
.command = 0x26,
.data = command_data,
.timeout_us = g_typec_cold_blocking_change_reply_timeout_us,
});
if (mailbox_result.is_error()) {
return mailbox_result.take_error();
}
const bool type_c_controller_in_cold_state = (mailbox_result.value() & 1) == 1;
if (type_c_controller_in_cold_state != blocked) {
return zx::ok();
}
} while (zx::clock::get_monotonic() < deadline);
return zx::error_result(ZX_ERR_IO_REFUSED);
}
zx::result<> PowerController::SetSystemAgentGeyservilleEnabled(bool enabled,
RetryBehavior retry_behavior) {
// Tiger Lake: IHD-OS-TGL-Vol 12-1.22-Rev2.0 section
// "MAILBOX_GTDRIVER_CMD_DE_LTR_SETTING", pages 214-215
// DG1: IHD-OS-DG1-Vol 12-2.21 section ""MAILBOX_GTDRIVER_CMD_DE_LTR_SETTING",
// pages 171-172
// Kaby Lake: IHD-OS-KBL-Vol 12-1.17 "System Agent Geyserville (SAGV)", page
// 206
// Skylake: IHD-OS-SKL-Vol 12-05.16 "System Agent Geyserville (SAGV)", pages
// 197-198
const zx::time deadline =
(retry_behavior == RetryBehavior::kRetryUntilStateChanges)
? zx::deadline_after(zx::usec(g_system_agent_enablement_change_total_timeout_us))
: zx::time::infinite_past();
// The data is documented as the EL_THLD (Threshold) LTR (most likely "Latency
// Tolerance Reporting") override on Tiger Lake and DG1.
const uint64_t command_data = enabled ? 3 : 0;
do {
zx::result<uint64_t> mailbox_result = Transact({
.command = 0x21,
.data = command_data,
.timeout_us = g_system_agent_enablement_change_reply_timeout_us,
});
if (mailbox_result.is_error()) {
return mailbox_result.take_error();
}
const bool success = (mailbox_result.value() & 1) == 1;
if (success) {
return zx::ok();
}
} while (zx::clock::get_monotonic() < deadline);
return zx::error_result(ZX_ERR_IO_REFUSED);
}
zx::result<uint32_t> PowerController::GetSystemAgentBlockTimeUsTigerLake() {
// Documented in the "Display Watermark Programming" > "SAGV Block Time"
// section in the PRMs.
//
// Tiger Lake: IHD-OS-TGL-Vol 12-1.22-Rev2.0 pages 436-437
// DG1: IHD-OS-DG1-Vol 12-2.21 page 362
zx::result<uint64_t> mailbox_result = Transact({
.command = 0x23,
.param1 = 0,
.param2 = 0,
.data = 0,
.timeout_us = g_get_memory_latency_reply_timeout_us,
});
if (mailbox_result.is_error()) {
return mailbox_result.take_error();
}
// This PCU command returns an error code in the Command/Error Code field
// of the Mailbox Interface register.
auto mailbox_interface = registers::PowerMailboxInterface::Get().ReadFrom(mmio_buffer_);
if (mailbox_interface.command_code() != 0) {
return zx::error_result(ZX_ERR_IO_REFUSED);
}
return zx::ok(static_cast<uint32_t>(mailbox_result.value()));
}
zx::result<uint32_t> PowerController::GetSystemAgentBlockTimeUsKabyLake() {
// Documented in the "Display Watermark Programming" > "SAGV Block Time"
// section in the PRMs.
//
// Kaby Lake: IHD-OS-KBL-Vol 12-1.17 page 209
// Skylake: IHD-OS-SKL-Vol 12-05.16 page 200
return zx::ok(30);
}
zx::result<std::array<uint8_t, 8>> PowerController::GetRawMemoryLatencyDataUs() {
// Documented in the "Display Watermark Programming" > "Memory Values" section
// in the PRMs.
//
// Tiger Lake: IHD-OS-TGL-Vol 12-1.22-Rev2.0 pages 435-436
// DG1: IHD-OS-DG1-Vol 12-2.21 pages 361-362
// Kaby Lake: IHD-OS-KBL-Vol 12-1.17 pages 208-209
// Skylake: IHD-OS-SKL-Vol 12-05.16 pages 199-200
uint32_t latency_data[2];
for (int values_index = 0; values_index < 2; ++values_index) {
zx::result<uint64_t> mailbox_result = Transact({
.command = 0x06,
.param1 = 0,
.param2 = 0,
.data = static_cast<uint64_t>(values_index),
.timeout_us = g_get_memory_latency_reply_timeout_us,
});
if (mailbox_result.is_error()) {
return mailbox_result.take_error();
}
// This PCU command returns an error code in the Command/Error Code field
// of the Mailbox Interface register.
auto mailbox_interface = registers::PowerMailboxInterface::Get().ReadFrom(mmio_buffer_);
if (mailbox_interface.command_code() != 0) {
return zx::error_result(ZX_ERR_IO_REFUSED);
}
latency_data[values_index] = static_cast<uint32_t>(mailbox_result.value());
}
std::array<uint8_t, 8> latency_levels;
static_assert(sizeof(latency_data) == sizeof(latency_levels));
std::memcpy(latency_levels.data(), latency_data, sizeof(latency_data));
return zx::ok(latency_levels);
}
namespace {
// MAILBOX_GTRDIVER_CMD_MEM_SS_INFO_SUBCOMMAND_READ_GLOBAL_INFO result.
//
// Tiger Lake: IHD-OS-TGL-Vol 12-1.22-Rev2.0 pages 212-213
// DG1: IHD-OS-TGL-Vol 12-1.22-Rev2.0 pages 169-170
class MemorySubsystemGlobalConfig
: public hwreg::RegisterBase<MemorySubsystemGlobalConfig, uint64_t> {
public:
DEF_FIELD(11, 8, enabled_qgv_point_count);
DEF_FIELD(7, 4, populated_channel_count);
DEF_ENUM_FIELD(MemorySubsystemInfo::RamType, 3, 0, ddr_type_select);
static auto GetFromValue(uint64_t mailbox_data) {
return hwreg::RegisterAddr<MemorySubsystemGlobalConfig>(0).FromValue(mailbox_data);
}
};
// MAILBOX_GTRDIVER_CMD_MEM_SS_INFO_SUBCOMMAND_READ_QGV_POINT_INFO result.
//
//
// Tiger Lake: IHD-OS-TGL-Vol 12-1.22-Rev2.0 pages 212-213
// DG1: IHD-OS-TGL-Vol 12-1.22-Rev2.0 pages 169-170
class MemorySubsystemPointInfo : public hwreg::RegisterBase<MemorySubsystemPointInfo, uint64_t> {
public:
// DRAM timings. See `MemorySubsystemInfo` for explanations.
DEF_FIELD(48, 40, tras_dclks);
DEF_FIELD(39, 32, trdpre_dclks);
DEF_FIELD(31, 24, trcd_dclks);
DEF_FIELD(23, 16, trp_dclks);
// DRAM clock in multiples of 16.6666 MHz.
DEF_FIELD(15, 0, dclk_multiplier);
static auto GetFromValue(uint64_t mailbox_data) {
return hwreg::RegisterAddr<MemorySubsystemPointInfo>(0).FromValue(mailbox_data);
}
};
} // namespace
// static
MemorySubsystemInfo::GlobalInfo MemorySubsystemInfo::GlobalInfo::CreateFromMailboxDataTigerLake(
uint64_t mailbox_data) {
auto global_config = MemorySubsystemGlobalConfig::GetFromValue(mailbox_data);
return MemorySubsystemInfo::GlobalInfo{
.ram_type = global_config.ddr_type_select(),
.memory_channel_count = static_cast<int8_t>(global_config.populated_channel_count()),
.agent_point_count = static_cast<int8_t>(global_config.enabled_qgv_point_count()),
};
}
MemorySubsystemInfo::AgentPoint MemorySubsystemInfo::AgentPoint::CreateFromMailboxDataTigerLake(
uint64_t mailbox_data) {
auto point_info = MemorySubsystemPointInfo::GetFromValue(mailbox_data);
return MemorySubsystemInfo::AgentPoint{
// The cast is lossless because the underlying field is 16 bits. The
// multiplication does not overflow because the maximum result is
// 1,092,206,310 which fits in 31 bits.
.dram_clock_khz =
static_cast<int32_t>(static_cast<int32_t>(point_info.dclk_multiplier()) * 16'666),
// The casts are lossless because the underlying fields are 9 bits.
.row_precharge_to_open_cycles = static_cast<int16_t>(point_info.trp_dclks()),
.row_access_to_column_access_delay_cycles = static_cast<int16_t>(point_info.trcd_dclks()),
.read_to_precharge_cycles = static_cast<int16_t>(point_info.trdpre_dclks()),
.row_activate_to_precharge_cycles = static_cast<int16_t>(point_info.tras_dclks()),
};
}
zx::result<MemorySubsystemInfo> PowerController::GetMemorySubsystemInfoTigerLake() {
// Documented in the "Mailbox Commands" > "MAILBOX_GTRDIVER_CMD_MEM_SS_INFO"
// section of the PRMs.
//
// Tiger Lake: IHD-OS-TGL-Vol 12-1.22-Rev2.0 pages 212-213
// DG1: IHD-OS-TGL-Vol 12-1.22-Rev2.0 pages 169-170
MemorySubsystemInfo result;
{
// MAILBOX_GTRDIVER_CMD_MEM_SS_INFO_SUBCOMMAND_READ_GLOBAL_INFO in the PRM.
zx::result<uint64_t> global_info = Transact({
.command = 0x0d,
.param1 = 0,
.param2 = 0,
.data = 0,
.timeout_us = g_get_memory_subsystem_info_reply_timeout_us,
});
if (global_info.is_error()) {
return global_info.take_error();
}
zxlogf(TRACE, "MAILBOX_GTRDIVER_CMD_MEM_SS_INFO_SUBCOMMAND_READ_GLOBAL_INFO - %lx",
global_info.value());
result.global_info =
MemorySubsystemInfo::GlobalInfo::CreateFromMailboxDataTigerLake(global_info.value());
}
const int point_count = result.global_info.agent_point_count;
for (int point_index = 0; point_index < point_count; ++point_index) {
// MAILBOX_GTRDIVER_CMD_MEM_SS_INFO_SUBCOMMAND_READ_QGV_POINT_INFO in the
// PRM.
zx::result<uint64_t> point_info = Transact({
.command = 0x0d,
.param1 = 1,
.param2 = static_cast<uint8_t>(point_index),
.data = 0,
.timeout_us = g_get_memory_subsystem_info_reply_timeout_us,
});
if (point_info.is_error()) {
return point_info.take_error();
}
// This PCU command returns an error code in the Command/Error Code field
// of the Mailbox Interface register.
auto mailbox_interface = registers::PowerMailboxInterface::Get().ReadFrom(mmio_buffer_);
if (mailbox_interface.command_code() != 0) {
return zx::error_result(ZX_ERR_IO_REFUSED);
}
zxlogf(TRACE, "MAILBOX_GTRDIVER_CMD_MEM_SS_INFO_SUBCOMMAND_READ_QGV_POINT_INFO - %lx",
point_info.value());
result.points[point_index] =
MemorySubsystemInfo::AgentPoint::CreateFromMailboxDataTigerLake(point_info.value());
}
return zx::ok(result);
}
// static
ScopedValueChange<int> PowerController::OverridePreviousCommandTimeoutUsForTesting(int timeout_us) {
return ScopedValueChange(g_previous_command_timeout_us, timeout_us);
}
// static
ScopedValueChange<int> PowerController::OverrideVoltageLevelRequestReplyTimeoutUsForTesting(
int timeout_us) {
return ScopedValueChange(g_voltage_level_request_reply_timeout_us, timeout_us);
}
// static
ScopedValueChange<int> PowerController::OverrideVoltageLevelRequestTotalTimeoutUsForTesting(
int timeout_us) {
return ScopedValueChange(g_voltage_level_request_total_timeout_us, timeout_us);
}
// static
ScopedValueChange<int> PowerController::OverrideTypeCColdBlockingChangeReplyTimeoutUsForTesting(
int timeout_us) {
return ScopedValueChange(g_typec_cold_blocking_change_reply_timeout_us, timeout_us);
}
// static
ScopedValueChange<int> PowerController::OverrideTypeCColdBlockingChangeTotalTimeoutUsForTesting(
int timeout_us) {
return ScopedValueChange(g_typec_cold_blocking_change_total_timeout_us, timeout_us);
}
// static
ScopedValueChange<int> PowerController::OverrideSystemAgentEnablementChangeReplyTimeoutUsForTesting(
int timeout_us) {
return ScopedValueChange(g_system_agent_enablement_change_reply_timeout_us, timeout_us);
}
// static
ScopedValueChange<int> PowerController::OverrideSystemAgentEnablementChangeTotalTimeoutUsForTesting(
int timeout_us) {
return ScopedValueChange(g_system_agent_enablement_change_total_timeout_us, timeout_us);
}
// static
ScopedValueChange<int> PowerController::OverrideGetMemorySubsystemInfoReplyTimeoutUsForTesting(
int timeout_us) {
return ScopedValueChange(g_get_memory_subsystem_info_reply_timeout_us, timeout_us);
}
// static
ScopedValueChange<int> PowerController::OverrideGetMemoryLatencyReplyTimeoutUsForTesting(
int timeout_us) {
return ScopedValueChange(g_get_memory_latency_reply_timeout_us, timeout_us);
}
} // namespace i915