blob: ac5faa45840270d1ed9e976a946a64f278f581e2 [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/ddi-physical-layer.h"
#include <lib/ddk/debug.h>
#include <lib/fit/defer.h>
#include <lib/mmio/mmio-buffer.h>
#include <lib/zx/result.h>
#include <zircon/assert.h>
#include <cinttypes>
#include <cstdint>
#include <fbl/string_printf.h>
#include "src/graphics/display/drivers/intel-i915/ddi-physical-layer-internal.h"
#include "src/graphics/display/drivers/intel-i915/hardware-common.h"
#include "src/graphics/display/drivers/intel-i915/intel-i915.h"
#include "src/graphics/display/drivers/intel-i915/poll-until.h"
#include "src/graphics/display/drivers/intel-i915/power-controller.h"
#include "src/graphics/display/drivers/intel-i915/registers-ddi-phy-tiger-lake.h"
#include "src/graphics/display/drivers/intel-i915/registers-typec.h"
namespace i915 {
namespace {
const char* DdiTypeToString(DdiPhysicalLayer::DdiType type) {
switch (type) {
case DdiPhysicalLayer::DdiType::kCombo:
return "COMBO";
case DdiPhysicalLayer::DdiType::kTypeC:
return "Type-C";
}
}
const char* PortTypeToString(DdiPhysicalLayer::ConnectionType type) {
switch (type) {
case DdiPhysicalLayer::ConnectionType::kNone:
return "None";
case DdiPhysicalLayer::ConnectionType::kBuiltIn:
return "Built In";
case DdiPhysicalLayer::ConnectionType::kTypeCDisplayPortAltMode:
return "Type-C DisplayPort Alt Mode";
case DdiPhysicalLayer::ConnectionType::kTypeCThunderbolt:
return "Type-C Thunderbolt Mode";
break;
}
}
} // namespace
void DdiPhysicalLayer::AddRef() {
ZX_DEBUG_ASSERT(IsEnabled());
++ref_count_;
zxlogf(TRACE, "DdiPhysicalLayer: Reference count of DDI %d increased to %d", ddi_id(),
ref_count_);
}
void DdiPhysicalLayer::Release() {
ZX_DEBUG_ASSERT(ref_count_ > 0);
if (--ref_count_ == 0) {
zxlogf(TRACE, "DdiPhysicalLayer: Reference count of DDI %d decreased to %d", ddi_id(),
ref_count_);
if (!Disable()) {
zxlogf(ERROR, "DdiPhysicalLayer: Failed to disable unused DDI %d", ddi_id());
}
}
}
fbl::String DdiPhysicalLayer::PhysicalLayerInfo::DebugString() const {
return fbl::StringPrintf("PhysicalLayerInfo { type: %s, port: %s, max_allowed_dp_lane: %u }",
DdiTypeToString(ddi_type), PortTypeToString(connection_type),
max_allowed_dp_lane_count);
}
bool DdiSkylake::Enable() {
if (enabled_) {
zxlogf(WARNING, "DDI %d: Enable: PHY already enabled", ddi_id());
}
enabled_ = true;
return true;
}
bool DdiSkylake::Disable() {
if (!enabled_) {
zxlogf(WARNING, "DDI %d: Disable: PHY already disabled", ddi_id());
}
enabled_ = false;
return true;
}
DdiPhysicalLayer::PhysicalLayerInfo DdiSkylake::GetPhysicalLayerInfo() const {
return {
.ddi_type = DdiPhysicalLayer::DdiType::kCombo,
.connection_type = DdiPhysicalLayer::ConnectionType::kBuiltIn,
.max_allowed_dp_lane_count = 4u,
};
}
ComboDdiTigerLake::ComboDdiTigerLake(DdiId ddi_id, fdf::MmioBuffer* mmio_space)
: DdiPhysicalLayer(ddi_id), mmio_space_(mmio_space) {
ZX_DEBUG_ASSERT(mmio_space);
}
bool ComboDdiTigerLake::Enable() {
if (enabled_) {
zxlogf(WARNING, "DDI %d: Enable: PHY already enabled", ddi_id());
}
enabled_ = true;
return true;
}
bool ComboDdiTigerLake::Disable() {
if (!enabled_) {
zxlogf(WARNING, "DDI %d: Enable: PHY already disabled", ddi_id());
}
enabled_ = false;
return true;
}
namespace {
struct TigerLakeProcessCompensationConfig {
struct VoltageReferences {
struct Pair {
uint16_t low = 0;
uint16_t high = 0;
// True for default-constructed values.
bool IsEmpty() const { return low == 0 && high == 0; }
};
Pair negative, positive;
// True for default-constructed values.
bool IsEmpty() const { return negative.IsEmpty() && positive.IsEmpty(); }
};
VoltageReferences nominal, low;
// True for default-constructed values.
bool IsEmpty() const { return nominal.IsEmpty() && low.IsEmpty(); }
};
TigerLakeProcessCompensationConfig ReadTigerLakeProcessCompensationConfig(
DdiId ddi_id, fdf::MmioBuffer* mmio_space) {
auto compensation1 = registers::PortCompensation1::GetForDdi(ddi_id).ReadFrom(mmio_space);
auto compensation_nominal =
registers::PortCompensationNominalVoltageReferences::GetForDdi(ddi_id).ReadFrom(mmio_space);
auto compensation_low =
registers::PortCompensationLowVoltageReferences::GetForDdi(ddi_id).ReadFrom(mmio_space);
zxlogf(TRACE, "DDI %d PORT_COMP_DW1: %08x PORT_COMP_DW_9: %08x PORT_COMP_DW10: %08x", ddi_id,
compensation1.reg_value(), compensation_nominal.reg_value(), compensation_low.reg_value());
return TigerLakeProcessCompensationConfig{
.nominal =
{
.negative =
{
.low = static_cast<uint16_t>(
compensation_nominal
.negative_nominal_voltage_reference_low_value_bits70() |
(compensation1.negative_nominal_voltage_reference_low_value_bits98()
<< 8)),
.high = static_cast<uint16_t>(
compensation_nominal
.negative_nominal_voltage_reference_high_value_bits70() |
(compensation1.negative_nominal_voltage_reference_high_value_bits98()
<< 8)),
},
.positive =
{
.low = static_cast<uint16_t>(
compensation_nominal
.positive_nominal_voltage_reference_low_value_bits70() |
(compensation1.positive_nominal_voltage_reference_low_value_bits98()
<< 8)),
.high = static_cast<uint16_t>(
compensation_nominal
.positive_nominal_voltage_reference_high_value_bits70() |
(compensation1.positive_nominal_voltage_reference_high_value_bits98()
<< 8)),
},
},
.low =
{
.negative{
.low = static_cast<uint16_t>(
compensation_low.negative_low_voltage_reference_low_value_bits70() |
(compensation1.negative_low_voltage_reference_low_value_bits98() << 8)),
.high = static_cast<uint16_t>(
compensation_low.negative_low_voltage_reference_high_value_bits70() |
(compensation1.negative_low_voltage_reference_high_value_bits98() << 8)),
},
.positive{
.low = static_cast<uint16_t>(
compensation_low.positive_low_voltage_reference_low_value_bits70() |
(compensation1.positive_low_voltage_reference_low_value_bits98() << 8)),
.high = static_cast<uint16_t>(
compensation_low.positive_low_voltage_reference_high_value_bits70() |
(compensation1.positive_low_voltage_reference_high_value_bits98() << 8)),
},
},
};
}
void WriteTigerLakeProcessCompensationConfig(const TigerLakeProcessCompensationConfig& config,
DdiId ddi_id, fdf::MmioBuffer* mmio_space) {
auto compensation1 = registers::PortCompensation1::GetForDdi(ddi_id).ReadFrom(mmio_space);
compensation1.set_negative_low_voltage_reference_low_value_bits98(config.low.negative.low >> 8)
.set_negative_low_voltage_reference_high_value_bits98(config.low.negative.high >> 8)
.set_positive_low_voltage_reference_low_value_bits98(config.low.positive.low >> 8)
.set_positive_low_voltage_reference_high_value_bits98(config.low.positive.high >> 8)
.set_negative_nominal_voltage_reference_low_value_bits98(config.nominal.negative.low >> 8)
.set_negative_nominal_voltage_reference_high_value_bits98(config.nominal.negative.high >> 8)
.set_positive_nominal_voltage_reference_low_value_bits98(config.nominal.positive.low >> 8)
.set_positive_nominal_voltage_reference_high_value_bits98(config.nominal.positive.high >> 8)
.WriteTo(mmio_space);
auto compensation_nominal =
registers::PortCompensationNominalVoltageReferences::GetForDdi(ddi_id).FromValue(0);
compensation_nominal
.set_negative_nominal_voltage_reference_low_value_bits70(config.nominal.negative.low & 0xff)
.set_negative_nominal_voltage_reference_high_value_bits70(config.nominal.negative.high & 0xff)
.set_positive_nominal_voltage_reference_low_value_bits70(config.nominal.positive.low & 0xff)
.set_positive_nominal_voltage_reference_high_value_bits70(config.nominal.positive.high & 0xff)
.WriteTo(mmio_space);
auto compensation_low =
registers::PortCompensationLowVoltageReferences::GetForDdi(ddi_id).FromValue(0);
compensation_low
.set_negative_low_voltage_reference_low_value_bits70(config.low.negative.low & 0xff)
.set_negative_low_voltage_reference_high_value_bits70(config.low.negative.high & 0xff)
.set_positive_low_voltage_reference_low_value_bits70(config.low.positive.low & 0xff)
.set_positive_low_voltage_reference_high_value_bits70(config.low.positive.high & 0xff)
.WriteTo(mmio_space);
}
// Returns an empty configuration for unsupported process monitor values.
TigerLakeProcessCompensationConfig ProcessCompensationConfigFor(
registers::PortCompensationStatus::ProcessSelect process,
registers::PortCompensationStatus::VoltageSelect voltage) {
switch (voltage) {
case registers::PortCompensationStatus::VoltageSelect::k850mv:
switch (process) {
case registers::PortCompensationStatus::ProcessSelect::kDot0:
return TigerLakeProcessCompensationConfig{
.nominal = {.negative = {.low = 0x62, .high = 0xab},
.positive = {.low = 0x67, .high = 0xbb}},
.low = {.negative = {.low = 0x51, .high = 0x91},
.positive = {.low = 0x4f, .high = 0x96}}};
case registers::PortCompensationStatus::ProcessSelect::kDot1:
case registers::PortCompensationStatus::ProcessSelect::kDot4:
break;
};
break;
case registers::PortCompensationStatus::VoltageSelect::k950mv:
switch (process) {
case registers::PortCompensationStatus::ProcessSelect::kDot0:
return TigerLakeProcessCompensationConfig{
.nominal = {.negative = {.low = 0x86, .high = 0xe1},
.positive = {.low = 0x72, .high = 0xc7}},
.low = {.negative = {.low = 0x77, .high = 0xca},
.positive = {.low = 0x5e, .high = 0xab}}};
case registers::PortCompensationStatus::ProcessSelect::kDot1:
return TigerLakeProcessCompensationConfig{
.nominal = {.negative = {.low = 0x93, .high = 0xf8},
.positive = {.low = 0x7e, .high = 0xf1}},
.low = {.negative = {.low = 0x8a, .high = 0xe8},
.positive = {.low = 0x71, .high = 0xc5}}};
case registers::PortCompensationStatus::ProcessSelect::kDot4:
break;
};
break;
case registers::PortCompensationStatus::VoltageSelect::k1050mv:
switch (process) {
case registers::PortCompensationStatus::ProcessSelect::kDot0:
return TigerLakeProcessCompensationConfig{
.nominal = {.negative = {.low = 0x98, .high = 0xfa},
.positive = {.low = 0x82, .high = 0xdd}},
.low = {.negative = {.low = 0x89, .high = 0xe4},
.positive = {.low = 0x6d, .high = 0xc1}}};
case registers::PortCompensationStatus::ProcessSelect::kDot1:
return TigerLakeProcessCompensationConfig{
.nominal = {.negative = {.low = 0x9a, .high = 0x100},
.positive = {.low = 0xab, .high = 0x125}},
.low = {.negative = {.low = 0x8a, .high = 0xe3},
.positive = {.low = 0x8f, .high = 0xf1}}};
case registers::PortCompensationStatus::ProcessSelect::kDot4:
break;
};
};
zxlogf(ERROR, "Undocumented process/voltage combination");
return {};
}
} // namespace
bool ComboDdiTigerLake::Initialize() {
// This implements the section "Digital Display Interface" > "Combo PHY
// Initialization Sequence" in display engine PRMs.
// Tiger Lake: IHD-OS-TGL-Vol 12-1.22-Rev2.0 pages 391-392
// DG1: IHD-OS-DG1-Vol 12-2.21 pages 337-338
// Ice Lake: IHD-OS-ICLLP-Vol 12-1.22-Rev2.0 pages 334-335
// TODO(https://fxbug.dev/42065111): Implement the compensation source dependency
// between DDI A and DDIs B-C.
auto procmon_status =
registers::PortCompensationStatus::GetForDdi(ddi_id()).ReadFrom(mmio_space_);
{
const char* process_name;
switch (procmon_status.process_select()) {
case registers::PortCompensationStatus::ProcessSelect::kDot0:
process_name = "dot-0";
break;
case registers::PortCompensationStatus::ProcessSelect::kDot1:
process_name = "dot-1";
break;
case registers::PortCompensationStatus::ProcessSelect::kDot4:
process_name = "dot-4";
break;
default:
zxlogf(WARNING, "DDI %d process monitor reports undocumented process variation %" PRIu32,
ddi_id(), static_cast<unsigned int>(procmon_status.process_select()));
process_name = "dot-undocumented";
};
const char* voltage_name;
switch (procmon_status.voltage_select()) {
case registers::PortCompensationStatus::VoltageSelect::k850mv:
voltage_name = "0.85v";
break;
case registers::PortCompensationStatus::VoltageSelect::k950mv:
voltage_name = "0.95v";
break;
case registers::PortCompensationStatus::VoltageSelect::k1050mv:
voltage_name = "1.05v";
break;
default:
zxlogf(WARNING, "DDI %d process monitor reports undocumented voltage variation %" PRIu32,
ddi_id(), static_cast<unsigned int>(procmon_status.voltage_select()));
voltage_name = "undocumented-v";
};
zxlogf(TRACE, "DDI %d Process variation: %s %s, Process monitor done: %s ", ddi_id(),
process_name, voltage_name, procmon_status.process_monitor_done() ? "yes" : "no");
zxlogf(TRACE,
"DDI %d Current comp: %u%s%s, MIPI LPDn code: %u%s%s, First compensation done: %s",
ddi_id(), procmon_status.current_compensation_code(),
procmon_status.current_compensation_code_maxout() ? " maxout" : "",
procmon_status.current_compensation_code_minout() ? " minout" : "",
procmon_status.mipi_low_power_data_negative_code(),
procmon_status.mipi_low_power_data_negative_code_maxout() ? " maxout" : "",
procmon_status.mipi_low_power_data_negative_code_minout() ? " minout" : "",
procmon_status.first_compensation_done() ? "yes" : "no");
}
{
TigerLakeProcessCompensationConfig process_compensation =
ReadTigerLakeProcessCompensationConfig(ddi_id(), mmio_space_);
zxlogf(
TRACE,
"DDI %d Process monitor nominal voltage references: -ve low %x high %x, +ve low %x high %x",
ddi_id(), process_compensation.nominal.negative.low,
process_compensation.nominal.negative.high, process_compensation.nominal.positive.low,
process_compensation.nominal.positive.high);
zxlogf(TRACE,
"DDI %d Process monitor low voltage references: -ve low %x high %x, +ve low %x high %x",
ddi_id(), process_compensation.low.negative.low, process_compensation.low.negative.high,
process_compensation.low.positive.low, process_compensation.low.positive.high);
}
auto common_lane5 = registers::PortCommonLane5::GetForDdi(ddi_id()).ReadFrom(mmio_space_);
zxlogf(TRACE,
"DDI %d PORT_CL_DW5: %08x, common lane power down %s, suspend clock config %d, "
"downlink broadcast %s, force %02x, CRI clock: count max %d select %d, "
"IOSF PD: count %d divider select %d, PHY power ack override %s, "
"staggering: port %s power gate %s, fuse flags: %s %s %s",
ddi_id(), common_lane5.reg_value(),
common_lane5.common_lane_power_down_enabled() ? "enabled" : "disabled",
common_lane5.suspend_clock_config(),
common_lane5.downlink_broadcast_enable() ? "enabled" : "disabled", common_lane5.force(),
common_lane5.common_register_interface_clock_count_max(),
common_lane5.common_register_interface_clock_select(),
common_lane5.onchip_system_fabric_presence_detection_count(),
common_lane5.onchip_system_fabric_clock_divider_select(),
common_lane5.phy_power_ack_override() ? "enabled" : "disabled",
common_lane5.port_staggering_enabled() ? "enabled" : "disabled",
common_lane5.port_staggering_enabled() ? "enabled" : "disabled",
common_lane5.fuse_valid_override() ? "valid override" : "-",
common_lane5.fuse_valid_reset() ? "valid reset" : "-",
common_lane5.fuse_repull() ? "repull" : "-");
static constexpr registers::PortLane kAllLanes[] = {
registers::PortLane::kAux, registers::PortLane::kMainLinkLane0,
registers::PortLane::kMainLinkLane1, registers::PortLane::kMainLinkLane2,
registers::PortLane::kMainLinkLane3};
for (registers::PortLane lane : kAllLanes) {
auto transmitter_dcc =
registers::PortTransmitterDutyCycleCorrection::GetForDdiLane(ddi_id(), lane)
.ReadFrom(mmio_space_);
zxlogf(TRACE,
"DDI %d Lane %d PORT_TX_DW8: %08x, output DCC clock: select %d divider select %d, "
"output DCC code: override %s %d limits %d - %d, output DCC fuse %s, "
"input DCC code: %d thermal %d",
ddi_id(), static_cast<int>(lane), transmitter_dcc.reg_value(),
transmitter_dcc.output_duty_cycle_correction_clock_select(),
static_cast<int>(transmitter_dcc.output_duty_cycle_correction_clock_divider_select()),
transmitter_dcc.output_duty_cycle_correction_code_override_valid() ? "valid" : "invalid",
transmitter_dcc.output_duty_cycle_correction_code_override(),
transmitter_dcc.output_duty_cycle_correction_lower_limit(),
transmitter_dcc.output_duty_cycle_correction_upper_limit(),
transmitter_dcc.output_duty_cycle_correction_fuse_enabled() ? "enabled" : "disabled",
transmitter_dcc.input_duty_cycle_correction_code(),
(transmitter_dcc.input_duty_cycle_correction_thermal_bits43() << 2) |
transmitter_dcc.input_duty_cycle_correction_thermal_bits20());
auto physical_coding1 =
registers::PortPhysicalCoding1::GetForDdiLane(ddi_id(), lane).ReadFrom(mmio_space_);
zxlogf(TRACE,
"DDI %d Lane %d PORT_PCS_DW1: %08x, power-gated %s, DCC schedule %d, "
"DCC calibration: force %s bypass %s on wake %s, clock request %d, "
"commmon keeper: %s / %s while power-gated / bias control %d, latency optimization %d, "
"soft lane reset: %s %s, transmitter fifo reset override: %s %s, "
"transmiter de-emphasis %d, TBC as symbol clock %s",
ddi_id(), static_cast<int>(lane), physical_coding1.reg_value(),
physical_coding1.power_gate_powered_down() ? "yes" : "no",
static_cast<int>(physical_coding1.duty_cycle_correction_schedule_select()),
physical_coding1.force_transmitter_duty_cycle_correction_calibration() ? "yes" : "no",
physical_coding1.duty_cycle_correction_calibration_bypassed() ? "enabled" : "disabled",
physical_coding1.duty_cycle_correction_calibration_on_wake() ? "yes" : "no",
physical_coding1.clock_request(),
physical_coding1.common_mode_keeper_enabled() ? "enabled" : "disabled",
physical_coding1.common_mode_keeper_enabled_while_power_gated() ? "enabled" : "disabled",
physical_coding1.common_mode_keeper_bias_control(),
physical_coding1.latency_optimization_value(),
physical_coding1.soft_lane_reset() ? "on" : "off",
physical_coding1.soft_lane_reset_valid() ? "valid" : "invalid",
physical_coding1.transmitter_fifo_reset_main_override() ? "on" : "off",
physical_coding1.transmitter_fifo_reset_main_override_valid() ? "valid" : "invalid",
physical_coding1.transmitter_deemphasis_value(),
physical_coding1.use_transmitter_buffer_clock_as_symbol_clock() ? "yes" : "no");
}
auto phy_misc = registers::PhyMisc::GetForDdi(ddi_id()).ReadFrom(mmio_space_);
zxlogf(TRACE, "DDI %d PHY_MISC %08x, DE to IO: %x, IO to DE: %x, Comp power down: %s", ddi_id(),
phy_misc.reg_value(), phy_misc.display_engine_to_io(), phy_misc.io_to_display_engine(),
phy_misc.compensation_resistors_powered_down() ? "enabled" : "disabled");
auto compensation_source =
registers::PortCompensationSource::GetForDdi(ddi_id()).ReadFrom(mmio_space_);
zxlogf(TRACE,
"DDI %d PORT_COMP_DW8 %08x, internal reference generation %s, periodic compensation %s",
ddi_id(), compensation_source.reg_value(),
compensation_source.generate_internal_references() ? "enabled" : "disabled",
compensation_source.periodic_current_compensation_disabled() ? "disabled" : "enabled");
auto compensation_initialized =
registers::PortCompensation0::GetForDdi(ddi_id()).ReadFrom(mmio_space_);
zxlogf(TRACE, "DDI %d PORT_COMP_DW0: %08x PORT_COMP_DW3: %08x ", ddi_id(),
compensation_initialized.reg_value(), procmon_status.reg_value());
if (compensation_initialized.initialized()) {
// The PRMs advise that we consider the PHY initialized if this bit is set,
// and skip the entire initialize process. A more robust approach would be
// to reset (de-initialize, initialize) the PHY if its current configuration
// doesn't match what we expect.
zxlogf(TRACE, "DDI %d PHY already initialized. Assuming everything is correct.", ddi_id());
return true;
}
for (registers::PortLane lane : kAllLanes) {
auto transmitter_dcc =
registers::PortTransmitterDutyCycleCorrection::GetForDdiLane(ddi_id(), lane)
.ReadFrom(mmio_space_);
transmitter_dcc.set_output_duty_cycle_correction_clock_select(1)
.set_output_duty_cycle_correction_clock_divider_select(
registers::PortTransmitterDutyCycleCorrection::ClockDividerSelect::k2)
.WriteTo(mmio_space_);
auto physical_coding1 =
registers::PortPhysicalCoding1::GetForDdiLane(ddi_id(), lane).ReadFrom(mmio_space_);
physical_coding1
.set_duty_cycle_correction_schedule_select(
registers::PortPhysicalCoding1::DutyCycleCorrectionScheduleSelect::kContinuously)
.WriteTo(mmio_space_);
}
phy_misc.set_compensation_resistors_powered_down(false).WriteTo(mmio_space_);
TigerLakeProcessCompensationConfig process_compensation = ProcessCompensationConfigFor(
procmon_status.process_select(), procmon_status.voltage_select());
if (process_compensation.IsEmpty()) {
return false;
}
WriteTigerLakeProcessCompensationConfig(process_compensation, ddi_id(), mmio_space_);
bool is_compensation_source = (ddi_id() == DdiId::DDI_A);
compensation_source.set_generate_internal_references(is_compensation_source).WriteTo(mmio_space_);
compensation_initialized.set_initialized(true).WriteTo(mmio_space_);
common_lane5.set_common_lane_power_down_enabled(true).WriteTo(mmio_space_);
return true;
}
bool ComboDdiTigerLake::Deinitialize() {
// This implements the section "Digital Display Interface" > "Combo PHY
// Un-Initialization Sequence" in display engine PRMs.
// Tiger Lake: IHD-OS-TGL-Vol 12-1.22-Rev2.0 page 392
// DG1: IHD-OS-DG1-Vol 12-2.21 page 338
// Ice Lake: IHD-OS-ICLLP-Vol 12-1.22-Rev2.0 page 335
// TODO(https://fxbug.dev/42065111): Implement the compensation source dependency
// between DDI A and DDIs B-C.
auto phy_misc = registers::PhyMisc::GetForDdi(ddi_id()).ReadFrom(mmio_space_);
phy_misc.set_compensation_resistors_powered_down(true).WriteTo(mmio_space_);
auto port_compensation0 = registers::PortCompensation0::GetForDdi(ddi_id()).ReadFrom(mmio_space_);
port_compensation0.set_initialized(false).WriteTo(mmio_space_);
return true;
}
DdiPhysicalLayer::PhysicalLayerInfo ComboDdiTigerLake::GetPhysicalLayerInfo() const {
return {
.ddi_type = DdiPhysicalLayer::DdiType::kCombo,
.connection_type = DdiPhysicalLayer::ConnectionType::kBuiltIn,
.max_allowed_dp_lane_count = 4u,
};
}
TypeCDdiTigerLake::TypeCDdiTigerLake(DdiId ddi_id, Power* power, fdf::MmioBuffer* mmio_space,
bool is_static_port)
: DdiPhysicalLayer(ddi_id),
power_(power),
mmio_space_(mmio_space),
initialization_phase_(InitializationPhase::kUninitialized),
is_static_port_(is_static_port),
physical_layer_info_(DefaultPhysicalLayerInfo()) {
ZX_ASSERT(power_);
ZX_ASSERT(mmio_space_);
ZX_ASSERT(ddi_id >= DdiId::DDI_TC_1);
ZX_ASSERT(ddi_id <= DdiId::DDI_TC_6);
}
TypeCDdiTigerLake::~TypeCDdiTigerLake() {
if (initialization_phase_ != InitializationPhase::kUninitialized) {
zxlogf(WARNING, "DDI %d: not fully disabled on port teardown", ddi_id());
}
}
bool TypeCDdiTigerLake::IsEnabled() const {
return initialization_phase_ == InitializationPhase::kInitialized;
}
bool TypeCDdiTigerLake::IsHealthy() const {
// All the other states indicate that the DDI PHY is not fully initialized
// or not fully deinitialized and thus in a limbo state.
return initialization_phase_ == InitializationPhase::kInitialized ||
initialization_phase_ == InitializationPhase::kUninitialized;
}
DdiPhysicalLayer::PhysicalLayerInfo TypeCDdiTigerLake::ReadPhysicalLayerInfo() const {
PhysicalLayerInfo physical_layer_info = {
.ddi_type = DdiType::kTypeC,
};
auto dp_sp = registers::DynamicFlexIoScratchPad::GetForDdi(ddi_id()).ReadFrom(mmio_space_);
auto type_c_live_state = dp_sp.type_c_live_state(ddi_id());
switch (type_c_live_state) {
using TypeCLiveState = registers::DynamicFlexIoScratchPad::TypeCLiveState;
case TypeCLiveState::kNoHotplugDisplay:
if (is_static_port_) {
physical_layer_info.connection_type = ConnectionType::kBuiltIn;
physical_layer_info.max_allowed_dp_lane_count = 4u;
} else {
physical_layer_info.connection_type = ConnectionType::kNone;
physical_layer_info.max_allowed_dp_lane_count = 0u;
}
break;
case TypeCLiveState::kTypeCHotplugDisplay:
physical_layer_info.connection_type = ConnectionType::kTypeCDisplayPortAltMode;
physical_layer_info.max_allowed_dp_lane_count = [&]() {
size_t count = dp_sp.display_port_assigned_tx_lane_count(ddi_id());
ZX_DEBUG_ASSERT_MSG(count <= std::numeric_limits<uint8_t>::max(), "%lu overflows uint8_t",
count);
return static_cast<uint8_t>(count);
}();
break;
case TypeCLiveState::kThunderboltHotplugDisplay:
physical_layer_info.connection_type = ConnectionType::kTypeCThunderbolt;
physical_layer_info.max_allowed_dp_lane_count = 4u;
break;
default:
ZX_ASSERT_MSG(false, "DDI %d: unsupported type C live state (0x%x)", ddi_id(),
static_cast<unsigned int>(type_c_live_state));
}
return physical_layer_info;
}
bool TypeCDdiTigerLake::AdvanceEnableFsm() {
switch (initialization_phase_) {
case InitializationPhase::kUninitialized:
initialization_phase_ = InitializationPhase::kTypeCColdBlocked;
return BlockTypeCColdPowerState();
case InitializationPhase::kTypeCColdBlocked:
initialization_phase_ = InitializationPhase::kSafeModeSet;
if (!SetPhySafeModeDisabled(/*target_disabled=*/true)) {
return false;
}
physical_layer_info_ = ReadPhysicalLayerInfo();
return physical_layer_info_.connection_type != ConnectionType::kNone;
case InitializationPhase::kSafeModeSet:
initialization_phase_ = InitializationPhase::kAuxPoweredOn;
return SetAuxIoPower(/*target_enabled=*/true);
case InitializationPhase::kAuxPoweredOn:
initialization_phase_ = InitializationPhase::kInitialized;
return true;
case InitializationPhase::kInitialized:
return false;
}
}
bool TypeCDdiTigerLake::AdvanceDisableFsm() {
switch (initialization_phase_) {
case InitializationPhase::kUninitialized:
return false;
case InitializationPhase::kTypeCColdBlocked:
if (UnblockTypeCColdPowerState()) {
physical_layer_info_ = DefaultPhysicalLayerInfo();
initialization_phase_ = InitializationPhase::kUninitialized;
return true;
}
return false;
case InitializationPhase::kSafeModeSet:
if (SetPhySafeModeDisabled(/*target_disabled=*/false)) {
initialization_phase_ = InitializationPhase::kTypeCColdBlocked;
return true;
}
return false;
case InitializationPhase::kAuxPoweredOn:
if (SetAuxIoPower(/*target_enabled=*/false)) {
initialization_phase_ = InitializationPhase::kSafeModeSet;
return true;
}
return false;
case InitializationPhase::kInitialized:
initialization_phase_ = InitializationPhase::kAuxPoweredOn;
return true;
}
}
bool TypeCDdiTigerLake::Enable() {
ZX_ASSERT(IsHealthy());
// `IsHealthy()` returns true entails that the device is either in
// `kInitialized` state where it needs to do nothing because of the function's
// idempotency, or in `kUninitialized` state where it needs to start the
// finite state machine.
if (initialization_phase_ == InitializationPhase::kInitialized) {
return true;
}
ZX_DEBUG_ASSERT(initialization_phase_ == InitializationPhase::kUninitialized);
while (AdvanceEnableFsm()) {
}
if (initialization_phase_ == InitializationPhase::kInitialized) {
zxlogf(TRACE, "DDI %d: Enabled. New physical layer info: %s", ddi_id(),
physical_layer_info_.DebugString().c_str());
return true;
}
while (AdvanceDisableFsm()) {
}
return false;
}
bool TypeCDdiTigerLake::Disable() {
switch (initialization_phase_) {
case InitializationPhase::kUninitialized:
// Do nothing because of the function's idempotency.
return true;
case InitializationPhase::kInitialized:
// Start the finite state machine of disable process.
while (AdvanceDisableFsm()) {
}
if (initialization_phase_ == InitializationPhase::kUninitialized) {
zxlogf(TRACE, "DDI %d: Disabled successfully.", ddi_id());
return true;
}
[[fallthrough]];
default:
ZX_ASSERT(!IsHealthy());
zxlogf(ERROR, "DDI %d: Failed to disable.", ddi_id());
return false;
}
}
bool TypeCDdiTigerLake::SetAuxIoPower(bool target_enabled) const {
power_->SetAuxIoPowerState(ddi_id(), /* enable */ target_enabled);
if (target_enabled) {
if (!PollUntil([&] { return power_->GetAuxIoPowerState(ddi_id()); }, zx::usec(1), 1500)) {
zxlogf(ERROR, "DDI %d: failed to enable AUX power for ddi", ddi_id());
return false;
}
const bool is_thunderbolt =
physical_layer_info_.connection_type == DdiPhysicalLayer::ConnectionType::kTypeCThunderbolt;
if (!is_thunderbolt) {
// For every Type-C port (static and DP Alternate but not thunderbolt),
// the driver need to wait for the microcontroller health bit on
// DKL_CMN_UC_DW27 register after enabling AUX power.
//
// TODO(https://fxbug.dev/42182480): Currently Thunderbolt is not supported, so we
// always check health bit of the IO subsystem microcontroller.
//
// Tiger Lake: IHD-OS-TGL-Vol 12-1.22-Rev 2.0, Page 417, "Type-C PHY
// Microcontroller health"
if (!PollUntil(
[&] {
return registers::DekelCommonConfigMicroControllerDword27::GetForDdi(ddi_id())
.ReadFrom(mmio_space_)
.microcontroller_firmware_is_ready();
},
zx::usec(1), 10)) {
zxlogf(ERROR, "DDI %d: microcontroller health bit is not set", ddi_id());
return false;
}
}
auto ddi_aux_ctl = registers::DdiAuxControl::GetForTigerLakeDdi(ddi_id()).ReadFrom(mmio_space_);
ddi_aux_ctl.set_use_thunderbolt(is_thunderbolt);
ddi_aux_ctl.WriteTo(mmio_space_);
zxlogf(TRACE, "DDI %d: AUX IO power enabled", ddi_id());
} else {
zx::nanosleep(zx::deadline_after(zx::usec(10)));
zxlogf(TRACE, "DDI %d: AUX IO power %sdisabled", ddi_id(),
power_->GetAuxIoPowerState(ddi_id()) ? "not " : "");
}
return true;
}
bool TypeCDdiTigerLake::SetPhySafeModeDisabled(bool target_disabled) const {
if (target_disabled && !registers::DynamicFlexIoDisplayPortPhyModeStatus::GetForDdi(ddi_id())
.ReadFrom(mmio_space_)
.phy_is_ready_for_ddi(ddi_id())) {
zxlogf(ERROR, "DDI %d: lane not in DP mode", ddi_id());
return false;
}
auto dp_csss =
registers::DynamicFlexIoDisplayPortControllerSafeStateSettings::GetForDdi(ddi_id()).ReadFrom(
mmio_space_);
dp_csss.set_safe_mode_disabled_for_ddi(ddi_id(), /*disabled=*/target_disabled);
dp_csss.WriteTo(mmio_space_);
dp_csss.ReadFrom(mmio_space_);
zxlogf(TRACE, "DDI %d: %s DP safe mode", ddi_id(), target_disabled ? "disabled" : "enabled");
return true;
}
bool TypeCDdiTigerLake::BlockTypeCColdPowerState() {
// TODO(https://fxbug.dev/42062380): TCCOLD (Type C cold power state) blocking should
// be decided at the display engine level. We may have already blocked TCCOLD
// while bringing up another Type C DDI.
zxlogf(TRACE, "Asking PCU firmware to block Type C cold power state");
PowerController power_controller(mmio_space_);
const zx::result<> power_status = power_controller.SetDisplayTypeCColdBlockingTigerLake(
/*blocked=*/true, PowerController::RetryBehavior::kRetryUntilStateChanges);
switch (power_status.status_value()) {
case ZX_OK:
zxlogf(TRACE, "PCU firmware blocked Type C cold power state");
return true;
default:
zxlogf(ERROR, "Type C ports unusable. PCU firmware didn't block Type C cold power state: %s",
power_status.status_string());
return false;
}
}
bool TypeCDdiTigerLake::UnblockTypeCColdPowerState() {
// TODO(https://fxbug.dev/42062380): TCCOLD (Type C cold power state) blocking should
// be decided at the display engine level. We may have already blocked TCCOLD
// while bringing up another Type C DDI.
zxlogf(TRACE, "Asking PCU firmware to unblock Type C cold power state");
PowerController power_controller(mmio_space_);
const zx::result<> power_status = power_controller.SetDisplayTypeCColdBlockingTigerLake(
/*blocked=*/false, PowerController::RetryBehavior::kNoRetry);
switch (power_status.status_value()) {
case ZX_OK:
zxlogf(TRACE, "PCU firmware unblocked and entered Type C cold power state");
return true;
case ZX_ERR_IO_REFUSED:
zxlogf(INFO,
"PCU firmware did not enter Type C cold power state. "
"Type C ports in use elsewhere.");
return true;
default:
zxlogf(ERROR,
"PCU firmware failed to unblock Type C cold power state. "
"Type C ports unusable.");
return false;
}
}
} // namespace i915