blob: 49f4f7183f2ba0ea7b6f31ca988e117471416e9a [file] [log] [blame] [edit]
// 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-display/hdmi-display.h"
#include <lib/driver/logging/cpp/logger.h>
#include <lib/fit/result.h>
#include <lib/stdcompat/span.h>
#include <lib/zx/result.h>
#include <lib/zx/time.h>
#include <zircon/assert.h>
#include <zircon/syscalls.h>
#include <zircon/time.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <utility>
#include <fbl/alloc_checker.h>
#include <fbl/vector.h>
#include "src/graphics/display/drivers/intel-display/ddi-physical-layer-manager.h"
#include "src/graphics/display/drivers/intel-display/display-device.h"
#include "src/graphics/display/drivers/intel-display/display-timing-mode-conversion.h"
#include "src/graphics/display/drivers/intel-display/dpll-config.h"
#include "src/graphics/display/drivers/intel-display/dpll.h"
#include "src/graphics/display/drivers/intel-display/hardware-common.h"
#include "src/graphics/display/drivers/intel-display/i2c/gmbus-gpio.h"
#include "src/graphics/display/drivers/intel-display/i2c/gmbus-i2c.h"
#include "src/graphics/display/drivers/intel-display/intel-display.h"
#include "src/graphics/display/drivers/intel-display/pci-ids.h"
#include "src/graphics/display/drivers/intel-display/registers-ddi.h"
#include "src/graphics/display/drivers/intel-display/registers-gmbus.h"
#include "src/graphics/display/drivers/intel-display/registers-transcoder.h"
#include "src/graphics/display/lib/api-types/cpp/display-id.h"
#include "src/graphics/display/lib/api-types/cpp/display-timing.h"
#include "src/graphics/display/lib/api-types/cpp/mode-and-id.h"
#include "src/graphics/display/lib/api-types/cpp/mode-id.h"
#include "src/graphics/display/lib/driver-utils/poll-until.h"
#include "src/graphics/display/lib/edid/edid.h"
namespace intel_display {
// I2c functions
namespace {
// Recommended DDI buffer translation programming values
struct DdiPhyConfigEntry {
uint32_t entry2;
uint32_t entry1;
};
// The tables below have the values recommended by the documentation.
//
// Kaby Lake: IHD-OS-KBL-Vol 12-1.17 pages 187-190
// Skylake: IHD-OS-SKL-Vol 12-05.16 pages 181-183
//
// TODO(https://fxbug.dev/42059656): Per-entry Iboost values.
constexpr DdiPhyConfigEntry kPhyConfigHdmiSkylakeUhs[11] = {
{0x000000ac, 0x00000018}, {0x0000009d, 0x00005012}, {0x00000088, 0x00007011},
{0x000000a1, 0x00000018}, {0x00000098, 0x00000018}, {0x00000088, 0x00004013},
{0x000000cd, 0x80006012}, {0x000000df, 0x00000018}, {0x000000cd, 0x80003015},
{0x000000c0, 0x80003015}, {0x000000c0, 0x80000018},
};
constexpr DdiPhyConfigEntry kPhyConfigHdmiSkylakeY[11] = {
{0x000000a1, 0x00000018}, {0x000000df, 0x00005012}, {0x000000cb, 0x80007011},
{0x000000a4, 0x00000018}, {0x0000009d, 0x00000018}, {0x00000080, 0x00004013},
{0x000000c0, 0x80006012}, {0x0000008a, 0x00000018}, {0x000000c0, 0x80003015},
{0x000000c0, 0x80003015}, {0x000000c0, 0x80000018},
};
cpp20::span<const DdiPhyConfigEntry> GetHdmiPhyConfigEntries(uint16_t device_id,
uint8_t* default_iboost) {
if (is_skl_y(device_id) || is_kbl_y(device_id)) {
*default_iboost = 3;
return kPhyConfigHdmiSkylakeY;
}
*default_iboost = 1;
return kPhyConfigHdmiSkylakeUhs;
}
} // namespace
// Modesetting functions
// On DisplayDevice creation we cannot determine whether it is an HDMI
// display; this will be updated when intel-display Controller gets EDID
// information for this device (before Init()).
HdmiDisplay::HdmiDisplay(Controller* controller, display::DisplayId id, DdiId ddi_id,
DdiReference ddi_reference, GMBusI2c* gmbus_i2c)
: DisplayDevice(controller, id, ddi_id, std::move(ddi_reference), Type::kHdmi),
gmbus_i2c_(*gmbus_i2c) {
ZX_DEBUG_ASSERT(controller != nullptr);
ZX_DEBUG_ASSERT(gmbus_i2c != nullptr);
}
HdmiDisplay::~HdmiDisplay() = default;
bool HdmiDisplay::Query() {
// HDMI isn't supported on these DDIs
const registers::Platform platform = GetPlatform(controller()->device_id());
if (!GMBusPinPair::HasValidPinPair(ddi_id(), platform)) {
return false;
}
// Reset the GMBus registers and disable GMBus interrupts
registers::GMBusClockPortSelect::Get().FromValue(0).WriteTo(mmio_space());
registers::GMBusControllerInterruptMask::Get().FromValue(0).WriteTo(mmio_space());
// The only way to tell if an HDMI monitor is actually connected is
// to try to read an E-EDID byte.
bool has_display = false;
static constexpr int kMaxProbeDisplayAttemptCount = 3;
for (int i = 0; i < kMaxProbeDisplayAttemptCount; ++i) {
has_display = gmbus_i2c_.ProbeDisplay();
if (has_display) {
fdf::trace("Found a hdmi/dvi monitor");
break;
}
zx_nanosleep(zx_deadline_after(ZX_MSEC(5)));
}
if (!has_display) {
fdf::trace("Failed to find a display after {} attempts", kMaxProbeDisplayAttemptCount);
return false;
}
zx::result<fbl::Vector<uint8_t>> read_extended_edid_result = gmbus_i2c_.ReadExtendedEdid();
if (read_extended_edid_result.is_error()) {
fdf::error("Failed to read E-EDID of the display: {}", read_extended_edid_result);
return false;
}
fit::result<const char*, edid::Edid> edid_result =
edid::Edid::Create(std::move(read_extended_edid_result).value());
if (edid_result.is_error()) {
fdf::error("Failed to parse E-EDID of the display: %s", edid_result.error_value());
return false;
}
edid_ = std::move(edid_result).value();
// Populate `timings_` with the display's supported timings.
zx::result<fbl::Vector<display::DisplayTiming>> timings_result =
edid_->GetSupportedDisplayTimings();
if (timings_result.is_error()) {
fdf::error("Failed to get supported display timings: {}", timings_result.status_string());
return false;
}
timings_ = std::move(timings_result).value();
return true;
}
bool HdmiDisplay::InitDdi() {
// All the init happens during modeset
return true;
}
bool HdmiDisplay::DdiModeset(const display::DisplayTiming& mode) {
pipe()->Reset();
controller()->ResetDdi(ddi_id(), pipe()->connected_transcoder_id());
const int64_t pixel_clock_frequency_khz = mode.pixel_clock_frequency_hz / 1'000;
DdiPllConfig pll_config = {
.ddi_clock_khz = static_cast<int32_t>(pixel_clock_frequency_khz) * 5,
.spread_spectrum_clocking = false,
.admits_display_port = false,
.admits_hdmi = true,
};
DisplayPll* dpll =
controller()->dpll_manager()->SetDdiPllConfig(ddi_id(), /*is_edp=*/false, pll_config);
if (dpll == nullptr) {
return false;
}
ZX_DEBUG_ASSERT(controller()->power());
controller()->power()->SetDdiIoPowerState(ddi_id(), /*enable=*/true);
if (!display::PollUntil([&] { return controller()->power()->GetDdiIoPowerState(ddi_id()); },
zx::usec(1), 20)) {
fdf::error("DDI {} IO power did not come up in 20us", ddi_id());
return false;
}
controller()->power()->SetAuxIoPowerState(ddi_id(), /*enable=*/true);
if (!display::PollUntil([&] { return controller()->power()->GetAuxIoPowerState(ddi_id()); },
zx::usec(1), 10)) {
fdf::error("DDI {} IO power did not come up in 10us", ddi_id());
return false;
}
return true;
}
bool HdmiDisplay::PipeConfigPreamble(const display::DisplayTiming& mode, PipeId pipe_id,
TranscoderId transcoder_id) {
ZX_DEBUG_ASSERT_MSG(transcoder_id != TranscoderId::TRANSCODER_EDP,
"The EDP transcoder doesn't do HDMI");
registers::TranscoderRegs transcoder_regs(transcoder_id);
// Configure Transcoder Clock Select
auto transcoder_clock_select = transcoder_regs.ClockSelect().ReadFrom(mmio_space());
if (is_tgl(controller()->device_id())) {
transcoder_clock_select.set_ddi_clock_tiger_lake(ddi_id());
} else {
transcoder_clock_select.set_ddi_clock_kaby_lake(ddi_id());
}
transcoder_clock_select.WriteTo(mmio_space());
return true;
}
bool HdmiDisplay::PipeConfigEpilogue(const display::DisplayTiming& mode, PipeId pipe_id,
TranscoderId transcoder_id) {
ZX_DEBUG_ASSERT(type() == DisplayDevice::Type::kHdmi || type() == DisplayDevice::Type::kDvi);
ZX_DEBUG_ASSERT_MSG(transcoder_id != TranscoderId::TRANSCODER_EDP,
"The EDP transcoder doesn't do HDMI");
registers::TranscoderRegs transcoder_regs(transcoder_id);
auto transcoder_ddi_control = transcoder_regs.DdiControl().ReadFrom(mmio_space());
transcoder_ddi_control.set_enabled(true);
if (is_tgl(controller()->device_id())) {
transcoder_ddi_control.set_ddi_tiger_lake(ddi_id());
} else {
transcoder_ddi_control.set_ddi_kaby_lake(ddi_id());
}
transcoder_ddi_control.set_ddi_mode(type() == DisplayDevice::Type::kHdmi
? registers::TranscoderDdiControl::kModeHdmi
: registers::TranscoderDdiControl::kModeDvi);
transcoder_ddi_control.set_bits_per_color(registers::TranscoderDdiControl::k8bpc)
.set_vsync_polarity_not_inverted(mode.vsync_polarity == display::SyncPolarity::kPositive)
.set_hsync_polarity_not_inverted(mode.hsync_polarity == display::SyncPolarity::kPositive)
.set_is_port_sync_secondary_kaby_lake(false)
.set_allocate_display_port_virtual_circuit_payload(false)
.WriteTo(mmio_space());
auto transcoder_config = transcoder_regs.Config().ReadFrom(mmio_space());
transcoder_config.set_enabled_target(true)
.set_interlaced_display(mode.fields_per_frame == display::FieldsPerFrame::kInterlaced)
.WriteTo(mmio_space());
// Configure voltage swing and related IO settings.
//
// TODO(https://fxbug.dev/42065767): Move voltage swing configuration logic to a
// DDI-specific class.
// kUseDefaultIdx always fails the idx-in-bounds check, so no additional handling is needed
uint8_t idx = controller()->igd_opregion().GetHdmiBufferTranslationIndex(ddi_id());
uint8_t i_boost_override = controller()->igd_opregion().GetIBoost(ddi_id(), false /* is_dp */);
uint8_t default_iboost;
const cpp20::span<const DdiPhyConfigEntry> entries =
GetHdmiPhyConfigEntries(controller()->device_id(), &default_iboost);
if (idx >= entries.size()) {
idx = 8; // Default index
}
registers::DdiRegs ddi_regs(ddi_id());
auto phy_config_entry1 = ddi_regs.PhyConfigEntry1(9).FromValue(0);
phy_config_entry1.set_reg_value(entries[idx].entry1);
if (i_boost_override) {
phy_config_entry1.set_balance_leg_enable(1);
}
phy_config_entry1.WriteTo(mmio_space());
auto phy_config_entry2 = ddi_regs.PhyConfigEntry2(9).FromValue(0);
phy_config_entry2.set_reg_value(entries[idx].entry2).WriteTo(mmio_space());
auto phy_balance_control = registers::DdiPhyBalanceControl::Get().ReadFrom(mmio_space());
phy_balance_control.set_disable_balance_leg(0);
phy_balance_control.balance_leg_select_for_ddi(ddi_id()).set(i_boost_override ? i_boost_override
: default_iboost);
phy_balance_control.WriteTo(mmio_space());
// Configure and enable DDI_BUF_CTL
auto buffer_control = ddi_regs.BufferControl().ReadFrom(mmio_space());
buffer_control.set_enabled(true);
buffer_control.WriteTo(mmio_space());
return true;
}
DdiPllConfig HdmiDisplay::ComputeDdiPllConfig(int32_t pixel_clock_khz) {
return DdiPllConfig{
.ddi_clock_khz = pixel_clock_khz * 5,
.spread_spectrum_clocking = false,
.admits_display_port = false,
.admits_hdmi = true,
};
}
bool HdmiDisplay::CheckPixelRate(int64_t pixel_rate_hz) {
// Pixel rates of 300M/165M pixels per second for HDMI/DVI. The Intel docs state
// that the maximum link bit rate of an HDMI port is 3GHz, not 3.4GHz that would
// be expected based on the HDMI spec.
if ((type() == DisplayDevice::Type::kHdmi ? 300'000'000 : 165'000'000) < pixel_rate_hz) {
return false;
}
int32_t pixel_rate_khz = static_cast<int32_t>(pixel_rate_hz / 1'000);
DdiPllConfig pll_config = ComputeDdiPllConfig(pixel_rate_khz);
if (pll_config.IsEmpty()) {
return false;
}
DpllOscillatorConfig dco_config = CreateDpllOscillatorConfigKabyLake(pll_config.ddi_clock_khz);
return dco_config.frequency_khz != 0;
}
AddedDisplayInfo HdmiDisplay::CreateAddedDisplayInfo() {
fbl::Vector<display::ModeAndId> preferred_modes;
size_t preferred_modes_size =
std::min(timings_.size(), size_t{AddedDisplayInfo::kMaxPreferredModes});
fbl::AllocChecker alloc_checker;
preferred_modes.reserve(preferred_modes_size, &alloc_checker);
ZX_ASSERT(alloc_checker.check());
for (uint16_t i = 0; i < preferred_modes_size; ++i) {
preferred_modes.push_back({{.id = display::ModeId(i + 1), .mode = ToDisplayMode(timings_[i])}});
}
return AddedDisplayInfo{
.display_id = id(),
.preferred_modes = std::move(preferred_modes),
};
}
std::optional<display::DisplayTiming> HdmiDisplay::GetDisplayTiming(display::ModeId mode_id) const {
ZX_DEBUG_ASSERT(mode_id != display::kInvalidModeId);
int index = int{mode_id.value()} - 1;
ZX_DEBUG_ASSERT(index >= 0);
if (static_cast<size_t>(index) >= timings_.size()) {
return std::nullopt;
}
return timings_[index];
}
} // namespace intel_display